From 9fb1668ae52f9bc81c9ed889c10fc3d6398e13b9 Mon Sep 17 00:00:00 2001 From: Bobby Lat Date: Mon, 31 Mar 2025 18:14:53 +0800 Subject: [PATCH 01/68] feat: implement stubs for Box create, and no arg ctors --- package-lock.json | 18 +-- package.json | 4 +- src/encoders.ts | 7 +- src/impl/encoded-types.ts | 100 +++++++++---- src/impl/primitives.ts | 16 +- src/impl/state.ts | 55 ++++++- tests/arc4/encode-decode-arc4.spec.ts | 3 +- tests/arc4/zero-constructor.spec.ts | 50 +++++++ tests/references/box.spec.ts | 204 +++++++++++++++++++++++++- 9 files changed, 399 insertions(+), 58 deletions(-) create mode 100644 tests/arc4/zero-constructor.spec.ts diff --git a/package-lock.json b/package-lock.json index e69a084a..80565661 100644 --- a/package-lock.json +++ b/package-lock.json @@ -8,8 +8,8 @@ "name": "@algorandfoundation/algorand-typescript-testing", "version": "1.0.0", "dependencies": { - "@algorandfoundation/algorand-typescript": "1.0.0-beta.65", - "@algorandfoundation/puya-ts": "1.0.0-beta.65", + "@algorandfoundation/algorand-typescript": "1.0.0-alpha.47", + "@algorandfoundation/puya-ts": "1.0.0-alpha.47", "elliptic": "^6.6.1", "js-sha256": "^0.11.0", "js-sha3": "^0.9.3", @@ -73,21 +73,21 @@ } }, "node_modules/@algorandfoundation/algorand-typescript": { - "version": "1.0.0-beta.65", - "resolved": "https://registry.npmjs.org/@algorandfoundation/algorand-typescript/-/algorand-typescript-1.0.0-beta.65.tgz", - "integrity": "sha512-XbMXOsMXw/p+5VaD9LiYrCVRgahHG9ZpEXwAj6hqwMtnyIpwxes1gVtCljg/NtQ4Zj3TlWsU2dtxLBSAqF8iKQ==", + "version": "1.0.0-alpha.47", + "resolved": "https://registry.npmjs.org/@algorandfoundation/algorand-typescript/-/algorand-typescript-1.0.0-alpha.47.tgz", + "integrity": "sha512-7DKwvm5IdJy//OCo2eK+kwIY59OyNU/Nxlu9KT61PA+pirY5t9eZqeLczQobA63NCBDgh+BXJKrzO0pEYIEAvg==", "peerDependencies": { "tslib": "^2.6.2" } }, "node_modules/@algorandfoundation/puya-ts": { - "version": "1.0.0-beta.65", - "resolved": "https://registry.npmjs.org/@algorandfoundation/puya-ts/-/puya-ts-1.0.0-beta.65.tgz", - "integrity": "sha512-+jM1el9mL0jOnGTDD4q3lMMHdFY7VZ0xg6UqGfoz/Macr+uZ5JvlgzgZUemCeH6zEzPcoLlOTosL9BeEiE1e7Q==", + "version": "1.0.0-alpha.47", + "resolved": "https://registry.npmjs.org/@algorandfoundation/puya-ts/-/puya-ts-1.0.0-alpha.47.tgz", + "integrity": "sha512-yBLzGb7jdt3Bma7ErT88YbWKGw8dqMC1xMh5rY8bIXvGthf+uwZsjvOQ/IxIRnYVljZ2ZBuolmvPVAhgF+4gZg==", "hasInstallScript": true, "license": "MIT", "dependencies": { - "@algorandfoundation/algorand-typescript": "1.0.0-beta.65", + "@algorandfoundation/algorand-typescript": "1.0.0-alpha.47", "arcsecond": "^5.0.0", "argparse": "^2.0.1", "chalk": "^5.4.1", diff --git a/package.json b/package.json index 7f796948..a437619b 100644 --- a/package.json +++ b/package.json @@ -66,8 +66,8 @@ "vitest": "3.0.9" }, "dependencies": { - "@algorandfoundation/algorand-typescript": "1.0.0-beta.65", - "@algorandfoundation/puya-ts": "1.0.0-beta.65", + "@algorandfoundation/algorand-typescript": "1.0.0-alpha.47", + "@algorandfoundation/puya-ts": "1.0.0-alpha.47", "elliptic": "^6.6.1", "js-sha256": "^0.11.0", "js-sha3": "^0.9.3", diff --git a/src/encoders.ts b/src/encoders.ts index 08e09dde..a609cf94 100644 --- a/src/encoders.ts +++ b/src/encoders.ts @@ -3,7 +3,7 @@ import { ARC4Encoded } from '@algorandfoundation/algorand-typescript/arc4' import { encodingUtil } from '@algorandfoundation/puya-ts' import { InternalError } from './errors' import { BytesBackedCls, Uint64BackedCls } from './impl/base' -import { arc4Encoders, encodeArc4Impl, getArc4Encoder } from './impl/encoded-types' +import { arc4Encoders, encodeArc4Impl, getArc4Encoder, tryArc4EncodedLengthImpl } from './impl/encoded-types' import { BigUint, Uint64, type StubBytesCompat } from './impl/primitives' import { AccountCls, ApplicationCls, AssetCls } from './impl/reference' import type { DeliberateAny } from './typescript-helpers' @@ -89,3 +89,8 @@ export const toBytes = (val: unknown): bytes => { } throw new InternalError(`Invalid type for bytes: ${nameOfType(val)}`) } + +export const minLengthForType = (typeInfo: TypeInfo): number => { + const minArc4StaticLength = tryArc4EncodedLengthImpl(typeInfo) + return minArc4StaticLength ?? 0 +} diff --git a/src/impl/encoded-types.ts b/src/impl/encoded-types.ts index 68b5f1d8..9fccf8e6 100644 --- a/src/impl/encoded-types.ts +++ b/src/impl/encoded-types.ts @@ -2,6 +2,7 @@ import type { Account as AccountType, BigUintCompat, bytes, + NTuple, StringCompat, uint64, Uint64Compat, @@ -127,14 +128,14 @@ export class UFixedNxMImpl extends UFixedNx private precision: M typeInfo: TypeInfo - constructor(typeInfo: TypeInfo | string, v: `${number}.${number}`) { + constructor(typeInfo: TypeInfo | string, v?: `${number}.${number}`) { super(v) this.typeInfo = typeof typeInfo === 'string' ? JSON.parse(typeInfo) : typeInfo const genericArgs = this.typeInfo.genericArgs as uFixedNxMGenericArgs this.bitSize = UFixedNxMImpl.getMaxBitsLength(this.typeInfo) as N this.precision = parseInt(genericArgs.m.name, 10) as M - const trimmedValue = trimTrailingDecimalZeros(v) + const trimmedValue = trimTrailingDecimalZeros(v ?? '0.0') assert(regExpNxM(this.precision).test(trimmedValue), `expected positive decimal literal with max of ${this.precision} decimal places`) const bigIntValue = BigInt(trimmedValue.replace('.', '')) @@ -308,7 +309,7 @@ const checkItemTypeName = (type: TypeInfo, value: ARC4Encoded) => { } type StaticArrayGenericArgs = { elementType: TypeInfo; size: TypeInfo } const arrayProxyHandler = () => ({ - get(target: { items: TItem[] }, prop: PropertyKey) { + get(target: { items: readonly TItem[] }, prop: PropertyKey) { const idx = prop ? parseInt(prop.toString(), 10) : NaN if (!isNaN(idx)) { if (idx >= 0 && idx < target.items.length) return target.items[idx] @@ -337,8 +338,9 @@ const arrayProxyHandler = () => ({ return Reflect.set(target, prop, value) }, }) +const isInitialisingFromBytesSymbol = Symbol('IsInitialisingFromBytes') export class StaticArrayImpl extends StaticArray { - private value?: TItem[] + private value?: NTuple private uint8ArrayValue?: Uint8Array private size: number typeInfo: TypeInfo @@ -347,23 +349,33 @@ export class StaticArrayImpl constructor(typeInfo: TypeInfo | string, ...items: TItem[] & { length: TLength }) constructor(typeInfo: TypeInfo | string, ...items: TItem[]) constructor(typeInfo: TypeInfo | string, ...items: TItem[] & { length: TLength }) { - super(...(items as DeliberateAny)) + // if first item is the symbol, we are initialising from bytes + // so we don't need to pass the items to the super constructor + const isInitialisingFromBytes = items.length === 1 && (items[0] as DeliberateAny) === isInitialisingFromBytesSymbol + super(...(isInitialisingFromBytes ? [] : (items as DeliberateAny))) + this.typeInfo = typeof typeInfo === 'string' ? JSON.parse(typeInfo) : typeInfo this.genericArgs = this.typeInfo.genericArgs as StaticArrayGenericArgs - this.size = parseInt(this.genericArgs.size.name, 10) - if (items.length && items.length !== this.size) { - throw new CodeError(`expected ${this.size} items, not ${items.length}`) - } - assert(areAllARC4Encoded(items), 'expected ARC4 type') + // if we are not initialising from bytes, we need to check and set the items + if (!isInitialisingFromBytes) { + if (items.length && items.length !== this.size) { + throw new CodeError(`expected ${this.size} items, not ${items.length}`) + } - items.forEach((item) => { - checkItemTypeName(this.genericArgs.elementType, item) - }) + assert(areAllARC4Encoded(items), 'expected ARC4 type') - this.value = items.length ? items : undefined + items.forEach((item) => { + checkItemTypeName(this.genericArgs.elementType, item) + }) + if (items.length) { + this.value = items as NTuple + } else { + this.uint8ArrayValue = new Uint8Array(StaticArrayImpl.getMaxBytesLength(this.typeInfo)) + } + } return new Proxy(this, arrayProxyHandler()) as StaticArrayImpl } @@ -382,10 +394,10 @@ export class StaticArrayImpl return this.size } - get items(): TItem[] { + get items(): NTuple { if (this.uint8ArrayValue) { const childTypes = Array(this.size).fill(this.genericArgs.elementType) - this.value = decode(this.uint8ArrayValue, childTypes) as TItem[] + this.value = decode(this.uint8ArrayValue, childTypes) as NTuple this.uint8ArrayValue = undefined return this.value } else if (this.value) { @@ -417,7 +429,7 @@ export class StaticArrayImpl ) } - get native(): TItem[] { + get native(): NTuple { return this.items } @@ -431,7 +443,8 @@ export class StaticArrayImpl assert(bytesValue.slice(0, 4).equals(ABI_RETURN_VALUE_LOG_PREFIX), 'ABI return prefix not found') bytesValue = bytesValue.slice(4) } - const result = new StaticArrayImpl(typeInfo) + // pass the symbol to the constructor to let it know we are initialising from bytes + const result = new StaticArrayImpl(typeInfo, isInitialisingFromBytesSymbol as DeliberateAny) result.uint8ArrayValue = asUint8Array(bytesValue) return result } @@ -504,7 +517,7 @@ export class AddressImpl extends Address { return Account(this.value.bytes) } - get items(): ByteImpl[] { + get items(): readonly ByteImpl[] { return this.value.items } @@ -646,18 +659,27 @@ export class TupleImpl extends T typeInfo: TypeInfo genericArgs: TypeInfo[] - constructor(typeInfo: TypeInfo | string) constructor(typeInfo: TypeInfo | string, ...items: TTuple) { - super(...items) + // if first item is the symbol, we are initialising from bytes + // so we don't need to pass the items to the super constructor + const isInitialisingFromBytes = items.length === 1 && (items[0] as DeliberateAny) === isInitialisingFromBytesSymbol + super(...(isInitialisingFromBytes ? ([] as DeliberateAny) : items)) this.typeInfo = typeof typeInfo === 'string' ? JSON.parse(typeInfo) : typeInfo this.genericArgs = Object.values(this.typeInfo.genericArgs as Record) - assert(areAllARC4Encoded(items), 'expected ARC4 type') + // if we are not initialising from bytes, we need to check and set the items + if (!isInitialisingFromBytes) { + assert(areAllARC4Encoded(items), 'expected ARC4 type') - items.forEach((item, index) => { - checkItemTypeName(this.genericArgs[index], item) - }) - this.value = items.length ? items : undefined + items.forEach((item, index) => { + checkItemTypeName(this.genericArgs[index], item) + }) + if (items.length) { + this.value = items + } else { + this.uint8ArrayValue = new Uint8Array(TupleImpl.getMaxBytesLength(this.typeInfo)) + } + } } get bytes(): bytes { @@ -705,7 +727,8 @@ export class TupleImpl extends T assert(bytesValue.slice(0, 4).equals(ABI_RETURN_VALUE_LOG_PREFIX), 'ABI return prefix not found') bytesValue = bytesValue.slice(4) } - const result = new TupleImpl(typeInfo) + // pass the symbol to the constructor to let it know we are initialising from bytes + const result = new TupleImpl(typeInfo, isInitialisingFromBytesSymbol as DeliberateAny) result.uint8ArrayValue = asUint8Array(bytesValue) return result } @@ -920,9 +943,9 @@ export class StaticBytesImpl extends StaticBytes { constructor(typeInfo: TypeInfo | string, value?: bytes | string) { super(value) - const uint8ArrayValue = asUint8Array(value ?? new Uint8Array()) - this.value = StaticArrayImpl.fromBytesImpl(uint8ArrayValue, typeInfo) as StaticArrayImpl this.typeInfo = typeof typeInfo === 'string' ? JSON.parse(typeInfo) : typeInfo + const uint8ArrayValue = asUint8Array(value ?? new Uint8Array(StaticBytesImpl.getMaxBytesLength(this.typeInfo))) + this.value = StaticArrayImpl.fromBytesImpl(uint8ArrayValue, typeInfo) as StaticArrayImpl return new Proxy(this, arrayProxyHandler()) as StaticBytesImpl } @@ -1086,6 +1109,8 @@ const getMaxLengthOfStaticContentType = (type: TypeInfo): number => { case 'biguint': return UINT512_SIZE / BITS_IN_BYTE case 'boolean': + return 8 + case 'Bool': return 1 case 'Address': return AddressImpl.getMaxBytesLength(type) @@ -1103,8 +1128,9 @@ const getMaxLengthOfStaticContentType = (type: TypeInfo): number => { return TupleImpl.getMaxBytesLength(type) case 'Struct': return StructImpl.getMaxBytesLength(type) + default: + throw new CodeError(`unsupported type ${type.name}`) } - throw new CodeError(`unsupported type ${type.name}`) } const encode = (values: ARC4Encoded[]) => { @@ -1338,7 +1364,7 @@ export const getArc4Encoded = (value: DeliberateAny): ARC4Encoded => { }, []) const genericArgs: TypeInfo[] = result.map((x) => (x as DeliberateAny).typeInfo) const typeInfo = { name: `Tuple<[${genericArgs.map((x) => x.name).join(',')}]>`, genericArgs } - return new TupleImpl(typeInfo, ...(result as [])) + return new TupleImpl(typeInfo, ...(result as [ARC4Encoded, ...ARC4Encoded[]])) } if (typeof value === 'object') { const result = Object.values(value).reduce((acc: ARC4Encoded[], cur: DeliberateAny) => { @@ -1359,3 +1385,15 @@ export const arc4EncodedLengthImpl = (typeInfoString: string): uint64 => { const typeInfo = JSON.parse(typeInfoString) return getMaxLengthOfStaticContentType(typeInfo) } + +export const tryArc4EncodedLengthImpl = (typeInfoString: string | TypeInfo): uint64 | undefined => { + const typeInfo = typeof typeInfoString === 'string' ? JSON.parse(typeInfoString) : typeInfoString + try { + return getMaxLengthOfStaticContentType(typeInfo) + } catch (e) { + if (e instanceof CodeError && e.message.startsWith('unsupported type')) { + return undefined + } + throw e + } +} diff --git a/src/impl/primitives.ts b/src/impl/primitives.ts index 83c882f9..5a728b29 100644 --- a/src/impl/primitives.ts +++ b/src/impl/primitives.ts @@ -564,9 +564,9 @@ export class BytesCls extends AlgoTsPrimitiveCls { export const arrayUtil = new (class ArrayUtil { arrayAt(arrayLike: Uint8Array, index: StubUint64Compat): Uint8Array - arrayAt(arrayLike: T[], index: StubUint64Compat): T - arrayAt(arrayLike: T[] | Uint8Array, index: StubUint64Compat): T | Uint8Array - arrayAt(arrayLike: T[] | Uint8Array, index: StubUint64Compat): T | Uint8Array { + arrayAt(arrayLike: readonly T[], index: StubUint64Compat): T + arrayAt(arrayLike: readonly T[] | Uint8Array, index: StubUint64Compat): T | Uint8Array + arrayAt(arrayLike: readonly T[] | Uint8Array, index: StubUint64Compat): T | Uint8Array { const indexNum = getNumber(index) if (arrayLike instanceof Uint8Array) { const res = arrayLike.slice(indexNum, indexNum === -1 ? undefined : indexNum + 1) @@ -576,9 +576,13 @@ export const arrayUtil = new (class ArrayUtil { return arrayLike.at(indexNum) ?? avmError('Index out of bounds') } arraySlice(arrayLike: Uint8Array, start: undefined | StubUint64Compat, end: undefined | StubUint64Compat): Uint8Array - arraySlice(arrayLike: T[], start: undefined | StubUint64Compat, end: undefined | StubUint64Compat): T[] - arraySlice(arrayLike: T[] | Uint8Array, start: undefined | StubUint64Compat, end: undefined | StubUint64Compat): Uint8Array | T[] - arraySlice(arrayLike: T[] | Uint8Array, start: undefined | StubUint64Compat, end: undefined | StubUint64Compat) { + arraySlice(arrayLike: readonly T[], start: undefined | StubUint64Compat, end: undefined | StubUint64Compat): T[] + arraySlice( + arrayLike: readonly T[] | Uint8Array, + start: undefined | StubUint64Compat, + end: undefined | StubUint64Compat, + ): Uint8Array | T[] + arraySlice(arrayLike: readonly T[] | Uint8Array, start: undefined | StubUint64Compat, end: undefined | StubUint64Compat) { const startNum = start === undefined ? undefined : getNumber(start) const endNum = end === undefined ? undefined : getNumber(end) if (arrayLike instanceof Uint8Array) { diff --git a/src/impl/state.ts b/src/impl/state.ts index 130cb59f..de5daf0a 100644 --- a/src/impl/state.ts +++ b/src/impl/state.ts @@ -15,9 +15,9 @@ import { AccountMap } from '../collections/custom-key-map' import { MAX_BOX_SIZE } from '../constants' import { lazyContext } from '../context-helpers/internal-context' import type { TypeInfo } from '../encoders' -import { getEncoder, toBytes } from '../encoders' -import { AssertError, InternalError } from '../errors' -import { getGenericTypeInfo } from '../runtime-helpers' +import { getEncoder, minLengthForType, toBytes } from '../encoders' +import { AssertError, CodeError, InternalError } from '../errors' +import { getGenericTypeInfo, tryArc4EncodedLengthImpl } from '../runtime-helpers' import { asBytes, asBytesCls, asNumber, asUint8Array, conactUint8Arrays } from '../util' import type { StubBytesCompat, StubUint64Compat } from './primitives' import { Bytes, Uint64, Uint64Cls } from './primitives' @@ -135,6 +135,16 @@ export class BoxCls { #valueType?: TypeInfo private readonly _type: string = BoxCls.name + private get valueType(): TypeInfo { + if (this.#valueType === undefined) { + const typeInfo = getGenericTypeInfo(this) + if (typeInfo === undefined || typeInfo.genericArgs === undefined || typeInfo.genericArgs.length !== 1) { + throw new InternalError('Box value type is not set') + } + this.#valueType = (typeInfo.genericArgs as TypeInfo[])[0] + } + return this.#valueType + } static [Symbol.hasInstance](x: unknown): x is BoxCls { return x instanceof Object && '_type' in x && (x as { _type: string })['_type'] === BoxCls.name @@ -147,8 +157,31 @@ export class BoxCls { } private get fromBytes() { - const valueType = this.#valueType ?? (getGenericTypeInfo(this)!.genericArgs! as TypeInfo[])[0] - return (val: Uint8Array) => getEncoder(valueType)(val, valueType) + return (val: Uint8Array) => getEncoder(this.valueType)(val, this.valueType) + } + + create(options?: { size?: StubUint64Compat }): boolean { + const optionSize = options?.size !== undefined ? asNumber(options.size) : undefined + const valueTypeSize = tryArc4EncodedLengthImpl(this.valueType) + if (valueTypeSize === undefined && optionSize === undefined) { + throw new InternalError(`${this.valueType.name} does not have a fixed byte size. Please specify a size argument`) + } + if (valueTypeSize !== undefined && optionSize !== undefined) { + if (optionSize < valueTypeSize) { + throw new InternalError(`Box size cannot be less than ${valueTypeSize}`) + } + if (optionSize > valueTypeSize) { + process.emitWarning( + `Box size is set to ${optionSize} but the value type ${this.valueType.name} has a fixed size of ${valueTypeSize}`, + ) + } + } + lazyContext.ledger.setBox( + this.#app, + this.key, + new Uint8Array(Math.max(asNumber(options?.size ?? 0), this.valueType ? minLengthForType(this.valueType) : 0)), + ) + return true } get value(): TValue { @@ -165,7 +198,15 @@ export class BoxCls { return materialised } set value(v: TValue) { - lazyContext.ledger.setBox(this.#app, this.key, asUint8Array(toBytes(v))) + const isStaticValueType = tryArc4EncodedLengthImpl(this.valueType) !== undefined + const newValueBytes = asUint8Array(toBytes(v)) + if (isStaticValueType && this.exists) { + const originalValueBytes = lazyContext.ledger.getBox(this.#app, this.key) + if (originalValueBytes.length !== newValueBytes.length) { + throw new CodeError(`attempt to box_put wrong size ${originalValueBytes.length} != ${newValueBytes.length}`) + } + } + lazyContext.ledger.setBox(this.#app, this.key, newValueBytes) lazyContext.ledger.setMatrialisedBox(this.#app, this.key, v) } @@ -192,7 +233,7 @@ export class BoxCls { if (!this.exists) { throw new InternalError('Box has not been created') } - return toBytes(this.value).length + return lazyContext.ledger.getBox(this.#app, this.key).length } get(options: { default: TValue }): TValue { diff --git a/tests/arc4/encode-decode-arc4.spec.ts b/tests/arc4/encode-decode-arc4.spec.ts index ed374392..b1449fa9 100644 --- a/tests/arc4/encode-decode-arc4.spec.ts +++ b/tests/arc4/encode-decode-arc4.spec.ts @@ -145,7 +145,8 @@ describe('arc4EncodedLength', () => { test('should return the correct length', () => { expect(arc4EncodedLength()).toEqual(8) expect(arc4EncodedLength()).toEqual(64) - expect(arc4EncodedLength()).toEqual(1) + expect(arc4EncodedLength()).toEqual(1) + expect(arc4EncodedLength()).toEqual(8) expect(arc4EncodedLength>()).toEqual(64) expect(arc4EncodedLength<[uint64, uint64, boolean]>()).toEqual(17) expect(arc4EncodedLength<[uint64, uint64, boolean, boolean]>()).toEqual(17) diff --git a/tests/arc4/zero-constructor.spec.ts b/tests/arc4/zero-constructor.spec.ts new file mode 100644 index 00000000..68e026c1 --- /dev/null +++ b/tests/arc4/zero-constructor.spec.ts @@ -0,0 +1,50 @@ +import { op } from '@algorandfoundation/algorand-typescript' +import { + Address, + Bool, + DynamicArray, + DynamicBytes, + encodeArc4, + StaticArray, + StaticBytes, + Str, + Tuple, + UFixedNxM, + UintN32, + UintN8, +} from '@algorandfoundation/algorand-typescript/arc4' +import { describe, expect, it } from 'vitest' + +describe('initialising ABI values with constructor args', () => { + it('should set correct zero values', () => { + expect(new StaticArray().bytes).toEqual(new StaticArray(new UintN8(0), new UintN8(0), new UintN8(0), new UintN8(0)).bytes) + expect(new StaticArray().bytes).toEqual( + new StaticArray(new Bool(false), new Bool(false), new Bool(false), new Bool(false)).bytes, + ) + expect(new StaticArray().bytes).toEqual( + new StaticArray( + new Bool(false), + new Bool(false), + new Bool(false), + new Bool(false), + new Bool(false), + new Bool(false), + new Bool(false), + new Bool(false), + new Bool(false), + ).bytes, + ) + expect(new DynamicArray().bytes).toEqual(op.bzero(2)) + expect(new Tuple<[Bool, Bool, Bool, Bool, Bool, Bool, Bool, Bool, Bool]>().bytes).toEqual( + encodeArc4([false, false, false, false, false, false, false, false, false]), + ) + expect(new DynamicArray().bytes).toEqual(op.bzero(2)) + expect(new Str().bytes).toEqual(op.bzero(2)) + expect(new DynamicBytes().bytes).toEqual(op.bzero(2)) + expect(new StaticBytes<5>().bytes).toEqual(op.bzero(5)) + expect(new Address().bytes).toEqual(op.bzero(32)) + expect(new UFixedNxM<32, 4>().bytes).toEqual(op.bzero(32 / 8)) + expect(new Bool().bytes).toEqual(op.bzero(1)) + expect(new UintN32().bytes).toEqual(op.bzero(32 / 8)) + }) +}) diff --git a/tests/references/box.spec.ts b/tests/references/box.spec.ts index 0ced8cbd..0ec86abe 100644 --- a/tests/references/box.spec.ts +++ b/tests/references/box.spec.ts @@ -1,7 +1,18 @@ import type { biguint, bytes, uint64 } from '@algorandfoundation/algorand-typescript' import { BigUint, Box, Bytes, op, Uint64 } from '@algorandfoundation/algorand-typescript' import { TestExecutionContext } from '@algorandfoundation/algorand-typescript-testing' -import { ARC4Encoded, DynamicArray, interpretAsArc4, Str, UintN64 } from '@algorandfoundation/algorand-typescript/arc4' +import { + ARC4Encoded, + Bool, + DynamicArray, + interpretAsArc4, + StaticArray, + Str, + Tuple, + UintN32, + UintN64, + UintN8, +} from '@algorandfoundation/algorand-typescript/arc4' import { itob } from '@algorandfoundation/algorand-typescript/op' import { afterEach, describe, expect, it, test } from 'vitest' import { toBytes } from '../../src/encoders' @@ -229,4 +240,195 @@ describe('Box', () => { expect(box.value.at(-1).native).toEqual(400) }) }) + + describe('Box.create', () => { + it('throw errors if size is not provided for dynamic value type', () => { + ctx.txn.createScope([ctx.any.txn.applicationCall()]).execute(() => { + const boxStr = Box({ key: 'a' }) + const boxStaticArray = Box, 10>>({ key: 'c' }) + const boxDynamicArray = Box>({ key: 'd' }) + const boxTuple = Box>({ key: 'e' }) + + const errorMessage = 'does not have a fixed byte size. Please specify a size argument' + expect(() => boxStr.create()).toThrow(errorMessage) + expect(() => boxStaticArray.create()).toThrow(errorMessage) + expect(() => boxDynamicArray.create()).toThrow(errorMessage) + expect(() => boxTuple.create()).toThrow(errorMessage) + }) + }) + + it('throws error if size is less than required for static value type', () => { + ctx.txn.createScope([ctx.any.txn.applicationCall()]).execute(() => { + const boxBool = Box({ key: 'bool' }) + const boxArc4Bool = Box({ key: 'arc4b' }) + const boxUint = Box({ key: 'b' }) + const boxStaticArray = Box>({ key: 'c' }) + const boxTuple = Box>({ key: 'e' }) + const errorMessage = 'Box size cannot be less than' + expect(() => boxBool.create({ size: 7 })).toThrow(`${errorMessage} 8`) + expect(() => boxArc4Bool.create({ size: 0 })).toThrow(`${errorMessage} 1`) + expect(() => boxUint.create({ size: 7 })).toThrow(`${errorMessage} 8`) + expect(() => boxStaticArray.create({ size: 39 })).toThrow(`${errorMessage} 40`) + expect(() => boxTuple.create({ size: 2 })).toThrow(`${errorMessage} 3`) + }) + }) + + it('throws error when setting value if size is larger than required for static value type', () => { + ctx.txn.createScope([ctx.any.txn.applicationCall()]).execute(() => { + const boxBool = Box({ key: 'bool' }) + const boxArc4Bool = Box({ key: 'arc4b' }) + const boxUint = Box({ key: 'b' }) + const boxStaticArray = Box>({ key: 'c' }) + const boxTuple = Box>({ key: 'e' }) + + const errorMessage = 'attempt to box_put wrong size' + boxBool.create({ size: 9 }) + expect(() => (boxBool.value = true)).toThrow(errorMessage) + + boxArc4Bool.create({ size: 2 }) + expect(() => (boxArc4Bool.value = new Bool(true))).toThrow(errorMessage) + + boxUint.create({ size: 9 }) + expect(() => (boxUint.value = Uint64(100))).toThrow(errorMessage) + + boxStaticArray.create({ size: 41 }) + expect( + () => + (boxStaticArray.value = new StaticArray( + new UintN32(100), + new UintN32(200), + new UintN32(300), + new UintN32(400), + new UintN32(500), + new UintN32(600), + new UintN32(700), + new UintN32(800), + new UintN32(900), + new UintN32(1000), + )), + ).toThrow(errorMessage) + + boxTuple.create({ size: 4 }) + expect(() => (boxTuple.value = new Tuple(new UintN8(1), new UintN8(2), new Bool(true), new Bool(false)))).toThrow(errorMessage) + }) + }) + + it('set correct size if size is not provided for static value type', () => { + ctx.txn.createScope([ctx.any.txn.applicationCall()]).execute(() => { + const boxBool = Box({ key: 'bool' }) + const boxArc4Bool = Box({ key: 'arc4b' }) + const boxUint = Box({ key: 'b' }) + const boxStaticArray = Box>({ key: 'c' }) + const boxTuple = Box>({ key: 'e' }) + + boxBool.create() + expect(boxBool.length).toEqual(8) + boxBool.value = true + expect(boxBool.length).toEqual(8) + + boxArc4Bool.create() + expect(boxArc4Bool.length).toEqual(1) + boxArc4Bool.value = new Bool(true) + expect(boxArc4Bool.length).toEqual(1) + + boxUint.create() + expect(boxUint.length).toEqual(8) + boxUint.value = Uint64(100) + expect(boxUint.length).toEqual(8) + + boxStaticArray.create() + expect(boxStaticArray.length).toEqual(40) + boxStaticArray.value = new StaticArray( + new UintN32(100), + new UintN32(200), + new UintN32(300), + new UintN32(400), + new UintN32(500), + new UintN32(600), + new UintN32(700), + new UintN32(800), + new UintN32(900), + new UintN32(1000), + ) + expect(boxStaticArray.length).toEqual(40) + + boxTuple.create() + expect(boxTuple.length).toEqual(3) + boxTuple.value = new Tuple(new UintN8(1), new UintN8(2), new Bool(true), new Bool(false)) + expect(boxTuple.length).toEqual(3) + }) + }) + + it('can set value if size provided is less than required for dynamic value type', () => { + ctx.txn.createScope([ctx.any.txn.applicationCall()]).execute(() => { + const boxStr = Box({ key: 'a' }) + const boxStaticArray = Box, 10>>({ key: 'c' }) + const boxDynamicArray = Box>({ key: 'd' }) + const boxTuple = Box>({ key: 'e' }) + + boxStr.create({ size: 2 }) + boxStr.value = 'hello' + expect(boxStr.length).toEqual(5) + + boxStaticArray.create({ size: 2 }) + boxStaticArray.value = new StaticArray( + new DynamicArray(new UintN32(100), new UintN32(200)), + new DynamicArray(new UintN32(300), new UintN32(400)), + new DynamicArray(new UintN32(500), new UintN32(600)), + new DynamicArray(new UintN32(700), new UintN32(800)), + new DynamicArray(new UintN32(900), new UintN32(1000)), + new DynamicArray(new UintN32(1100), new UintN32(1200)), + new DynamicArray(new UintN32(1300), new UintN32(1400)), + new DynamicArray(new UintN32(1500), new UintN32(1600)), + new DynamicArray(new UintN32(1700), new UintN32(1800)), + new DynamicArray(new UintN32(1900), new UintN32(2000)), + ) + expect(boxStaticArray.length).toEqual(120) + + boxDynamicArray.create({ size: 2 }) + boxDynamicArray.value = new DynamicArray(new UintN8(100), new UintN8(200)) + expect(boxDynamicArray.length).toEqual(4) + + boxTuple.create({ size: 2 }) + boxTuple.value = new Tuple(new UintN8(1), new UintN8(2), new Bool(true), new Bool(false), new Str('hello')) + expect(boxTuple.length).toEqual(12) + }) + }) + + it('can set value if size provided is larger than required for dynamic value type', () => { + ctx.txn.createScope([ctx.any.txn.applicationCall()]).execute(() => { + const boxStr = Box({ key: 'a' }) + const boxStaticArray = Box, 10>>({ key: 'c' }) + const boxDynamicArray = Box>({ key: 'd' }) + const boxTuple = Box>({ key: 'e' }) + + boxStr.create({ size: 200 }) + boxStr.value = 'hello' + expect(boxStr.length).toEqual(5) + + boxStaticArray.create({ size: 200 }) + boxStaticArray.value = new StaticArray( + new DynamicArray(new UintN32(100), new UintN32(200)), + new DynamicArray(new UintN32(300), new UintN32(400)), + new DynamicArray(new UintN32(500), new UintN32(600)), + new DynamicArray(new UintN32(700), new UintN32(800)), + new DynamicArray(new UintN32(900), new UintN32(1000)), + new DynamicArray(new UintN32(1100), new UintN32(1200)), + new DynamicArray(new UintN32(1300), new UintN32(1400)), + new DynamicArray(new UintN32(1500), new UintN32(1600)), + new DynamicArray(new UintN32(1700), new UintN32(1800)), + new DynamicArray(new UintN32(1900), new UintN32(2000)), + ) + expect(boxStaticArray.length).toEqual(120) + + boxDynamicArray.create({ size: 200 }) + boxDynamicArray.value = new DynamicArray(new UintN8(100), new UintN8(200)) + expect(boxDynamicArray.length).toEqual(4) + + boxTuple.create({ size: 200 }) + boxTuple.value = new Tuple(new UintN8(1), new UintN8(2), new Bool(true), new Bool(false), new Str('hello')) + expect(boxTuple.length).toEqual(12) + }) + }) + }) }) From 51d68f6e60dc3bfa90fa7ceaba62e24bd042fa50 Mon Sep 17 00:00:00 2001 From: Bobby Lat Date: Mon, 7 Apr 2025 18:54:24 +0800 Subject: [PATCH 02/68] feat: implement stubs for encode/decode native arrays --- src/encoders.ts | 2 +- src/impl/encoded-types.ts | 47 +++++++++++---- src/test-transformer/node-factory.ts | 8 ++- src/test-transformer/visitors.ts | 16 +++-- tests/arc4/encode-decode-arc4.spec.ts | 87 +++++++++++++++++++++++++-- 5 files changed, 135 insertions(+), 25 deletions(-) diff --git a/src/encoders.ts b/src/encoders.ts index a609cf94..4afb0671 100644 --- a/src/encoders.ts +++ b/src/encoders.ts @@ -85,7 +85,7 @@ export const toBytes = (val: unknown): bytes => { return val.bytes } if (Array.isArray(val) || typeof val === 'object') { - return encodeArc4Impl('', val) + return encodeArc4Impl(undefined, val) } throw new InternalError(`Invalid type for bytes: ${nameOfType(val)}`) } diff --git a/src/impl/encoded-types.ts b/src/impl/encoded-types.ts index 9fccf8e6..08b4c408 100644 --- a/src/impl/encoded-types.ts +++ b/src/impl/encoded-types.ts @@ -48,6 +48,7 @@ import { conactUint8Arrays, uint8ArrayToNumber, } from '../util' +import { BytesBackedCls, Uint64BackedCls } from './base' import type { StubBytesCompat } from './primitives' import { AlgoTsPrimitiveCls, arrayUtil, BigUintCls, Bytes, BytesCls, getUint8Array, isBytes, Uint64Cls } from './primitives' import { Account, AccountCls, ApplicationCls, AssetCls } from './reference' @@ -1297,31 +1298,49 @@ export const getArc4TypeName = (typeInfo: TypeInfo): string | undefined => { return undefined } -export function decodeArc4Impl(sourceTypeInfoString: string, bytes: StubBytesCompat, prefix: 'none' | 'log' = 'none'): T { +export function decodeArc4Impl( + sourceTypeInfoString: string, + targetTypeInfoString: string, + bytes: StubBytesCompat, + prefix: 'none' | 'log' = 'none', +): T { const sourceTypeInfo = JSON.parse(sourceTypeInfoString) + const targetTypeInfo = JSON.parse(targetTypeInfoString) const encoder = getArc4Encoder(sourceTypeInfo) - const source = encoder(bytes, sourceTypeInfo, prefix) - return getNativeValue(source) as T + const source = encoder(bytes, sourceTypeInfo, prefix) as { typeInfo: TypeInfo } + return getNativeValue(source, targetTypeInfo) as T } -export function encodeArc4Impl(_targetTypeInfoString: string | undefined, source: T): bytes { - const arc4Encoded = getArc4Encoded(source) +export function encodeArc4Impl(sourceTypeInfoString: string | undefined, source: T): bytes { + const arc4Encoded = getArc4Encoded(source, sourceTypeInfoString) return arc4Encoded.bytes } -const getNativeValue = (value: DeliberateAny): DeliberateAny => { +const getNativeValue = (value: DeliberateAny, targetTypeInfo: TypeInfo | undefined): DeliberateAny => { + if (value.typeInfo && value.typeInfo.name === targetTypeInfo?.name) { + return value + } const native = (value as DeliberateAny).native if (Array.isArray(native)) { - return native.map((item) => getNativeValue(item)) + return native.map((item) => getNativeValue(item, (targetTypeInfo?.genericArgs as { elementType: TypeInfo })?.elementType)) } else if (native instanceof AlgoTsPrimitiveCls) { return native + } else if (native instanceof BytesBackedCls) { + return native.bytes + } else if (native instanceof Uint64BackedCls) { + return native.uint64 } else if (typeof native === 'object') { - return Object.fromEntries(Object.entries(native).map(([key, value]) => [key, getNativeValue(value)])) + return Object.fromEntries( + Object.entries(native).map(([key, value], index) => [ + key, + getNativeValue(value, (targetTypeInfo?.genericArgs as TypeInfo[])?.[index]), + ]), + ) } return native } -export const getArc4Encoded = (value: DeliberateAny): ARC4Encoded => { +export const getArc4Encoded = (value: DeliberateAny, sourceTypeInfoString?: string): ARC4Encoded => { if (value instanceof ARC4Encoded) { return value } @@ -1362,9 +1381,15 @@ export const getArc4Encoded = (value: DeliberateAny): ARC4Encoded => { const result: ARC4Encoded[] = value.reduce((acc: ARC4Encoded[], cur: DeliberateAny) => { return acc.concat(getArc4Encoded(cur)) }, []) + const sourceTypeInfo = sourceTypeInfoString ? JSON.parse(sourceTypeInfoString) : undefined const genericArgs: TypeInfo[] = result.map((x) => (x as DeliberateAny).typeInfo) - const typeInfo = { name: `Tuple<[${genericArgs.map((x) => x.name).join(',')}]>`, genericArgs } - return new TupleImpl(typeInfo, ...(result as [ARC4Encoded, ...ARC4Encoded[]])) + if (sourceTypeInfo?.name?.startsWith('Array')) { + const typeInfo = { name: `DynamicArray<${genericArgs[0].name}>`, genericArgs: { elementType: genericArgs[0] } } + return new DynamicArrayImpl(typeInfo, ...(result as [ARC4Encoded, ...ARC4Encoded[]])) + } else { + const typeInfo = { name: `Tuple<[${genericArgs.map((x) => x.name).join(',')}]>`, genericArgs } + return new TupleImpl(typeInfo, ...(result as [ARC4Encoded, ...ARC4Encoded[]])) + } } if (typeof value === 'object') { const result = Object.values(value).reduce((acc: ARC4Encoded[], cur: DeliberateAny) => { diff --git a/src/test-transformer/node-factory.ts b/src/test-transformer/node-factory.ts index 1a65542c..575546ff 100644 --- a/src/test-transformer/node-factory.ts +++ b/src/test-transformer/node-factory.ts @@ -94,8 +94,10 @@ export const nodeFactory = { ) }, - callStubbedFunction(functionName: string, node: ts.CallExpression, typeInfo?: TypeInfo) { - const typeInfoArg = typeInfo ? factory.createStringLiteral(JSON.stringify(typeInfo)) : undefined + callStubbedFunction(functionName: string, node: ts.CallExpression, typeInfo?: TypeInfo | TypeInfo[]) { + const typeInfoArgs = typeInfo + ? (Array.isArray(typeInfo) ? typeInfo : [typeInfo]).map((t) => factory.createStringLiteral(JSON.stringify(t))) + : undefined const updatedPropertyAccessExpression = factory.createPropertyAccessExpression( factory.createIdentifier('runtimeHelpers'), `${functionName}Impl`, @@ -104,7 +106,7 @@ export const nodeFactory = { return factory.createCallExpression( updatedPropertyAccessExpression, node.typeArguments, - [typeInfoArg, ...(node.arguments ?? [])].filter((arg) => !!arg), + [...(typeInfoArgs ?? []), ...(node.arguments ?? [])].filter((arg) => !!arg), ) }, diff --git a/src/test-transformer/visitors.ts b/src/test-transformer/visitors.ts index 8131f4ea..1b6efdcb 100644 --- a/src/test-transformer/visitors.ts +++ b/src/test-transformer/visitors.ts @@ -159,13 +159,16 @@ class ExpressionVisitor { if (ts.isCallExpression(updatedNode)) { const stubbedFunctionName = this.stubbedFunctionName ?? tryGetStubbedFunctionName(updatedNode, this.helper) this.stubbedFunctionName = undefined - let infoArg = info + let infoArg: TypeInfo | TypeInfo[] | undefined = info if (isCallingEmit(stubbedFunctionName)) { infoArg = this.helper.resolveTypeParameters(updatedNode).map(getGenericTypeInfo)[0] } else if (isCallingDecodeArc4(stubbedFunctionName)) { - const targetType = ptypes.ptypeToArc4EncodedType(type, this.helper.sourceLocation(node)) - const targetTypeInfo = getGenericTypeInfo(targetType) - infoArg = targetTypeInfo + const sourceType = ptypes.ptypeToArc4EncodedType(type, this.helper.sourceLocation(node)) + const sourceTypeInfo = getGenericTypeInfo(sourceType) + const targetTypeInfo = getGenericTypeInfo(type) + infoArg = [sourceTypeInfo, targetTypeInfo] + } else if (isCallingEncodeArc4(stubbedFunctionName)) { + infoArg = this.helper.resolveTypeParameters(updatedNode).map(getGenericTypeInfo)[0] } else if (isCallingArc4EncodedLength(stubbedFunctionName)) { infoArg = this.helper.resolveTypeParameters(updatedNode).map(getGenericTypeInfo)[0] } @@ -384,7 +387,7 @@ const getGenericTypeInfo = (type: ptypes.PType): TypeInfo => { } else if (type instanceof ptypes.BoxMapPType) { genericArgs.push(getGenericTypeInfo(type.keyType)) genericArgs.push(getGenericTypeInfo(type.contentType)) - } else if (instanceOfAny(type, ptypes.StaticArrayType, ptypes.DynamicArrayType)) { + } else if (instanceOfAny(type, ptypes.StaticArrayType, ptypes.DynamicArrayType, ptypes.ArrayPType)) { const entries = [] entries.push(['elementType', getGenericTypeInfo(type.elementType)]) if (instanceOfAny(type, ptypes.StaticArrayType)) { @@ -428,7 +431,8 @@ const tryGetStubbedFunctionName = (node: ts.CallExpression, helper: VisitorHelpe return stubbedFunctionNames.includes(functionName) ? functionName : undefined } -const isCallingDecodeArc4 = (functionName: string | undefined): boolean => ['decodeArc4', 'encodeArc4'].includes(functionName ?? '') +const isCallingDecodeArc4 = (functionName: string | undefined): boolean => ['decodeArc4'].includes(functionName ?? '') +const isCallingEncodeArc4 = (functionName: string | undefined): boolean => ['encodeArc4'].includes(functionName ?? '') const isCallingArc4EncodedLength = (functionName: string | undefined): boolean => 'arc4EncodedLength' === (functionName ?? '') const isCallingEmit = (functionName: string | undefined): boolean => 'emit' === (functionName ?? '') const isCallingMethodSelector = (functionName: string | undefined): boolean => 'methodSelector' === (functionName ?? '') diff --git a/tests/arc4/encode-decode-arc4.spec.ts b/tests/arc4/encode-decode-arc4.spec.ts index b1449fa9..a0464cd5 100644 --- a/tests/arc4/encode-decode-arc4.spec.ts +++ b/tests/arc4/encode-decode-arc4.spec.ts @@ -1,17 +1,23 @@ import type { biguint, bytes, uint64 } from '@algorandfoundation/algorand-typescript' import { Bytes } from '@algorandfoundation/algorand-typescript' -import type { Address, StaticArray, StaticBytes, UFixedNxM, UintN64 } from '@algorandfoundation/algorand-typescript/arc4' +import type { StaticBytes, UFixedNxM } from '@algorandfoundation/algorand-typescript/arc4' import { + Address, arc4EncodedLength, Bool, decodeArc4, + DynamicArray, DynamicBytes, encodeArc4, + StaticArray, Str, Struct, Tuple, UintN, + UintN64, } from '@algorandfoundation/algorand-typescript/arc4' +import { itob } from '@algorandfoundation/algorand-typescript/op' +import { encodingUtil } from '@algorandfoundation/puya-ts' import { describe, expect, test } from 'vitest' import { MAX_UINT128 } from '../../src/constants' import type { StubBytesCompat } from '../../src/impl/primitives' @@ -30,6 +36,7 @@ const abiUint512 = new UintN<512>(MAX_UINT128) const abiBool = new Bool(true) const abiBytes = new DynamicBytes(Bytes('hello')) +type TestObj = { a: UintN64; b: DynamicBytes } class Swapped1 extends Struct<{ b: UintN<64> c: Bool @@ -40,7 +47,7 @@ class Swapped1 extends Struct<{ const testData = [ { nativeValues() { - return [nativeNumber, nativeNumber, nativeBigInt, nativeBytes] + return [nativeNumber, nativeNumber, nativeBigInt, nativeBytes] as readonly [uint64, uint64, biguint, bytes] }, abiValues() { return [abiUint64, abiUint64, abiUint512, abiBytes] as readonly [UintN<64>, UintN<64>, UintN<512>, DynamicBytes] @@ -48,6 +55,9 @@ const testData = [ arc4Value() { return new Tuple<[UintN<64>, UintN<64>, UintN<512>, DynamicBytes]>(abiUint64, abiUint64, abiUint512, abiBytes) }, + encode() { + return encodeArc4(this.nativeValues()) + }, decode(value: StubBytesCompat) { return decodeArc4<[uint64, uint64, biguint, bytes]>(asBytes(value)) }, @@ -58,6 +68,19 @@ const testData = [ [nativeBool, [nativeString, nativeBool]], [nativeNumber, nativeNumber], [nativeBigInt, nativeBytes, { b: nativeNumber, c: nativeBool, d: nativeString, a: [nativeNumber, nativeBool, nativeBool] }], + ] as readonly [ + [boolean, [string, boolean]], + [uint64, uint64], + [ + biguint, + bytes, + { + b: uint64 + c: boolean + d: string + a: [uint64, boolean, boolean] + }, + ], ] }, abiValues() { @@ -76,6 +99,9 @@ const testData = [ ...this.abiValues(), ) }, + encode() { + return encodeArc4(this.nativeValues()) + }, decode(value: StubBytesCompat) { return decodeArc4< [ @@ -105,6 +131,9 @@ const testData = [ arc4Value() { return new Swapped1(this.abiValues()) }, + encode() { + return encodeArc4(this.nativeValues()) + }, decode(value: StubBytesCompat) { return decodeArc4<{ b: uint64; c: boolean; d: string; a: [uint64, boolean, boolean] }>(asBytes(value)) }, @@ -120,17 +149,67 @@ describe('decodeArc4', () => { compareNativeValues(result, nativeValues) }) + test('should be able to decode arrays', () => { + const a = 234234 + const aBytes = asBytes(encodingUtil.bigIntToUint8Array(234234n, 8)) + const b = true + const bBytes = asBytes(encodingUtil.bigIntToUint8Array(128n, 1)) + const c = 340943934n + const cBytes = asBytes(encodingUtil.bigIntToUint8Array(340943934n, 512 / 8)) + const d = 'hello world' + const dBytes = asBytes( + new Uint8Array([ + ...encodingUtil.bigIntToUint8Array(BigInt('hello world'.length), 2), + ...encodingUtil.utf8ToUint8Array('hello world'), + ]), + ) + const e = { a: 50n, b: new Uint8Array([1, 2, 3, 4, 5]) } + const eBytes = asBytes(new Uint8Array([...encodingUtil.bigIntToUint8Array(50n, 8), 0, 10, 0, 5, 1, 2, 3, 4, 5])) + const f = new Address(Bytes.fromHex(`${'00'.repeat(31)}ff`)) + const fBytes = Bytes.fromHex(`${'00'.repeat(31)}ff`) + expect(decodeArc4(aBytes)).toEqual(a) + expect(decodeArc4(bBytes)).toEqual(b) + expect(decodeArc4(cBytes)).toEqual(c) + expect(decodeArc4(dBytes)).toEqual(d) + expect(decodeArc4(eBytes)).toEqual(e) + + const lenPrefix = itob(1).slice(6, 8) + const offsetHeader = itob(2).slice(6, 8) + expect(decodeArc4(lenPrefix.concat(aBytes))).toEqual([a]) + expect(decodeArc4(lenPrefix.concat(bBytes))).toEqual([b]) + expect(decodeArc4(lenPrefix.concat(cBytes))).toEqual([c]) + expect(decodeArc4(Bytes`${lenPrefix}${offsetHeader}${dBytes}`)).toEqual([d]) + expect(decodeArc4(Bytes`${lenPrefix}${offsetHeader}${eBytes}`)).toEqual([e]) + expect(JSON.stringify(decodeArc4(Bytes`${lenPrefix}${fBytes}`))).toEqual(JSON.stringify([f])) + }) }) describe('encodeArc4', () => { test.each(testData)('should encode native values', (data) => { - const nativeValues = data.nativeValues() const arc4Value = data.arc4Value() - const result = encodeArc4(nativeValues) + const result = data.encode() expect(result).toEqual(arc4Value.bytes) }) + test('should be able to encode arrays', () => { + const address = new Address(Bytes.fromHex(`${'00'.repeat(31)}ff`)) + expect(encodeArc4(address)).toEqual(address.bytes) + + expect(encodeArc4([nativeNumber])).toEqual(new StaticArray(new UintN64(nativeNumber)).bytes) + expect(encodeArc4([nativeBool])).toEqual(new StaticArray(new Bool(nativeBool)).bytes) + expect(encodeArc4([nativeBigInt])).toEqual(new StaticArray(new UintN<512>(nativeBigInt)).bytes) + expect(encodeArc4([nativeBytes])).toEqual(new StaticArray(new DynamicBytes(nativeBytes)).bytes) + expect(encodeArc4([nativeString])).toEqual(new StaticArray(new Str(nativeString)).bytes) + expect(encodeArc4([address])).toEqual(new StaticArray(address).bytes) + + expect(encodeArc4([nativeNumber])).toEqual(new DynamicArray(new UintN64(nativeNumber)).bytes) + expect(encodeArc4([nativeBool])).toEqual(new DynamicArray(new Bool(nativeBool)).bytes) + expect(encodeArc4([nativeBigInt])).toEqual(new DynamicArray(new UintN<512>(nativeBigInt)).bytes) + expect(encodeArc4([nativeBytes])).toEqual(new DynamicArray(new DynamicBytes(nativeBytes)).bytes) + expect(encodeArc4([nativeString])).toEqual(new DynamicArray(new Str(nativeString)).bytes) + expect(encodeArc4([address])).toEqual(new DynamicArray(address).bytes) + }) }) class StaticStruct extends Struct<{ From 05a92d17dfe7a105fd7f4704dd63888f88484960 Mon Sep 17 00:00:00 2001 From: Bobby Lat Date: Tue, 15 Apr 2025 16:36:24 +0800 Subject: [PATCH 03/68] feat: recognise conventional methods and use appropriate OnCompleteAction as default --- examples/auction/contract.algo.ts | 3 +- src/abi-metadata.ts | 41 +++++++++++++++++++++++++- src/constants.ts | 10 +++++++ src/impl/c2c.ts | 9 +++++- src/subcontexts/contract-context.ts | 3 ++ tests/references/arc4-contract.spec.ts | 9 +++--- 6 files changed, 66 insertions(+), 9 deletions(-) diff --git a/examples/auction/contract.algo.ts b/examples/auction/contract.algo.ts index 725d7479..c2ef3296 100644 --- a/examples/auction/contract.algo.ts +++ b/examples/auction/contract.algo.ts @@ -26,7 +26,6 @@ export class Auction extends Contract { claimableAmount = LocalState() - @abimethod({ allowActions: 'NoOp', onCreate: 'require' }) public createApplication(): void { this.auctionEnd.value = 0 this.previousBid.value = 0 @@ -82,7 +81,7 @@ export class Auction extends Contract { } @abimethod({ allowActions: 'OptIn' }) - public optInToApplication(): void {} + public optInToApplication(): void { } public bid(payment: gtxn.PaymentTxn): void { /// Ensure auction hasn't ended diff --git a/src/abi-metadata.ts b/src/abi-metadata.ts index 271f61d9..966fd12e 100644 --- a/src/abi-metadata.ts +++ b/src/abi-metadata.ts @@ -1,6 +1,7 @@ import type { OnCompleteActionStr } from '@algorandfoundation/algorand-typescript' import type { CreateOptions } from '@algorandfoundation/algorand-typescript/arc4' import js_sha512 from 'js-sha512' +import { ConventionalRouting } from './constants' import type { TypeInfo } from './encoders' import { Arc4MethodConfigSymbol, Contract } from './impl/contract' import { getArc4TypeName as getArc4TypeNameForARC4Encoded } from './impl/encoded-types' @@ -22,7 +23,12 @@ export const attachAbiMetadata = (contract: { new (): Contract }, methodName: st metadataStore.set(contract, {}) } const metadatas: Record = metadataStore.get(contract) as Record - metadatas[methodName] = metadata + const conventionalRoutingConfig = getConventionalRoutingConfig(methodName) + metadatas[methodName] = { + ...metadata, + allowActions: metadata.allowActions ?? conventionalRoutingConfig?.allowActions, + onCreate: metadata.onCreate ?? conventionalRoutingConfig?.onCreate, + } } export const getContractAbiMetadata = (contract: T | { new (): T }): Record => { @@ -110,3 +116,36 @@ const getArc4TypeName = (t: TypeInfo): string => { } return entry } + +/** + * Get routing properties inferred by conventional naming + * @param methodName The name of the method + */ +const getConventionalRoutingConfig = (methodName: string): Pick | undefined => { + switch (methodName) { + case ConventionalRouting.methodNames.closeOutOfApplication: + return { + allowActions: ['CloseOut'], + onCreate: 'disallow', + } + case ConventionalRouting.methodNames.createApplication: + return { + onCreate: 'require', + } + case ConventionalRouting.methodNames.deleteApplication: + return { + allowActions: ['DeleteApplication'], + } + case ConventionalRouting.methodNames.optInToApplication: + return { + allowActions: ['OptIn'], + } + case ConventionalRouting.methodNames.updateApplication: + return { + allowActions: ['UpdateApplication'], + onCreate: 'disallow', + } + default: + return undefined + } +} diff --git a/src/constants.ts b/src/constants.ts index ddaa1740..7c1592b9 100644 --- a/src/constants.ts +++ b/src/constants.ts @@ -66,3 +66,13 @@ export enum OnApplicationComplete { UpdateApplicationOC = 4, DeleteApplicationOC = 5, } + +export const ConventionalRouting = { + methodNames: { + closeOutOfApplication: 'closeOutOfApplication', + createApplication: 'createApplication', + deleteApplication: 'deleteApplication', + optInToApplication: 'optInToApplication', + updateApplication: 'updateApplication', + }, +} diff --git a/src/impl/c2c.ts b/src/impl/c2c.ts index 9b60e4be..39c59183 100644 --- a/src/impl/c2c.ts +++ b/src/impl/c2c.ts @@ -89,8 +89,15 @@ export function abiCall( methodArgs: TypedApplicationCallFields, contract?: Contract | { new (): Contract }, ): { itxn: ApplicationCallInnerTxn; returnValue: TReturn | undefined } { + const abiMetadata = contract ? getContractMethodAbiMetadata(contract, method.name) : undefined const selector = methodSelector(method, contract) - const itxnContext = ApplicationCallInnerTxnContext.createFromTypedApplicationCallFields(methodArgs, selector) + const itxnContext = ApplicationCallInnerTxnContext.createFromTypedApplicationCallFields( + { + ...methodArgs, + onCompletion: methodArgs.onCompletion ?? abiMetadata?.allowActions?.map((action) => OnCompleteAction[action])[0], + }, + selector, + ) invokeCallback(itxnContext) return { diff --git a/src/subcontexts/contract-context.ts b/src/subcontexts/contract-context.ts index e153b4e6..6b2b75f4 100644 --- a/src/subcontexts/contract-context.ts +++ b/src/subcontexts/contract-context.ts @@ -279,6 +279,9 @@ const getContractOptions = (contract: BaseContract): ContractOptionsParameter | } const hasCreateMethods = (contract: Contract) => { + const createFn = Reflect.get(contract, 'createApplication') + if (createFn !== undefined && typeof createFn === 'function') return true + const metadatas = getContractAbiMetadata(contract) return Object.values(metadatas).some((metadata) => (metadata.onCreate ?? 'disallow') !== 'disallow') } diff --git a/tests/references/arc4-contract.spec.ts b/tests/references/arc4-contract.spec.ts index 8f28da08..05bff693 100644 --- a/tests/references/arc4-contract.spec.ts +++ b/tests/references/arc4-contract.spec.ts @@ -1,5 +1,5 @@ import type { Account, bytes, uint64 } from '@algorandfoundation/algorand-typescript' -import { arc4, assert, BaseContract, Bytes, contract, Contract, Global, Txn, Uint64 } from '@algorandfoundation/algorand-typescript' +import { assert, BaseContract, Bytes, contract, Contract, Global, Txn, Uint64 } from '@algorandfoundation/algorand-typescript' import { TestExecutionContext } from '@algorandfoundation/algorand-typescript-testing' import { afterEach, describe, expect, it } from 'vitest' import { lazyContext } from '../../src/context-helpers/internal-context' @@ -39,8 +39,7 @@ class ContractARC4Create extends Contract { this.#stateTotals = Uint64() } - @arc4.abimethod({ onCreate: 'require' }) - create(val: uint64): void { + createApplication(val: uint64): void { this.arg1 = val assert(Global.currentApplicationId.globalNumBytes === 4) assert(Global.currentApplicationId.globalNumUint === 5) @@ -82,7 +81,7 @@ describe('arc4 contract creation', () => { const contract = ctx.contract.create(ContractARC4Create) ctx.txn.createScope([ctx.any.txn.applicationCall({ appId: ctx.ledger.getApplicationForContract(contract), sender })]).execute(() => { - contract.create(arg1) + contract.createApplication(arg1) expect(contract.arg1).toEqual(arg1) expect(contract.creator).toEqual(sender) }) @@ -96,7 +95,7 @@ describe('arc4 contract creation', () => { const appData = lazyContext.getApplicationData(contract) expect(appData.isCreating).toBe(true) - contract.create(arg1) + contract.createApplication(arg1) expect(appData.isCreating).toBe(false) expect(contract.arg1).toEqual(arg1) From 2a69a0dc88d4e77937b4c5e4871b3c6c5e459a50 Mon Sep 17 00:00:00 2001 From: Bobby Lat Date: Wed, 16 Apr 2025 11:19:33 +0800 Subject: [PATCH 04/68] fix: encode bigint values as bytes correctly without overflowing --- examples/auction/contract.algo.ts | 2 +- package-lock.json | 18 +++++++++--------- package.json | 4 ++-- src/encoders.ts | 2 +- src/util.ts | 4 +++- 5 files changed, 16 insertions(+), 14 deletions(-) diff --git a/examples/auction/contract.algo.ts b/examples/auction/contract.algo.ts index c2ef3296..f80ad001 100644 --- a/examples/auction/contract.algo.ts +++ b/examples/auction/contract.algo.ts @@ -81,7 +81,7 @@ export class Auction extends Contract { } @abimethod({ allowActions: 'OptIn' }) - public optInToApplication(): void { } + public optInToApplication(): void {} public bid(payment: gtxn.PaymentTxn): void { /// Ensure auction hasn't ended diff --git a/package-lock.json b/package-lock.json index 80565661..d9d7e254 100644 --- a/package-lock.json +++ b/package-lock.json @@ -8,8 +8,8 @@ "name": "@algorandfoundation/algorand-typescript-testing", "version": "1.0.0", "dependencies": { - "@algorandfoundation/algorand-typescript": "1.0.0-alpha.47", - "@algorandfoundation/puya-ts": "1.0.0-alpha.47", + "@algorandfoundation/algorand-typescript": "1.0.0-alpha.49", + "@algorandfoundation/puya-ts": "1.0.0-alpha.49", "elliptic": "^6.6.1", "js-sha256": "^0.11.0", "js-sha3": "^0.9.3", @@ -73,21 +73,21 @@ } }, "node_modules/@algorandfoundation/algorand-typescript": { - "version": "1.0.0-alpha.47", - "resolved": "https://registry.npmjs.org/@algorandfoundation/algorand-typescript/-/algorand-typescript-1.0.0-alpha.47.tgz", - "integrity": "sha512-7DKwvm5IdJy//OCo2eK+kwIY59OyNU/Nxlu9KT61PA+pirY5t9eZqeLczQobA63NCBDgh+BXJKrzO0pEYIEAvg==", + "version": "1.0.0-alpha.49", + "resolved": "https://registry.npmjs.org/@algorandfoundation/algorand-typescript/-/algorand-typescript-1.0.0-alpha.49.tgz", + "integrity": "sha512-fZhuOGl9iAzeea7VvK0VCvi0aXy1yDjgIYKvNgG+4wwFNeUTVBDZGjwWspDYhlO5Jk+iaJX1kVLgwwh+/XG4+A==", "peerDependencies": { "tslib": "^2.6.2" } }, "node_modules/@algorandfoundation/puya-ts": { - "version": "1.0.0-alpha.47", - "resolved": "https://registry.npmjs.org/@algorandfoundation/puya-ts/-/puya-ts-1.0.0-alpha.47.tgz", - "integrity": "sha512-yBLzGb7jdt3Bma7ErT88YbWKGw8dqMC1xMh5rY8bIXvGthf+uwZsjvOQ/IxIRnYVljZ2ZBuolmvPVAhgF+4gZg==", + "version": "1.0.0-alpha.49", + "resolved": "https://registry.npmjs.org/@algorandfoundation/puya-ts/-/puya-ts-1.0.0-alpha.49.tgz", + "integrity": "sha512-WV4nBLgJ1u/vMQja1c1K0UYFwmZlmzGDovBzR4AJkyq8+/GSwKviIC1c1/FbR6w4ibE/dlzDuZcc2AouY00gjg==", "hasInstallScript": true, "license": "MIT", "dependencies": { - "@algorandfoundation/algorand-typescript": "1.0.0-alpha.47", + "@algorandfoundation/algorand-typescript": "1.0.0-alpha.49", "arcsecond": "^5.0.0", "argparse": "^2.0.1", "chalk": "^5.4.1", diff --git a/package.json b/package.json index a437619b..1ab5a994 100644 --- a/package.json +++ b/package.json @@ -66,8 +66,8 @@ "vitest": "3.0.9" }, "dependencies": { - "@algorandfoundation/algorand-typescript": "1.0.0-alpha.47", - "@algorandfoundation/puya-ts": "1.0.0-alpha.47", + "@algorandfoundation/algorand-typescript": "1.0.0-alpha.49", + "@algorandfoundation/puya-ts": "1.0.0-alpha.49", "elliptic": "^6.6.1", "js-sha256": "^0.11.0", "js-sha3": "^0.9.3", diff --git a/src/encoders.ts b/src/encoders.ts index 4afb0671..14470793 100644 --- a/src/encoders.ts +++ b/src/encoders.ts @@ -63,7 +63,7 @@ export const getEncoder = (typeInfo: TypeInfo): fromBytes => { } export const toBytes = (val: unknown): bytes => { - const uint64Val = asMaybeUint64Cls(val) + const uint64Val = asMaybeUint64Cls(val, false) if (uint64Val !== undefined) { return uint64Val.toBytes().asAlgoTs() } diff --git a/src/util.ts b/src/util.ts index 62e4a92a..d2c0eea3 100644 --- a/src/util.ts +++ b/src/util.ts @@ -42,12 +42,14 @@ export const asBytes = (val: StubBytesCompat | Uint8Array) => asBytesCls(val).as export const asUint8Array = (val: StubBytesCompat | Uint8Array) => asBytesCls(val).asUint8Array() -export const asMaybeUint64Cls = (val: DeliberateAny) => { +export const asMaybeUint64Cls = (val: DeliberateAny, throwsOverflow: boolean = true) => { try { return Uint64Cls.fromCompat(val) } catch (e) { if (e instanceof InternalError) { // swallow error and return undefined + } else if (!throwsOverflow && e instanceof AvmError && e.message.includes('overflow')) { + // swallow overflow error and return undefined } else { throw e } From 93c826b2b9c5acbd60b430cf7d4964a9eb895686 Mon Sep 17 00:00:00 2001 From: Bobby Lat Date: Thu, 24 Apr 2025 14:32:49 +0800 Subject: [PATCH 05/68] feat: support not expressions on match and assertMatch --- package-lock.json | 18 +++++++-------- package.json | 4 ++-- src/impl/match.ts | 9 +++++--- tests/artifacts/state-ops/contract.algo.ts | 2 +- tests/match.spec.ts | 27 ++++++++++++++++++++++ 5 files changed, 45 insertions(+), 15 deletions(-) diff --git a/package-lock.json b/package-lock.json index d9d7e254..94859f0e 100644 --- a/package-lock.json +++ b/package-lock.json @@ -8,8 +8,8 @@ "name": "@algorandfoundation/algorand-typescript-testing", "version": "1.0.0", "dependencies": { - "@algorandfoundation/algorand-typescript": "1.0.0-alpha.49", - "@algorandfoundation/puya-ts": "1.0.0-alpha.49", + "@algorandfoundation/algorand-typescript": "1.0.0-alpha.51", + "@algorandfoundation/puya-ts": "1.0.0-alpha.51", "elliptic": "^6.6.1", "js-sha256": "^0.11.0", "js-sha3": "^0.9.3", @@ -73,21 +73,21 @@ } }, "node_modules/@algorandfoundation/algorand-typescript": { - "version": "1.0.0-alpha.49", - "resolved": "https://registry.npmjs.org/@algorandfoundation/algorand-typescript/-/algorand-typescript-1.0.0-alpha.49.tgz", - "integrity": "sha512-fZhuOGl9iAzeea7VvK0VCvi0aXy1yDjgIYKvNgG+4wwFNeUTVBDZGjwWspDYhlO5Jk+iaJX1kVLgwwh+/XG4+A==", + "version": "1.0.0-alpha.51", + "resolved": "https://registry.npmjs.org/@algorandfoundation/algorand-typescript/-/algorand-typescript-1.0.0-alpha.51.tgz", + "integrity": "sha512-r6QHmKvoO+//Vo3gM2vfjbAHBsUnrLTejNrGyETD1s/95hHAo5+VgqOIwo4fkfuvtErQXEj8hfCHcMtSO4MBGQ==", "peerDependencies": { "tslib": "^2.6.2" } }, "node_modules/@algorandfoundation/puya-ts": { - "version": "1.0.0-alpha.49", - "resolved": "https://registry.npmjs.org/@algorandfoundation/puya-ts/-/puya-ts-1.0.0-alpha.49.tgz", - "integrity": "sha512-WV4nBLgJ1u/vMQja1c1K0UYFwmZlmzGDovBzR4AJkyq8+/GSwKviIC1c1/FbR6w4ibE/dlzDuZcc2AouY00gjg==", + "version": "1.0.0-alpha.51", + "resolved": "https://registry.npmjs.org/@algorandfoundation/puya-ts/-/puya-ts-1.0.0-alpha.51.tgz", + "integrity": "sha512-5rfhj4+DIKMRthqHaibiKBze0kQyLQuIalQ8Y6U6bEPYyItWWq7e7cyAcA1gmv87uGVYcQF3JN6O9S1a3f3l3w==", "hasInstallScript": true, "license": "MIT", "dependencies": { - "@algorandfoundation/algorand-typescript": "1.0.0-alpha.49", + "@algorandfoundation/algorand-typescript": "1.0.0-alpha.51", "arcsecond": "^5.0.0", "argparse": "^2.0.1", "chalk": "^5.4.1", diff --git a/package.json b/package.json index 1ab5a994..7894422f 100644 --- a/package.json +++ b/package.json @@ -66,8 +66,8 @@ "vitest": "3.0.9" }, "dependencies": { - "@algorandfoundation/algorand-typescript": "1.0.0-alpha.49", - "@algorandfoundation/puya-ts": "1.0.0-alpha.49", + "@algorandfoundation/algorand-typescript": "1.0.0-alpha.51", + "@algorandfoundation/puya-ts": "1.0.0-alpha.51", "elliptic": "^6.6.1", "js-sha256": "^0.11.0", "js-sha3": "^0.9.3", diff --git a/src/impl/match.ts b/src/impl/match.ts index c1c2c105..ca553c95 100644 --- a/src/impl/match.ts +++ b/src/impl/match.ts @@ -7,6 +7,9 @@ import type { StubBytesCompat, Uint64Cls } from './primitives' import { BytesCls } from './primitives' export const matchImpl: typeof match = (subject, test): boolean => { + if (Object.hasOwn(test, 'not')) { + return !matchImpl(subject, (test as DeliberateAny).not) + } const bigIntSubjectValue = getBigIntValue(subject) if (bigIntSubjectValue !== undefined) { const bigIntTestValue = getBigIntValue(test) @@ -31,9 +34,9 @@ export const matchImpl: typeof match = (subject, test): boolean => { } else if (subject instanceof BytesBackedCls) { return subject.bytes.equals((test as unknown as BytesBackedCls).bytes) } else if (subject instanceof Uint64BackedCls) { - return ( - getBigIntValue(subject.uint64 as unknown as Uint64Cls) === - getBigIntValue((test as unknown as Uint64BackedCls).uint64 as unknown as Uint64Cls) + return matchImpl( + getBigIntValue(subject.uint64 as unknown as Uint64Cls), + getBigIntValue((test as unknown as Uint64BackedCls).uint64 as unknown as Uint64Cls), ) } else if (test instanceof ARC4Encoded) { return (subject as unknown as ARC4Encoded).bytes.equals(test.bytes) diff --git a/tests/artifacts/state-ops/contract.algo.ts b/tests/artifacts/state-ops/contract.algo.ts index 840a54df..c45e0f22 100644 --- a/tests/artifacts/state-ops/contract.algo.ts +++ b/tests/artifacts/state-ops/contract.algo.ts @@ -585,7 +585,7 @@ export class ItxnDemoContract extends BaseContract { createAppParams = itxn.applicationCall({ approvalProgram: APPROVE, clearStateProgram: APPROVE, - appArgs: [Bytes('3'), '4', Bytes('5')], + appArgs: [Bytes('3'), Bytes('4'), Bytes('5')], note: 'no args param set', }) } diff --git a/tests/match.spec.ts b/tests/match.spec.ts index 901d2a82..2ced7493 100644 --- a/tests/match.spec.ts +++ b/tests/match.spec.ts @@ -44,6 +44,14 @@ describe('match', () => { expected: true, }, { subject: { a: 42 }, test: { a: { between: [BigUint(MAX_UINT64), BigUint(MAX_UINT512)] as [biguint, biguint] } }, expected: false }, + { subject: 0, test: { not: 0 }, expected: false }, + { subject: 0, test: { not: 1 }, expected: true }, + { subject: 1, test: { not: 0 }, expected: true }, + { subject: 1, test: { not: 1 }, expected: false }, + { subject: 42n, test: { not: MAX_UINT512 }, expected: true }, + { subject: MAX_UINT512, test: { not: MAX_UINT512 }, expected: false }, + { subject: { a: 42 }, test: { a: { not: 3 } }, expected: true }, + { subject: { a: 42 }, test: { a: { not: 42 } }, expected: false }, ] const account1 = ctx.any.account() @@ -64,31 +72,50 @@ describe('match', () => { const testData = [ { subject: '', test: '', expected: true }, + { subject: '', test: { not: '' }, expected: false }, { subject: 'hello', test: 'hello', expected: true }, { subject: 'hello', test: 'world', expected: false }, + { subject: 'hello', test: { not: 'world' }, expected: true }, { subject: '', test: 'world', expected: false }, + { subject: '', test: { not: 'world' }, expected: true }, { subject: Bytes(), test: Bytes(), expected: true }, + { subject: Bytes(), test: { not: Bytes() }, expected: false }, { subject: Bytes('hello'), test: Bytes('hello'), expected: true }, + { subject: Bytes('hello'), test: { not: Bytes('hello') }, expected: false }, { subject: Bytes('hello'), test: Bytes('world'), expected: false }, + { subject: Bytes('hello'), test: { not: Bytes('world') }, expected: true }, { subject: Bytes(''), test: Bytes('world'), expected: false }, { subject: account1, test: account1, expected: true }, { subject: account1, test: sameAccount, expected: true }, + { subject: account1, test: { not: sameAccount }, expected: false }, { subject: account1, test: differentAccount, expected: false }, + { subject: account1, test: { not: differentAccount }, expected: true }, { subject: app1, test: app1, expected: true }, { subject: app1, test: sameApp, expected: true }, + { subject: app1, test: { not: sameApp }, expected: false }, { subject: app1, test: differentApp, expected: false }, + { subject: app1, test: { not: differentApp }, expected: true }, { subject: asset1, test: asset1, expected: true }, { subject: asset1, test: sameAsset, expected: true }, + { subject: asset1, test: { not: sameAsset }, expected: false }, { subject: asset1, test: differentAsset, expected: false }, + { subject: asset1, test: { not: differentAsset }, expected: true }, { subject: arc4Str1, test: arc4Str1, expected: true }, { subject: arc4Str1, test: sameArc4Str, expected: true }, { subject: arc4Str1, test: differentArc4Str, expected: false }, { subject: { a: 'hello', b: 42, c: arc4Str1 }, test: { a: 'hello', b: { lessThanEq: 42 }, c: sameArc4Str }, expected: true }, + { subject: { a: 'hello', b: 42, c: arc4Str1 }, test: { not: { a: 'hello', b: { lessThanEq: 42 }, c: sameArc4Str } }, expected: false }, { subject: { a: 'hello', b: 42, c: arc4Str1 }, test: { c: sameArc4Str }, expected: true }, { subject: { a: 'hello', b: 42, c: arc4Str1 }, test: { c: differentArc4Str }, expected: false }, + { subject: { a: 'hello', b: 42, c: arc4Str1 }, test: { not: { c: differentArc4Str } }, expected: true }, { subject: ['hello', 42, arc4Str1], test: ['hello', { lessThanEq: 42 }, sameArc4Str], expected: true }, { subject: ['hello', 42, arc4Str1], test: ['hello'], expected: true }, + { subject: ['hello', 42, arc4Str1], test: { not: ['hello'] }, expected: false }, { subject: ['hello', 42, arc4Str1], test: ['world'], expected: false }, + { subject: ['hello', 42, arc4Str1], test: { not: ['world'] }, expected: true }, + { subject: { x: 43 }, test: { not: { x: 3 } }, expected: true }, + { subject: { x: 43 }, test: { x: { not: 3 } }, expected: true }, + { subject: { x: 43 }, test: { not: { x: { not: 3 } } }, expected: false }, ] test.each(numericTestData)('should be able to match numeric data %s', (data) => { From 6032c0fbed21d89fe76c88ae6a0243db1011971c Mon Sep 17 00:00:00 2001 From: Bobby Lat Date: Thu, 8 May 2025 13:52:22 +0800 Subject: [PATCH 06/68] chore: update dependencies --- package-lock.json | 179 ++++++++++++++++++++++++++-------------------- package.json | 8 +-- 2 files changed, 104 insertions(+), 83 deletions(-) diff --git a/package-lock.json b/package-lock.json index 94859f0e..82001ec0 100644 --- a/package-lock.json +++ b/package-lock.json @@ -8,8 +8,8 @@ "name": "@algorandfoundation/algorand-typescript-testing", "version": "1.0.0", "dependencies": { - "@algorandfoundation/algorand-typescript": "1.0.0-alpha.51", - "@algorandfoundation/puya-ts": "1.0.0-alpha.51", + "@algorandfoundation/algorand-typescript": "1.0.0-alpha.54", + "@algorandfoundation/puya-ts": "1.0.0-alpha.54", "elliptic": "^6.6.1", "js-sha256": "^0.11.0", "js-sha3": "^0.9.3", @@ -34,7 +34,7 @@ "@types/node": "22.13.14", "@typescript-eslint/eslint-plugin": "8.28.0", "@typescript-eslint/parser": "8.28.0", - "@vitest/coverage-v8": "3.0.9", + "@vitest/coverage-v8": "3.1.3", "better-npm-audit": "3.11.0", "conventional-changelog-conventionalcommits": "^8.0.0", "copyfiles": "2.4.1", @@ -53,7 +53,7 @@ "typedoc-plugin-markdown": "^4.6.0", "typescript": "^5.8.2", "upath": "^2.0.1", - "vitest": "3.0.9" + "vitest": "3.1.3" } }, "node_modules/@algorandfoundation/algokit-utils": { @@ -73,21 +73,21 @@ } }, "node_modules/@algorandfoundation/algorand-typescript": { - "version": "1.0.0-alpha.51", - "resolved": "https://registry.npmjs.org/@algorandfoundation/algorand-typescript/-/algorand-typescript-1.0.0-alpha.51.tgz", - "integrity": "sha512-r6QHmKvoO+//Vo3gM2vfjbAHBsUnrLTejNrGyETD1s/95hHAo5+VgqOIwo4fkfuvtErQXEj8hfCHcMtSO4MBGQ==", + "version": "1.0.0-alpha.54", + "resolved": "https://registry.npmjs.org/@algorandfoundation/algorand-typescript/-/algorand-typescript-1.0.0-alpha.54.tgz", + "integrity": "sha512-GN3wb73oVJjjNPB8m7YdjHC1+50CDIHClB1YqJG6/1As/fMy80lchkqiCWuF3nCXGIWKGQj3Le4/5xMuE953hg==", "peerDependencies": { "tslib": "^2.6.2" } }, "node_modules/@algorandfoundation/puya-ts": { - "version": "1.0.0-alpha.51", - "resolved": "https://registry.npmjs.org/@algorandfoundation/puya-ts/-/puya-ts-1.0.0-alpha.51.tgz", - "integrity": "sha512-5rfhj4+DIKMRthqHaibiKBze0kQyLQuIalQ8Y6U6bEPYyItWWq7e7cyAcA1gmv87uGVYcQF3JN6O9S1a3f3l3w==", + "version": "1.0.0-alpha.54", + "resolved": "https://registry.npmjs.org/@algorandfoundation/puya-ts/-/puya-ts-1.0.0-alpha.54.tgz", + "integrity": "sha512-XudFwIpvppWr3gJu+gRuOt/n2V0sRurgDULcq5qduW0ddv7Tr33bqmw7rHjN9eRew4jxbjMNbPH9rG2p17J+kw==", "hasInstallScript": true, "license": "MIT", "dependencies": { - "@algorandfoundation/algorand-typescript": "1.0.0-alpha.51", + "@algorandfoundation/algorand-typescript": "1.0.0-alpha.54", "arcsecond": "^5.0.0", "argparse": "^2.0.1", "chalk": "^5.4.1", @@ -2680,9 +2680,9 @@ } }, "node_modules/@vitest/coverage-v8": { - "version": "3.0.9", - "resolved": "https://registry.npmjs.org/@vitest/coverage-v8/-/coverage-v8-3.0.9.tgz", - "integrity": "sha512-15OACZcBtQ34keIEn19JYTVuMFTlFrClclwWjHo/IRPg/8ELpkgNTl0o7WLP9WO9XGH6+tip9CPYtEOrIDJvBA==", + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/@vitest/coverage-v8/-/coverage-v8-3.1.3.tgz", + "integrity": "sha512-cj76U5gXCl3g88KSnf80kof6+6w+K4BjOflCl7t6yRJPDuCrHtVu0SgNYOUARJOL5TI8RScDbm5x4s1/P9bvpw==", "dev": true, "license": "MIT", "dependencies": { @@ -2695,7 +2695,7 @@ "istanbul-reports": "^3.1.7", "magic-string": "^0.30.17", "magicast": "^0.3.5", - "std-env": "^3.8.0", + "std-env": "^3.9.0", "test-exclude": "^7.0.1", "tinyrainbow": "^2.0.0" }, @@ -2703,8 +2703,8 @@ "url": "https://opencollective.com/vitest" }, "peerDependencies": { - "@vitest/browser": "3.0.9", - "vitest": "3.0.9" + "@vitest/browser": "3.1.3", + "vitest": "3.1.3" }, "peerDependenciesMeta": { "@vitest/browser": { @@ -2713,14 +2713,14 @@ } }, "node_modules/@vitest/expect": { - "version": "3.0.9", - "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-3.0.9.tgz", - "integrity": "sha512-5eCqRItYgIML7NNVgJj6TVCmdzE7ZVgJhruW0ziSQV4V7PvLkDL1bBkBdcTs/VuIz0IxPb5da1IDSqc1TR9eig==", + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-3.1.3.tgz", + "integrity": "sha512-7FTQQuuLKmN1Ig/h+h/GO+44Q1IlglPlR2es4ab7Yvfx+Uk5xsv+Ykk+MEt/M2Yn/xGmzaLKxGw2lgy2bwuYqg==", "dev": true, "license": "MIT", "dependencies": { - "@vitest/spy": "3.0.9", - "@vitest/utils": "3.0.9", + "@vitest/spy": "3.1.3", + "@vitest/utils": "3.1.3", "chai": "^5.2.0", "tinyrainbow": "^2.0.0" }, @@ -2729,13 +2729,13 @@ } }, "node_modules/@vitest/mocker": { - "version": "3.0.9", - "resolved": "https://registry.npmjs.org/@vitest/mocker/-/mocker-3.0.9.tgz", - "integrity": "sha512-ryERPIBOnvevAkTq+L1lD+DTFBRcjueL9lOUfXsLfwP92h4e+Heb+PjiqS3/OURWPtywfafK0kj++yDFjWUmrA==", + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/@vitest/mocker/-/mocker-3.1.3.tgz", + "integrity": "sha512-PJbLjonJK82uCWHjzgBJZuR7zmAOrSvKk1QBxrennDIgtH4uK0TB1PvYmc0XBCigxxtiAVPfWtAdy4lpz8SQGQ==", "dev": true, "license": "MIT", "dependencies": { - "@vitest/spy": "3.0.9", + "@vitest/spy": "3.1.3", "estree-walker": "^3.0.3", "magic-string": "^0.30.17" }, @@ -2766,9 +2766,9 @@ } }, "node_modules/@vitest/pretty-format": { - "version": "3.0.9", - "resolved": "https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-3.0.9.tgz", - "integrity": "sha512-OW9F8t2J3AwFEwENg3yMyKWweF7oRJlMyHOMIhO5F3n0+cgQAJZBjNgrF8dLwFTEXl5jUqBLXd9QyyKv8zEcmA==", + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-3.1.3.tgz", + "integrity": "sha512-i6FDiBeJUGLDKADw2Gb01UtUNb12yyXAqC/mmRWuYl+m/U9GS7s8us5ONmGkGpUUo7/iAYzI2ePVfOZTYvUifA==", "dev": true, "license": "MIT", "dependencies": { @@ -2779,13 +2779,13 @@ } }, "node_modules/@vitest/runner": { - "version": "3.0.9", - "resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-3.0.9.tgz", - "integrity": "sha512-NX9oUXgF9HPfJSwl8tUZCMP1oGx2+Sf+ru6d05QjzQz4OwWg0psEzwY6VexP2tTHWdOkhKHUIZH+fS6nA7jfOw==", + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-3.1.3.tgz", + "integrity": "sha512-Tae+ogtlNfFei5DggOsSUvkIaSuVywujMj6HzR97AHK6XK8i3BuVyIifWAm/sE3a15lF5RH9yQIrbXYuo0IFyA==", "dev": true, "license": "MIT", "dependencies": { - "@vitest/utils": "3.0.9", + "@vitest/utils": "3.1.3", "pathe": "^2.0.3" }, "funding": { @@ -2793,13 +2793,13 @@ } }, "node_modules/@vitest/snapshot": { - "version": "3.0.9", - "resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-3.0.9.tgz", - "integrity": "sha512-AiLUiuZ0FuA+/8i19mTYd+re5jqjEc2jZbgJ2up0VY0Ddyyxg/uUtBDpIFAy4uzKaQxOW8gMgBdAJJ2ydhu39A==", + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-3.1.3.tgz", + "integrity": "sha512-XVa5OPNTYUsyqG9skuUkFzAeFnEzDp8hQu7kZ0N25B1+6KjGm4hWLtURyBbsIAOekfWQ7Wuz/N/XXzgYO3deWQ==", "dev": true, "license": "MIT", "dependencies": { - "@vitest/pretty-format": "3.0.9", + "@vitest/pretty-format": "3.1.3", "magic-string": "^0.30.17", "pathe": "^2.0.3" }, @@ -2808,9 +2808,9 @@ } }, "node_modules/@vitest/spy": { - "version": "3.0.9", - "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-3.0.9.tgz", - "integrity": "sha512-/CcK2UDl0aQ2wtkp3YVWldrpLRNCfVcIOFGlVGKO4R5eajsH393Z1yiXLVQ7vWsj26JOEjeZI0x5sm5P4OGUNQ==", + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-3.1.3.tgz", + "integrity": "sha512-x6w+ctOEmEXdWaa6TO4ilb7l9DxPR5bwEb6hILKuxfU1NqWT2mpJD9NJN7t3OTfxmVlOMrvtoFJGdgyzZ605lQ==", "dev": true, "license": "MIT", "dependencies": { @@ -2821,13 +2821,13 @@ } }, "node_modules/@vitest/utils": { - "version": "3.0.9", - "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-3.0.9.tgz", - "integrity": "sha512-ilHM5fHhZ89MCp5aAaM9uhfl1c2JdxVxl3McqsdVyVNN6JffnEen8UMCdRTzOhGXNQGo5GNL9QugHrz727Wnng==", + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-3.1.3.tgz", + "integrity": "sha512-2Ltrpht4OmHO9+c/nmHtF09HWiyWdworqnHIwjfvDyWjuwKbdkcS9AnhsDn+8E2RM4x++foD1/tNuLPVvWG1Rg==", "dev": true, "license": "MIT", "dependencies": { - "@vitest/pretty-format": "3.0.9", + "@vitest/pretty-format": "3.1.3", "loupe": "^3.1.3", "tinyrainbow": "^2.0.0" }, @@ -4816,9 +4816,9 @@ } }, "node_modules/es-module-lexer": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-1.6.0.tgz", - "integrity": "sha512-qqnD1yMU6tk/jnaMosogGySTZP8YtUgAffA9nMN+E/rjxcfRQ6IEk7IiozUjgxKoFHBGjTLnrHB/YC45r/59EQ==", + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-1.7.0.tgz", + "integrity": "sha512-jEQoCwk8hyb2AZziIOLhDqpm5+2ww5uIE6lkO/6jcOCusfk6LhMHpXXfBLXTZ7Ydyt0j4VoUQv6uGNYbdW+kBA==", "dev": true, "license": "MIT" }, @@ -5399,9 +5399,9 @@ } }, "node_modules/expect-type": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/expect-type/-/expect-type-1.2.0.tgz", - "integrity": "sha512-80F22aiJ3GLyVnS/B3HzgR6RelZVumzj9jkL0Rhz4h0xYbNW9PjlQz5h3J/SShErbXBc295vseR4/MIbVmUbeA==", + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/expect-type/-/expect-type-1.2.1.tgz", + "integrity": "sha512-/kP8CAwxzLVEeFrMm4kMmy4CCDlpipyA7MYLVrdJIkV0fYF0UaigQHRsxHiuY/GEea+bh4KSv3TIlgr+2UL6bw==", "dev": true, "license": "Apache-2.0", "engines": { @@ -5511,9 +5511,9 @@ } }, "node_modules/fdir": { - "version": "6.4.3", - "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.4.3.tgz", - "integrity": "sha512-PMXmW2y1hDDfTSRc9gaXIuCCRpuoz3Kaz8cUelp3smouvfT632ozg2vrT6lJsHKKOF59YLbOGfAWGUcKEfRMQw==", + "version": "6.4.4", + "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.4.4.tgz", + "integrity": "sha512-1NZP+GK4GfuAv3PqKvxQRDMjdSRZjnkq7KfhlNrCNNlZ0ygQFpebfrnfnq/W7fpUnAv9aGWmY1zKx7FYL3gwhg==", "dev": true, "license": "MIT", "peerDependencies": { @@ -12610,9 +12610,9 @@ "license": "MIT" }, "node_modules/std-env": { - "version": "3.8.1", - "resolved": "https://registry.npmjs.org/std-env/-/std-env-3.8.1.tgz", - "integrity": "sha512-vj5lIj3Mwf9D79hBkltk5qmkFI+biIKWS2IBxEyEU3AX1tUf7AoL8nSazCOiiqQsGKIq01SClsKEzweu34uwvA==", + "version": "3.9.0", + "resolved": "https://registry.npmjs.org/std-env/-/std-env-3.9.0.tgz", + "integrity": "sha512-UGvjygr6F6tpH7o2qyqR6QYpwraIjKSdtzyBdyytFOHmPZY917kwdwLG0RbOjWOnKmnm3PeHjaoLLMie7kPLQw==", "dev": true, "license": "MIT" }, @@ -13312,6 +13312,23 @@ "dev": true, "license": "MIT" }, + "node_modules/tinyglobby": { + "version": "0.2.13", + "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.13.tgz", + "integrity": "sha512-mEwzpUgrLySlveBwEVDMKk5B57bhLPYovRfPAXD5gA/98Opn0rCDj3GtLwFvCvH5RK9uPCExUROW5NjDwvqkxw==", + "dev": true, + "license": "MIT", + "dependencies": { + "fdir": "^6.4.4", + "picomatch": "^4.0.2" + }, + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/SuperchupuDev" + } + }, "node_modules/tinypool": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/tinypool/-/tinypool-1.0.2.tgz", @@ -13745,15 +13762,18 @@ } }, "node_modules/vite": { - "version": "6.2.6", - "resolved": "https://registry.npmjs.org/vite/-/vite-6.2.6.tgz", - "integrity": "sha512-9xpjNl3kR4rVDZgPNdTL0/c6ao4km69a/2ihNQbcANz8RuCOK3hQBmLSJf3bRKVQjVMda+YvizNE8AwvogcPbw==", + "version": "6.3.5", + "resolved": "https://registry.npmjs.org/vite/-/vite-6.3.5.tgz", + "integrity": "sha512-cZn6NDFE7wdTpINgs++ZJ4N49W2vRp8LCKrn3Ob1kYNtOo21vfDoaV5GzBfLU4MovSAB8uNRm4jgzVQZ+mBzPQ==", "dev": true, "license": "MIT", "dependencies": { "esbuild": "^0.25.0", + "fdir": "^6.4.4", + "picomatch": "^4.0.2", "postcss": "^8.5.3", - "rollup": "^4.30.1" + "rollup": "^4.34.9", + "tinyglobby": "^0.2.13" }, "bin": { "vite": "bin/vite.js" @@ -13817,15 +13837,15 @@ } }, "node_modules/vite-node": { - "version": "3.0.9", - "resolved": "https://registry.npmjs.org/vite-node/-/vite-node-3.0.9.tgz", - "integrity": "sha512-w3Gdx7jDcuT9cNn9jExXgOyKmf5UOTb6WMHz8LGAm54eS1Elf5OuBhCxl6zJxGhEeIkgsE1WbHuoL0mj/UXqXg==", + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/vite-node/-/vite-node-3.1.3.tgz", + "integrity": "sha512-uHV4plJ2IxCl4u1up1FQRrqclylKAogbtBfOTwcuJ28xFi+89PZ57BRh+naIRvH70HPwxy5QHYzg1OrEaC7AbA==", "dev": true, "license": "MIT", "dependencies": { "cac": "^6.7.14", "debug": "^4.4.0", - "es-module-lexer": "^1.6.0", + "es-module-lexer": "^1.7.0", "pathe": "^2.0.3", "vite": "^5.0.0 || ^6.0.0" }, @@ -13840,31 +13860,32 @@ } }, "node_modules/vitest": { - "version": "3.0.9", - "resolved": "https://registry.npmjs.org/vitest/-/vitest-3.0.9.tgz", - "integrity": "sha512-BbcFDqNyBlfSpATmTtXOAOj71RNKDDvjBM/uPfnxxVGrG+FSH2RQIwgeEngTaTkuU/h0ScFvf+tRcKfYXzBybQ==", + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/vitest/-/vitest-3.1.3.tgz", + "integrity": "sha512-188iM4hAHQ0km23TN/adso1q5hhwKqUpv+Sd6p5sOuh6FhQnRNW3IsiIpvxqahtBabsJ2SLZgmGSpcYK4wQYJw==", "dev": true, "license": "MIT", "dependencies": { - "@vitest/expect": "3.0.9", - "@vitest/mocker": "3.0.9", - "@vitest/pretty-format": "^3.0.9", - "@vitest/runner": "3.0.9", - "@vitest/snapshot": "3.0.9", - "@vitest/spy": "3.0.9", - "@vitest/utils": "3.0.9", + "@vitest/expect": "3.1.3", + "@vitest/mocker": "3.1.3", + "@vitest/pretty-format": "^3.1.3", + "@vitest/runner": "3.1.3", + "@vitest/snapshot": "3.1.3", + "@vitest/spy": "3.1.3", + "@vitest/utils": "3.1.3", "chai": "^5.2.0", "debug": "^4.4.0", - "expect-type": "^1.1.0", + "expect-type": "^1.2.1", "magic-string": "^0.30.17", "pathe": "^2.0.3", - "std-env": "^3.8.0", + "std-env": "^3.9.0", "tinybench": "^2.9.0", "tinyexec": "^0.3.2", + "tinyglobby": "^0.2.13", "tinypool": "^1.0.2", "tinyrainbow": "^2.0.0", "vite": "^5.0.0 || ^6.0.0", - "vite-node": "3.0.9", + "vite-node": "3.1.3", "why-is-node-running": "^2.3.0" }, "bin": { @@ -13880,8 +13901,8 @@ "@edge-runtime/vm": "*", "@types/debug": "^4.1.12", "@types/node": "^18.0.0 || ^20.0.0 || >=22.0.0", - "@vitest/browser": "3.0.9", - "@vitest/ui": "3.0.9", + "@vitest/browser": "3.1.3", + "@vitest/ui": "3.1.3", "happy-dom": "*", "jsdom": "*" }, diff --git a/package.json b/package.json index 7894422f..7e98b6e5 100644 --- a/package.json +++ b/package.json @@ -44,7 +44,7 @@ "@types/node": "22.13.14", "@typescript-eslint/eslint-plugin": "8.28.0", "@typescript-eslint/parser": "8.28.0", - "@vitest/coverage-v8": "3.0.9", + "@vitest/coverage-v8": "3.1.3", "better-npm-audit": "3.11.0", "conventional-changelog-conventionalcommits": "^8.0.0", "copyfiles": "2.4.1", @@ -63,11 +63,11 @@ "typedoc-plugin-markdown": "^4.6.0", "typescript": "^5.8.2", "upath": "^2.0.1", - "vitest": "3.0.9" + "vitest": "3.1.3" }, "dependencies": { - "@algorandfoundation/algorand-typescript": "1.0.0-alpha.51", - "@algorandfoundation/puya-ts": "1.0.0-alpha.51", + "@algorandfoundation/algorand-typescript": "1.0.0-alpha.54", + "@algorandfoundation/puya-ts": "1.0.0-alpha.54", "elliptic": "^6.6.1", "js-sha256": "^0.11.0", "js-sha3": "^0.9.3", From 2932aaef8b72661edb551a1856c164209b928361 Mon Sep 17 00:00:00 2001 From: Bobby Lat Date: Thu, 8 May 2025 17:04:16 +0800 Subject: [PATCH 07/68] feat: allow tuples and objects to be stored in box, local, and global state --- src/impl/encoded-types.ts | 3 +- src/test-transformer/visitors.ts | 6 ++++ tests/artifacts/state-ops/contract.algo.ts | 36 ++++++++++++++++++++++ tests/global-state-arc4-values.spec.ts | 18 ++++++++++- tests/local-state-arc4-values.spec.ts | 12 ++++++++ tests/references/box-map.spec.ts | 31 ++++++++++++++++++- tests/references/box.spec.ts | 28 +++++++++++++++++ 7 files changed, 131 insertions(+), 3 deletions(-) diff --git a/src/impl/encoded-types.ts b/src/impl/encoded-types.ts index 08b4c408..ae1bace4 100644 --- a/src/impl/encoded-types.ts +++ b/src/impl/encoded-types.ts @@ -782,7 +782,7 @@ export class StructImpl extends (Struct> = { 'Struct(<.*>)?': StructImpl.fromBytesImpl, DynamicBytes: DynamicBytesImpl.fromBytesImpl, 'StaticBytes<.*>': StaticBytesImpl.fromBytesImpl, + object: StructImpl.fromBytesImpl, } export const getArc4Encoder = (typeInfo: TypeInfo, encoders?: Record>): fromBytes => { const encoder = Object.entries(encoders ?? arc4Encoders).find(([k, _]) => new RegExp(`^${k}$`, 'i').test(typeInfo.name))?.[1] diff --git a/src/test-transformer/visitors.ts b/src/test-transformer/visitors.ts index 1b6efdcb..bf68cf10 100644 --- a/src/test-transformer/visitors.ts +++ b/src/test-transformer/visitors.ts @@ -407,6 +407,12 @@ const getGenericTypeInfo = (type: ptypes.PType): TypeInfo => { ) } else if (type instanceof ptypes.ARC4TupleType || type instanceof ptypes.TuplePType) { genericArgs.push(...type.items.map(getGenericTypeInfo)) + } else if (type instanceof ptypes.ObjectPType) { + genericArgs = Object.fromEntries( + Object.entries(type.properties) + .map(([key, value]) => [key, getGenericTypeInfo(value)]) + .filter((x) => !!x), + ) } const result: TypeInfo = { name: typeName } diff --git a/tests/artifacts/state-ops/contract.algo.ts b/tests/artifacts/state-ops/contract.algo.ts index c45e0f22..364e86bf 100644 --- a/tests/artifacts/state-ops/contract.algo.ts +++ b/tests/artifacts/state-ops/contract.algo.ts @@ -605,6 +605,8 @@ export class GlobalStateContract extends arc4.Contract { implicitKeyArc4Address = GlobalState({ initialValue: new Address(Global.creatorAddress) }) implicitKeyArc4UintN128 = GlobalState({ initialValue: new UintN128(2n ** 100n) }) implicitKeyArc4DynamicBytes = GlobalState({ initialValue: new DynamicBytes('dynamic bytes') }) + implicitKeyTuple = GlobalState<[uint64, bytes, boolean]>({ initialValue: [Uint64(42), Bytes('Hello'), false] }) + implicitKeyObj = GlobalState<{ a: uint64; b: bytes; c: boolean }>({ initialValue: { a: 42, b: Bytes('World'), c: true } }) // Explicit key state variables arc4UintN64 = GlobalState({ initialValue: new UintN64(1337), key: 'explicit_key_arc4_uintn64' }) @@ -651,6 +653,16 @@ export class GlobalStateContract extends arc4.Contract { return this.implicitKeyArc4DynamicBytes.value } + @arc4.abimethod() + get_implicit_key_tuple(): [uint64, bytes, boolean] { + return this.implicitKeyTuple.value + } + + @arc4.abimethod() + get_implicit_key_obj(): { a: uint64; b: bytes; c: boolean } { + return this.implicitKeyObj.value + } + // Getter methods for explicit key state variables @arc4.abimethod() get_arc4_uintn64(): UintN64 { @@ -723,6 +735,16 @@ export class GlobalStateContract extends arc4.Contract { this.implicitKeyArc4DynamicBytes.value = value } + @arc4.abimethod() + set_implicit_key_tuple(value: [uint64, bytes, boolean]) { + this.implicitKeyTuple.value = value + } + + @arc4.abimethod() + set_implicit_key_obj(value: { a: uint64; b: bytes; c: boolean }) { + this.implicitKeyObj.value = value + } + // Setter methods for explicit key state variables @arc4.abimethod() set_arc4_uintn64(value: UintN64) { @@ -769,6 +791,8 @@ export class LocalStateContract extends arc4.Contract { implicitKeyArc4Address = LocalState
() implicitKeyArc4UintN128 = LocalState() implicitKeyArc4DynamicBytes = LocalState() + implicitKeyTuple = LocalState<[uint64, bytes, boolean]>() + implicitKeyObj = LocalState<{ a: uint64; b: bytes; c: boolean }>() // Explicit key state variables arc4UintN64 = LocalState({ key: 'explicit_key_arc4_uintn64' }) @@ -788,6 +812,8 @@ export class LocalStateContract extends arc4.Contract { this.implicitKeyArc4Address(Global.creatorAddress).value = new Address(Global.creatorAddress) this.implicitKeyArc4UintN128(Global.creatorAddress).value = new UintN128(2n ** 100n) this.implicitKeyArc4DynamicBytes(Global.creatorAddress).value = new DynamicBytes('dynamic bytes') + this.implicitKeyTuple(Global.creatorAddress).value = [42, Bytes('dummy_bytes'), true] + this.implicitKeyObj(Global.creatorAddress).value = { a: Uint64(42), b: Bytes('dummy_bytes'), c: true } this.arc4UintN64(Global.creatorAddress).value = new UintN64(1337) this.arc4Str(Global.creatorAddress).value = new Str('Hello') @@ -834,6 +860,16 @@ export class LocalStateContract extends arc4.Contract { return this.implicitKeyArc4DynamicBytes(a).value } + @arc4.abimethod() + get_implicit_key_tuple(a: Account): [uint64, bytes, boolean] { + return this.implicitKeyTuple(a).value + } + + @arc4.abimethod() + get_implicit_key_obj(a: Account): { a: uint64; b: bytes; c: boolean } { + return this.implicitKeyObj(a).value + } + // Getter methods for explicit key state variables @arc4.abimethod() get_arc4_uintn64(a: Account): arc4.UintN64 { diff --git a/tests/global-state-arc4-values.spec.ts b/tests/global-state-arc4-values.spec.ts index 46e82e1e..12a6f45a 100644 --- a/tests/global-state-arc4-values.spec.ts +++ b/tests/global-state-arc4-values.spec.ts @@ -1,4 +1,4 @@ -import { Bytes } from '@algorandfoundation/algorand-typescript' +import { Bytes, Uint64 } from '@algorandfoundation/algorand-typescript' import { TestExecutionContext } from '@algorandfoundation/algorand-typescript-testing' import type { AddressImpl, @@ -106,6 +106,22 @@ describe('ARC4 AppGlobal values', async () => { expect(arc4Value.native).toEqual(expectedValue) }, }, + { + nativeValue: [21, asUint8Array(Bytes('Hello')), true], + abiValue: [Uint64(21), Bytes('Hello'), true], + methodName: `get_implicit_key_tuple`, + assert: (value: DeliberateAny, expectedValue: DeliberateAny) => { + expect(value).toEqual(expectedValue) + }, + }, + { + nativeValue: { a: 12, b: asUint8Array(Bytes('world')), c: true }, + abiValue: { a: 12, b: Bytes('world'), c: true }, + methodName: `get_implicit_key_obj`, + assert: (value: DeliberateAny, expectedValue: DeliberateAny) => { + expect(value).toEqual(expectedValue) + }, + }, ]) test.for(testData)('should be able to get arc4 state values', async (data, { appClientGlobalStateContract: appClient, testAccount }) => { diff --git a/tests/local-state-arc4-values.spec.ts b/tests/local-state-arc4-values.spec.ts index 02a2331c..05be6e35 100644 --- a/tests/local-state-arc4-values.spec.ts +++ b/tests/local-state-arc4-values.spec.ts @@ -93,6 +93,18 @@ describe('ARC4 AppLocal values', async () => { expect(arc4Value.native).toEqual(expectedValue) }, }, + { + methodName: `get_implicit_key_tuple`, + assert: (value: DeliberateAny, expectedValue: DeliberateAny) => { + expect(value).toEqual(expectedValue) + }, + }, + { + methodName: `get_implicit_key_obj`, + assert: (value: DeliberateAny, expectedValue: DeliberateAny) => { + expect(value).toEqual(expectedValue) + }, + }, ]) test.for(testData)('should be able to get arc4 state values', async (data, { appClientLocalStateContract: appClient, testAccount }) => { diff --git a/tests/references/box-map.spec.ts b/tests/references/box-map.spec.ts index 20cc3602..ed980e5f 100644 --- a/tests/references/box-map.spec.ts +++ b/tests/references/box-map.spec.ts @@ -1,7 +1,8 @@ import type { biguint, bytes, uint64 } from '@algorandfoundation/algorand-typescript' import { BigUint, BoxMap, Bytes, op, Uint64 } from '@algorandfoundation/algorand-typescript' import { TestExecutionContext } from '@algorandfoundation/algorand-typescript-testing' -import { ARC4Encoded, DynamicArray, interpretAsArc4, Str, UintN64 } from '@algorandfoundation/algorand-typescript/arc4' +import type { Bool, DynamicBytes, Tuple } from '@algorandfoundation/algorand-typescript/arc4' +import { ARC4Encoded, DynamicArray, interpretAsArc4, Str, Struct, UintN64 } from '@algorandfoundation/algorand-typescript/arc4' import { afterEach, describe, expect, it, test } from 'vitest' import { MAX_UINT64 } from '../../src/constants' import { toBytes } from '../../src/encoders' @@ -9,6 +10,8 @@ import { asBytes } from '../../src/util' const BOX_NOT_CREATED_ERROR = 'Box has not been created' +class MyStruct extends Struct<{ a: Str; b: DynamicBytes; c: Bool }> {} + describe('BoxMap', () => { const ctx = new TestExecutionContext() const keyPrefix = Bytes('test_key_prefix') @@ -97,6 +100,30 @@ describe('BoxMap', () => { }) }, }, + { + key: new Str('TTest'), + value: ['hello', Bytes('world'), true] as const, + newValue: ['world', Bytes('hello'), false] as const, + emptyValue: interpretAsArc4>(Bytes('')), + withBoxContext: (test: (boxMap: BoxMap) => void) => { + ctx.txn.createScope([ctx.any.txn.applicationCall()]).execute(() => { + const boxMap = BoxMap({ keyPrefix }) + test(boxMap) + }) + }, + }, + { + key: new Str('OTest'), + value: { a: 'hello', b: Bytes('world'), c: true } as unknown as MyStruct, + newValue: { a: 'world', b: Bytes('hello'), c: false } as unknown as MyStruct, + emptyValue: interpretAsArc4(Bytes('')), + withBoxContext: (test: (boxMap: BoxMap) => void) => { + ctx.txn.createScope([ctx.any.txn.applicationCall()]).execute(() => { + const boxMap = BoxMap({ keyPrefix }) + test(boxMap) + }) + }, + }, ] afterEach(() => { @@ -199,6 +226,8 @@ describe('BoxMap', () => { expect(opContent).toEqual(newBytesValue) if (newValue instanceof ARC4Encoded) { expect((boxMap(key as never).value as ARC4Encoded).bytes).toEqual(newValue.bytes) + } else if (boxMap(key as never).value instanceof ARC4Encoded) { + expect((boxMap(key as never).value as ARC4Encoded).bytes).toEqual(newBytesValue) } else { expect(boxMap(key as never).value).toEqual(newValue) } diff --git a/tests/references/box.spec.ts b/tests/references/box.spec.ts index 0ec86abe..fc01f9d1 100644 --- a/tests/references/box.spec.ts +++ b/tests/references/box.spec.ts @@ -1,6 +1,7 @@ import type { biguint, bytes, uint64 } from '@algorandfoundation/algorand-typescript' import { BigUint, Box, Bytes, op, Uint64 } from '@algorandfoundation/algorand-typescript' import { TestExecutionContext } from '@algorandfoundation/algorand-typescript-testing' +import type { DynamicBytes } from '@algorandfoundation/algorand-typescript/arc4' import { ARC4Encoded, Bool, @@ -8,6 +9,7 @@ import { interpretAsArc4, StaticArray, Str, + Struct, Tuple, UintN32, UintN64, @@ -22,6 +24,8 @@ import { BoxContract } from '../artifacts/box-contract/contract.algo' const BOX_NOT_CREATED_ERROR = 'Box has not been created' +class MyStruct extends Struct<{ a: Str; b: DynamicBytes; c: Bool }> {} + describe('Box', () => { const ctx = new TestExecutionContext() const key = Bytes('test_key') @@ -103,6 +107,28 @@ describe('Box', () => { }) }, }, + { + value: ['hello', Bytes('world'), true] as const, + newValue: ['world', Bytes('hello'), false] as const, + emptyValue: interpretAsArc4>(Bytes('')), + withBoxContext: (test: (boxMap: Box) => void) => { + ctx.txn.createScope([ctx.any.txn.applicationCall()]).execute(() => { + const boxMap = Box({ key }) + test(boxMap) + }) + }, + }, + { + value: { a: 'hello', b: Bytes('world'), c: true }, + newValue: { a: 'world', b: Bytes('hello'), c: false }, + emptyValue: interpretAsArc4(Bytes('')), + withBoxContext: (test: (boxMap: Box<{ a: string; b: bytes; c: boolean }>) => void) => { + ctx.txn.createScope([ctx.any.txn.applicationCall()]).execute(() => { + const boxMap = Box<{ a: string; b: bytes; c: boolean }>({ key }) + test(boxMap) + }) + }, + }, ] afterEach(() => { @@ -211,6 +237,8 @@ describe('Box', () => { expect(opContent).toEqual(newBytesValue) if (newValue instanceof ARC4Encoded) { expect((box as DeliberateAny).get(key).bytes).toEqual(newValue.bytes) + } else if (box.value instanceof ARC4Encoded) { + expect(box.value.bytes).toEqual(newBytesValue) } else { expect(box.value).toEqual(newValue) } From aefabb4411dc61e8f8b04c4b10f45df51bd9ce76 Mon Sep 17 00:00:00 2001 From: Bobby Lat Date: Thu, 8 May 2025 18:07:23 +0800 Subject: [PATCH 08/68] feat: include a `ref` property on the `Box` type which can be used to directly access box bytes --- docs/code/index/classes/ApplicationSpy.md | 82 +++++++++++++++++++---- package-lock.json | 18 ++--- package.json | 4 +- src/impl/state.ts | 4 ++ tests/references/box-map.spec.ts | 36 +++++++++- tests/references/box.spec.ts | 17 ++++- 6 files changed, 133 insertions(+), 28 deletions(-) diff --git a/docs/code/index/classes/ApplicationSpy.md b/docs/code/index/classes/ApplicationSpy.md index 3cc1bffa..7504b178 100644 --- a/docs/code/index/classes/ApplicationSpy.md +++ b/docs/code/index/classes/ApplicationSpy.md @@ -6,7 +6,7 @@ # Class: ApplicationSpy\ -Defined in: [src/application-spy.ts:32](https://github.com/algorandfoundation/algorand-typescript-testing/blob/main/src/application-spy.ts#L32) +Defined in: [src/application-spy.ts:34](https://github.com/algorandfoundation/algorand-typescript-testing/blob/main/src/application-spy.ts#L34) ## Type Parameters @@ -20,7 +20,7 @@ Defined in: [src/application-spy.ts:32](https://github.com/algorandfoundation/al > **new ApplicationSpy**\<`TContract`\>(`contract`?): `ApplicationSpy`\<`TContract`\> -Defined in: [src/application-spy.ts:44](https://github.com/algorandfoundation/algorand-typescript-testing/blob/main/src/application-spy.ts#L44) +Defined in: [src/application-spy.ts:46](https://github.com/algorandfoundation/algorand-typescript-testing/blob/main/src/application-spy.ts#L46) #### Parameters @@ -38,7 +38,7 @@ Defined in: [src/application-spy.ts:44](https://github.com/algorandfoundation/al > `optional` **contract**: `TContract` \| `ConstructorFor`\<`TContract`\> -Defined in: [src/application-spy.ts:42](https://github.com/algorandfoundation/algorand-typescript-testing/blob/main/src/application-spy.ts#L42) +Defined in: [src/application-spy.ts:44](https://github.com/algorandfoundation/algorand-typescript-testing/blob/main/src/application-spy.ts#L44) *** @@ -46,7 +46,7 @@ Defined in: [src/application-spy.ts:42](https://github.com/algorandfoundation/al > `readonly` **on**: `_TypedApplicationSpyCallBacks`\<`TContract`\> -Defined in: [src/application-spy.ts:39](https://github.com/algorandfoundation/algorand-typescript-testing/blob/main/src/application-spy.ts#L39) +Defined in: [src/application-spy.ts:41](https://github.com/algorandfoundation/algorand-typescript-testing/blob/main/src/application-spy.ts#L41) The `on` property is a proxy that allows you to register callbacks for specific method signatures. It dynamically creates methods based on the contract's methods. @@ -57,7 +57,7 @@ It dynamically creates methods based on the contract's methods. > **notify**(`itxn`): `void` -Defined in: [src/application-spy.ts:50](https://github.com/algorandfoundation/algorand-typescript-testing/blob/main/src/application-spy.ts#L50) +Defined in: [src/application-spy.ts:52](https://github.com/algorandfoundation/algorand-typescript-testing/blob/main/src/application-spy.ts#L52) #### Parameters @@ -73,23 +73,51 @@ Defined in: [src/application-spy.ts:50](https://github.com/algorandfoundation/al ### onAbiCall() +#### Call Signature + > **onAbiCall**(`methodSignature`, `callback`): `void` -Defined in: [src/application-spy.ts:69](https://github.com/algorandfoundation/algorand-typescript-testing/blob/main/src/application-spy.ts#L69) +Defined in: [src/application-spy.ts:80](https://github.com/algorandfoundation/algorand-typescript-testing/blob/main/src/application-spy.ts#L80) Registers a callback for a specific method signature. -#### Parameters +##### Parameters -##### methodSignature +###### methodSignature `bytes` -##### callback +###### callback `AppSpyCb` -#### Returns +##### Returns + +`void` + +#### Call Signature + +> **onAbiCall**(`methodSignature`, `ocas`, `callback`): `void` + +Defined in: [src/application-spy.ts:81](https://github.com/algorandfoundation/algorand-typescript-testing/blob/main/src/application-spy.ts#L81) + +Registers a callback for a specific method signature. + +##### Parameters + +###### methodSignature + +`bytes` + +###### ocas + +`OnCompleteAction`[] + +###### callback + +`AppSpyCb` + +##### Returns `void` @@ -97,20 +125,46 @@ Registers a callback for a specific method signature. ### onBareCall() +#### Call Signature + > **onBareCall**(`callback`): `void` -Defined in: [src/application-spy.ts:60](https://github.com/algorandfoundation/algorand-typescript-testing/blob/main/src/application-spy.ts#L60) +Defined in: [src/application-spy.ts:62](https://github.com/algorandfoundation/algorand-typescript-testing/blob/main/src/application-spy.ts#L62) Registers a callback for a bare call (no arguments). -#### Parameters +##### Parameters -##### callback +###### callback `AppSpyCb` The callback to be executed when a bare call is detected. -#### Returns +##### Returns + +`void` + +#### Call Signature + +> **onBareCall**(`ocas`, `callback`): `void` + +Defined in: [src/application-spy.ts:63](https://github.com/algorandfoundation/algorand-typescript-testing/blob/main/src/application-spy.ts#L63) + +Registers a callback for a bare call (no arguments). + +##### Parameters + +###### ocas + +`OnCompleteAction`[] + +###### callback + +`AppSpyCb` + +The callback to be executed when a bare call is detected. + +##### Returns `void` diff --git a/package-lock.json b/package-lock.json index 82001ec0..72873bf0 100644 --- a/package-lock.json +++ b/package-lock.json @@ -8,8 +8,8 @@ "name": "@algorandfoundation/algorand-typescript-testing", "version": "1.0.0", "dependencies": { - "@algorandfoundation/algorand-typescript": "1.0.0-alpha.54", - "@algorandfoundation/puya-ts": "1.0.0-alpha.54", + "@algorandfoundation/algorand-typescript": "1.0.0-alpha.55", + "@algorandfoundation/puya-ts": "1.0.0-alpha.55", "elliptic": "^6.6.1", "js-sha256": "^0.11.0", "js-sha3": "^0.9.3", @@ -73,21 +73,21 @@ } }, "node_modules/@algorandfoundation/algorand-typescript": { - "version": "1.0.0-alpha.54", - "resolved": "https://registry.npmjs.org/@algorandfoundation/algorand-typescript/-/algorand-typescript-1.0.0-alpha.54.tgz", - "integrity": "sha512-GN3wb73oVJjjNPB8m7YdjHC1+50CDIHClB1YqJG6/1As/fMy80lchkqiCWuF3nCXGIWKGQj3Le4/5xMuE953hg==", + "version": "1.0.0-alpha.55", + "resolved": "https://registry.npmjs.org/@algorandfoundation/algorand-typescript/-/algorand-typescript-1.0.0-alpha.55.tgz", + "integrity": "sha512-JMWdko5cpqzFgojWocNsr1UjVOMHxEiZyWxTHbMCEebacVfm7WjGX9NmqBbdWoPEpFsuqgmCRmes1aKMjbQR6A==", "peerDependencies": { "tslib": "^2.6.2" } }, "node_modules/@algorandfoundation/puya-ts": { - "version": "1.0.0-alpha.54", - "resolved": "https://registry.npmjs.org/@algorandfoundation/puya-ts/-/puya-ts-1.0.0-alpha.54.tgz", - "integrity": "sha512-XudFwIpvppWr3gJu+gRuOt/n2V0sRurgDULcq5qduW0ddv7Tr33bqmw7rHjN9eRew4jxbjMNbPH9rG2p17J+kw==", + "version": "1.0.0-alpha.55", + "resolved": "https://registry.npmjs.org/@algorandfoundation/puya-ts/-/puya-ts-1.0.0-alpha.55.tgz", + "integrity": "sha512-bwdf6qy5c44K9kgpRvNFWiRM3uBpMWddVsQd3d6U2s4lwshXmlBj2Km/fOk/dQ44e03Lv5b1ekZU1YS4ypMVhQ==", "hasInstallScript": true, "license": "MIT", "dependencies": { - "@algorandfoundation/algorand-typescript": "1.0.0-alpha.54", + "@algorandfoundation/algorand-typescript": "1.0.0-alpha.55", "arcsecond": "^5.0.0", "argparse": "^2.0.1", "chalk": "^5.4.1", diff --git a/package.json b/package.json index 7e98b6e5..75114f09 100644 --- a/package.json +++ b/package.json @@ -66,8 +66,8 @@ "vitest": "3.1.3" }, "dependencies": { - "@algorandfoundation/algorand-typescript": "1.0.0-alpha.54", - "@algorandfoundation/puya-ts": "1.0.0-alpha.54", + "@algorandfoundation/algorand-typescript": "1.0.0-alpha.55", + "@algorandfoundation/puya-ts": "1.0.0-alpha.55", "elliptic": "^6.6.1", "js-sha256": "^0.11.0", "js-sha3": "^0.9.3", diff --git a/src/impl/state.ts b/src/impl/state.ts index de5daf0a..9f430d4a 100644 --- a/src/impl/state.ts +++ b/src/impl/state.ts @@ -236,6 +236,10 @@ export class BoxCls { return lazyContext.ledger.getBox(this.#app, this.key).length } + get ref(): BoxRefCls { + return new BoxRefCls(this.key) + } + get(options: { default: TValue }): TValue { const [value, exists] = this.maybe() return exists ? value : options.default diff --git a/tests/references/box-map.spec.ts b/tests/references/box-map.spec.ts index ed980e5f..06317542 100644 --- a/tests/references/box-map.spec.ts +++ b/tests/references/box-map.spec.ts @@ -1,8 +1,8 @@ import type { biguint, bytes, uint64 } from '@algorandfoundation/algorand-typescript' import { BigUint, BoxMap, Bytes, op, Uint64 } from '@algorandfoundation/algorand-typescript' import { TestExecutionContext } from '@algorandfoundation/algorand-typescript-testing' -import type { Bool, DynamicBytes, Tuple } from '@algorandfoundation/algorand-typescript/arc4' -import { ARC4Encoded, DynamicArray, interpretAsArc4, Str, Struct, UintN64 } from '@algorandfoundation/algorand-typescript/arc4' +import type { Bool, DynamicBytes, StaticArray, Tuple, UintN16 } from '@algorandfoundation/algorand-typescript/arc4' +import { ARC4Encoded, DynamicArray, interpretAsArc4, Str, Struct, UintN64, UintN8 } from '@algorandfoundation/algorand-typescript/arc4' import { afterEach, describe, expect, it, test } from 'vitest' import { MAX_UINT64 } from '../../src/constants' import { toBytes } from '../../src/encoders' @@ -259,4 +259,36 @@ describe('BoxMap', () => { expect(boxMap(key).value.at(-1).native).toEqual(400) }) }) + + test('should be able to replace specific bytes values using ref', () => { + ctx.txn.createScope([ctx.any.txn.applicationCall()]).execute(() => { + const boxMap = BoxMap>({ keyPrefix: 'a' }) + + const box1 = boxMap(1) + box1.create() + + const boxRefA = box1.ref + boxRefA.replace(1, new UintN8(123).bytes) + expect(box1.value[0].native).toEqual(123) + expect(boxMap(1).value[0].native).toEqual(123) + + const boxRefB = box1.ref + boxRefB.replace(2, new UintN8(255).bytes) + expect(box1.value[1].native).toEqual(65280) + expect(boxMap(1).value[1].native).toEqual(65280) + + const box2 = boxMap(2) + box2.create() + + const boxRefC = box2.ref + boxRefC.replace(1, new UintN8(223).bytes) + expect(box2.value[0].native).toEqual(223) + expect(boxMap(2).value[0].native).toEqual(223) + + const boxRefD = box2.ref + boxRefD.replace(3, new UintN8(255).bytes) + expect(box2.value[1].native).toEqual(255) + expect(boxMap(2).value[1].native).toEqual(255) + }) + }) }) diff --git a/tests/references/box.spec.ts b/tests/references/box.spec.ts index fc01f9d1..e5348e49 100644 --- a/tests/references/box.spec.ts +++ b/tests/references/box.spec.ts @@ -1,7 +1,7 @@ import type { biguint, bytes, uint64 } from '@algorandfoundation/algorand-typescript' import { BigUint, Box, Bytes, op, Uint64 } from '@algorandfoundation/algorand-typescript' import { TestExecutionContext } from '@algorandfoundation/algorand-typescript-testing' -import type { DynamicBytes } from '@algorandfoundation/algorand-typescript/arc4' +import type { DynamicBytes, UintN16 } from '@algorandfoundation/algorand-typescript/arc4' import { ARC4Encoded, Bool, @@ -269,6 +269,21 @@ describe('Box', () => { }) }) + test('should be able to replace specific bytes values using ref', () => { + ctx.txn.createScope([ctx.any.txn.applicationCall()]).execute(() => { + const box = Box>({ key: 'a' }) + box.create() + + const boxRef1 = box.ref + boxRef1.replace(1, new UintN8(123).bytes) + expect(box.value[0].native).toEqual(123) + + const boxRef2 = box.ref + boxRef2.replace(2, new UintN8(255).bytes) + expect(box.value[1].native).toEqual(65280) + }) + }) + describe('Box.create', () => { it('throw errors if size is not provided for dynamic value type', () => { ctx.txn.createScope([ctx.any.txn.applicationCall()]).execute(() => { From 93e5deef8045ddef924880c566fc788587ca1d7f Mon Sep 17 00:00:00 2001 From: Bobby Lat Date: Tue, 13 May 2025 10:25:16 +0800 Subject: [PATCH 09/68] chore: update versions of puya dependencies --- package-lock.json | 18 +++++++++--------- package.json | 4 ++-- tests/references/box-map.spec.ts | 12 ++++++++++++ 3 files changed, 23 insertions(+), 11 deletions(-) diff --git a/package-lock.json b/package-lock.json index 72873bf0..5d7afac1 100644 --- a/package-lock.json +++ b/package-lock.json @@ -8,8 +8,8 @@ "name": "@algorandfoundation/algorand-typescript-testing", "version": "1.0.0", "dependencies": { - "@algorandfoundation/algorand-typescript": "1.0.0-alpha.55", - "@algorandfoundation/puya-ts": "1.0.0-alpha.55", + "@algorandfoundation/algorand-typescript": "1.0.0-alpha.59", + "@algorandfoundation/puya-ts": "1.0.0-alpha.59", "elliptic": "^6.6.1", "js-sha256": "^0.11.0", "js-sha3": "^0.9.3", @@ -73,21 +73,21 @@ } }, "node_modules/@algorandfoundation/algorand-typescript": { - "version": "1.0.0-alpha.55", - "resolved": "https://registry.npmjs.org/@algorandfoundation/algorand-typescript/-/algorand-typescript-1.0.0-alpha.55.tgz", - "integrity": "sha512-JMWdko5cpqzFgojWocNsr1UjVOMHxEiZyWxTHbMCEebacVfm7WjGX9NmqBbdWoPEpFsuqgmCRmes1aKMjbQR6A==", + "version": "1.0.0-alpha.59", + "resolved": "https://registry.npmjs.org/@algorandfoundation/algorand-typescript/-/algorand-typescript-1.0.0-alpha.59.tgz", + "integrity": "sha512-2kuInFJzMdY4zjLGXn9aYgmeUVgb2Kd6fpdDzulGkkgiQ0HjAoSRaH0k64th+zH4rGvfeG2dXh6GB2ma3yZeBQ==", "peerDependencies": { "tslib": "^2.6.2" } }, "node_modules/@algorandfoundation/puya-ts": { - "version": "1.0.0-alpha.55", - "resolved": "https://registry.npmjs.org/@algorandfoundation/puya-ts/-/puya-ts-1.0.0-alpha.55.tgz", - "integrity": "sha512-bwdf6qy5c44K9kgpRvNFWiRM3uBpMWddVsQd3d6U2s4lwshXmlBj2Km/fOk/dQ44e03Lv5b1ekZU1YS4ypMVhQ==", + "version": "1.0.0-alpha.59", + "resolved": "https://registry.npmjs.org/@algorandfoundation/puya-ts/-/puya-ts-1.0.0-alpha.59.tgz", + "integrity": "sha512-LSqy5o5CuYAc3rFqJyz90tHKb0+PZcvkBEzataNrrOL3Nrw6C5IK7K1zimW19m3Lvv5ruKnijWbZbiL2LfZB5Q==", "hasInstallScript": true, "license": "MIT", "dependencies": { - "@algorandfoundation/algorand-typescript": "1.0.0-alpha.55", + "@algorandfoundation/algorand-typescript": "1.0.0-alpha.59", "arcsecond": "^5.0.0", "argparse": "^2.0.1", "chalk": "^5.4.1", diff --git a/package.json b/package.json index 75114f09..48223d98 100644 --- a/package.json +++ b/package.json @@ -66,8 +66,8 @@ "vitest": "3.1.3" }, "dependencies": { - "@algorandfoundation/algorand-typescript": "1.0.0-alpha.55", - "@algorandfoundation/puya-ts": "1.0.0-alpha.55", + "@algorandfoundation/algorand-typescript": "1.0.0-alpha.59", + "@algorandfoundation/puya-ts": "1.0.0-alpha.59", "elliptic": "^6.6.1", "js-sha256": "^0.11.0", "js-sha3": "^0.9.3", diff --git a/tests/references/box-map.spec.ts b/tests/references/box-map.spec.ts index 06317542..215584b1 100644 --- a/tests/references/box-map.spec.ts +++ b/tests/references/box-map.spec.ts @@ -124,6 +124,18 @@ describe('BoxMap', () => { }) }, }, + { + key: { x: Uint64(21), y: Uint64(42) }, + value: { a: 'hello', b: Bytes('world'), c: true } as unknown as MyStruct, + newValue: { a: 'world', b: Bytes('hello'), c: false } as unknown as MyStruct, + emptyValue: interpretAsArc4(Bytes('')), + withBoxContext: (test: (boxMap: BoxMap<{ x: uint64; y: uint64 }, MyStruct>) => void) => { + ctx.txn.createScope([ctx.any.txn.applicationCall()]).execute(() => { + const boxMap = BoxMap<{ x: uint64; y: uint64 }, MyStruct>({ keyPrefix }) + test(boxMap) + }) + }, + }, ] afterEach(() => { From 630f57635de0f8682c38b940d2ecf517635b554c Mon Sep 17 00:00:00 2001 From: Bobby Lat Date: Tue, 13 May 2025 17:29:25 +0800 Subject: [PATCH 10/68] fix: arc4 encoded length of native boolean should be 1 instead of 8 --- src/encoders.ts | 16 +++++++++++----- src/impl/encoded-types.ts | 18 +++--------------- src/impl/state.ts | 12 ++++-------- tests/arc4/encode-decode-arc4.spec.ts | 6 +++++- 4 files changed, 23 insertions(+), 29 deletions(-) diff --git a/src/encoders.ts b/src/encoders.ts index 14470793..91e77b5d 100644 --- a/src/encoders.ts +++ b/src/encoders.ts @@ -1,9 +1,9 @@ import type { biguint, bytes, OnCompleteAction, TransactionType, uint64 } from '@algorandfoundation/algorand-typescript' import { ARC4Encoded } from '@algorandfoundation/algorand-typescript/arc4' import { encodingUtil } from '@algorandfoundation/puya-ts' -import { InternalError } from './errors' +import { CodeError, InternalError } from './errors' import { BytesBackedCls, Uint64BackedCls } from './impl/base' -import { arc4Encoders, encodeArc4Impl, getArc4Encoder, tryArc4EncodedLengthImpl } from './impl/encoded-types' +import { arc4Encoders, encodeArc4Impl, getArc4Encoder, getMaxLengthOfStaticContentType } from './impl/encoded-types' import { BigUint, Uint64, type StubBytesCompat } from './impl/primitives' import { AccountCls, ApplicationCls, AssetCls } from './impl/reference' import type { DeliberateAny } from './typescript-helpers' @@ -90,7 +90,13 @@ export const toBytes = (val: unknown): bytes => { throw new InternalError(`Invalid type for bytes: ${nameOfType(val)}`) } -export const minLengthForType = (typeInfo: TypeInfo): number => { - const minArc4StaticLength = tryArc4EncodedLengthImpl(typeInfo) - return minArc4StaticLength ?? 0 +export const minLengthForType = (typeInfo: TypeInfo): number | undefined => { + try { + return getMaxLengthOfStaticContentType(typeInfo, false) + } catch (e) { + if (e instanceof CodeError && e.message.startsWith('unsupported type')) { + return undefined + } + throw e + } } diff --git a/src/impl/encoded-types.ts b/src/impl/encoded-types.ts index ae1bace4..091c8a8b 100644 --- a/src/impl/encoded-types.ts +++ b/src/impl/encoded-types.ts @@ -1103,14 +1103,14 @@ const findBoolTypes = (values: TypeInfo[], index: number, delta: number): number return until } -const getMaxLengthOfStaticContentType = (type: TypeInfo): number => { +export const getMaxLengthOfStaticContentType = (type: TypeInfo, asArc4Encoded: boolean = true): number => { switch (trimGenericTypeName(type.name)) { case 'uint64': return UINT64_SIZE / BITS_IN_BYTE case 'biguint': return UINT512_SIZE / BITS_IN_BYTE case 'boolean': - return 8 + return asArc4Encoded ? 1 : 8 case 'Bool': return 1 case 'Address': @@ -1409,17 +1409,5 @@ export const getArc4Encoded = (value: DeliberateAny, sourceTypeInfoString?: stri export const arc4EncodedLengthImpl = (typeInfoString: string): uint64 => { const typeInfo = JSON.parse(typeInfoString) - return getMaxLengthOfStaticContentType(typeInfo) -} - -export const tryArc4EncodedLengthImpl = (typeInfoString: string | TypeInfo): uint64 | undefined => { - const typeInfo = typeof typeInfoString === 'string' ? JSON.parse(typeInfoString) : typeInfoString - try { - return getMaxLengthOfStaticContentType(typeInfo) - } catch (e) { - if (e instanceof CodeError && e.message.startsWith('unsupported type')) { - return undefined - } - throw e - } + return getMaxLengthOfStaticContentType(typeInfo, true) } diff --git a/src/impl/state.ts b/src/impl/state.ts index 9f430d4a..a5e17ff5 100644 --- a/src/impl/state.ts +++ b/src/impl/state.ts @@ -17,7 +17,7 @@ import { lazyContext } from '../context-helpers/internal-context' import type { TypeInfo } from '../encoders' import { getEncoder, minLengthForType, toBytes } from '../encoders' import { AssertError, CodeError, InternalError } from '../errors' -import { getGenericTypeInfo, tryArc4EncodedLengthImpl } from '../runtime-helpers' +import { getGenericTypeInfo } from '../runtime-helpers' import { asBytes, asBytesCls, asNumber, asUint8Array, conactUint8Arrays } from '../util' import type { StubBytesCompat, StubUint64Compat } from './primitives' import { Bytes, Uint64, Uint64Cls } from './primitives' @@ -162,7 +162,7 @@ export class BoxCls { create(options?: { size?: StubUint64Compat }): boolean { const optionSize = options?.size !== undefined ? asNumber(options.size) : undefined - const valueTypeSize = tryArc4EncodedLengthImpl(this.valueType) + const valueTypeSize = minLengthForType(this.valueType) if (valueTypeSize === undefined && optionSize === undefined) { throw new InternalError(`${this.valueType.name} does not have a fixed byte size. Please specify a size argument`) } @@ -176,11 +176,7 @@ export class BoxCls { ) } } - lazyContext.ledger.setBox( - this.#app, - this.key, - new Uint8Array(Math.max(asNumber(options?.size ?? 0), this.valueType ? minLengthForType(this.valueType) : 0)), - ) + lazyContext.ledger.setBox(this.#app, this.key, new Uint8Array(Math.max(asNumber(options?.size ?? 0), valueTypeSize ?? 0))) return true } @@ -198,7 +194,7 @@ export class BoxCls { return materialised } set value(v: TValue) { - const isStaticValueType = tryArc4EncodedLengthImpl(this.valueType) !== undefined + const isStaticValueType = minLengthForType(this.valueType) !== undefined const newValueBytes = asUint8Array(toBytes(v)) if (isStaticValueType && this.exists) { const originalValueBytes = lazyContext.ledger.getBox(this.#app, this.key) diff --git a/tests/arc4/encode-decode-arc4.spec.ts b/tests/arc4/encode-decode-arc4.spec.ts index a0464cd5..d81b4363 100644 --- a/tests/arc4/encode-decode-arc4.spec.ts +++ b/tests/arc4/encode-decode-arc4.spec.ts @@ -225,12 +225,16 @@ describe('arc4EncodedLength', () => { expect(arc4EncodedLength()).toEqual(8) expect(arc4EncodedLength()).toEqual(64) expect(arc4EncodedLength()).toEqual(1) - expect(arc4EncodedLength()).toEqual(8) + expect(arc4EncodedLength()).toEqual(1) expect(arc4EncodedLength>()).toEqual(64) expect(arc4EncodedLength<[uint64, uint64, boolean]>()).toEqual(17) expect(arc4EncodedLength<[uint64, uint64, boolean, boolean]>()).toEqual(17) expect(arc4EncodedLength, Bool]>>()).toEqual(3) expect(arc4EncodedLength()).toEqual(395) + expect(arc4EncodedLength<[StaticArray, boolean, boolean]>()).toEqual(3) + expect( + arc4EncodedLength<[[boolean, boolean, boolean, boolean, boolean, boolean, boolean, boolean, boolean, boolean], boolean, boolean]>(), + ).toEqual(3) }) }) From e507637c1145f4ae4f652b791cdce27dc0c7b6dd Mon Sep 17 00:00:00 2001 From: Bobby Lat Date: Mon, 19 May 2025 16:56:42 +0800 Subject: [PATCH 11/68] feat: Add length generic to bytes type for declaring statically sized byte sequences --- examples/voting/contract.algo.ts | 10 ++--- examples/voting/contract.spec.ts | 2 +- package-lock.json | 18 ++++---- package.json | 4 +- src/constants.ts | 2 +- src/impl/asset-params.ts | 4 +- src/impl/block.ts | 12 +++--- src/impl/crypto.ts | 26 ++++++------ src/impl/encoded-types.ts | 18 ++++---- src/impl/global.ts | 8 ++-- src/impl/gtxn.ts | 10 ++--- src/impl/itxn.ts | 28 ++++++------- src/impl/primitives.ts | 8 ++++ src/impl/reference.ts | 8 +++- src/impl/transactions.ts | 24 +++++------ src/impl/txn.ts | 10 ++--- tests/artifacts/crypto-ops/contract.algo.ts | 20 ++++----- tests/crypto-op-codes.spec.ts | 46 +++++++++++---------- tests/references/asset.spec.ts | 2 +- tests/state-op-codes.spec.ts | 8 ++-- 20 files changed, 142 insertions(+), 126 deletions(-) diff --git a/examples/voting/contract.algo.ts b/examples/voting/contract.algo.ts index 37b86bc8..be5c2d6d 100644 --- a/examples/voting/contract.algo.ts +++ b/examples/voting/contract.algo.ts @@ -47,7 +47,7 @@ export class VotingRoundApp extends arc4.Contract { tallyBox = BoxRef({ key: Bytes`V` }) votesByAccount = BoxMap({ keyPrefix: Bytes() }) voteId = GlobalState() - snapshotPublicKey = GlobalState() + snapshotPublicKey = GlobalState>() metadataIpfsCid = GlobalState() startTime = GlobalState() nftImageUrl = GlobalState() @@ -60,7 +60,7 @@ export class VotingRoundApp extends arc4.Contract { @abimethod({ onCreate: 'require' }) public create( voteId: string, - snapshotPublicKey: bytes, + snapshotPublicKey: bytes<32>, metadataIpfsCid: string, startTime: uint64, endTime: uint64, @@ -158,7 +158,7 @@ export class VotingRoundApp extends arc4.Contract { } @abimethod({ readonly: true }) - public getPreconditions(signature: bytes): VotingPreconditions { + public getPreconditions(signature: bytes<64>): VotingPreconditions { return { is_allowed_to_vote: Uint64(this.allowedToVote(signature)), is_voting_open: Uint64(this.votingOpen()), @@ -167,7 +167,7 @@ export class VotingRoundApp extends arc4.Contract { } } - private allowedToVote(signature: bytes): boolean { + private allowedToVote(signature: bytes<64>): boolean { ensureBudget(2000) return op.ed25519verifyBare(Txn.sender.bytes, signature, this.snapshotPublicKey.value) } @@ -176,7 +176,7 @@ export class VotingRoundApp extends arc4.Contract { return this.votesByAccount(Txn.sender).exists } - public vote(fundMinBalReq: gtxn.PaymentTxn, signature: bytes, answerIds: VoteIndexArray): void { + public vote(fundMinBalReq: gtxn.PaymentTxn, signature: bytes<64>, answerIds: VoteIndexArray): void { ensureBudget(7700, OpUpFeeSource.GroupCredit) assert(this.allowedToVote(signature), 'Not allowed to vote') assert(this.votingOpen(), 'Voting not open') diff --git a/examples/voting/contract.spec.ts b/examples/voting/contract.spec.ts index e8dbc42c..ae1fd33b 100644 --- a/examples/voting/contract.spec.ts +++ b/examples/voting/contract.spec.ts @@ -16,7 +16,7 @@ describe('VotingRoundApp', () => { const createContract = () => { const contract = ctx.contract.create(VotingRoundApp) - const snapshotPublicKey = Bytes(keyPair.publicKey) + const snapshotPublicKey = Bytes(keyPair.publicKey).toFixed({ length: 32 }) const metadataIpfsCid = ctx.any.string(16) const startTime = ctx.any.uint64(Date.now() - 10_000, Date.now()) const endTime = ctx.any.uint64(Date.now() + 10_000, Date.now() + 100_000) diff --git a/package-lock.json b/package-lock.json index 5d7afac1..caf02984 100644 --- a/package-lock.json +++ b/package-lock.json @@ -8,8 +8,8 @@ "name": "@algorandfoundation/algorand-typescript-testing", "version": "1.0.0", "dependencies": { - "@algorandfoundation/algorand-typescript": "1.0.0-alpha.59", - "@algorandfoundation/puya-ts": "1.0.0-alpha.59", + "@algorandfoundation/algorand-typescript": "1.0.0-alpha.61", + "@algorandfoundation/puya-ts": "1.0.0-alpha.61", "elliptic": "^6.6.1", "js-sha256": "^0.11.0", "js-sha3": "^0.9.3", @@ -73,21 +73,21 @@ } }, "node_modules/@algorandfoundation/algorand-typescript": { - "version": "1.0.0-alpha.59", - "resolved": "https://registry.npmjs.org/@algorandfoundation/algorand-typescript/-/algorand-typescript-1.0.0-alpha.59.tgz", - "integrity": "sha512-2kuInFJzMdY4zjLGXn9aYgmeUVgb2Kd6fpdDzulGkkgiQ0HjAoSRaH0k64th+zH4rGvfeG2dXh6GB2ma3yZeBQ==", + "version": "1.0.0-alpha.61", + "resolved": "https://registry.npmjs.org/@algorandfoundation/algorand-typescript/-/algorand-typescript-1.0.0-alpha.61.tgz", + "integrity": "sha512-8TyZ9A9M8BClcrx46IlcFlPsnkD01y9lWmAX05xX/bSh9UHFkZanvY0j9DoHh3M/E6czjrPrkTa7rWwbWgF3JA==", "peerDependencies": { "tslib": "^2.6.2" } }, "node_modules/@algorandfoundation/puya-ts": { - "version": "1.0.0-alpha.59", - "resolved": "https://registry.npmjs.org/@algorandfoundation/puya-ts/-/puya-ts-1.0.0-alpha.59.tgz", - "integrity": "sha512-LSqy5o5CuYAc3rFqJyz90tHKb0+PZcvkBEzataNrrOL3Nrw6C5IK7K1zimW19m3Lvv5ruKnijWbZbiL2LfZB5Q==", + "version": "1.0.0-alpha.61", + "resolved": "https://registry.npmjs.org/@algorandfoundation/puya-ts/-/puya-ts-1.0.0-alpha.61.tgz", + "integrity": "sha512-/38wr4GljyQ5mVJZP9pro+E6uU2mK+kbEWNui+q1KyMnRX/CkXBBw+ZMB1/4XCUqN0xW6cKAPZRZrGCtMSvoLg==", "hasInstallScript": true, "license": "MIT", "dependencies": { - "@algorandfoundation/algorand-typescript": "1.0.0-alpha.59", + "@algorandfoundation/algorand-typescript": "1.0.0-alpha.61", "arcsecond": "^5.0.0", "argparse": "^2.0.1", "chalk": "^5.4.1", diff --git a/package.json b/package.json index 48223d98..3c050aa8 100644 --- a/package.json +++ b/package.json @@ -66,8 +66,8 @@ "vitest": "3.1.3" }, "dependencies": { - "@algorandfoundation/algorand-typescript": "1.0.0-alpha.59", - "@algorandfoundation/puya-ts": "1.0.0-alpha.59", + "@algorandfoundation/algorand-typescript": "1.0.0-alpha.61", + "@algorandfoundation/puya-ts": "1.0.0-alpha.61", "elliptic": "^6.6.1", "js-sha256": "^0.11.0", "js-sha3": "^0.9.3", diff --git a/src/constants.ts b/src/constants.ts index 7c1592b9..d3efc41f 100644 --- a/src/constants.ts +++ b/src/constants.ts @@ -25,7 +25,7 @@ export const DEFAULT_GLOBAL_GENESIS_HASH = Bytes( 133, 89, 181, 20, 120, 253, 137, 193, 118, 67, 208, 93, 21, 168, 174, 107, 16, 171, 71, 187, 109, 138, 49, 136, 17, 86, 230, 189, 59, 174, 149, 209, ]), -) +).toFixed({ length: 32 }) // algorand encoded address of 32 zero bytes export const ZERO_ADDRESS = Bytes.fromBase32('AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA') diff --git a/src/impl/asset-params.ts b/src/impl/asset-params.ts index ed775f3b..27f0375c 100644 --- a/src/impl/asset-params.ts +++ b/src/impl/asset-params.ts @@ -50,9 +50,9 @@ export const AssetParams: typeof op.AssetParams = { const asset = getAsset(a) return asset === undefined ? [Bytes(), false] : [asset.url, true] }, - assetMetadataHash(a: AssetType | StubUint64Compat): readonly [bytes, boolean] { + assetMetadataHash(a: AssetType | StubUint64Compat): readonly [bytes<32>, boolean] { const asset = getAsset(a) - return asset === undefined ? [Bytes(), false] : [asset.metadataHash, true] + return asset === undefined ? [Bytes() as bytes<32>, false] : [asset.metadataHash, true] }, assetManager(a: AssetType | StubUint64Compat): readonly [AccountType, boolean] { const asset = getAsset(a) diff --git a/src/impl/block.ts b/src/impl/block.ts index e230c9df..284be871 100644 --- a/src/impl/block.ts +++ b/src/impl/block.ts @@ -5,24 +5,24 @@ import { Uint64, type StubUint64Compat } from './primitives' import { Account } from './reference' export class BlockData { - seed: bytes + seed: bytes<32> timestamp: uint64 proposer: AccountType feesCollected: uint64 bonus: uint64 - branch: bytes + branch: bytes<32> feeSink: AccountType protocol: bytes txnCounter: uint64 proposerPayout: uint64 constructor() { - this.seed = getRandomBytes(32).asAlgoTs() + this.seed = getRandomBytes(32).asAlgoTs().toFixed({ length: 32 }) this.timestamp = asUint64(Date.now()) this.proposer = Account() this.feesCollected = Uint64(0) this.bonus = Uint64(0) - this.branch = getRandomBytes(32).asAlgoTs() + this.branch = getRandomBytes(32).asAlgoTs().toFixed({ length: 32 }) this.feeSink = Account() this.protocol = getRandomBytes(32).asAlgoTs() this.txnCounter = Uint64(0) @@ -31,7 +31,7 @@ export class BlockData { } export const Block: typeof op.Block = { - blkSeed: function (a: StubUint64Compat): bytes { + blkSeed: function (a: StubUint64Compat): bytes<32> { return lazyContext.ledger.getBlockData(a).seed }, blkTimestamp: function (a: StubUint64Compat): uint64 { @@ -46,7 +46,7 @@ export const Block: typeof op.Block = { blkBonus: function (a: uint64): uint64 { return lazyContext.ledger.getBlockData(a).bonus }, - blkBranch: function (a: uint64): bytes { + blkBranch: function (a: uint64): bytes<32> { return lazyContext.ledger.getBlockData(a).branch }, blkFeeSink: function (a: uint64): AccountType { diff --git a/src/impl/crypto.ts b/src/impl/crypto.ts index 5b85bca8..b3ae4dee 100644 --- a/src/impl/crypto.ts +++ b/src/impl/crypto.ts @@ -12,32 +12,32 @@ import { asBytes, asBytesCls, asUint8Array, conactUint8Arrays } from '../util' import type { StubBytesCompat, StubUint64Compat } from './primitives' import { Bytes, BytesCls, Uint64Cls } from './primitives' -export const sha256 = (a: StubBytesCompat): bytes => { +export const sha256 = (a: StubBytesCompat): bytes<32> => { const bytesA = BytesCls.fromCompat(a) const hashArray = js_sha256.sha256.create().update(bytesA.asUint8Array()).digest() const hashBytes = BytesCls.fromCompat(new Uint8Array(hashArray)) - return hashBytes.asAlgoTs() + return hashBytes.asAlgoTs().toFixed({ length: 32 }) } -export const sha3_256 = (a: StubBytesCompat): bytes => { +export const sha3_256 = (a: StubBytesCompat): bytes<32> => { const bytesA = BytesCls.fromCompat(a) const hashArray = js_sha3.sha3_256.create().update(bytesA.asUint8Array()).digest() const hashBytes = BytesCls.fromCompat(new Uint8Array(hashArray)) - return hashBytes.asAlgoTs() + return hashBytes.asAlgoTs().toFixed({ length: 32 }) } -export const keccak256 = (a: StubBytesCompat): bytes => { +export const keccak256 = (a: StubBytesCompat): bytes<32> => { const bytesA = BytesCls.fromCompat(a) const hashArray = js_sha3.keccak256.create().update(bytesA.asUint8Array()).digest() const hashBytes = BytesCls.fromCompat(new Uint8Array(hashArray)) - return hashBytes.asAlgoTs() + return hashBytes.asAlgoTs().toFixed({ length: 32 }) } -export const sha512_256 = (a: StubBytesCompat): bytes => { +export const sha512_256 = (a: StubBytesCompat): bytes<32> => { const bytesA = BytesCls.fromCompat(a) const hashArray = js_sha512.sha512_256.create().update(bytesA.asUint8Array()).digest() const hashBytes = BytesCls.fromCompat(new Uint8Array(hashArray)) - return hashBytes.asAlgoTs() + return hashBytes.asAlgoTs().toFixed({ length: 32 }) } export const ed25519verifyBare = (a: StubBytesCompat, b: StubBytesCompat, c: StubBytesCompat): boolean => { @@ -88,7 +88,7 @@ export const ecdsaPkRecover = ( b: StubUint64Compat, c: StubBytesCompat, d: StubBytesCompat, -): readonly [bytes, bytes] => { +): readonly [bytes<32>, bytes<32>] => { if (v !== Ecdsa.Secp256k1) { throw new InternalError(`Unsupported ECDSA curve: ${v}`) } @@ -106,10 +106,10 @@ export const ecdsaPkRecover = ( const x = pubKey.getX().toArray('be') const y = pubKey.getY().toArray('be') - return [Bytes(x), Bytes(y)] + return [Bytes(x).toFixed({ length: 32 }), Bytes(y).toFixed({ length: 32 })] } -export const ecdsaPkDecompress = (v: Ecdsa, a: StubBytesCompat): readonly [bytes, bytes] => { +export const ecdsaPkDecompress = (v: Ecdsa, a: StubBytesCompat): readonly [bytes<32>, bytes<32>] => { const bytesA = BytesCls.fromCompat(a) const ecdsa = new elliptic.ec(curveMap[v]) @@ -118,10 +118,10 @@ export const ecdsaPkDecompress = (v: Ecdsa, a: StubBytesCompat): readonly [bytes const x = pubKey.getX().toArray('be') const y = pubKey.getY().toArray('be') - return [Bytes(new Uint8Array(x)), Bytes(new Uint8Array(y))] + return [Bytes(new Uint8Array(x)).toFixed({ length: 32 }), Bytes(new Uint8Array(y)).toFixed({ length: 32 })] } -export const vrfVerify = (_s: VrfVerify, _a: StubBytesCompat, _b: StubBytesCompat, _c: StubBytesCompat): readonly [bytes, boolean] => { +export const vrfVerify = (_s: VrfVerify, _a: StubBytesCompat, _b: StubBytesCompat, _c: StubBytesCompat): readonly [bytes<64>, boolean] => { throw new NotImplementedError('vrfVerify') } diff --git a/src/impl/encoded-types.ts b/src/impl/encoded-types.ts index 091c8a8b..1643b50c 100644 --- a/src/impl/encoded-types.ts +++ b/src/impl/encoded-types.ts @@ -938,16 +938,16 @@ export class DynamicBytesImpl extends DynamicBytes { } } -export class StaticBytesImpl extends StaticBytes { - private value: StaticArrayImpl +export class StaticBytesImpl extends StaticBytes { + private value: StaticArrayImpl typeInfo: TypeInfo - constructor(typeInfo: TypeInfo | string, value?: bytes | string) { - super(value) + constructor(typeInfo: TypeInfo | string, value?: bytes) { + super(value ?? (Bytes() as bytes)) this.typeInfo = typeof typeInfo === 'string' ? JSON.parse(typeInfo) : typeInfo const uint8ArrayValue = asUint8Array(value ?? new Uint8Array(StaticBytesImpl.getMaxBytesLength(this.typeInfo))) - this.value = StaticArrayImpl.fromBytesImpl(uint8ArrayValue, typeInfo) as StaticArrayImpl - return new Proxy(this, arrayProxyHandler()) as StaticBytesImpl + this.value = StaticArrayImpl.fromBytesImpl(uint8ArrayValue, typeInfo) as StaticArrayImpl + return new Proxy(this, arrayProxyHandler()) as StaticBytesImpl } get bytes(): bytes { @@ -965,8 +965,8 @@ export class StaticBytesImpl extends StaticBytes { return this.value.length } - get native(): bytes { - return this.value.bytes + get native(): bytes { + return this.value.bytes as bytes } get items(): ByteImpl[] { @@ -994,7 +994,7 @@ export class StaticBytesImpl extends StaticBytes { static fromBytesImpl(value: StubBytesCompat | Uint8Array, typeInfo: string | TypeInfo, prefix: 'none' | 'log' = 'none'): StaticBytesImpl { const staticArrayValue = StaticArrayImpl.fromBytesImpl(value, typeInfo, prefix) as StaticArrayImpl const result = new StaticBytesImpl(typeInfo) - result.value = staticArrayValue + result.value = staticArrayValue as StaticArrayImpl return result } diff --git a/src/impl/global.ts b/src/impl/global.ts index 61ecf41e..fd1afe97 100644 --- a/src/impl/global.ts +++ b/src/impl/global.ts @@ -24,11 +24,11 @@ export class GlobalData { logicSigVersion?: uint64 round?: uint64 latestTimestamp?: uint64 - groupId?: bytes + groupId?: bytes<32> callerApplicationId: uint64 assetCreateMinBalance: uint64 assetOptInMinBalance: uint64 - genesisHash: bytes + genesisHash: bytes<32> opcodeBudget?: uint64 payoutsEnabled: boolean payoutsGoOnlineFee: uint64 @@ -146,7 +146,7 @@ export const Global: typeof op.Global = { /** * ID of the transaction group. 32 zero bytes if the transaction is not part of a group. */ - get groupId(): bytes { + get groupId(): bytes<32> { const data = getGlobalData() if (data.groupId !== undefined) return data.groupId const reference = getObjectReference(lazyContext.activeGroup) @@ -194,7 +194,7 @@ export const Global: typeof op.Global = { /** * The Genesis Hash for the network. */ - get genesisHash(): bytes { + get genesisHash(): bytes<32> { return getGlobalData().genesisHash }, diff --git a/src/impl/gtxn.ts b/src/impl/gtxn.ts index 21ef7a7d..76cb7577 100644 --- a/src/impl/gtxn.ts +++ b/src/impl/gtxn.ts @@ -22,7 +22,7 @@ export const GTxn: typeof op.GTxn = { note(t: StubUint64Compat): bytes { return lazyContext.activeGroup.getTransaction(t).note }, - lease(t: StubUint64Compat): bytes { + lease(t: StubUint64Compat): bytes<32> { return lazyContext.activeGroup.getTransaction(t).lease }, receiver(t: StubUint64Compat): Account { @@ -34,10 +34,10 @@ export const GTxn: typeof op.GTxn = { closeRemainderTo(t: StubUint64Compat): Account { return lazyContext.activeGroup.getPaymentTransaction(t).closeRemainderTo }, - votePk(t: StubUint64Compat): bytes { + votePk(t: StubUint64Compat): bytes<32> { return lazyContext.activeGroup.getKeyRegistrationTransaction(t).voteKey }, - selectionPk(t: StubUint64Compat): bytes { + selectionPk(t: StubUint64Compat): bytes<32> { return lazyContext.activeGroup.getKeyRegistrationTransaction(t).selectionKey }, voteFirst(t: StubUint64Compat): uint64 { @@ -73,7 +73,7 @@ export const GTxn: typeof op.GTxn = { groupIndex(t: StubUint64Compat): uint64 { return lazyContext.activeGroup.getTransaction(t).groupIndex }, - txId(t: StubUint64Compat): bytes { + txId(t: StubUint64Compat): bytes<32> { return lazyContext.activeGroup.getTransaction(t).txnId }, applicationId(t: StubUint64Compat): Application { @@ -125,7 +125,7 @@ export const GTxn: typeof op.GTxn = { configAssetUrl(t: StubUint64Compat): bytes { return lazyContext.activeGroup.getAssetConfigTransaction(t).url }, - configAssetMetadataHash(t: StubUint64Compat): bytes { + configAssetMetadataHash(t: StubUint64Compat): bytes<32> { return lazyContext.activeGroup.getAssetConfigTransaction(t).metadataHash }, configAssetManager(t: StubUint64Compat): Account { diff --git a/src/impl/itxn.ts b/src/impl/itxn.ts index aaf3652a..a99de88d 100644 --- a/src/impl/itxn.ts +++ b/src/impl/itxn.ts @@ -41,7 +41,7 @@ export const GITxn: typeof op.GITxn = { note: function (t: StubUint64Compat): bytes { return lazyContext.activeGroup.getItxnGroup().getInnerTxn(t).note }, - lease: function (t: StubUint64Compat): bytes { + lease: function (t: StubUint64Compat): bytes<32> { return lazyContext.activeGroup.getItxnGroup().getInnerTxn(t).lease }, receiver: function (t: StubUint64Compat): Account { @@ -53,10 +53,10 @@ export const GITxn: typeof op.GITxn = { closeRemainderTo: function (t: StubUint64Compat): Account { return lazyContext.activeGroup.getItxnGroup().getPaymentInnerTxn(t).closeRemainderTo }, - votePk: function (t: StubUint64Compat): bytes { + votePk: function (t: StubUint64Compat): bytes<32> { return lazyContext.activeGroup.getItxnGroup().getKeyRegistrationInnerTxn(t).voteKey }, - selectionPk: function (t: StubUint64Compat): bytes { + selectionPk: function (t: StubUint64Compat): bytes<32> { return lazyContext.activeGroup.getItxnGroup().getKeyRegistrationInnerTxn(t).selectionKey }, voteFirst: function (t: StubUint64Compat): uint64 { @@ -92,7 +92,7 @@ export const GITxn: typeof op.GITxn = { groupIndex: function (t: StubUint64Compat): uint64 { return lazyContext.activeGroup.getItxnGroup().getInnerTxn(t).groupIndex }, - txId: function (t: StubUint64Compat): bytes { + txId: function (t: StubUint64Compat): bytes<32> { return lazyContext.activeGroup.getItxnGroup().getInnerTxn(t).txnId }, applicationId: function (t: StubUint64Compat): Application { @@ -144,7 +144,7 @@ export const GITxn: typeof op.GITxn = { configAssetUrl: function (t: StubUint64Compat): bytes { return lazyContext.activeGroup.getItxnGroup().getAssetConfigInnerTxn(t).url }, - configAssetMetadataHash: function (t: StubUint64Compat): bytes { + configAssetMetadataHash: function (t: StubUint64Compat): bytes<32> { return lazyContext.activeGroup.getItxnGroup().getAssetConfigInnerTxn(t).metadataHash }, configAssetManager: function (t: StubUint64Compat): Account { @@ -269,7 +269,7 @@ export const ITxn: typeof op.ITxn = { /** * 32 byte lease value */ - get lease(): bytes { + get lease(): bytes<32> { return lazyContext.activeGroup.getItxnGroup().getInnerTxn().lease }, /** @@ -293,13 +293,13 @@ export const ITxn: typeof op.ITxn = { /** * 32 byte address */ - get votePk(): bytes { + get votePk(): bytes<32> { return lazyContext.activeGroup.getItxnGroup().getKeyRegistrationInnerTxn().voteKey }, /** * 32 byte address */ - get selectionPk(): bytes { + get selectionPk(): bytes<32> { return lazyContext.activeGroup.getItxnGroup().getKeyRegistrationInnerTxn().selectionKey }, /** @@ -371,7 +371,7 @@ export const ITxn: typeof op.ITxn = { /** * The computed ID for this transaction. 32 bytes. */ - get txId(): bytes { + get txId(): bytes<32> { return lazyContext.activeGroup.getItxnGroup().getInnerTxn().txnId }, /** @@ -474,7 +474,7 @@ export const ITxn: typeof op.ITxn = { /** * 32 byte commitment to unspecified asset metadata */ - get configAssetMetadataHash(): bytes { + get configAssetMetadataHash(): bytes<32> { return lazyContext.activeGroup.getItxnGroup().getAssetConfigInnerTxn().metadataHash }, /** @@ -672,10 +672,10 @@ export const ITxnCreate: typeof op.ITxnCreate = { setConstructingItxnField({ closeRemainderTo: a }) }, setVotePk: function (a: StubBytesCompat): void { - setConstructingItxnField({ voteKey: asBytes(a) }) + setConstructingItxnField({ voteKey: asBytes(a).toFixed({ length: 32 }) }) }, setSelectionPk: function (a: StubBytesCompat): void { - setConstructingItxnField({ selectionKey: asBytes(a) }) + setConstructingItxnField({ selectionKey: asBytes(a).toFixed({ length: 32 }) }) }, setVoteFirst: function (a: StubUint64Compat): void { setConstructingItxnField({ voteFirst: asUint64(a) }) @@ -754,7 +754,7 @@ export const ITxnCreate: typeof op.ITxnCreate = { setConstructingItxnField({ url: asBytes(a) }) }, setConfigAssetMetadataHash: function (a: StubBytesCompat): void { - setConstructingItxnField({ metadataHash: asBytes(a) }) + setConstructingItxnField({ metadataHash: asBytes(a).toFixed({ length: 32 }) }) }, setConfigAssetManager: function (a: Account): void { setConstructingItxnField({ manager: a }) @@ -812,7 +812,7 @@ export const ITxnCreate: typeof op.ITxnCreate = { setConstructingItxnField({ nonparticipation: a }) }, setStateProofPk: function (a: StubBytesCompat): void { - setConstructingItxnField({ stateProofKey: asBytes(a) }) + setConstructingItxnField({ stateProofKey: asBytes(a).toFixed({ length: 64 }) }) }, setApprovalProgramPages: function (a: StubBytesCompat): void { let pages = (getConstructingItxn().approvalProgram ?? []) as bytes[] diff --git a/src/impl/primitives.ts b/src/impl/primitives.ts index 5a728b29..9997f4ea 100644 --- a/src/impl/primitives.ts +++ b/src/impl/primitives.ts @@ -505,6 +505,14 @@ export class BytesCls extends AlgoTsPrimitiveCls { return encodingUtil.uint8ArrayToHex(this.#v) } + toFixed(options: { length: TNewLength; checked?: boolean }): bytes { + if (options.checked !== false) { + if (this.#v.length !== options.length) { + throw new CodeError(`Invalid bytes constant length of ${this.#v.length}, expected ${options.length}`) + } + } + return new BytesCls(this.#v) as unknown as bytes + } static [Symbol.hasInstance](x: unknown): x is BytesCls { return isInstanceOfTypeByName(x, BytesCls) } diff --git a/src/impl/reference.ts b/src/impl/reference.ts index 44a574b9..2850acfe 100644 --- a/src/impl/reference.ts +++ b/src/impl/reference.ts @@ -77,6 +77,10 @@ export class AccountCls extends BytesBackedCls implements AccountType { super(addressBytes.slice(0, 32)) } + get bytes() { + return super.bytes.toFixed({ length: 32 }) + } + private get data(): AccountData { return lazyContext.getAccountData(this) } @@ -218,7 +222,7 @@ export const getDefaultAssetData = (): AssetData => ({ unitName: lazyContext.any.bytes(4), name: lazyContext.any.bytes(32), url: lazyContext.any.bytes(10), - metadataHash: lazyContext.any.bytes(32), + metadataHash: lazyContext.any.bytes(32).toFixed({ length: 32 }), manager: Account(ZERO_ADDRESS), freeze: Account(ZERO_ADDRESS), clawback: Account(ZERO_ADDRESS), @@ -261,7 +265,7 @@ export class AssetCls extends Uint64BackedCls implements AssetType { get url(): bytes { return this.data.url } - get metadataHash(): bytes { + get metadataHash(): bytes<32> { return this.data.metadataHash } get manager(): AccountType { diff --git a/src/impl/transactions.ts b/src/impl/transactions.ts index d3e4537a..80a010ac 100644 --- a/src/impl/transactions.ts +++ b/src/impl/transactions.ts @@ -23,9 +23,9 @@ const baseDefaultFields = () => ({ firstValidTime: Uint64(0), lastValid: Uint64(0), note: Bytes(), - lease: Bytes(), + lease: Bytes() as bytes<32>, groupIndex: Uint64(0), - txnId: getRandomBytes(32).asAlgoTs(), + txnId: getRandomBytes(32).asAlgoTs().toFixed({ length: 32 }), rekeyTo: Account(), }) @@ -53,9 +53,9 @@ abstract class TransactionBase { readonly firstValidTime: uint64 readonly lastValid: uint64 readonly note: bytes - readonly lease: bytes + readonly lease: bytes<32> readonly groupIndex: uint64 - readonly txnId: bytes + readonly txnId: bytes<32> readonly rekeyTo: AccountType readonly scratchSpace: Array @@ -106,13 +106,13 @@ export class KeyRegistrationTransaction extends TransactionBase implements gtxn. protected constructor(fields: TxnFields) { super(fields) - this.voteKey = fields.voteKey ?? Bytes() - this.selectionKey = fields.selectionKey ?? Bytes() + this.voteKey = fields.voteKey ?? (Bytes() as bytes<32>) + this.selectionKey = fields.selectionKey ?? (Bytes() as bytes<32>) this.voteFirst = fields.voteFirst ?? Uint64(0) this.voteLast = fields.voteLast ?? Uint64(0) this.voteKeyDilution = fields.voteKeyDilution ?? Uint64(0) this.nonparticipation = fields.nonparticipation ?? false - this.stateProofKey = fields.stateProofKey ?? Bytes() + this.stateProofKey = fields.stateProofKey ?? (Bytes() as bytes<64>) const globalData = lazyContext.ledger.globalData if (this.fee >= globalData.payoutsGoOnlineFee && globalData.payoutsEnabled) { lazyContext.ledger.patchAccountData(this.sender, { @@ -121,13 +121,13 @@ export class KeyRegistrationTransaction extends TransactionBase implements gtxn. } } - readonly voteKey: bytes - readonly selectionKey: bytes + readonly voteKey: bytes<32> + readonly selectionKey: bytes<32> readonly voteFirst: uint64 readonly voteLast: uint64 readonly voteKeyDilution: uint64 readonly nonparticipation: boolean - readonly stateProofKey: bytes + readonly stateProofKey: bytes<64> readonly type: TransactionType.KeyRegistration = TransactionType.KeyRegistration readonly typeBytes: bytes = asUint64Cls(TransactionType.KeyRegistration).toBytes().asAlgoTs() } @@ -147,7 +147,7 @@ export class AssetConfigTransaction extends TransactionBase implements gtxn.Asse this.unitName = fields.unitName ?? Bytes() this.assetName = fields.assetName ?? Bytes() this.url = fields.url ?? Bytes() - this.metadataHash = fields.metadataHash ?? Bytes() + this.metadataHash = fields.metadataHash ?? (Bytes() as bytes<32>) this.manager = fields.manager ?? Account() this.reserve = fields.reserve ?? Account() this.freeze = fields.freeze ?? Account() @@ -162,7 +162,7 @@ export class AssetConfigTransaction extends TransactionBase implements gtxn.Asse readonly unitName: bytes readonly assetName: bytes readonly url: bytes - readonly metadataHash: bytes + readonly metadataHash: bytes<32> readonly manager: AccountType readonly reserve: AccountType readonly freeze: AccountType diff --git a/src/impl/txn.ts b/src/impl/txn.ts index a17b757a..f449a898 100644 --- a/src/impl/txn.ts +++ b/src/impl/txn.ts @@ -60,7 +60,7 @@ export const Txn: typeof op.Txn = { /** * 32 byte lease value */ - get lease(): bytes { + get lease(): bytes<32> { return lazyContext.activeGroup.getTransaction().lease }, @@ -88,14 +88,14 @@ export const Txn: typeof op.Txn = { /** * 32 byte address */ - get votePk(): bytes { + get votePk(): bytes<32> { return lazyContext.activeGroup.getKeyRegistrationTransaction().voteKey }, /** * 32 byte address */ - get selectionPk(): bytes { + get selectionPk(): bytes<32> { return lazyContext.activeGroup.getKeyRegistrationTransaction().selectionKey }, @@ -179,7 +179,7 @@ export const Txn: typeof op.Txn = { /** * The computed ID for this transaction. 32 bytes. */ - get txId(): bytes { + get txId(): bytes<32> { return lazyContext.activeGroup.getTransaction().txnId }, @@ -299,7 +299,7 @@ export const Txn: typeof op.Txn = { /** * 32 byte commitment to unspecified asset metadata */ - get configAssetMetadataHash(): bytes { + get configAssetMetadataHash(): bytes<32> { return lazyContext.activeGroup.getAssetConfigTransaction().metadataHash }, diff --git a/tests/artifacts/crypto-ops/contract.algo.ts b/tests/artifacts/crypto-ops/contract.algo.ts index 056d8741..1f5a4ffe 100644 --- a/tests/artifacts/crypto-ops/contract.algo.ts +++ b/tests/artifacts/crypto-ops/contract.algo.ts @@ -42,41 +42,41 @@ export class CryptoOpsContract extends arc4.Contract { } @arc4.abimethod() - public verify_ed25519verify(a: bytes, b: bytes, c: bytes): Bool { + public verify_ed25519verify(a: bytes, b: bytes<64>, c: bytes<32>): Bool { ensureBudget(1900, OpUpFeeSource.GroupCredit) const result = op.ed25519verify(a, b, c) return new Bool(result) } @arc4.abimethod() - public verify_ed25519verify_bare(a: bytes, b: bytes, c: bytes): Bool { + public verify_ed25519verify_bare(a: bytes, b: bytes<64>, c: bytes<32>): Bool { ensureBudget(1900, OpUpFeeSource.GroupCredit) const result = op.ed25519verifyBare(a, b, c) return new Bool(result) } @arc4.abimethod() - public verify_ecdsa_verify_k1(a: bytes, b: bytes, c: bytes, d: bytes, e: bytes): boolean { + public verify_ecdsa_verify_k1(a: bytes<32>, b: bytes<32>, c: bytes<32>, d: bytes<32>, e: bytes<32>): boolean { ensureBudget(3000, OpUpFeeSource.GroupCredit) const result_k1 = op.ecdsaVerify(Ecdsa.Secp256k1, a, b, c, d, e) return result_k1 } @arc4.abimethod() - public verify_ecdsa_verify_r1(a: bytes, b: bytes, c: bytes, d: bytes, e: bytes): boolean { + public verify_ecdsa_verify_r1(a: bytes<32>, b: bytes<32>, c: bytes<32>, d: bytes<32>, e: bytes<32>): boolean { ensureBudget(3000, OpUpFeeSource.GroupCredit) const result_r1 = op.ecdsaVerify(Ecdsa.Secp256r1, a, b, c, d, e) return result_r1 } @arc4.abimethod() - public verify_ecdsa_recover_k1(a: bytes, b: uint64, c: bytes, d: bytes): readonly [bytes, bytes] { + public verify_ecdsa_recover_k1(a: bytes<32>, b: uint64, c: bytes<32>, d: bytes<32>): readonly [bytes<32>, bytes<32>] { ensureBudget(3000, OpUpFeeSource.GroupCredit) return op.ecdsaPkRecover(Ecdsa.Secp256k1, a, b, c, d) } @arc4.abimethod() - public verify_ecdsa_recover_r1(a: bytes, b: uint64, c: bytes, d: bytes): readonly [bytes, bytes] { + public verify_ecdsa_recover_r1(a: bytes<32>, b: uint64, c: bytes<32>, d: bytes<32>): readonly [bytes<32>, bytes<32>] { /** * Must fail, AVM does not support Secp256r1 for recover */ @@ -85,26 +85,26 @@ export class CryptoOpsContract extends arc4.Contract { } @arc4.abimethod() - public verify_ecdsa_decompress_k1(a: bytes): readonly [bytes, bytes] { + public verify_ecdsa_decompress_k1(a: bytes<33>): readonly [bytes<32>, bytes<32>] { ensureBudget(700, OpUpFeeSource.GroupCredit) return op.ecdsaPkDecompress(Ecdsa.Secp256k1, a) } @arc4.abimethod() - public verify_ecdsa_decompress_r1(a: bytes): readonly [bytes, bytes] { + public verify_ecdsa_decompress_r1(a: bytes<33>): readonly [bytes<32>, bytes<32>] { ensureBudget(700, OpUpFeeSource.GroupCredit) return op.ecdsaPkDecompress(Ecdsa.Secp256r1, a) } @arc4.abimethod() - public verify_vrf_verify(a: bytes, b: bytes, c: bytes): readonly [bytes, boolean] { + public verify_vrf_verify(a: bytes, b: bytes<80>, c: bytes<32>): readonly [bytes<64>, boolean] { ensureBudget(5700, OpUpFeeSource.GroupCredit) const result = op.vrfVerify(VrfVerify.VrfAlgorand, a, b, c) return result } @arc4.abimethod() - public verify_mimc(a: bytes): bytes { + public verify_mimc(a: bytes): bytes<32> { ensureBudget(5700, OpUpFeeSource.GroupCredit) const result = op.mimc(MimcConfigurations.BN254Mp110, a) return result diff --git a/tests/crypto-op-codes.spec.ts b/tests/crypto-op-codes.spec.ts index e47beaa1..f34b44d0 100644 --- a/tests/crypto-op-codes.spec.ts +++ b/tests/crypto-op-codes.spec.ts @@ -168,11 +168,11 @@ describe('crypto op codes', async () => { describe('ecdsaVerify', async () => { test('should be able to verify k1 signature', async ({ appClientCryptoOpsContract: appClient }) => { - const messageHash = Bytes.fromHex('f809fd0aa0bb0f20b354c6b2f86ea751957a4e262a546bd716f34f69b9516ae1') - const sigR = Bytes.fromHex('f7f913754e5c933f3825d3aef22e8bf75cfe35a18bede13e15a6e4adcfe816d2') - const sigS = Bytes.fromHex('0b5599159aa859d79677f33280848ae4c09c2061e8b5881af8507f8112966754') - const pubkeyX = Bytes.fromHex('a710244d62747aa8db022ddd70617240adaf881b439e5f69993800e614214076') - const pubkeyY = Bytes.fromHex('48d0d337704fe2c675909d2c93f7995e199156f302f63c74a8b96827b28d777b') + const messageHash = Bytes.fromHex('f809fd0aa0bb0f20b354c6b2f86ea751957a4e262a546bd716f34f69b9516ae1').toFixed({ length: 32 }) + const sigR = Bytes.fromHex('f7f913754e5c933f3825d3aef22e8bf75cfe35a18bede13e15a6e4adcfe816d2').toFixed({ length: 32 }) + const sigS = Bytes.fromHex('0b5599159aa859d79677f33280848ae4c09c2061e8b5881af8507f8112966754').toFixed({ length: 32 }) + const pubkeyX = Bytes.fromHex('a710244d62747aa8db022ddd70617240adaf881b439e5f69993800e614214076').toFixed({ length: 32 }) + const pubkeyY = Bytes.fromHex('48d0d337704fe2c675909d2c93f7995e199156f302f63c74a8b96827b28d777b').toFixed({ length: 32 }) const avmResult = await getAvmResult( { @@ -193,11 +193,11 @@ describe('crypto op codes', async () => { expect(result).toEqual(avmResult) }) test('should be able to verify r1 signature', async ({ appClientCryptoOpsContract: appClient }) => { - const messageHash = Bytes.fromHex('f809fd0aa0bb0f20b354c6b2f86ea751957a4e262a546bd716f34f69b9516ae1') - const sigR = Bytes.fromHex('18d96c7cda4bc14d06277534681ded8a94828eb731d8b842e0da8105408c83cf') - const sigS = Bytes.fromHex('7d33c61acf39cbb7a1d51c7126f1718116179adebd31618c4604a1f03b5c274a') - const pubkeyX = Bytes.fromHex('f8140e3b2b92f7cbdc8196bc6baa9ce86cf15c18e8ad0145d50824e6fa890264') - const pubkeyY = Bytes.fromHex('bd437b75d6f1db67155a95a0da4b41f2b6b3dc5d42f7db56238449e404a6c0a3') + const messageHash = Bytes.fromHex('f809fd0aa0bb0f20b354c6b2f86ea751957a4e262a546bd716f34f69b9516ae1').toFixed({ length: 32 }) + const sigR = Bytes.fromHex('18d96c7cda4bc14d06277534681ded8a94828eb731d8b842e0da8105408c83cf').toFixed({ length: 32 }) + const sigS = Bytes.fromHex('7d33c61acf39cbb7a1d51c7126f1718116179adebd31618c4604a1f03b5c274a').toFixed({ length: 32 }) + const pubkeyX = Bytes.fromHex('f8140e3b2b92f7cbdc8196bc6baa9ce86cf15c18e8ad0145d50824e6fa890264').toFixed({ length: 32 }) + const pubkeyY = Bytes.fromHex('bd437b75d6f1db67155a95a0da4b41f2b6b3dc5d42f7db56238449e404a6c0a3').toFixed({ length: 32 }) const avmResult = await getAvmResult( { @@ -236,7 +236,7 @@ describe('crypto op codes', async () => { asUint8Array(d), ) - const result = op.ecdsaPkRecover(Ecdsa.Secp256k1, asBytes(a), asUint64(b), asBytes(c), asBytes(d)) + const result = op.ecdsaPkRecover(Ecdsa.Secp256k1, a, asUint64(b), c, d) expect(result[0]).toEqual(avmResult[0]) expect(result[1]).toEqual(avmResult[1]) @@ -264,7 +264,7 @@ describe('crypto op codes', async () => { ), ).rejects.toThrow('unsupported curve') - expect(() => op.ecdsaPkRecover(Ecdsa.Secp256r1, asBytes(a), asUint64(b), asBytes(c), asBytes(d))).toThrow('Unsupported ECDSA curve') + expect(() => op.ecdsaPkRecover(Ecdsa.Secp256r1, a, asUint64(b), c, d)).toThrow('Unsupported ECDSA curve') }) }) @@ -293,15 +293,15 @@ describe('crypto op codes', async () => { const b = BytesCls.fromHex( '372a3afb42f55449c94aaa5f274f26543e77e8d8af4babee1a6fbc1c0391aa9e6e0b8d8d7f4ed045d5b517fea8ad3566025ae90d2f29f632e38384b4c4f5b9eb741c6e446b0f540c1b3761d814438b04', ) - const c = BytesCls.fromHex('3a2740da7a0788ebb12a52154acbcca1813c128ca0b249e93f8eb6563fee418d') + .asAlgoTs() + .toFixed({ length: 80 }) + const c = BytesCls.fromHex('3a2740da7a0788ebb12a52154acbcca1813c128ca0b249e93f8eb6563fee418d').asAlgoTs().toFixed({ length: 32 }) test('should throw not available error', async () => { const mockedVrfVerify = op.vrfVerify as Mock // restore to original stub implemention which should throw not available error mockedVrfVerify.mockRestore() - expect(() => op.vrfVerify(VrfVerify.VrfAlgorand, asBytes(a), asBytes(b), asBytes(c))).toThrow( - 'vrfVerify is not available in test context', - ) + expect(() => op.vrfVerify(VrfVerify.VrfAlgorand, asBytes(a), b, c)).toThrow('vrfVerify is not available in test context') }) test('should return mocked result', async ({ appClientCryptoOpsContract: appClient }) => { @@ -313,8 +313,8 @@ describe('crypto op codes', async () => { asUint8Array(c), ) const mockedVrfVerify = op.vrfVerify as Mock - mockedVrfVerify.mockReturnValue([BytesCls.fromCompat(new Uint8Array(avmResult[0])).asAlgoTs(), avmResult[1]]) - const result = op.vrfVerify(VrfVerify.VrfAlgorand, asBytes(a), asBytes(b), asBytes(c)) + mockedVrfVerify.mockReturnValue([BytesCls.fromCompat(new Uint8Array(avmResult[0])).asAlgoTs().toFixed({ length: 64 }), avmResult[1]]) + const result = op.vrfVerify(VrfVerify.VrfAlgorand, asBytes(a), b, c) expect(asUint8Array(result[0])).toEqual(new Uint8Array(avmResult[0])) expect(result[1]).toEqual(avmResult[1]) @@ -375,9 +375,13 @@ const generateEcdsaTestData = (v: Ecdsa) => { const recoveryId = 0 // Recovery ID is typically 0 or 1 return { - data: BytesCls.fromCompat(new Uint8Array(messageHash)), - r: BytesCls.fromCompat(new Uint8Array(signature.r.toArray('be', 32))), - s: BytesCls.fromCompat(new Uint8Array(signature.s.toArray('be', 32))), + data: BytesCls.fromCompat(new Uint8Array(messageHash)).asAlgoTs().toFixed({ length: 32 }), + r: BytesCls.fromCompat(new Uint8Array(signature.r.toArray('be', 32))) + .asAlgoTs() + .toFixed({ length: 32 }), + s: BytesCls.fromCompat(new Uint8Array(signature.s.toArray('be', 32))) + .asAlgoTs() + .toFixed({ length: 32 }), recoveryId: Uint64Cls.fromCompat(recoveryId), pubkeyX: BytesCls.fromCompat(new Uint8Array(pk.slice(0, 32))), pubkeyY: BytesCls.fromCompat(new Uint8Array(pk.slice(32))), diff --git a/tests/references/asset.spec.ts b/tests/references/asset.spec.ts index 1e851440..466ddb72 100644 --- a/tests/references/asset.spec.ts +++ b/tests/references/asset.spec.ts @@ -55,7 +55,7 @@ describe('Asset', () => { unitName: asBytes('TEST'), name: asBytes('Test Asset'), url: asBytes('https://test.com'), - metadataHash: Bytes(new Uint8Array(32)), + metadataHash: Bytes(new Uint8Array(32)).toFixed({ length: 32 }), manager: Account(), freeze: Account(), clawback: Account(), diff --git a/tests/state-op-codes.spec.ts b/tests/state-op-codes.spec.ts index 386e1555..fbb42d66 100644 --- a/tests/state-op-codes.spec.ts +++ b/tests/state-op-codes.spec.ts @@ -14,7 +14,7 @@ import { BytesCls, Uint64Cls } from '../src/impl/primitives' import { AccountCls, encodeAddress } from '../src/impl/reference' import type { ApplicationCallTransaction } from '../src/impl/transactions' import type { DeliberateAny } from '../src/typescript-helpers' -import { asBigInt, asBigUintCls, asNumber, asUint64Cls, asUint8Array, getRandomBytes } from '../src/util' +import { asBigInt, asNumber, asUint64Cls, asUint8Array, getRandomBytes } from '../src/util' import { AppExpectingEffects } from './artifacts/created-app-asset/contract.algo' import { ItxnDemoContract, @@ -268,7 +268,7 @@ describe('State op codes', async () => { 'should return the correct field value of the asset', async ([methodName, expectedValue], { appClientStateAssetParamsContract: appClient, testAccount, assetFactory }) => { const creator = Account(Bytes.fromBase32(testAccount.addr.toString())) - const metadataHash = Bytes(`test${' '.repeat(28)}`) + const metadataHash = Bytes(`test${' '.repeat(28)}`).toFixed({ length: 32 }) const mockAsset = ctx.any.asset({ total: 100, decimals: 0, @@ -528,12 +528,12 @@ describe('State op codes', async () => { describe('Block', async () => { test('should return the correct field value of the block', async () => { const index = 42 - const seed = asBigUintCls(123n).toBytes().asAlgoTs() + const seed = getRandomBytes(32).asAlgoTs().toFixed({ length: 32 }) const timestamp = 1234567890 const proposer = ctx.any.account() const feesCollected = 1000 const bonus = 12 - const branch = getRandomBytes(32).asAlgoTs() + const branch = getRandomBytes(32).asAlgoTs().toFixed({ length: 32 }) const feeSink = ctx.any.account() const protocol = getRandomBytes(32).asAlgoTs() const txnCounter = 32 From 80c3f11176962c2d5cbd3c4c53fe53518ebcda0e Mon Sep 17 00:00:00 2001 From: Bobby Lat Date: Thu, 22 May 2025 12:13:18 +0800 Subject: [PATCH 12/68] chore: bump node version to 22 to match puya-ts --- .github/workflows/pr.yml | 2 +- .github/workflows/release.yml | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/pr.yml b/.github/workflows/pr.yml index 28a0d2c6..a1893465 100644 --- a/.github/workflows/pr.yml +++ b/.github/workflows/pr.yml @@ -21,7 +21,7 @@ jobs: pipx install algokit --python 3.12.6 algokit localnet reset --update pipx install puyapy --python 3.12.6 - node-version: 20.x + node-version: 22.x run-build: true run-commit-lint: true audit-script: npm run audit diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index aa02c36c..fff9b747 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -24,7 +24,7 @@ jobs: pipx install algokit algokit localnet reset --update pipx install puyapy - node-version: 20.x + node-version: 22.x run-build: true run-commit-lint: true audit-script: npm run audit @@ -44,7 +44,7 @@ jobs: - name: Use Node.js 20.x uses: actions/setup-node@v4 with: - node-version: 20.x + node-version: 22.x - run: npm ci --ignore-scripts From 363614f976043e02b41ab54d41346b4861fc00ab Mon Sep 17 00:00:00 2001 From: Bobby Lat Date: Mon, 12 May 2025 18:14:01 +0800 Subject: [PATCH 13/68] feat: implement ItxnCompose type for composing variable length transaction groups --- .../transaction-context/classes/ItxnGroup.md | 20 ++-- .../classes/TransactionGroup.md | 18 ++-- docs/coverage.md | 1 + package.json | 2 +- src/impl/c2c.ts | 22 +++-- src/impl/inner-transactions.ts | 25 +++-- src/impl/itxn-compose.ts | 70 ++++++++++++++ src/internal/index.ts | 2 + src/subcontexts/transaction-context.ts | 15 ++- src/test-transformer/visitors.ts | 29 ++++-- src/value-generators/arc4.ts | 2 +- tests/artifacts/itxn-compose/contract.algo.ts | 96 +++++++++++++++++++ tests/itxn-compose.spec.ts | 76 +++++++++++++++ 13 files changed, 334 insertions(+), 44 deletions(-) create mode 100644 src/impl/itxn-compose.ts create mode 100644 tests/artifacts/itxn-compose/contract.algo.ts create mode 100644 tests/itxn-compose.spec.ts diff --git a/docs/code/subcontexts/transaction-context/classes/ItxnGroup.md b/docs/code/subcontexts/transaction-context/classes/ItxnGroup.md index 9537f953..10a5bf2e 100644 --- a/docs/code/subcontexts/transaction-context/classes/ItxnGroup.md +++ b/docs/code/subcontexts/transaction-context/classes/ItxnGroup.md @@ -6,7 +6,7 @@ # Class: ItxnGroup -Defined in: [src/subcontexts/transaction-context.ts:485](https://github.com/algorandfoundation/algorand-typescript-testing/blob/main/src/subcontexts/transaction-context.ts#L485) +Defined in: [src/subcontexts/transaction-context.ts:496](https://github.com/algorandfoundation/algorand-typescript-testing/blob/main/src/subcontexts/transaction-context.ts#L496) Represents a group of inner transactions. @@ -16,7 +16,7 @@ Represents a group of inner transactions. > **new ItxnGroup**(`itxns`): `ItxnGroup` -Defined in: [src/subcontexts/transaction-context.ts:487](https://github.com/algorandfoundation/algorand-typescript-testing/blob/main/src/subcontexts/transaction-context.ts#L487) +Defined in: [src/subcontexts/transaction-context.ts:498](https://github.com/algorandfoundation/algorand-typescript-testing/blob/main/src/subcontexts/transaction-context.ts#L498) #### Parameters @@ -34,7 +34,7 @@ Defined in: [src/subcontexts/transaction-context.ts:487](https://github.com/algo > **itxns**: `InnerTxn`[] = `[]` -Defined in: [src/subcontexts/transaction-context.ts:486](https://github.com/algorandfoundation/algorand-typescript-testing/blob/main/src/subcontexts/transaction-context.ts#L486) +Defined in: [src/subcontexts/transaction-context.ts:497](https://github.com/algorandfoundation/algorand-typescript-testing/blob/main/src/subcontexts/transaction-context.ts#L497) ## Methods @@ -42,7 +42,7 @@ Defined in: [src/subcontexts/transaction-context.ts:486](https://github.com/algo > **getApplicationCallInnerTxn**(`index`?): `ApplicationCallInnerTxn` -Defined in: [src/subcontexts/transaction-context.ts:496](https://github.com/algorandfoundation/algorand-typescript-testing/blob/main/src/subcontexts/transaction-context.ts#L496) +Defined in: [src/subcontexts/transaction-context.ts:507](https://github.com/algorandfoundation/algorand-typescript-testing/blob/main/src/subcontexts/transaction-context.ts#L507) Gets an application inner transaction by index. @@ -66,7 +66,7 @@ The application inner transaction. > **getAssetConfigInnerTxn**(`index`?): `AssetConfigInnerTxn` -Defined in: [src/subcontexts/transaction-context.ts:505](https://github.com/algorandfoundation/algorand-typescript-testing/blob/main/src/subcontexts/transaction-context.ts#L505) +Defined in: [src/subcontexts/transaction-context.ts:516](https://github.com/algorandfoundation/algorand-typescript-testing/blob/main/src/subcontexts/transaction-context.ts#L516) Gets an asset configuration inner transaction by index. @@ -90,7 +90,7 @@ The asset configuration inner transaction. > **getAssetFreezeInnerTxn**(`index`?): `AssetFreezeInnerTxn` -Defined in: [src/subcontexts/transaction-context.ts:523](https://github.com/algorandfoundation/algorand-typescript-testing/blob/main/src/subcontexts/transaction-context.ts#L523) +Defined in: [src/subcontexts/transaction-context.ts:534](https://github.com/algorandfoundation/algorand-typescript-testing/blob/main/src/subcontexts/transaction-context.ts#L534) Gets an asset freeze inner transaction by index. @@ -114,7 +114,7 @@ The asset freeze inner transaction. > **getAssetTransferInnerTxn**(`index`?): `AssetTransferInnerTxn` -Defined in: [src/subcontexts/transaction-context.ts:514](https://github.com/algorandfoundation/algorand-typescript-testing/blob/main/src/subcontexts/transaction-context.ts#L514) +Defined in: [src/subcontexts/transaction-context.ts:525](https://github.com/algorandfoundation/algorand-typescript-testing/blob/main/src/subcontexts/transaction-context.ts#L525) Gets an asset transfer inner transaction by index. @@ -138,7 +138,7 @@ The asset transfer inner transaction. > **getInnerTxn**(`index`?): `InnerTxn` -Defined in: [src/subcontexts/transaction-context.ts:550](https://github.com/algorandfoundation/algorand-typescript-testing/blob/main/src/subcontexts/transaction-context.ts#L550) +Defined in: [src/subcontexts/transaction-context.ts:561](https://github.com/algorandfoundation/algorand-typescript-testing/blob/main/src/subcontexts/transaction-context.ts#L561) Gets an inner transaction by index. @@ -162,7 +162,7 @@ The inner transaction. > **getKeyRegistrationInnerTxn**(`index`?): `KeyRegistrationInnerTxn` -Defined in: [src/subcontexts/transaction-context.ts:532](https://github.com/algorandfoundation/algorand-typescript-testing/blob/main/src/subcontexts/transaction-context.ts#L532) +Defined in: [src/subcontexts/transaction-context.ts:543](https://github.com/algorandfoundation/algorand-typescript-testing/blob/main/src/subcontexts/transaction-context.ts#L543) Gets a key registration inner transaction by index. @@ -186,7 +186,7 @@ The key registration inner transaction. > **getPaymentInnerTxn**(`index`?): `PaymentInnerTxn` -Defined in: [src/subcontexts/transaction-context.ts:541](https://github.com/algorandfoundation/algorand-typescript-testing/blob/main/src/subcontexts/transaction-context.ts#L541) +Defined in: [src/subcontexts/transaction-context.ts:552](https://github.com/algorandfoundation/algorand-typescript-testing/blob/main/src/subcontexts/transaction-context.ts#L552) Gets a payment inner transaction by index. diff --git a/docs/code/subcontexts/transaction-context/classes/TransactionGroup.md b/docs/code/subcontexts/transaction-context/classes/TransactionGroup.md index 19e61a0e..1efa02d4 100644 --- a/docs/code/subcontexts/transaction-context/classes/TransactionGroup.md +++ b/docs/code/subcontexts/transaction-context/classes/TransactionGroup.md @@ -196,7 +196,7 @@ If there is already an inner transaction group being constructed or the active t > **getApplicationCallTransaction**(`index`?): `ApplicationCallTransaction` -Defined in: [src/subcontexts/transaction-context.ts:394](https://github.com/algorandfoundation/algorand-typescript-testing/blob/main/src/subcontexts/transaction-context.ts#L394) +Defined in: [src/subcontexts/transaction-context.ts:405](https://github.com/algorandfoundation/algorand-typescript-testing/blob/main/src/subcontexts/transaction-context.ts#L405) Gets an application transaction by index. @@ -220,7 +220,7 @@ The application transaction. > **getAssetConfigTransaction**(`index`?): `AssetConfigTransaction` -Defined in: [src/subcontexts/transaction-context.ts:403](https://github.com/algorandfoundation/algorand-typescript-testing/blob/main/src/subcontexts/transaction-context.ts#L403) +Defined in: [src/subcontexts/transaction-context.ts:414](https://github.com/algorandfoundation/algorand-typescript-testing/blob/main/src/subcontexts/transaction-context.ts#L414) Gets an asset configuration transaction by index. @@ -244,7 +244,7 @@ The asset configuration transaction. > **getAssetFreezeTransaction**(`index`?): `AssetFreezeTransaction` -Defined in: [src/subcontexts/transaction-context.ts:421](https://github.com/algorandfoundation/algorand-typescript-testing/blob/main/src/subcontexts/transaction-context.ts#L421) +Defined in: [src/subcontexts/transaction-context.ts:432](https://github.com/algorandfoundation/algorand-typescript-testing/blob/main/src/subcontexts/transaction-context.ts#L432) Gets an asset freeze transaction by index. @@ -268,7 +268,7 @@ The asset freeze transaction. > **getAssetTransferTransaction**(`index`?): `AssetTransferTransaction` -Defined in: [src/subcontexts/transaction-context.ts:412](https://github.com/algorandfoundation/algorand-typescript-testing/blob/main/src/subcontexts/transaction-context.ts#L412) +Defined in: [src/subcontexts/transaction-context.ts:423](https://github.com/algorandfoundation/algorand-typescript-testing/blob/main/src/subcontexts/transaction-context.ts#L423) Gets an asset transfer transaction by index. @@ -292,7 +292,7 @@ The asset transfer transaction. > **getItxnGroup**(`index`?): [`ItxnGroup`](ItxnGroup.md) -Defined in: [src/subcontexts/transaction-context.ts:376](https://github.com/algorandfoundation/algorand-typescript-testing/blob/main/src/subcontexts/transaction-context.ts#L376) +Defined in: [src/subcontexts/transaction-context.ts:387](https://github.com/algorandfoundation/algorand-typescript-testing/blob/main/src/subcontexts/transaction-context.ts#L387) Gets an inner transaction group by index. @@ -320,7 +320,7 @@ If the index is invalid or there are no previous inner transactions. > **getKeyRegistrationTransaction**(`index`?): `KeyRegistrationTransaction` -Defined in: [src/subcontexts/transaction-context.ts:430](https://github.com/algorandfoundation/algorand-typescript-testing/blob/main/src/subcontexts/transaction-context.ts#L430) +Defined in: [src/subcontexts/transaction-context.ts:441](https://github.com/algorandfoundation/algorand-typescript-testing/blob/main/src/subcontexts/transaction-context.ts#L441) Gets a key registration transaction by index. @@ -344,7 +344,7 @@ The key registration transaction. > **getPaymentTransaction**(`index`?): `PaymentTransaction` -Defined in: [src/subcontexts/transaction-context.ts:439](https://github.com/algorandfoundation/algorand-typescript-testing/blob/main/src/subcontexts/transaction-context.ts#L439) +Defined in: [src/subcontexts/transaction-context.ts:450](https://github.com/algorandfoundation/algorand-typescript-testing/blob/main/src/subcontexts/transaction-context.ts#L450) Gets a payment transaction by index. @@ -408,7 +408,7 @@ The scratch space. > **getTransaction**(`index`?): `Transaction` -Defined in: [src/subcontexts/transaction-context.ts:448](https://github.com/algorandfoundation/algorand-typescript-testing/blob/main/src/subcontexts/transaction-context.ts#L448) +Defined in: [src/subcontexts/transaction-context.ts:459](https://github.com/algorandfoundation/algorand-typescript-testing/blob/main/src/subcontexts/transaction-context.ts#L459) Gets a transaction by index. @@ -432,7 +432,7 @@ The transaction. > **lastItxnGroup**(): [`ItxnGroup`](ItxnGroup.md) -Defined in: [src/subcontexts/transaction-context.ts:366](https://github.com/algorandfoundation/algorand-typescript-testing/blob/main/src/subcontexts/transaction-context.ts#L366) +Defined in: [src/subcontexts/transaction-context.ts:377](https://github.com/algorandfoundation/algorand-typescript-testing/blob/main/src/subcontexts/transaction-context.ts#L377) Gets the last inner transaction group. diff --git a/docs/coverage.md b/docs/coverage.md index b8f777e5..9bd57f45 100644 --- a/docs/coverage.md +++ b/docs/coverage.md @@ -37,6 +37,7 @@ See which `algorand-typescript` stubs are implemented by the `algorand-typescrip | ensureBudget | Emulated | | err | Native | | interpretAsArc4 | Native | +| itxnCompose | Emulated | | log | Emulated | | logicSig | Emulated | | logicsig | Emulated | diff --git a/package.json b/package.json index 3c050aa8..3535528a 100644 --- a/package.json +++ b/package.json @@ -23,7 +23,7 @@ "test:ci": "vitest run --coverage --reporter junit --outputFile test-results.xml", "script:documentation": "typedoc", "script:build-examples": "rollup --config examples/rollup.config.ts --configPlugin typescript", - "script:compile-examples": "puya-ts --out-dir data ./examples/*/", + "script:compile-examples": "puya-ts --out-dir data ./examples/*/ --puya-path=/home/parallels/.local/bin/puya", "script:refresh-test-artifacts": "puya-ts --out-dir data ./tests/artifacts/*/" }, "devDependencies": { diff --git a/src/impl/c2c.ts b/src/impl/c2c.ts index 39c59183..8b9efcc8 100644 --- a/src/impl/c2c.ts +++ b/src/impl/c2c.ts @@ -43,7 +43,7 @@ export function compileArc4( }, selector, ) - invokeCallback(itxnContext) + invokeAbiCall(itxnContext) return { itxn: itxnContext, returnValue: itxnContext.loggedReturnValue, @@ -57,7 +57,7 @@ export function compileArc4( ...getCommonApplicationCallFields(app, options), ...methodArgs, }) - invokeCallback(itxnContext) + invokeAbiCall(itxnContext) return itxnContext }, approvalProgram: app?.application.approvalProgram ?? [lazyContext.any.bytes(10), lazyContext.any.bytes(10)], @@ -70,7 +70,7 @@ export function compileArc4( } as unknown as ContractProxy } -const invokeCallback = (itxnContext: ApplicationCallInnerTxnContext) => { +export const invokeAbiCall = (itxnContext: ApplicationCallInnerTxnContext) => { lazyContext.value.notifyApplicationSpies(itxnContext) lazyContext.txn.activeGroup.addInnerTransactionGroup(...(itxnContext.itxns ?? []), itxnContext) } @@ -84,21 +84,29 @@ const getCommonApplicationCallFields = (app: ApplicationData | undefined, option localNumBytes: options?.localBytes ?? app?.application.localNumBytes ?? lazyContext.any.uint64(), }) -export function abiCall( +export function getApplicationCallInnerTxnContext( method: InstanceMethod, methodArgs: TypedApplicationCallFields, contract?: Contract | { new (): Contract }, -): { itxn: ApplicationCallInnerTxn; returnValue: TReturn | undefined } { +) { const abiMetadata = contract ? getContractMethodAbiMetadata(contract, method.name) : undefined const selector = methodSelector(method, contract) - const itxnContext = ApplicationCallInnerTxnContext.createFromTypedApplicationCallFields( + return ApplicationCallInnerTxnContext.createFromTypedApplicationCallFields( { ...methodArgs, onCompletion: methodArgs.onCompletion ?? abiMetadata?.allowActions?.map((action) => OnCompleteAction[action])[0], }, selector, ) - invokeCallback(itxnContext) +} +export function abiCall( + method: InstanceMethod, + methodArgs: TypedApplicationCallFields, + contract?: Contract | { new (): Contract }, +): { itxn: ApplicationCallInnerTxn; returnValue: TReturn | undefined } { + const itxnContext = getApplicationCallInnerTxnContext(method, methodArgs, contract) + + invokeAbiCall(itxnContext) return { itxn: itxnContext, diff --git a/src/impl/inner-transactions.ts b/src/impl/inner-transactions.ts index 0927e6d8..9c281a3b 100644 --- a/src/impl/inner-transactions.ts +++ b/src/impl/inner-transactions.ts @@ -12,7 +12,7 @@ import { lazyContext } from '../context-helpers/internal-context' import { InternalError, invariant } from '../errors' import { extractArraysFromArgs } from '../subcontexts/contract-context' import type { DeliberateAny } from '../typescript-helpers' -import { asBytes, asNumber, asUint8Array } from '../util' +import { asBytes, asNumber, asUint64, asUint8Array } from '../util' import { getApp } from './app-params' import { getAsset } from './asset-params' import { encodeArc4Impl } from './encoded-types' @@ -247,16 +247,27 @@ export class ItxnParams(this.#fields)) as unknown as TTransaction - lazyContext.txn.activeGroup.addInnerTransactionGroup(innerTxn) - return innerTxn + const innerTxns = [ + ...(itxnContext?.itxns ?? []), + itxnContext ?? createInnerTxn(this.#fields), + ] as unknown as TTransaction[] + return innerTxns + } + + submit(): TTransaction { + const innerTxns = this.createInnerTxns() + innerTxns.forEach((itxn, index) => Object.assign(itxn, { groupIndex: asUint64(index) })) + const lastInnerTxn = innerTxns.at(-1) + if (lastInnerTxn instanceof ApplicationCallInnerTxnContext) { + lazyContext.value.notifyApplicationSpies(lastInnerTxn) + } + lazyContext.txn.activeGroup.addInnerTransactionGroup(...innerTxns) + return lastInnerTxn as TTransaction } set(p: Partial) { diff --git a/src/impl/itxn-compose.ts b/src/impl/itxn-compose.ts new file mode 100644 index 00000000..142764cd --- /dev/null +++ b/src/impl/itxn-compose.ts @@ -0,0 +1,70 @@ +import { + type AnyTransactionComposeFields, + type ApplicationCallComposeFields, + type AssetConfigComposeFields, + type AssetFreezeComposeFields, + type AssetTransferComposeFields, + type ComposeItxnParams, + type Contract, + type ItxnCompose, + type KeyRegistrationComposeFields, + type PaymentComposeFields, +} from '@algorandfoundation/algorand-typescript' +import type { TypedApplicationCallFields } from '@algorandfoundation/algorand-typescript/arc4' +import { lazyContext } from '../context-helpers/internal-context' +import type { DeliberateAny, InstanceMethod } from '../typescript-helpers' +import { getApplicationCallInnerTxnContext } from './c2c' + +class ItxnComposeImpl { + begin(fields: PaymentComposeFields): void + begin(fields: KeyRegistrationComposeFields): void + begin(fields: AssetConfigComposeFields): void + begin(fields: AssetTransferComposeFields): void + begin(fields: AssetFreezeComposeFields): void + begin(fields: ApplicationCallComposeFields): void + begin(fields: AnyTransactionComposeFields): void + begin(fields: ComposeItxnParams): void + begin( + method: InstanceMethod, + fields: TypedApplicationCallFields, + contract?: Contract | { new (): Contract }, + ): void + begin(...args: unknown[]): void { + lazyContext.txn.activeGroup.constructingItxnGroup.push( + args.length === 1 + ? (args[0] as AnyTransactionComposeFields) + : getApplicationCallInnerTxnContext( + args[0] as InstanceMethod, + args[1] as TypedApplicationCallFields, + args[2] as Contract | { new (): Contract }, + ), + ) + } + + next(fields: PaymentComposeFields): void + next(fields: KeyRegistrationComposeFields): void + next(fields: AssetConfigComposeFields): void + next(fields: AssetTransferComposeFields): void + next(fields: AssetFreezeComposeFields): void + next(fields: ApplicationCallComposeFields): void + next(fields: AnyTransactionComposeFields): void + next(fields: ComposeItxnParams): void + next(_method: InstanceMethod, _fields: TypedApplicationCallFields): void + next(...args: unknown[]): void { + lazyContext.txn.activeGroup.constructingItxnGroup.push( + args.length === 1 + ? (args[0] as AnyTransactionComposeFields) + : getApplicationCallInnerTxnContext( + args[0] as InstanceMethod, + args[1] as TypedApplicationCallFields, + args[2] as Contract | { new (): Contract }, + ), + ) + } + + submit(): void { + lazyContext.txn.activeGroup.submitInnerTransactionGroup() + } +} + +export const itxnCompose: ItxnCompose = new ItxnComposeImpl() diff --git a/src/internal/index.ts b/src/internal/index.ts index c265ce36..77f493f9 100644 --- a/src/internal/index.ts +++ b/src/internal/index.ts @@ -45,3 +45,5 @@ export const itxn = { assetFreeze, applicationCall, } + +export { itxnCompose } from '../impl/itxn-compose' diff --git a/src/subcontexts/transaction-context.ts b/src/subcontexts/transaction-context.ts index 45643205..f328c55b 100644 --- a/src/subcontexts/transaction-context.ts +++ b/src/subcontexts/transaction-context.ts @@ -15,7 +15,7 @@ import type { KeyRegistrationInnerTxn, PaymentInnerTxn, } from '../impl/inner-transactions' -import { createInnerTxn } from '../impl/inner-transactions' +import { ApplicationCallInnerTxnContext, createInnerTxn, ItxnParams } from '../impl/inner-transactions' import type { InnerTxn, InnerTxnFields } from '../impl/itxn' import type { StubBytesCompat, StubUint64Compat } from '../impl/primitives' import type { @@ -353,8 +353,19 @@ export class TransactionGroup { if (this.constructingItxnGroup.length > TRANSACTION_GROUP_MAX_SIZE) { throw new InternalError(`Cannot submit more than ${TRANSACTION_GROUP_MAX_SIZE} inner transactions at once`) } - const itxns = this.constructingItxnGroup.map((t) => createInnerTxn(t)) + const itxns = this.constructingItxnGroup.flatMap((t) => + t instanceof ApplicationCallInnerTxnContext + ? [...(t.itxns ?? []), t] + : t instanceof ItxnParams + ? t.createInnerTxns() + : [createInnerTxn(t)], + ) itxns.forEach((itxn, index) => Object.assign(itxn, { groupIndex: asUint64(index) })) + for (const itxn of itxns) { + if (itxn instanceof ApplicationCallInnerTxnContext) { + lazyContext.value.notifyApplicationSpies(itxn) + } + } this.itxnGroups.push(new ItxnGroup(itxns)) this.constructingItxnGroup = [] } diff --git a/src/test-transformer/visitors.ts b/src/test-transformer/visitors.ts index bf68cf10..76b0d213 100644 --- a/src/test-transformer/visitors.ts +++ b/src/test-transformer/visitors.ts @@ -427,14 +427,29 @@ const tryGetStubbedFunctionName = (node: ts.CallExpression, helper: VisitorHelpe const identityExpression = ts.isPropertyAccessExpression(node.expression) ? (node.expression as ts.PropertyAccessExpression).name : (node.expression as ts.Identifier) - const functionSymbol = helper.tryGetSymbol(identityExpression) - if (functionSymbol) { - const sourceFileName = functionSymbol.valueDeclaration?.getSourceFile().fileName + const functionName = tryGetAlgoTsSymbolName(identityExpression, helper) + if (functionName === undefined) return undefined + const stubbedFunctionNames = ['interpretAsArc4', 'decodeArc4', 'encodeArc4', 'emit', 'methodSelector', 'arc4EncodedLength', 'abiCall'] + + if (stubbedFunctionNames.includes(functionName)) { + return functionName + } + + if (['begin', 'next'].includes(functionName) && ts.isPropertyAccessExpression(node.expression)) { + const objectExpression = (node.expression as ts.PropertyAccessExpression).expression + const objectName = tryGetAlgoTsSymbolName(objectExpression, helper) + if (objectName === 'itxnCompose') return functionName + } + return undefined +} + +const tryGetAlgoTsSymbolName = (node: ts.Node, helper: VisitorHelper): string | undefined => { + const s = helper.tryGetSymbol(node) + if (s) { + const sourceFileName = s.valueDeclaration?.getSourceFile().fileName if (sourceFileName && !algotsModulePaths.some((s) => sourceFileName.includes(s))) return undefined } - const functionName = functionSymbol?.getName() ?? identityExpression.text - const stubbedFunctionNames = ['interpretAsArc4', 'decodeArc4', 'encodeArc4', 'emit', 'methodSelector', 'arc4EncodedLength', 'abiCall'] - return stubbedFunctionNames.includes(functionName) ? functionName : undefined + return s?.getName() ?? (ts.isMemberName(node) ? node.text : node.getText()) } const isCallingDecodeArc4 = (functionName: string | undefined): boolean => ['decodeArc4'].includes(functionName ?? '') @@ -442,4 +457,4 @@ const isCallingEncodeArc4 = (functionName: string | undefined): boolean => ['enc const isCallingArc4EncodedLength = (functionName: string | undefined): boolean => 'arc4EncodedLength' === (functionName ?? '') const isCallingEmit = (functionName: string | undefined): boolean => 'emit' === (functionName ?? '') const isCallingMethodSelector = (functionName: string | undefined): boolean => 'methodSelector' === (functionName ?? '') -const isCallingAbiCall = (functionName: string | undefined): boolean => 'abiCall' === (functionName ?? '') +const isCallingAbiCall = (functionName: string | undefined): boolean => ['abiCall', 'begin', 'next'].includes(functionName ?? '') diff --git a/src/value-generators/arc4.ts b/src/value-generators/arc4.ts index 13b23851..2eeb2767 100644 --- a/src/value-generators/arc4.ts +++ b/src/value-generators/arc4.ts @@ -12,7 +12,7 @@ export class Arc4ValueGenerator { address(): arc4.Address { const source = new AvmValueGenerator().account() const result = new AddressImpl( - { name: 'StaticArray', genericArgs: { elementType: { name: 'Byte', genericArgs: [{ name: '8' }] }, size: { name: '32' } } }, + { name: 'Address', genericArgs: { elementType: { name: 'Byte', genericArgs: [{ name: '8' }] }, size: { name: '32' } } }, source, ) return result diff --git a/tests/artifacts/itxn-compose/contract.algo.ts b/tests/artifacts/itxn-compose/contract.algo.ts new file mode 100644 index 00000000..e3ff8742 --- /dev/null +++ b/tests/artifacts/itxn-compose/contract.algo.ts @@ -0,0 +1,96 @@ +import type { Application, PaymentComposeFields, uint64 } from '@algorandfoundation/algorand-typescript' +import { + assert, + assertMatch, + Contract, + Global, + GlobalState, + gtxn, + itxn, + itxnCompose, + TransactionType, + Txn, + urange, +} from '@algorandfoundation/algorand-typescript' +import type { Address } from '@algorandfoundation/algorand-typescript/arc4' +import { abimethod, compileArc4 } from '@algorandfoundation/algorand-typescript/arc4' + +abstract class HelloBase extends Contract { + greeting = GlobalState({ initialValue: '' }) + + @abimethod({ allowActions: 'DeleteApplication' }) + delete() {} + + @abimethod({ allowActions: 'UpdateApplication' }) + update() {} + + greet(name: string): string { + return `${this.greeting.value} ${name}` + } +} + +class Hello extends HelloBase { + @abimethod({ name: 'helloCreate', onCreate: 'require' }) + create(greeting: string) { + this.greeting.value = greeting + } +} + +export class ItxnComposeAlgo extends Contract { + distribute(addresses: Address[], funds: gtxn.PaymentTxn, verifier: Application) { + assertMatch(funds, { + receiver: Global.currentApplicationAddress, + }) + assert(addresses.length, 'must provide some accounts') + const share: uint64 = funds.amount / addresses.length + + const payFields = { + type: TransactionType.Payment, + amount: share, + receiver: addresses[0].bytes, + } satisfies PaymentComposeFields + itxnCompose.begin(payFields) + for (const i of urange(1, addresses.length)) { + const addr = addresses[i] + itxnCompose.next({ + ...payFields, + + receiver: addr.bytes, + }) + } + + itxnCompose.next(VerifierContract.prototype.verify, { + appId: verifier, + }) + + itxnCompose.next( + itxn.assetConfig({ + assetName: 'abc', + }), + ) + + itxnCompose.submit() + } + conditionalBegin(count: uint64) { + const hello = compileArc4(Hello) + const appId = hello.call.create({ args: ['Hi'] }).itxn.createdApp + + for (const i of urange(count)) { + if (i === 0) { + itxnCompose.begin(Hello.prototype.greet, { appId, args: ['ho'] }) + } else { + itxnCompose.next(Hello.prototype.greet, { appId, args: ['ho'] }) + } + } + itxnCompose.submit() + } +} + +export class VerifierContract extends Contract { + verify() { + for (let i: uint64 = 0; i < Txn.groupIndex; i++) { + const txn = gtxn.Transaction(i) + assert(txn.type === TransactionType.Payment, 'Txn must be pay') + } + } +} diff --git a/tests/itxn-compose.spec.ts b/tests/itxn-compose.spec.ts new file mode 100644 index 00000000..6eec7eef --- /dev/null +++ b/tests/itxn-compose.spec.ts @@ -0,0 +1,76 @@ +import { algos } from '@algorandfoundation/algokit-utils' +import { Bytes, TransactionType } from '@algorandfoundation/algorand-typescript' +import { TestExecutionContext } from '@algorandfoundation/algorand-typescript-testing' +import { Address } from '@algorandfoundation/algorand-typescript/arc4' +import { afterEach, beforeAll, describe, expect } from 'vitest' +import { ItxnComposeAlgo, VerifierContract } from './artifacts/itxn-compose/contract.algo' +import { createArc4TestFixture } from './test-fixture' + +describe('itxn compose', async () => { + const [test, localnetFixture] = createArc4TestFixture('tests/artifacts/itxn-compose/contract.algo.ts', { + ItxnComposeAlgo: { funding: algos(5) }, + VerifierContract: {}, + }) + const ctx = new TestExecutionContext() + + beforeAll(async () => { + await localnetFixture.newScope() + }) + afterEach(() => { + ctx.reset() + }) + + test('should compose transactions', async ({ algorand, localnet, testAccount, appClientVerifierContract, appClientItxnComposeAlgo }) => { + const testAccounts = [ + await localnet.context.generateAccount({ initialFunds: algos(1) }), + await localnet.context.generateAccount({ initialFunds: algos(1) }), + await localnet.context.generateAccount({ initialFunds: algos(1) }), + ] + + const pay = algorand.createTransaction.payment({ + sender: testAccount.addr, + amount: algos(9), + receiver: appClientItxnComposeAlgo.appAddress, + }) + + await appClientItxnComposeAlgo.send.call({ + method: 'distribute', + args: [testAccounts.map((a) => a.addr.publicKey), pay, appClientVerifierContract.appId], + extraFee: algos(1), + }) + + const itxnComposeAlgoContract = ctx.contract.create(ItxnComposeAlgo) + const verifierContract = ctx.contract.create(VerifierContract) + + itxnComposeAlgoContract.distribute( + testAccounts.map((a) => new Address(Bytes(a.addr.publicKey))), + ctx.any.txn.payment({ amount: 9, receiver: ctx.ledger.getApplicationForContract(itxnComposeAlgoContract).address }), + ctx.ledger.getApplicationForContract(verifierContract), + ) + + const itxns = ctx.txn.lastGroup.getItxnGroup().itxns + expect(itxns.length).toBe(5) + expect(itxns[0].type).toBe(TransactionType.Payment) + expect(itxns[1].type).toBe(TransactionType.Payment) + expect(itxns[2].type).toBe(TransactionType.Payment) + expect(itxns[3].type).toBe(TransactionType.ApplicationCall) + expect(itxns[4].type).toBe(TransactionType.AssetConfig) + }) + + test('should compose app call transactions ', async ({ appClientItxnComposeAlgo }) => { + await appClientItxnComposeAlgo.send.call({ + method: 'conditionalBegin', + args: [4], + extraFee: algos(1), + }) + + const itxnComposeAlgoContract = ctx.contract.create(ItxnComposeAlgo) + itxnComposeAlgoContract.conditionalBegin(4) + const itxns = ctx.txn.lastGroup.getItxnGroup().itxns + expect(itxns.length).toBe(4) + expect(itxns[0].type).toBe(TransactionType.ApplicationCall) + expect(itxns[1].type).toBe(TransactionType.ApplicationCall) + expect(itxns[2].type).toBe(TransactionType.ApplicationCall) + expect(itxns[3].type).toBe(TransactionType.ApplicationCall) + }) +}) From 21d8601081426c67be5b5ad2e0d3894afb03e3b8 Mon Sep 17 00:00:00 2001 From: Bobby Lat Date: Fri, 23 May 2025 17:44:56 +0800 Subject: [PATCH 14/68] refactor: split off encoded-types file and consolidate encoders --- src/abi-metadata.ts | 2 +- src/encoders.ts | 102 ---- src/impl/app-global.ts | 2 +- src/impl/app-local.ts | 2 +- src/impl/base.ts | 2 +- src/impl/box.ts | 2 +- src/impl/encoded-types/array-proxy.ts | 34 ++ src/impl/encoded-types/constants.ts | 4 + src/impl/{ => encoded-types}/encoded-types.ts | 525 +++++------------- src/impl/encoded-types/helpers.ts | 159 ++++++ src/impl/encoded-types/index.ts | 48 ++ src/impl/encoded-types/types.ts | 16 + src/impl/encoded-types/utils.ts | 125 +++++ src/impl/log.ts | 24 +- src/impl/state.ts | 5 +- src/impl/transactions.ts | 2 +- src/runtime-helpers.ts | 6 +- src/subcontexts/contract-context.ts | 3 +- src/subcontexts/ledger-context.ts | 2 +- src/test-transformer/node-factory.ts | 2 +- src/test-transformer/visitors.ts | 2 +- src/util.ts | 11 - tests/arc4/dynamic-array.spec.ts | 181 +----- tests/references/box-map.spec.ts | 2 +- tests/references/box.spec.ts | 2 +- 25 files changed, 559 insertions(+), 706 deletions(-) delete mode 100644 src/encoders.ts create mode 100644 src/impl/encoded-types/array-proxy.ts create mode 100644 src/impl/encoded-types/constants.ts rename src/impl/{ => encoded-types}/encoded-types.ts (70%) create mode 100644 src/impl/encoded-types/helpers.ts create mode 100644 src/impl/encoded-types/index.ts create mode 100644 src/impl/encoded-types/types.ts create mode 100644 src/impl/encoded-types/utils.ts diff --git a/src/abi-metadata.ts b/src/abi-metadata.ts index 966fd12e..bc5f8564 100644 --- a/src/abi-metadata.ts +++ b/src/abi-metadata.ts @@ -2,8 +2,8 @@ import type { OnCompleteActionStr } from '@algorandfoundation/algorand-typescrip import type { CreateOptions } from '@algorandfoundation/algorand-typescript/arc4' import js_sha512 from 'js-sha512' import { ConventionalRouting } from './constants' -import type { TypeInfo } from './encoders' import { Arc4MethodConfigSymbol, Contract } from './impl/contract' +import type { TypeInfo } from './impl/encoded-types' import { getArc4TypeName as getArc4TypeNameForARC4Encoded } from './impl/encoded-types' import type { DeliberateAny } from './typescript-helpers' diff --git a/src/encoders.ts b/src/encoders.ts deleted file mode 100644 index 91e77b5d..00000000 --- a/src/encoders.ts +++ /dev/null @@ -1,102 +0,0 @@ -import type { biguint, bytes, OnCompleteAction, TransactionType, uint64 } from '@algorandfoundation/algorand-typescript' -import { ARC4Encoded } from '@algorandfoundation/algorand-typescript/arc4' -import { encodingUtil } from '@algorandfoundation/puya-ts' -import { CodeError, InternalError } from './errors' -import { BytesBackedCls, Uint64BackedCls } from './impl/base' -import { arc4Encoders, encodeArc4Impl, getArc4Encoder, getMaxLengthOfStaticContentType } from './impl/encoded-types' -import { BigUint, Uint64, type StubBytesCompat } from './impl/primitives' -import { AccountCls, ApplicationCls, AssetCls } from './impl/reference' -import type { DeliberateAny } from './typescript-helpers' -import { asBytes, asMaybeBigUintCls, asMaybeBytesCls, asMaybeUint64Cls, asUint64Cls, asUint8Array, nameOfType } from './util' - -export type TypeInfo = { - name: string - genericArgs?: TypeInfo[] | Record -} - -export type fromBytes = (val: Uint8Array | StubBytesCompat, typeInfo: TypeInfo, prefix?: 'none' | 'log') => T - -const booleanFromBytes: fromBytes = (val) => { - return encodingUtil.uint8ArrayToBigInt(asUint8Array(val)) > 0n -} - -const bigUintFromBytes: fromBytes = (val) => { - return BigUint(encodingUtil.uint8ArrayToBigInt(asUint8Array(val))) -} - -const bytesFromBytes: fromBytes = (val) => { - return asBytes(val) -} - -const stringFromBytes: fromBytes = (val) => { - return asBytes(val).toString() -} - -const uint64FromBytes: fromBytes = (val) => { - return Uint64(encodingUtil.uint8ArrayToBigInt(asUint8Array(val))) -} - -const onCompletionFromBytes: fromBytes = (val) => { - return Uint64(encodingUtil.uint8ArrayToBigInt(asUint8Array(val))) as OnCompleteAction -} - -const transactionTypeFromBytes: fromBytes = (val) => { - return Uint64(encodingUtil.uint8ArrayToBigInt(asUint8Array(val))) as TransactionType -} - -export const encoders: Record> = { - account: AccountCls.fromBytes, - application: ApplicationCls.fromBytes, - asset: AssetCls.fromBytes, - boolean: booleanFromBytes, - biguint: bigUintFromBytes, - bytes: bytesFromBytes, - string: stringFromBytes, - uint64: uint64FromBytes, - OnCompleteAction: onCompletionFromBytes, - TransactionType: transactionTypeFromBytes, - ...arc4Encoders, -} - -export const getEncoder = (typeInfo: TypeInfo): fromBytes => { - return getArc4Encoder(typeInfo, encoders) -} - -export const toBytes = (val: unknown): bytes => { - const uint64Val = asMaybeUint64Cls(val, false) - if (uint64Val !== undefined) { - return uint64Val.toBytes().asAlgoTs() - } - const bytesVal = asMaybeBytesCls(val) - if (bytesVal !== undefined) { - return bytesVal.asAlgoTs() - } - const bigUintVal = asMaybeBigUintCls(val) - if (bigUintVal !== undefined) { - return bigUintVal.toBytes().asAlgoTs() - } - if (val instanceof BytesBackedCls) { - return val.bytes - } - if (val instanceof Uint64BackedCls) { - return asUint64Cls(val.uint64).toBytes().asAlgoTs() - } - if (val instanceof ARC4Encoded) { - return val.bytes - } - if (Array.isArray(val) || typeof val === 'object') { - return encodeArc4Impl(undefined, val) - } - throw new InternalError(`Invalid type for bytes: ${nameOfType(val)}`) -} - -export const minLengthForType = (typeInfo: TypeInfo): number | undefined => { - try { - return getMaxLengthOfStaticContentType(typeInfo, false) - } catch (e) { - if (e instanceof CodeError && e.message.startsWith('unsupported type')) { - return undefined - } - throw e - } -} diff --git a/src/impl/app-global.ts b/src/impl/app-global.ts index 48ef86a3..fb75523b 100644 --- a/src/impl/app-global.ts +++ b/src/impl/app-global.ts @@ -1,8 +1,8 @@ import type { Application, bytes, op, uint64 } from '@algorandfoundation/algorand-typescript' import { lazyContext } from '../context-helpers/internal-context' -import { toBytes } from '../encoders' import { asBytes } from '../util' import { getApp } from './app-params' +import { toBytes } from './encoded-types' import { Bytes, Uint64, type StubBytesCompat, type StubUint64Compat } from './primitives' export const AppGlobal: typeof op.AppGlobal = { diff --git a/src/impl/app-local.ts b/src/impl/app-local.ts index 64560164..610170e9 100644 --- a/src/impl/app-local.ts +++ b/src/impl/app-local.ts @@ -1,9 +1,9 @@ import type { Account, Application, bytes, op, uint64 } from '@algorandfoundation/algorand-typescript' import { lazyContext } from '../context-helpers/internal-context' -import { toBytes } from '../encoders' import { asBytes } from '../util' import { getAccount } from './acct-params' import { getApp } from './app-params' +import { toBytes } from './encoded-types' import { Bytes, Uint64, type StubBytesCompat, type StubUint64Compat } from './primitives' export const AppLocal: typeof op.AppLocal = { diff --git a/src/impl/base.ts b/src/impl/base.ts index 6c2b958a..fcbf481e 100644 --- a/src/impl/base.ts +++ b/src/impl/base.ts @@ -1,6 +1,6 @@ import type { bytes, uint64 } from '@algorandfoundation/algorand-typescript' import { encodingUtil } from '@algorandfoundation/puya-ts' -import type { TypeInfo } from '../encoders' +import type { TypeInfo } from '../impl/encoded-types' import type { StubBytesCompat } from './primitives' import { BytesCls, Uint64 } from './primitives' diff --git a/src/impl/box.ts b/src/impl/box.ts index 33c6b01e..440193e0 100644 --- a/src/impl/box.ts +++ b/src/impl/box.ts @@ -1,9 +1,9 @@ import type { bytes, op, uint64 } from '@algorandfoundation/algorand-typescript' import { MAX_BOX_SIZE } from '../constants' import { lazyContext } from '../context-helpers/internal-context' -import { toBytes } from '../encoders' import { AvmError, InternalError } from '../errors' import { asBytes, asBytesCls, asNumber, asUint8Array, conactUint8Arrays } from '../util' +import { toBytes } from './encoded-types' import type { StubBytesCompat, StubUint64Compat } from './primitives' export const Box: typeof op.Box = { diff --git a/src/impl/encoded-types/array-proxy.ts b/src/impl/encoded-types/array-proxy.ts new file mode 100644 index 00000000..713085d7 --- /dev/null +++ b/src/impl/encoded-types/array-proxy.ts @@ -0,0 +1,34 @@ +import type { Uint64Compat } from '@algorandfoundation/algorand-typescript' +import { AvmError } from '../../errors' +import { arrayUtil } from '../primitives' + +export const arrayProxyHandler = () => ({ + get(target: { items: readonly TItem[] }, prop: PropertyKey) { + const idx = prop ? parseInt(prop.toString(), 10) : NaN + if (!isNaN(idx)) { + if (idx >= 0 && idx < target.items.length) return target.items[idx] + throw new AvmError('Index out of bounds') + } else if (prop === Symbol.iterator) { + return target.items[Symbol.iterator].bind(target.items) + } else if (prop === 'entries') { + return target.items.entries.bind(target.items) + } else if (prop === 'at') { + return (index: Uint64Compat): TItem => { + return arrayUtil.arrayAt(target.items, index) + } + } + return Reflect.get(target, prop) + }, + set(target: { items: TItem[]; setItem: (index: number, value: TItem) => void }, prop: PropertyKey, value: TItem) { + const idx = prop ? parseInt(prop.toString(), 10) : NaN + if (!isNaN(idx)) { + if (idx >= 0 && idx < target.items.length) { + target.setItem(idx, value) + return true + } + throw new AvmError('Index out of bounds') + } + + return Reflect.set(target, prop, value) + }, +}) diff --git a/src/impl/encoded-types/constants.ts b/src/impl/encoded-types/constants.ts new file mode 100644 index 00000000..f1ef34be --- /dev/null +++ b/src/impl/encoded-types/constants.ts @@ -0,0 +1,4 @@ +export const ABI_LENGTH_SIZE = 2 +export const TRUE_BIGINT_VALUE = 128n +export const FALSE_BIGINT_VALUE = 0n +export const IS_INITIALISING_FROM_BYTES_SYMBOL = Symbol('IsInitialisingFromBytes') diff --git a/src/impl/encoded-types.ts b/src/impl/encoded-types/encoded-types.ts similarity index 70% rename from src/impl/encoded-types.ts rename to src/impl/encoded-types/encoded-types.ts index 1643b50c..1c9c12c8 100644 --- a/src/impl/encoded-types.ts +++ b/src/impl/encoded-types/encoded-types.ts @@ -1,12 +1,4 @@ -import type { - Account as AccountType, - BigUintCompat, - bytes, - NTuple, - StringCompat, - uint64, - Uint64Compat, -} from '@algorandfoundation/algorand-typescript' +import type { Account as AccountType, bytes, NTuple, StringCompat, uint64 } from '@algorandfoundation/algorand-typescript' import type { BitSize } from '@algorandfoundation/algorand-typescript/arc4' import { Address, @@ -25,42 +17,65 @@ import { } from '@algorandfoundation/algorand-typescript/arc4' import { encodingUtil } from '@algorandfoundation/puya-ts' import assert from 'assert' -import { - ABI_RETURN_VALUE_LOG_PREFIX, - ALGORAND_ADDRESS_BYTE_LENGTH, - ALGORAND_CHECKSUM_BYTE_LENGTH, - BITS_IN_BYTE, - UINT512_SIZE, - UINT64_SIZE, -} from '../constants' -import { lazyContext } from '../context-helpers/internal-context' -import type { fromBytes, TypeInfo } from '../encoders' -import { AvmError, avmInvariant, CodeError } from '../errors' -import type { DeliberateAny } from '../typescript-helpers' +import { ABI_RETURN_VALUE_LOG_PREFIX, ALGORAND_ADDRESS_BYTE_LENGTH, ALGORAND_CHECKSUM_BYTE_LENGTH, UINT64_SIZE } from '../../constants' +import { lazyContext } from '../../context-helpers/internal-context' +import { AvmError, avmInvariant, CodeError, InternalError } from '../../errors' +import { nameOfType, type DeliberateAny } from '../../typescript-helpers' import { asBigInt, asBigUint, asBigUintCls, asBytes, asBytesCls, + asMaybeBigUintCls, + asMaybeBytesCls, + asMaybeUint64Cls, asUint64, + asUint64Cls, asUint8Array, conactUint8Arrays, uint8ArrayToNumber, -} from '../util' -import { BytesBackedCls, Uint64BackedCls } from './base' -import type { StubBytesCompat } from './primitives' -import { AlgoTsPrimitiveCls, arrayUtil, BigUintCls, Bytes, BytesCls, getUint8Array, isBytes, Uint64Cls } from './primitives' -import { Account, AccountCls, ApplicationCls, AssetCls } from './reference' -import type { ApplicationCallTransaction } from './transactions' - -const ABI_LENGTH_SIZE = 2 -const maxBigIntValue = (bitSize: number) => 2n ** BigInt(bitSize) - 1n -const maxBytesLength = (bitSize: number) => Math.floor(bitSize / BITS_IN_BYTE) -const encodeLength = (length: number) => new BytesCls(encodingUtil.bigIntToUint8Array(BigInt(length), ABI_LENGTH_SIZE)) - -type CompatForArc4Int = N extends 8 | 16 | 24 | 32 | 40 | 48 | 56 | 64 ? Uint64Compat : BigUintCompat -const validBitSizes = [...Array(64).keys()].map((x) => (x + 1) * 8) +} from '../../util' +import { BytesBackedCls, Uint64BackedCls } from '../base' +import type { StubBytesCompat } from '../primitives' +import { BigUintCls, Bytes, BytesCls, getUint8Array, isBytes, Uint64Cls } from '../primitives' +import { Account, AccountCls, ApplicationCls, AssetCls } from '../reference' +import type { ApplicationCallTransaction } from '../transactions' +import { arrayProxyHandler } from './array-proxy' +import { ABI_LENGTH_SIZE, FALSE_BIGINT_VALUE, IS_INITIALISING_FROM_BYTES_SYMBOL, TRUE_BIGINT_VALUE } from './constants' +import { + areAllARC4Encoded, + bigUintFromBytes, + booleanFromBytes, + bytesFromBytes, + checkItemTypeName, + compressMultipleBool, + encodeLength, + findBool, + findBoolTypes, + holdsDynamicLengthContent, + maxBigIntValue, + maxBytesLength, + onCompletionFromBytes, + readLength, + regExpNxM, + stringFromBytes, + transactionTypeFromBytes, + trimTrailingDecimalZeros, + uint64FromBytes, + validBitSizes, +} from './helpers' +import type { + CompatForArc4Int, + DynamicArrayGenericArgs, + fromBytes, + StaticArrayGenericArgs, + StructConstraint, + TypeInfo, + uFixedNxMGenericArgs, +} from './types' +import { getMaxLengthOfStaticContentType } from './utils' + export class UintNImpl extends UintN { private value: Uint8Array private bitSize: N @@ -114,15 +129,8 @@ export class UintNImpl extends UintN { static getMaxBitsLength(typeInfo: TypeInfo): BitSize { return parseInt((typeInfo.genericArgs as TypeInfo[])![0].name, 10) as BitSize } - - static getArc4TypeName = (t: TypeInfo): string => { - return `uint${this.getMaxBitsLength(t)}` - } } -const regExpNxM = (maxPrecision: number) => new RegExp(`^\\d*\\.?\\d{0,${maxPrecision}}$`) -const trimTrailingDecimalZeros = (v: string) => v.replace(/(\d+\.\d*?)0+$/, '$1').replace(/\.$/, '') -type uFixedNxMGenericArgs = { n: TypeInfo; m: TypeInfo } export class UFixedNxMImpl extends UFixedNxM { private value: Uint8Array private bitSize: N @@ -181,11 +189,6 @@ export class UFixedNxMImpl extends UFixedNx const genericArgs = typeInfo.genericArgs as uFixedNxMGenericArgs return parseInt(genericArgs.n.name, 10) as BitSize } - - static getArc4TypeName = (t: TypeInfo): string => { - const genericArgs = t.genericArgs as uFixedNxMGenericArgs - return `ufixed${genericArgs.n.name}x${genericArgs.m.name}` - } } export class ByteImpl extends Byte { @@ -263,8 +266,6 @@ export class StrImpl extends Str { } } -const TRUE_BIGINT_VALUE = 128n -const FALSE_BIGINT_VALUE = 0n export class BoolImpl extends Bool { private value: Uint8Array typeInfo: TypeInfo @@ -302,44 +303,6 @@ export class BoolImpl extends Bool { } } -const areAllARC4Encoded = (items: T[]): items is T[] => items.every((item) => item instanceof ARC4Encoded) -const checkItemTypeName = (type: TypeInfo, value: ARC4Encoded) => { - const typeName = trimGenericTypeName(type.name) - const validTypeNames = [typeName, `${typeName}Impl`] - assert(validTypeNames.includes(value.constructor.name), `item must be of type ${typeName}, not ${value.constructor.name}`) -} -type StaticArrayGenericArgs = { elementType: TypeInfo; size: TypeInfo } -const arrayProxyHandler = () => ({ - get(target: { items: readonly TItem[] }, prop: PropertyKey) { - const idx = prop ? parseInt(prop.toString(), 10) : NaN - if (!isNaN(idx)) { - if (idx >= 0 && idx < target.items.length) return target.items[idx] - throw new AvmError('Index out of bounds') - } else if (prop === Symbol.iterator) { - return target.items[Symbol.iterator].bind(target.items) - } else if (prop === 'entries') { - return target.items.entries.bind(target.items) - } else if (prop === 'at') { - return (index: Uint64Compat): TItem => { - return arrayUtil.arrayAt(target.items, index) - } - } - return Reflect.get(target, prop) - }, - set(target: { items: TItem[]; setItem: (index: number, value: TItem) => void }, prop: PropertyKey, value: TItem) { - const idx = prop ? parseInt(prop.toString(), 10) : NaN - if (!isNaN(idx)) { - if (idx >= 0 && idx < target.items.length) { - target.setItem(idx, value) - return true - } - throw new AvmError('Index out of bounds') - } - - return Reflect.set(target, prop, value) - }, -}) -const isInitialisingFromBytesSymbol = Symbol('IsInitialisingFromBytes') export class StaticArrayImpl extends StaticArray { private value?: NTuple private uint8ArrayValue?: Uint8Array @@ -352,7 +315,7 @@ export class StaticArrayImpl constructor(typeInfo: TypeInfo | string, ...items: TItem[] & { length: TLength }) { // if first item is the symbol, we are initialising from bytes // so we don't need to pass the items to the super constructor - const isInitialisingFromBytes = items.length === 1 && (items[0] as DeliberateAny) === isInitialisingFromBytesSymbol + const isInitialisingFromBytes = items.length === 1 && (items[0] as DeliberateAny) === IS_INITIALISING_FROM_BYTES_SYMBOL super(...(isInitialisingFromBytes ? [] : (items as DeliberateAny))) this.typeInfo = typeof typeInfo === 'string' ? JSON.parse(typeInfo) : typeInfo @@ -374,7 +337,7 @@ export class StaticArrayImpl if (items.length) { this.value = items as NTuple } else { - this.uint8ArrayValue = new Uint8Array(StaticArrayImpl.getMaxBytesLength(this.typeInfo)) + this.uint8ArrayValue = new Uint8Array(getMaxLengthOfStaticContentType(this.typeInfo)) } } return new Proxy(this, arrayProxyHandler()) as StaticArrayImpl @@ -413,7 +376,7 @@ export class StaticArrayImpl } copy(): StaticArrayImpl { - return StaticArrayImpl.fromBytesImpl(this.bytes, JSON.stringify(this.typeInfo)) as StaticArrayImpl + return StaticArrayImpl.fromBytesImpl(this.bytes, JSON.stringify(this.typeInfo)) as unknown as StaticArrayImpl } concat(other: Parameters['concat']>[0]): DynamicArrayImpl { @@ -445,35 +408,10 @@ export class StaticArrayImpl bytesValue = bytesValue.slice(4) } // pass the symbol to the constructor to let it know we are initialising from bytes - const result = new StaticArrayImpl(typeInfo, isInitialisingFromBytesSymbol as DeliberateAny) + const result = new StaticArrayImpl(typeInfo, IS_INITIALISING_FROM_BYTES_SYMBOL as DeliberateAny) result.uint8ArrayValue = asUint8Array(bytesValue) return result } - - static getMaxBytesLength(typeInfo: TypeInfo): number { - const genericArgs = typeInfo.genericArgs as StaticArrayGenericArgs - const arraySize = parseInt(genericArgs.size.name, 10) - const childTypes = Array(arraySize).fill(genericArgs.elementType) - let i = 0 - let size = 0 - if (['Bool', 'boolean'].includes(genericArgs.elementType.name)) { - while (i < childTypes.length) { - const after = findBoolTypes(childTypes, i, 1) - const boolNum = after + 1 - size += Math.floor(boolNum / BITS_IN_BYTE) - size += boolNum % BITS_IN_BYTE ? 1 : 0 - i += after + 1 - } - } else { - size = getMaxLengthOfStaticContentType(genericArgs.elementType) * arraySize - } - return size - } - - static getArc4TypeName = (t: TypeInfo): string => { - const genericArgs = t.genericArgs as StaticArrayGenericArgs - return `${getArc4TypeName(genericArgs.elementType)}[${genericArgs.size.name}]` - } } export class AddressImpl extends Address { @@ -532,18 +470,8 @@ export class AddressImpl extends Address { result.value = staticArrayValue return result } - - static getMaxBytesLength(typeInfo: TypeInfo): number { - return StaticArrayImpl.getMaxBytesLength(typeInfo) - } } -type DynamicArrayGenericArgs = { elementType: TypeInfo } -const readLength = (value: Uint8Array): readonly [number, Uint8Array] => { - const length = Number(encodingUtil.uint8ArrayToBigInt(value.slice(0, ABI_LENGTH_SIZE))) - const data = value.slice(ABI_LENGTH_SIZE) - return [length, data] -} export class DynamicArrayImpl extends DynamicArray { private value?: TItem[] private uint8ArrayValue?: Uint8Array @@ -644,11 +572,6 @@ export class DynamicArrayImpl extends DynamicArray { - const genericArgs = t.genericArgs as DynamicArrayGenericArgs - return `${getArc4TypeName(genericArgs.elementType)}[]` - } - private encodeWithLength(items: TItem[]) { return conactUint8Arrays(encodeLength(items.length).asUint8Array(), encode(items)) } @@ -663,7 +586,7 @@ export class TupleImpl extends T constructor(typeInfo: TypeInfo | string, ...items: TTuple) { // if first item is the symbol, we are initialising from bytes // so we don't need to pass the items to the super constructor - const isInitialisingFromBytes = items.length === 1 && (items[0] as DeliberateAny) === isInitialisingFromBytesSymbol + const isInitialisingFromBytes = items.length === 1 && (items[0] as DeliberateAny) === IS_INITIALISING_FROM_BYTES_SYMBOL super(...(isInitialisingFromBytes ? ([] as DeliberateAny) : items)) this.typeInfo = typeof typeInfo === 'string' ? JSON.parse(typeInfo) : typeInfo this.genericArgs = Object.values(this.typeInfo.genericArgs as Record) @@ -678,7 +601,7 @@ export class TupleImpl extends T if (items.length) { this.value = items } else { - this.uint8ArrayValue = new Uint8Array(TupleImpl.getMaxBytesLength(this.typeInfo)) + this.uint8ArrayValue = new Uint8Array(getMaxLengthOfStaticContentType(this.typeInfo)) } } } @@ -729,39 +652,12 @@ export class TupleImpl extends T bytesValue = bytesValue.slice(4) } // pass the symbol to the constructor to let it know we are initialising from bytes - const result = new TupleImpl(typeInfo, isInitialisingFromBytesSymbol as DeliberateAny) + const result = new TupleImpl(typeInfo, IS_INITIALISING_FROM_BYTES_SYMBOL as DeliberateAny) result.uint8ArrayValue = asUint8Array(bytesValue) return result } - - static getMaxBytesLength(typeInfo: TypeInfo): number { - const genericArgs = Object.values(typeInfo.genericArgs as Record) - let i = 0 - let size = 0 - - while (i < genericArgs.length) { - const childType = genericArgs[i] - if (['Bool', 'boolean'].includes(childType.name)) { - const after = findBoolTypes(genericArgs, i, 1) - const boolNum = after + 1 - size += Math.floor(boolNum / BITS_IN_BYTE) - size += boolNum % BITS_IN_BYTE ? 1 : 0 - i += after - } else { - size += getMaxLengthOfStaticContentType(childType) - } - i += 1 - } - return size - } - - static getArc4TypeName = (t: TypeInfo): string => { - const genericArgs = Object.values(t.genericArgs as Record) - return `(${genericArgs.map(getArc4TypeName).join(',')})` - } } -type StructConstraint = Record export class StructImpl extends (Struct as DeliberateAny) { private uint8ArrayValue?: Uint8Array genericArgs: Record @@ -841,32 +737,6 @@ export class StructImpl extends (Struct { - const genericArgs = Object.values(t.genericArgs as Record) - return `(${genericArgs.map(getArc4TypeName).join(',')})` - } - - static getMaxBytesLength(typeInfo: TypeInfo): number { - const genericArgs = Object.values(typeInfo.genericArgs as Record) - let i = 0 - let size = 0 - - while (i < genericArgs.length) { - const childType = genericArgs[i] - if (['Bool', 'boolean'].includes(childType.name)) { - const after = findBoolTypes(genericArgs, i, 1) - const boolNum = after + 1 - size += Math.floor(boolNum / BITS_IN_BYTE) - size += boolNum % BITS_IN_BYTE ? 1 : 0 - i += after - } else { - size += getMaxLengthOfStaticContentType(childType) - } - i += 1 - } - return size - } } export class DynamicBytesImpl extends DynamicBytes { @@ -932,10 +802,6 @@ export class DynamicBytesImpl extends DynamicBytes { result.value = dynamicArrayValue return result } - - static getArc4TypeName = (_t: TypeInfo): string => { - return 'byte[]' - } } export class StaticBytesImpl extends StaticBytes { @@ -945,8 +811,8 @@ export class StaticBytesImpl extends StaticBytes) { super(value ?? (Bytes() as bytes)) this.typeInfo = typeof typeInfo === 'string' ? JSON.parse(typeInfo) : typeInfo - const uint8ArrayValue = asUint8Array(value ?? new Uint8Array(StaticBytesImpl.getMaxBytesLength(this.typeInfo))) - this.value = StaticArrayImpl.fromBytesImpl(uint8ArrayValue, typeInfo) as StaticArrayImpl + const uint8ArrayValue = asUint8Array(value ?? new Uint8Array(getMaxLengthOfStaticContentType(this.typeInfo))) + this.value = StaticArrayImpl.fromBytesImpl(uint8ArrayValue, typeInfo) as unknown as StaticArrayImpl return new Proxy(this, arrayProxyHandler()) as StaticBytesImpl } @@ -997,15 +863,6 @@ export class StaticBytesImpl extends StaticBytes return result } - - static getMaxBytesLength(typeInfo: TypeInfo): number { - return StaticArrayImpl.getMaxBytesLength(typeInfo) - } - - static getArc4TypeName = (t: TypeInfo): string => { - const genericArgs = t.genericArgs as StaticArrayGenericArgs - return `byte[${genericArgs.size.name}]` - } } const decode = (value: Uint8Array, childTypes: TypeInfo[]) => { @@ -1078,62 +935,11 @@ const decode = (value: Uint8Array, childTypes: TypeInfo[]) => { const values: ARC4Encoded[] = [] childTypes.forEach((childType, index) => { - values.push(getArc4Encoder(childType)(valuePartitions[index], childType)) + values.push(getEncoder(childType)(valuePartitions[index], childType)) }) return values } -const findBoolTypes = (values: TypeInfo[], index: number, delta: number): number => { - // Helper function to find consecutive booleans from current index in a tuple. - let until = 0 - const length = values.length - while (true) { - const curr = index + delta * until - if (['Bool', 'boolean'].includes(values[curr].name)) { - if ((curr != length - 1 && delta > 0) || (curr > 0 && delta < 0)) { - until += 1 - } else { - break - } - } else { - until -= 1 - break - } - } - return until -} - -export const getMaxLengthOfStaticContentType = (type: TypeInfo, asArc4Encoded: boolean = true): number => { - switch (trimGenericTypeName(type.name)) { - case 'uint64': - return UINT64_SIZE / BITS_IN_BYTE - case 'biguint': - return UINT512_SIZE / BITS_IN_BYTE - case 'boolean': - return asArc4Encoded ? 1 : 8 - case 'Bool': - return 1 - case 'Address': - return AddressImpl.getMaxBytesLength(type) - case 'Byte': - return ByteImpl.getMaxBitsLength(type) / BITS_IN_BYTE - case 'UintN': - return UintNImpl.getMaxBitsLength(type) / BITS_IN_BYTE - case 'UFixedNxM': - return UFixedNxMImpl.getMaxBitsLength(type) / BITS_IN_BYTE - case 'StaticArray': - return StaticArrayImpl.getMaxBytesLength(type) - case 'StaticBytes': - return StaticBytesImpl.getMaxBytesLength(type) - case 'Tuple': - return TupleImpl.getMaxBytesLength(type) - case 'Struct': - return StructImpl.getMaxBytesLength(type) - default: - throw new CodeError(`unsupported type ${type.name}`) - } -} - const encode = (values: ARC4Encoded[]) => { const length = values.length const heads = [] @@ -1188,159 +994,22 @@ const encode = (values: ARC4Encoded[]) => { return conactUint8Arrays(valuesLengthBytes, ...heads, ...tails) } -const findBool = (values: ARC4Encoded[], index: number, delta: number) => { - let until = 0 - const length = values.length - while (true) { - const curr = index + delta * until - if (values[curr] instanceof Bool) { - if ((curr !== length - 1 && delta > 0) || (curr > 0 && delta < 0)) { - until += 1 - } else { - break - } - } else { - until -= 1 - break - } - } - return until -} - -const compressMultipleBool = (values: Bool[]): number => { - let result = 0 - if (values.length > 8) { - throw new Error('length of list should not be greater than 8') - } - for (let i = 0; i < values.length; i++) { - const value = values[i] - if (value.native) { - result |= 1 << (7 - i) - } - } - return result -} - -const trimGenericTypeName = (typeName: string) => typeName.replace(/<.*>/, '') - -const isDynamicLengthType = (value: ARC4Encoded) => - value instanceof StrImpl || - (value instanceof StaticArrayImpl && holdsDynamicLengthContent(value)) || - (value instanceof TupleImpl && value.genericArgs.some(holdsDynamicLengthContent)) || - (value instanceof StructImpl && Object.values(value.genericArgs).some(holdsDynamicLengthContent)) || - value instanceof DynamicArrayImpl || - value instanceof DynamicBytesImpl - -const holdsDynamicLengthContent = (value: StaticArrayImpl | DynamicArrayImpl | TypeInfo): boolean => { - const itemTypeName = trimGenericTypeName( - value instanceof StaticArrayImpl || value instanceof DynamicArrayImpl ? value.genericArgs.elementType.name : value.name, - ) +const isDynamicLengthType = (value: ARC4Encoded) => { return ( - itemTypeName === 'Str' || - itemTypeName === 'DynamicArray' || - itemTypeName === 'DynamicBytes' || - (itemTypeName === 'StaticArray' && - holdsDynamicLengthContent(((value as StaticArrayImpl).genericArgs as StaticArrayGenericArgs).elementType)) || - ((itemTypeName === 'Tuple' || itemTypeName === 'Struct') && - Object.values(value.genericArgs as Record).some(holdsDynamicLengthContent)) + value instanceof StrImpl || + (value instanceof StaticArrayImpl && holdsDynamicLengthContent(value.typeInfo)) || + (value instanceof TupleImpl && value.genericArgs.some(holdsDynamicLengthContent)) || + (value instanceof StructImpl && Object.values(value.genericArgs).some(holdsDynamicLengthContent)) || + value instanceof DynamicArrayImpl || + value instanceof DynamicBytesImpl ) } -export function interpretAsArc4Impl( - typeInfoString: string, - bytes: StubBytesCompat, - prefix: 'none' | 'log' = 'none', -): T { - const typeInfo = JSON.parse(typeInfoString) - return getArc4Encoder(typeInfo)(bytes, typeInfo, prefix) -} - -export const arc4Encoders: Record> = { - Address: AddressImpl.fromBytesImpl, - Bool: BoolImpl.fromBytesImpl, - Byte: ByteImpl.fromBytesImpl, - Str: StrImpl.fromBytesImpl, - 'UintN<.*>': UintNImpl.fromBytesImpl, - 'UFixedNxM<.*>': UFixedNxMImpl.fromBytesImpl, - 'StaticArray<.*>': StaticArrayImpl.fromBytesImpl, - 'DynamicArray<.*>': DynamicArrayImpl.fromBytesImpl, - 'Tuple(<.*>)?': TupleImpl.fromBytesImpl, - 'Struct(<.*>)?': StructImpl.fromBytesImpl, - DynamicBytes: DynamicBytesImpl.fromBytesImpl, - 'StaticBytes<.*>': StaticBytesImpl.fromBytesImpl, - object: StructImpl.fromBytesImpl, -} -export const getArc4Encoder = (typeInfo: TypeInfo, encoders?: Record>): fromBytes => { - const encoder = Object.entries(encoders ?? arc4Encoders).find(([k, _]) => new RegExp(`^${k}$`, 'i').test(typeInfo.name))?.[1] - if (!encoder) { - throw new Error(`No encoder found for type ${typeInfo.name}`) - } - return encoder as fromBytes -} - -export const getArc4TypeName = (typeInfo: TypeInfo): string | undefined => { - const map = { - Address: 'address', - Bool: 'bool', - Byte: 'byte', - Str: 'string', - 'UintN<.*>': UintNImpl.getArc4TypeName, - 'UFixedNxM<.*>': UFixedNxMImpl.getArc4TypeName, - 'StaticArray<.*>': StaticArrayImpl.getArc4TypeName, - 'DynamicArray<.*>': DynamicArrayImpl.getArc4TypeName, - 'Tuple(<.*>)?': TupleImpl.getArc4TypeName, - 'Struct(<.*>)?': StructImpl.getArc4TypeName, - DynamicBytes: DynamicBytesImpl.getArc4TypeName, - 'StaticBytes<.*>': StaticBytesImpl.getArc4TypeName, - } - const name = Object.entries(map).find(([k, _]) => new RegExp(`^${k}$`, 'i').test(typeInfo.name))?.[1] - if (typeof name === 'string') return name - else if (typeof name === 'function') return name(typeInfo) - return undefined -} - -export function decodeArc4Impl( - sourceTypeInfoString: string, - targetTypeInfoString: string, - bytes: StubBytesCompat, - prefix: 'none' | 'log' = 'none', -): T { - const sourceTypeInfo = JSON.parse(sourceTypeInfoString) - const targetTypeInfo = JSON.parse(targetTypeInfoString) - const encoder = getArc4Encoder(sourceTypeInfo) - const source = encoder(bytes, sourceTypeInfo, prefix) as { typeInfo: TypeInfo } - return getNativeValue(source, targetTypeInfo) as T -} - export function encodeArc4Impl(sourceTypeInfoString: string | undefined, source: T): bytes { const arc4Encoded = getArc4Encoded(source, sourceTypeInfoString) return arc4Encoded.bytes } -const getNativeValue = (value: DeliberateAny, targetTypeInfo: TypeInfo | undefined): DeliberateAny => { - if (value.typeInfo && value.typeInfo.name === targetTypeInfo?.name) { - return value - } - const native = (value as DeliberateAny).native - if (Array.isArray(native)) { - return native.map((item) => getNativeValue(item, (targetTypeInfo?.genericArgs as { elementType: TypeInfo })?.elementType)) - } else if (native instanceof AlgoTsPrimitiveCls) { - return native - } else if (native instanceof BytesBackedCls) { - return native.bytes - } else if (native instanceof Uint64BackedCls) { - return native.uint64 - } else if (typeof native === 'object') { - return Object.fromEntries( - Object.entries(native).map(([key, value], index) => [ - key, - getNativeValue(value, (targetTypeInfo?.genericArgs as TypeInfo[])?.[index]), - ]), - ) - } - return native -} - export const getArc4Encoded = (value: DeliberateAny, sourceTypeInfoString?: string): ARC4Encoded => { if (value instanceof ARC4Encoded) { return value @@ -1407,7 +1076,61 @@ export const getArc4Encoded = (value: DeliberateAny, sourceTypeInfoString?: stri throw new CodeError(`Unsupported type for encoding: ${typeof value}`) } -export const arc4EncodedLengthImpl = (typeInfoString: string): uint64 => { - const typeInfo = JSON.parse(typeInfoString) - return getMaxLengthOfStaticContentType(typeInfo, true) +export const toBytes = (val: unknown): bytes => { + const uint64Val = asMaybeUint64Cls(val, false) + if (uint64Val !== undefined) { + return uint64Val.toBytes().asAlgoTs() + } + const bytesVal = asMaybeBytesCls(val) + if (bytesVal !== undefined) { + return bytesVal.asAlgoTs() + } + const bigUintVal = asMaybeBigUintCls(val) + if (bigUintVal !== undefined) { + return bigUintVal.toBytes().asAlgoTs() + } + if (val instanceof BytesBackedCls) { + return val.bytes + } + if (val instanceof Uint64BackedCls) { + return asUint64Cls(val.uint64).toBytes().asAlgoTs() + } + if (Array.isArray(val) || typeof val === 'object') { + return encodeArc4Impl(undefined, val) + } + throw new InternalError(`Invalid type for bytes: ${nameOfType(val)}`) +} + +export const getEncoder = (typeInfo: TypeInfo): fromBytes => { + const encoders: Record> = { + account: AccountCls.fromBytes, + application: ApplicationCls.fromBytes, + asset: AssetCls.fromBytes, + boolean: booleanFromBytes, + biguint: bigUintFromBytes, + bytes: bytesFromBytes, + string: stringFromBytes, + uint64: uint64FromBytes, + OnCompleteAction: onCompletionFromBytes, + TransactionType: transactionTypeFromBytes, + Address: AddressImpl.fromBytesImpl, + Bool: BoolImpl.fromBytesImpl, + Byte: ByteImpl.fromBytesImpl, + Str: StrImpl.fromBytesImpl, + 'UintN<.*>': UintNImpl.fromBytesImpl, + 'UFixedNxM<.*>': UFixedNxMImpl.fromBytesImpl, + 'StaticArray<.*>': StaticArrayImpl.fromBytesImpl, + 'DynamicArray<.*>': DynamicArrayImpl.fromBytesImpl, + 'Tuple(<.*>)?': TupleImpl.fromBytesImpl, + 'Struct(<.*>)?': StructImpl.fromBytesImpl, + DynamicBytes: DynamicBytesImpl.fromBytesImpl, + 'StaticBytes<.*>': StaticBytesImpl.fromBytesImpl, + object: StructImpl.fromBytesImpl, + } + + const encoder = Object.entries(encoders).find(([k, _]) => new RegExp(`^${k}$`, 'i').test(typeInfo.name))?.[1] + if (!encoder) { + throw new Error(`No encoder found for type ${typeInfo.name}`) + } + return encoder as fromBytes } diff --git a/src/impl/encoded-types/helpers.ts b/src/impl/encoded-types/helpers.ts new file mode 100644 index 00000000..46304598 --- /dev/null +++ b/src/impl/encoded-types/helpers.ts @@ -0,0 +1,159 @@ +import type { biguint, bytes, OnCompleteAction, TransactionType, uint64 } from '@algorandfoundation/algorand-typescript' +import { ARC4Encoded, Bool } from '@algorandfoundation/algorand-typescript/arc4' +import { encodingUtil } from '@algorandfoundation/puya-ts' +import { BITS_IN_BYTE } from '../../constants' +import type { DeliberateAny } from '../../typescript-helpers' +import { asBytesCls, assert } from '../../util' +import { BytesBackedCls, Uint64BackedCls } from '../base' +import type { BytesCls } from '../primitives' +import { AlgoTsPrimitiveCls } from '../primitives' +import { ABI_LENGTH_SIZE } from './constants' +import type { StaticArrayGenericArgs, TypeInfo } from './types' + +import { asBytes, asUint8Array } from '../../util' +import { BigUint, Uint64 } from '../primitives' +import type { fromBytes } from './types' + +export const validBitSizes = [...Array(64).keys()].map((x) => (x + 1) * 8) +export const maxBigIntValue = (bitSize: number) => 2n ** BigInt(bitSize) - 1n +export const maxBytesLength = (bitSize: number) => Math.floor(bitSize / BITS_IN_BYTE) +export const regExpNxM = (maxPrecision: number) => new RegExp(`^\\d*\\.?\\d{0,${maxPrecision}}$`) +export const trimTrailingDecimalZeros = (v: string) => v.replace(/(\d+\.\d*?)0+$/, '$1').replace(/\.$/, '') + +export const trimGenericTypeName = (typeName: string): string => typeName.replace(/<.*>/, '') + +export const areAllARC4Encoded = (items: T[]): items is T[] => items.every((item) => item instanceof ARC4Encoded) + +export const checkItemTypeName = (type: TypeInfo, value: ARC4Encoded) => { + const typeName = trimGenericTypeName(type.name) + const validTypeNames = [typeName, `${typeName}Impl`] + assert(validTypeNames.includes(value.constructor.name), `item must be of type ${typeName}, not ${value.constructor.name}`) +} + +export const findBoolTypes = (values: TypeInfo[], index: number, delta: number): number => { + // Helper function to find consecutive booleans from current index in a tuple. + let until = 0 + const length = values.length + while (true) { + const curr = index + delta * until + if (['Bool', 'boolean'].includes(values[curr].name)) { + if ((curr != length - 1 && delta > 0) || (curr > 0 && delta < 0)) { + until += 1 + } else { + break + } + } else { + until -= 1 + break + } + } + return until +} + +export const getNativeValue = (value: DeliberateAny, targetTypeInfo: TypeInfo | undefined): DeliberateAny => { + if (value.typeInfo && value.typeInfo.name === targetTypeInfo?.name) { + return value + } + const native = (value as DeliberateAny).native + if (Array.isArray(native)) { + return native.map((item) => getNativeValue(item, (targetTypeInfo?.genericArgs as { elementType: TypeInfo })?.elementType)) + } else if (native instanceof AlgoTsPrimitiveCls) { + return native + } else if (native instanceof BytesBackedCls) { + return native.bytes + } else if (native instanceof Uint64BackedCls) { + return native.uint64 + } else if (typeof native === 'object') { + return Object.fromEntries( + Object.entries(native).map(([key, value], index) => [ + key, + getNativeValue(value, (targetTypeInfo?.genericArgs as TypeInfo[])?.[index]), + ]), + ) + } + return native +} + +export const readLength = (value: Uint8Array): readonly [number, Uint8Array] => { + const length = Number(encodingUtil.uint8ArrayToBigInt(value.slice(0, ABI_LENGTH_SIZE))) + const data = value.slice(ABI_LENGTH_SIZE) + return [length, data] +} + +export const encodeLength = (length: number): BytesCls => { + return asBytesCls(encodingUtil.bigIntToUint8Array(BigInt(length), ABI_LENGTH_SIZE)) +} + +export const findBool = (values: ARC4Encoded[], index: number, delta: number) => { + let until = 0 + const length = values.length + while (true) { + const curr = index + delta * until + if (values[curr] instanceof Bool) { + if ((curr !== length - 1 && delta > 0) || (curr > 0 && delta < 0)) { + until += 1 + } else { + break + } + } else { + until -= 1 + break + } + } + return until +} + +export const compressMultipleBool = (values: Bool[]): number => { + let result = 0 + if (values.length > 8) { + throw new Error('length of list should not be greater than 8') + } + for (let i = 0; i < values.length; i++) { + const value = values[i] + if (value.native) { + result |= 1 << (7 - i) + } + } + return result +} + +export const holdsDynamicLengthContent = (value: TypeInfo): boolean => { + const itemTypeName = trimGenericTypeName(value.name) + + return ( + itemTypeName === 'Str' || + itemTypeName === 'DynamicArray' || + itemTypeName === 'DynamicBytes' || + (itemTypeName === 'StaticArray' && holdsDynamicLengthContent((value.genericArgs as StaticArrayGenericArgs).elementType)) || + ((itemTypeName === 'Tuple' || itemTypeName === 'Struct') && + Object.values(value.genericArgs as Record).some(holdsDynamicLengthContent)) + ) +} + +export const booleanFromBytes: fromBytes = (val) => { + return encodingUtil.uint8ArrayToBigInt(asUint8Array(val)) > 0n +} + +export const bigUintFromBytes: fromBytes = (val) => { + return BigUint(encodingUtil.uint8ArrayToBigInt(asUint8Array(val))) +} + +export const bytesFromBytes: fromBytes = (val) => { + return asBytes(val) +} + +export const stringFromBytes: fromBytes = (val) => { + return asBytes(val).toString() +} + +export const uint64FromBytes: fromBytes = (val) => { + return Uint64(encodingUtil.uint8ArrayToBigInt(asUint8Array(val))) +} + +export const onCompletionFromBytes: fromBytes = (val) => { + return Uint64(encodingUtil.uint8ArrayToBigInt(asUint8Array(val))) as OnCompleteAction +} + +export const transactionTypeFromBytes: fromBytes = (val) => { + return Uint64(encodingUtil.uint8ArrayToBigInt(asUint8Array(val))) as TransactionType +} diff --git a/src/impl/encoded-types/index.ts b/src/impl/encoded-types/index.ts new file mode 100644 index 00000000..455ba749 --- /dev/null +++ b/src/impl/encoded-types/index.ts @@ -0,0 +1,48 @@ +import type { ARC4Encoded } from '@algorandfoundation/algorand-typescript/arc4' +import type { StubBytesCompat } from '../primitives' +import { getEncoder } from './encoded-types' +import { getNativeValue } from './helpers' +import type { TypeInfo } from './types' + +export { + AddressImpl, + BoolImpl, + ByteImpl, + DynamicArrayImpl, + DynamicBytesImpl, + encodeArc4Impl, + getArc4Encoded, + getEncoder, + StaticArrayImpl, + StaticBytesImpl, + StrImpl, + StructImpl, + toBytes, + TupleImpl, + UFixedNxMImpl, + UintNImpl, +} from './encoded-types' +export { TypeInfo } from './types' +export { arc4EncodedLengthImpl, getArc4TypeName, getMaxLengthOfStaticContentType, minLengthForType } from './utils' + +export function decodeArc4Impl( + sourceTypeInfoString: string, + targetTypeInfoString: string, + bytes: StubBytesCompat, + prefix: 'none' | 'log' = 'none', +): T { + const sourceTypeInfo = JSON.parse(sourceTypeInfoString) + const targetTypeInfo = JSON.parse(targetTypeInfoString) + const encoder = getEncoder(sourceTypeInfo) + const source = encoder(bytes, sourceTypeInfo, prefix) as { typeInfo: TypeInfo } + return getNativeValue(source, targetTypeInfo) as T +} + +export function interpretAsArc4Impl( + typeInfoString: string, + bytes: StubBytesCompat, + prefix: 'none' | 'log' = 'none', +): T { + const typeInfo = JSON.parse(typeInfoString) + return getEncoder(typeInfo)(bytes, typeInfo, prefix) +} diff --git a/src/impl/encoded-types/types.ts b/src/impl/encoded-types/types.ts new file mode 100644 index 00000000..e374d39a --- /dev/null +++ b/src/impl/encoded-types/types.ts @@ -0,0 +1,16 @@ +import type { BigUintCompat, Uint64Compat } from '@algorandfoundation/algorand-typescript' +import type { ARC4Encoded, BitSize } from '@algorandfoundation/algorand-typescript/arc4' + +import type { StubBytesCompat } from '../primitives' + +export type CompatForArc4Int = N extends 8 | 16 | 24 | 32 | 40 | 48 | 56 | 64 ? Uint64Compat : BigUintCompat +export type uFixedNxMGenericArgs = { n: TypeInfo; m: TypeInfo } +export type StaticArrayGenericArgs = { elementType: TypeInfo; size: TypeInfo } +export type DynamicArrayGenericArgs = { elementType: TypeInfo } +export type StructConstraint = Record +export type TypeInfo = { + name: string + genericArgs?: TypeInfo[] | Record +} + +export type fromBytes = (val: Uint8Array | StubBytesCompat, typeInfo: TypeInfo, prefix?: 'none' | 'log') => T diff --git a/src/impl/encoded-types/utils.ts b/src/impl/encoded-types/utils.ts new file mode 100644 index 00000000..bd2a2ebc --- /dev/null +++ b/src/impl/encoded-types/utils.ts @@ -0,0 +1,125 @@ +import type { uint64 } from '@algorandfoundation/algorand-typescript' +import { BITS_IN_BYTE, UINT512_SIZE, UINT64_SIZE } from '../../constants' +import { CodeError } from '../../errors' +import { findBoolTypes, trimGenericTypeName } from './helpers' +import type { DynamicArrayGenericArgs, StaticArrayGenericArgs, TypeInfo, uFixedNxMGenericArgs } from './types' + +export const getMaxLengthOfStaticContentType = (type: TypeInfo, asArc4Encoded: boolean = true): number => { + const getMaxBytesLengthForStaticArray = (typeInfo: { genericArgs: StaticArrayGenericArgs }) => { + const genericArgs = typeInfo.genericArgs + const arraySize = parseInt(genericArgs.size.name, 10) + const childTypes = Array(arraySize).fill(genericArgs.elementType) + let i = 0 + let size = 0 + if (['Bool', 'boolean'].includes(genericArgs.elementType.name)) { + while (i < childTypes.length) { + const after = findBoolTypes(childTypes, i, 1) + const boolNum = after + 1 + size += Math.floor(boolNum / BITS_IN_BYTE) + size += boolNum % BITS_IN_BYTE ? 1 : 0 + i += after + 1 + } + } else { + size = getMaxLengthOfStaticContentType(genericArgs.elementType) * arraySize + } + return size + } + const getMaxBytesLengthForObjectType = (typeInfo: TypeInfo) => { + const genericArgs = Object.values(typeInfo.genericArgs as Record) + let i = 0 + let size = 0 + + while (i < genericArgs.length) { + const childType = genericArgs[i] + if (['Bool', 'boolean'].includes(childType.name)) { + const after = findBoolTypes(genericArgs, i, 1) + const boolNum = after + 1 + size += Math.floor(boolNum / BITS_IN_BYTE) + size += boolNum % BITS_IN_BYTE ? 1 : 0 + i += after + } else { + size += getMaxLengthOfStaticContentType(childType) + } + i += 1 + } + return size + } + switch (trimGenericTypeName(type.name)) { + case 'uint64': + return UINT64_SIZE / BITS_IN_BYTE + case 'biguint': + return UINT512_SIZE / BITS_IN_BYTE + case 'boolean': + return asArc4Encoded ? 1 : 8 + case 'Bool': + return 1 + case 'Byte': + case 'UintN': + return parseInt((type.genericArgs as TypeInfo[])![0].name, 10) / BITS_IN_BYTE + case 'UFixedNxM': + return parseInt((type.genericArgs as uFixedNxMGenericArgs).n.name, 10) / BITS_IN_BYTE + case 'Address': + case 'StaticBytes': + case 'StaticArray': + return getMaxBytesLengthForStaticArray(type as unknown as { genericArgs: StaticArrayGenericArgs }) + case 'Tuple': + return getMaxBytesLengthForObjectType(type) + case 'Struct': + return getMaxBytesLengthForObjectType(type) + default: + throw new CodeError(`unsupported type ${type.name}`) + } +} + +export const getArc4TypeName = (typeInfo: TypeInfo): string | undefined => { + const getArc4TypeNameForObjectType = (typeInfo: TypeInfo): string => { + const genericArgs = Object.values(typeInfo.genericArgs as Record) + return `(${genericArgs.map(getArc4TypeName).join(',')})` + } + const map = { + Address: 'address', + Bool: 'bool', + Byte: 'byte', + Str: 'string', + 'UintN<.*>': (t: TypeInfo) => `uint${getMaxLengthOfStaticContentType(t) * BITS_IN_BYTE}`, + 'UFixedNxM<.*>': (t: TypeInfo) => { + const genericArgs = t.genericArgs as uFixedNxMGenericArgs + return `ufixed${genericArgs.n.name}x${genericArgs.m.name}` + }, + 'StaticArray<.*>': (t: TypeInfo) => { + const genericArgs = t.genericArgs as StaticArrayGenericArgs + return `${getArc4TypeName(genericArgs.elementType)}[${genericArgs.size.name}]` + }, + 'DynamicArray<.*>': (t: TypeInfo) => { + const genericArgs = t.genericArgs as DynamicArrayGenericArgs + return `${getArc4TypeName(genericArgs.elementType)}[]` + }, + 'Tuple(<.*>)?': getArc4TypeNameForObjectType, + 'Struct(<.*>)?': getArc4TypeNameForObjectType, + DynamicBytes: 'byte[]', + 'StaticBytes<.*>': (t: TypeInfo) => { + const genericArgs = t.genericArgs as StaticArrayGenericArgs + return `byte[${genericArgs.size.name}]` + }, + } + const name = Object.entries(map).find(([k, _]) => new RegExp(`^${k}$`, 'i').test(typeInfo.name))?.[1] + if (typeof name === 'string') return name + else if (typeof name === 'function') return name(typeInfo) + return undefined +} + +export const arc4EncodedLengthImpl = (typeInfoString: string): uint64 => { + const typeInfo = JSON.parse(typeInfoString) + return getMaxLengthOfStaticContentType(typeInfo, true) +} + +export const minLengthForType = (typeInfo: TypeInfo): number | undefined => { + try { + return getMaxLengthOfStaticContentType(typeInfo, false) + } catch (e) { + if (e instanceof CodeError && e.message.startsWith('unsupported type')) { + return undefined + } + throw e + } +} diff --git a/src/impl/log.ts b/src/impl/log.ts index e2bc1752..7864f77c 100644 --- a/src/impl/log.ts +++ b/src/impl/log.ts @@ -1,26 +1,8 @@ -import type { bytes, BytesBacked, StringCompat } from '@algorandfoundation/algorand-typescript' -import { ARC4Encoded } from '@algorandfoundation/algorand-typescript/arc4' +import type { BytesBacked, StringCompat } from '@algorandfoundation/algorand-typescript' import { lazyContext } from '../context-helpers/internal-context' -import { InternalError } from '../errors' -import { nameOfType } from '../util' -import type { StubBigUintCompat, StubBytesCompat, StubUint64Compat } from './primitives' -import { AlgoTsPrimitiveCls, BigUintCls, BytesCls, Uint64Cls } from './primitives' - -const toBytes = (val: unknown): bytes => { - if (val instanceof AlgoTsPrimitiveCls) return val.toBytes().asAlgoTs() - if (val instanceof ARC4Encoded) return val.bytes - switch (typeof val) { - case 'string': - return BytesCls.fromCompat(val).asAlgoTs() - case 'bigint': - return BigUintCls.fromCompat(val).toBytes().asAlgoTs() - case 'number': - return Uint64Cls.fromCompat(val).toBytes().asAlgoTs() - default: - throw new InternalError(`Unsupported arg type ${nameOfType(val)}`) - } -} +import { toBytes } from './encoded-types' +import type { StubBigUintCompat, StubBytesCompat, StubUint64Compat } from './primitives' export function log(...args: Array): void { lazyContext.txn.appendLog(args.map(toBytes).reduce((left, right) => left.concat(right))) diff --git a/src/impl/state.ts b/src/impl/state.ts index a5e17ff5..9fdb4c3b 100644 --- a/src/impl/state.ts +++ b/src/impl/state.ts @@ -14,11 +14,12 @@ import type { import { AccountMap } from '../collections/custom-key-map' import { MAX_BOX_SIZE } from '../constants' import { lazyContext } from '../context-helpers/internal-context' -import type { TypeInfo } from '../encoders' -import { getEncoder, minLengthForType, toBytes } from '../encoders' import { AssertError, CodeError, InternalError } from '../errors' +import type { TypeInfo } from '../impl/encoded-types' +import { toBytes } from '../impl/encoded-types' import { getGenericTypeInfo } from '../runtime-helpers' import { asBytes, asBytesCls, asNumber, asUint8Array, conactUint8Arrays } from '../util' +import { getEncoder, minLengthForType } from './encoded-types' import type { StubBytesCompat, StubUint64Compat } from './primitives' import { Bytes, Uint64, Uint64Cls } from './primitives' diff --git a/src/impl/transactions.ts b/src/impl/transactions.ts index 80a010ac..1c7aacfa 100644 --- a/src/impl/transactions.ts +++ b/src/impl/transactions.ts @@ -9,10 +9,10 @@ import type { import { OnCompleteAction, TransactionType } from '@algorandfoundation/algorand-typescript' import { ABI_RETURN_VALUE_LOG_PREFIX, MAX_ITEMS_IN_LOG } from '../constants' import { lazyContext } from '../context-helpers/internal-context' -import { toBytes } from '../encoders' import { InternalError } from '../errors' import type { Mutable, ObjectKeys } from '../typescript-helpers' import { asBytes, asMaybeBytesCls, asMaybeUint64Cls, asNumber, asUint64Cls, combineIntoMaxBytePages, getRandomBytes } from '../util' +import { toBytes } from './encoded-types' import { Bytes, Uint64, type StubBytesCompat, type StubUint64Compat } from './primitives' import { Account, Application, Asset } from './reference' diff --git a/src/runtime-helpers.ts b/src/runtime-helpers.ts index 73cfcd17..607d2d50 100644 --- a/src/runtime-helpers.ts +++ b/src/runtime-helpers.ts @@ -1,13 +1,13 @@ import { ARC4Encoded } from '@algorandfoundation/algorand-typescript/arc4' import { encodingUtil } from '@algorandfoundation/puya-ts' import { MAX_UINT64 } from './constants' -import type { TypeInfo } from './encoders' import { AvmError, CodeError, InternalError } from './errors' import { Uint64BackedCls } from './impl/base' +import type { TypeInfo } from './impl/encoded-types' import { AlgoTsPrimitiveCls, BigUintCls, BytesCls, checkBigUint, checkBytes, Uint64Cls } from './impl/primitives' import { AccountCls } from './impl/reference' -import type { DeliberateAny } from './typescript-helpers' -import { flattenAsBytes, nameOfType } from './util' +import { nameOfType, type DeliberateAny } from './typescript-helpers' +import { flattenAsBytes } from './util' export { attachAbiMetadata } from './abi-metadata' export { emitImpl } from './impl/emit' diff --git a/src/subcontexts/contract-context.ts b/src/subcontexts/contract-context.ts index 6b2b75f4..48725393 100644 --- a/src/subcontexts/contract-context.ts +++ b/src/subcontexts/contract-context.ts @@ -12,11 +12,10 @@ import { getArc4Selector, getContractAbiMetadata, getContractMethodAbiMetadata } import { BytesMap } from '../collections/custom-key-map' import { checkRoutingConditions } from '../context-helpers/context-util' import { lazyContext } from '../context-helpers/internal-context' -import { type TypeInfo } from '../encoders' import { CodeError } from '../errors' import { BaseContract, ContractOptionsSymbol } from '../impl/base-contract' import { Contract } from '../impl/contract' -import { getArc4Encoded, UintNImpl } from '../impl/encoded-types' +import { getArc4Encoded, UintNImpl, type TypeInfo } from '../impl/encoded-types' import { Bytes } from '../impl/primitives' import { AccountCls, ApplicationCls, AssetCls } from '../impl/reference' import { BoxCls, BoxMapCls, BoxRefCls, GlobalStateCls } from '../impl/state' diff --git a/src/subcontexts/ledger-context.ts b/src/subcontexts/ledger-context.ts index c8fb887d..ed02fe97 100644 --- a/src/subcontexts/ledger-context.ts +++ b/src/subcontexts/ledger-context.ts @@ -9,9 +9,9 @@ import type { } from '@algorandfoundation/algorand-typescript' import { AccountMap, Uint64Map } from '../collections/custom-key-map' import { MAX_UINT64 } from '../constants' -import { toBytes } from '../encoders' import { InternalError } from '../errors' import { BlockData } from '../impl/block' +import { toBytes } from '../impl/encoded-types' import { GlobalData } from '../impl/global' import { Uint64Cls, type StubBytesCompat, type StubUint64Compat } from '../impl/primitives' import type { AssetData } from '../impl/reference' diff --git a/src/test-transformer/node-factory.ts b/src/test-transformer/node-factory.ts index 575546ff..9d199d2c 100644 --- a/src/test-transformer/node-factory.ts +++ b/src/test-transformer/node-factory.ts @@ -1,6 +1,6 @@ import type { ptypes } from '@algorandfoundation/puya-ts' import ts from 'typescript' -import type { TypeInfo } from '../encoders' +import type { TypeInfo } from '../impl/encoded-types' import type { DeliberateAny } from '../typescript-helpers' import { getPropertyNameAsString, trimGenericTypeName } from './helpers' diff --git a/src/test-transformer/visitors.ts b/src/test-transformer/visitors.ts index 76b0d213..d987eb01 100644 --- a/src/test-transformer/visitors.ts +++ b/src/test-transformer/visitors.ts @@ -1,7 +1,7 @@ import { LoggingContext, ptypes, SourceLocation, TypeResolver } from '@algorandfoundation/puya-ts' import path from 'path' import ts from 'typescript' -import type { TypeInfo } from '../encoders' +import type { TypeInfo } from '../impl/encoded-types' import { instanceOfAny } from '../typescript-helpers' import { nodeFactory } from './node-factory' import type { TransformerConfig } from './program-factory' diff --git a/src/util.ts b/src/util.ts index d2c0eea3..400335cb 100644 --- a/src/util.ts +++ b/src/util.ts @@ -6,17 +6,6 @@ import type { StubBigUintCompat, StubBytesCompat, StubUint64Compat } from './imp import { BigUintCls, Bytes, BytesCls, Uint64Cls } from './impl/primitives' import type { DeliberateAny } from './typescript-helpers' -export const nameOfType = (x: unknown) => { - if (typeof x === 'object') { - if (x === null) return 'Null' - if (x === undefined) return 'undefined' - if ('constructor' in x) { - return x.constructor.name - } - } - return typeof x -} - export function* iterBigInt(start: bigint, end: bigint): Generator { for (let i = start; i < end; i++) { yield BigInt(i) diff --git a/tests/arc4/dynamic-array.spec.ts b/tests/arc4/dynamic-array.spec.ts index e470cc01..f511f176 100644 --- a/tests/arc4/dynamic-array.spec.ts +++ b/tests/arc4/dynamic-array.spec.ts @@ -1,5 +1,4 @@ import { getABIEncodedValue } from '@algorandfoundation/algokit-utils/types/app-arc56' -import { Bytes } from '@algorandfoundation/algorand-typescript' import { TestExecutionContext } from '@algorandfoundation/algorand-typescript-testing' import { Address, @@ -15,7 +14,7 @@ import { } from '@algorandfoundation/algorand-typescript/arc4' import { encodingUtil } from '@algorandfoundation/puya-ts' import { afterEach, describe, expect, it, test } from 'vitest' -import type { StubBytesCompat } from '../../src/impl/primitives' +import { Bytes, type StubBytesCompat } from '../../src/impl/primitives' import { AccountCls } from '../../src/impl/reference' import type { DeliberateAny } from '../../src/typescript-helpers' import { asBytes, asUint8Array } from '../../src/util' @@ -413,48 +412,34 @@ const structDynamicArray = { return getABIEncodedValue([...this.nativeValues(), ...this.nativeValues()], this.abiTypeString, {}) }, } - +const testDataArray = [ + addressDynamicArray, + boolDynamicArray, + uint256DynamicArray, + ufixednxmDynamicArray, + stringDynamicArray, + addressDynamicArrayOfArray, + boolDynamicArrayOfArray, + uint256DynamicArrayOfArray, + uint256DynamicArrayOfStaticArray, + stringDynamicArrayOfArray, + stringDynamicArrayOfArrayOfArray, + tupleDynamicArray, + structDynamicArray, +] describe('arc4.DynamicArray', () => { const ctx = new TestExecutionContext() afterEach(() => { ctx.reset() }) - test.each([ - addressDynamicArray, - boolDynamicArray, - uint256DynamicArray, - ufixednxmDynamicArray, - stringDynamicArray, - addressDynamicArrayOfArray, - boolDynamicArrayOfArray, - uint256DynamicArrayOfArray, - uint256DynamicArrayOfStaticArray, - stringDynamicArrayOfArray, - stringDynamicArrayOfArrayOfArray, - tupleDynamicArray, - structDynamicArray, - ])('should be able to get bytes representation', (data) => { + test.each(testDataArray)('should be able to get bytes representation', (data) => { const sdkResult = getABIEncodedValue(data.nativeValues(), data.abiTypeString, {}) const result = data.array().bytes expect(result).toEqual(sdkResult) }) - test.each([ - addressDynamicArray, - boolDynamicArray, - uint256DynamicArray, - ufixednxmDynamicArray, - stringDynamicArray, - addressDynamicArrayOfArray, - boolDynamicArrayOfArray, - uint256DynamicArrayOfArray, - uint256DynamicArrayOfStaticArray, - stringDynamicArrayOfArray, - stringDynamicArrayOfArrayOfArray, - tupleDynamicArray, - structDynamicArray, - ])('copy dynamic array', (data) => { + test.each(testDataArray)('copy dynamic array', (data) => { const sdkResult = getABIEncodedValue(data.nativeValues(), data.abiTypeString, {}) const original = data.array() const copy = original.copy() @@ -463,21 +448,7 @@ describe('arc4.DynamicArray', () => { expect(result).toEqual(sdkResult) }) - test.each([ - addressDynamicArray, - boolDynamicArray, - uint256DynamicArray, - ufixednxmDynamicArray, - stringDynamicArray, - addressDynamicArrayOfArray, - boolDynamicArrayOfArray, - uint256DynamicArrayOfArray, - uint256DynamicArrayOfStaticArray, - stringDynamicArrayOfArray, - stringDynamicArrayOfArrayOfArray, - tupleDynamicArray, - structDynamicArray, - ])('concat dynamic array', (data) => { + test.each(testDataArray)('concat dynamic array', (data) => { const sdkResult = data.concatABIValue() const original = data.array() const concatenated = data.concat() @@ -487,21 +458,7 @@ describe('arc4.DynamicArray', () => { expect(result).toEqual(asBytes(sdkResult)) }) - test.each([ - addressDynamicArray, - boolDynamicArray, - uint256DynamicArray, - ufixednxmDynamicArray, - stringDynamicArray, - addressDynamicArrayOfArray, - boolDynamicArrayOfArray, - uint256DynamicArrayOfArray, - uint256DynamicArrayOfStaticArray, - stringDynamicArrayOfArray, - stringDynamicArrayOfArrayOfArray, - tupleDynamicArray, - structDynamicArray, - ])('get item from dynamic array', (data) => { + test.each(testDataArray)('get item from dynamic array', (data) => { const dynamicArray = data.array() const nativeValues = data.nativeValues() for (let i = 0; i < dynamicArray.length; i++) { @@ -510,21 +467,7 @@ describe('arc4.DynamicArray', () => { expect(dynamicArray.length).toEqual(nativeValues.length) }) - test.each([ - addressDynamicArray, - boolDynamicArray, - uint256DynamicArray, - ufixednxmDynamicArray, - stringDynamicArray, - addressDynamicArrayOfArray, - boolDynamicArrayOfArray, - uint256DynamicArrayOfArray, - uint256DynamicArrayOfStaticArray, - stringDynamicArrayOfArray, - stringDynamicArrayOfArrayOfArray, - tupleDynamicArray, - structDynamicArray, - ])('set item in dynamic array', (data) => { + test.each(testDataArray)('set item in dynamic array', (data) => { const nativeValues = data.nativeValues() const nativeValuesCopy = [...nativeValues] const nativeTemp = nativeValuesCopy.at(-1)! @@ -542,21 +485,7 @@ describe('arc4.DynamicArray', () => { expect(result).toEqual(Bytes(sdkResult)) }) - test.each([ - addressDynamicArray, - boolDynamicArray, - uint256DynamicArray, - ufixednxmDynamicArray, - stringDynamicArray, - addressDynamicArrayOfArray, - boolDynamicArrayOfArray, - uint256DynamicArrayOfArray, - uint256DynamicArrayOfStaticArray, - stringDynamicArrayOfArray, - stringDynamicArrayOfArrayOfArray, - tupleDynamicArray, - structDynamicArray, - ])('create dynamic array from bytes', (data) => { + test.each(testDataArray)('create dynamic array from bytes', (data) => { const sdkEncodedBytes = getABIEncodedValue(data.nativeValues(), data.abiTypeString, {}) const result = data.create(Bytes(sdkEncodedBytes)) const nativeValues = data.nativeValues() @@ -565,21 +494,7 @@ describe('arc4.DynamicArray', () => { } }) - test.each([ - addressDynamicArray, - boolDynamicArray, - uint256DynamicArray, - ufixednxmDynamicArray, - stringDynamicArray, - addressDynamicArrayOfArray, - boolDynamicArrayOfArray, - uint256DynamicArrayOfArray, - uint256DynamicArrayOfStaticArray, - stringDynamicArrayOfArray, - stringDynamicArrayOfArrayOfArray, - tupleDynamicArray, - structDynamicArray, - ])('push item to dynamic array', (data) => { + test.each(testDataArray)('push item to dynamic array', (data) => { const nativeValues = data.nativeValues() const nativeValuesCopy = [...nativeValues] nativeValuesCopy.push(nativeValues.at(-1)!) @@ -594,21 +509,7 @@ describe('arc4.DynamicArray', () => { expect(result).toEqual(Bytes(sdkResult)) }) - test.each([ - addressDynamicArray, - boolDynamicArray, - uint256DynamicArray, - ufixednxmDynamicArray, - stringDynamicArray, - addressDynamicArrayOfArray, - boolDynamicArrayOfArray, - uint256DynamicArrayOfArray, - uint256DynamicArrayOfStaticArray, - stringDynamicArrayOfArray, - stringDynamicArrayOfArrayOfArray, - tupleDynamicArray, - structDynamicArray, - ])('push item to empty dynamic array', (data) => { + test.each(testDataArray)('push item to empty dynamic array', (data) => { const nativeValues = data.nativeValues() const sdkResult = getABIEncodedValue(nativeValues, data.abiTypeString, {}) @@ -618,21 +519,7 @@ describe('arc4.DynamicArray', () => { expect(emptyArray.bytes).toEqual(sdkResult) }) - test.each([ - addressDynamicArray, - boolDynamicArray, - uint256DynamicArray, - ufixednxmDynamicArray, - stringDynamicArray, - addressDynamicArrayOfArray, - boolDynamicArrayOfArray, - uint256DynamicArrayOfArray, - uint256DynamicArrayOfStaticArray, - stringDynamicArrayOfArray, - stringDynamicArrayOfArrayOfArray, - tupleDynamicArray, - structDynamicArray, - ])('push item to empty dynamic array created from bytes', (data) => { + test.each(testDataArray)('push item to empty dynamic array created from bytes', (data) => { const nativeValues = data.nativeValues() const sdkResult = getABIEncodedValue(nativeValues, data.abiTypeString, {}) @@ -642,21 +529,7 @@ describe('arc4.DynamicArray', () => { expect(emptyArray.bytes).toEqual(sdkResult) }) - test.each([ - addressDynamicArray, - boolDynamicArray, - uint256DynamicArray, - ufixednxmDynamicArray, - stringDynamicArray, - addressDynamicArrayOfArray, - boolDynamicArrayOfArray, - uint256DynamicArrayOfArray, - uint256DynamicArrayOfStaticArray, - stringDynamicArrayOfArray, - stringDynamicArrayOfArrayOfArray, - tupleDynamicArray, - structDynamicArray, - ])('pop item from dynamic array', (data) => { + test.each(testDataArray)('pop item from dynamic array', (data) => { const nativeValues = data.nativeValues() const nativeValuesCopy = [...nativeValues] const nativeValue1 = nativeValuesCopy.pop() @@ -724,6 +597,8 @@ const compareARC4AndABIValue = (arc4Value: DeliberateAny, nativeValue: Deliberat } else { expect(arc4Value.native).toEqual(nativeValue) } + } else if (arc4Value.bytes === undefined) { + expect(arc4Value).toEqual(nativeValue) } else { expect(arc4Value.bytes).toEqual(encodingUtil.bigIntToUint8Array(arc4Value, arc4Value.bytes.length)) } diff --git a/tests/references/box-map.spec.ts b/tests/references/box-map.spec.ts index 215584b1..4f04c488 100644 --- a/tests/references/box-map.spec.ts +++ b/tests/references/box-map.spec.ts @@ -5,7 +5,7 @@ import type { Bool, DynamicBytes, StaticArray, Tuple, UintN16 } from '@algorandf import { ARC4Encoded, DynamicArray, interpretAsArc4, Str, Struct, UintN64, UintN8 } from '@algorandfoundation/algorand-typescript/arc4' import { afterEach, describe, expect, it, test } from 'vitest' import { MAX_UINT64 } from '../../src/constants' -import { toBytes } from '../../src/encoders' +import { toBytes } from '../../src/impl/encoded-types' import { asBytes } from '../../src/util' const BOX_NOT_CREATED_ERROR = 'Box has not been created' diff --git a/tests/references/box.spec.ts b/tests/references/box.spec.ts index e5348e49..a481db68 100644 --- a/tests/references/box.spec.ts +++ b/tests/references/box.spec.ts @@ -17,7 +17,7 @@ import { } from '@algorandfoundation/algorand-typescript/arc4' import { itob } from '@algorandfoundation/algorand-typescript/op' import { afterEach, describe, expect, it, test } from 'vitest' -import { toBytes } from '../../src/encoders' +import { toBytes } from '../../src/impl/encoded-types' import type { DeliberateAny } from '../../src/typescript-helpers' import { asBytes } from '../../src/util' import { BoxContract } from '../artifacts/box-contract/contract.algo' From b49ecd2490487ee0a4acfe19c0b05b74e8a9c2f6 Mon Sep 17 00:00:00 2001 From: Bobby Lat Date: Mon, 9 Jun 2025 17:32:39 +0800 Subject: [PATCH 15/68] feat: implement clone function --- examples/marketplace/contract.algo.ts | 14 ++-- examples/voting/contract.algo.ts | 5 +- examples/zk-whitelist/contract.algo.ts | 3 +- src/impl/clone.ts | 11 ++++ src/impl/encoded/arc4-impl.ts | 0 src/impl/encoded/array-proxy.ts | 0 src/impl/encoded/containers.ts | 0 src/impl/encoded/encoders.ts | 0 src/impl/encoded/helpers.ts | 0 src/impl/encoded/primitives.ts | 0 src/impl/encoded/types.ts | 0 src/impl/encoded/utils.ts | 0 src/runtime-helpers.ts | 1 + src/test-transformer/visitors.ts | 62 ++++++++++++------ tests/arc4/dynamic-array.spec.ts | 52 +++++++++++++-- tests/arc4/encode-decode-arc4.spec.ts | 7 +- tests/arc4/static-array.spec.ts | 64 +++++++++++++++++-- .../arc4-abi-method/contract.algo.ts | 4 +- .../arc4-primitive-ops/contract.algo.ts | 8 +-- tests/references/box-map.spec.ts | 4 +- tests/references/box.spec.ts | 4 +- 21 files changed, 190 insertions(+), 49 deletions(-) create mode 100644 src/impl/clone.ts create mode 100644 src/impl/encoded/arc4-impl.ts create mode 100644 src/impl/encoded/array-proxy.ts create mode 100644 src/impl/encoded/containers.ts create mode 100644 src/impl/encoded/encoders.ts create mode 100644 src/impl/encoded/helpers.ts create mode 100644 src/impl/encoded/primitives.ts create mode 100644 src/impl/encoded/types.ts create mode 100644 src/impl/encoded/utils.ts diff --git a/examples/marketplace/contract.algo.ts b/examples/marketplace/contract.algo.ts index 2799e2d7..d31d1cd3 100644 --- a/examples/marketplace/contract.algo.ts +++ b/examples/marketplace/contract.algo.ts @@ -1,5 +1,5 @@ import type { Asset, gtxn, uint64 } from '@algorandfoundation/algorand-typescript' -import { arc4, assert, BoxMap, Global, itxn, op, Txn } from '@algorandfoundation/algorand-typescript' +import { arc4, assert, BoxMap, clone, Global, itxn, op, Txn } from '@algorandfoundation/algorand-typescript' export class ListingKey extends arc4.Struct<{ owner: arc4.Address @@ -111,7 +111,7 @@ export default class DigitalMarketplace extends arc4.Contract { assert(xfer.assetReceiver === Global.currentApplicationAddress) assert(xfer.assetAmount > 0) - const existing = this.listings(key).value.copy() + const existing = clone(this.listings(key).value) this.listings(key).value = new ListingValue({ bid: existing.bid, bidUnitaryPrice: existing.bidUnitaryPrice, @@ -129,7 +129,7 @@ export default class DigitalMarketplace extends arc4.Contract { nonce: nonce, }) - const existing = this.listings(key).value.copy() + const existing = clone(this.listings(key).value) this.listings(key).value = new ListingValue({ bid: existing.bid, bidUnitaryPrice: existing.bidUnitaryPrice, @@ -147,7 +147,7 @@ export default class DigitalMarketplace extends arc4.Contract { nonce: nonce, }) - const listing = this.listings(key).value.copy() + const listing = clone(this.listings(key).value) const amountToBePaid = this.quantityPrice(quantity, listing.unitaryPrice.native, asset.decimals) @@ -180,7 +180,7 @@ export default class DigitalMarketplace extends arc4.Contract { nonce: nonce, }) - const listing = this.listings(key).value.copy() + const listing = clone(this.listings(key).value) if (listing.bidder !== new arc4.Address()) { const currentBidDeposit = this.quantityPrice(listing.bid.native, listing.bidUnitaryPrice.native, asset.decimals) itxn.payment({ receiver: listing.bidder.native, amount: currentBidDeposit }).submit() @@ -203,7 +203,7 @@ export default class DigitalMarketplace extends arc4.Contract { bid(owner: arc4.Address, asset: Asset, nonce: arc4.UintN64, bidPay: gtxn.PaymentTxn, quantity: arc4.UintN64, unitaryPrice: arc4.UintN64) { const key = new ListingKey({ owner, asset: new arc4.UintN64(asset.id), nonce }) - const listing = this.listings(key).value.copy() + const listing = clone(this.listings(key).value) if (listing.bidder !== new arc4.Address()) { assert(unitaryPrice.native > listing.bidUnitaryPrice.native) @@ -231,7 +231,7 @@ export default class DigitalMarketplace extends arc4.Contract { acceptBid(asset: Asset, nonce: arc4.UintN64) { const key = new ListingKey({ owner: new arc4.Address(Txn.sender), asset: new arc4.UintN64(asset.id), nonce }) - const listing = this.listings(key).value.copy() + const listing = clone(this.listings(key).value) assert(listing.bidder !== new arc4.Address()) const minQuantity = listing.deposited.native < listing.bid.native ? listing.deposited.native : listing.bid.native diff --git a/examples/voting/contract.algo.ts b/examples/voting/contract.algo.ts index be5c2d6d..5b0faace 100644 --- a/examples/voting/contract.algo.ts +++ b/examples/voting/contract.algo.ts @@ -7,6 +7,7 @@ import { BoxMap, BoxRef, Bytes, + clone, ensureBudget, Global, GlobalState, @@ -207,7 +208,7 @@ export class VotingRoundApp extends arc4.Contract { assert(answerOptionIndex < optionsCount, 'Answer option index invalid') this.incrementVoteInBox(cumulativeOffset + answerOptionIndex) cumulativeOffset += optionsCount - this.votesByAccount(Txn.sender).value = answerIds.copy() + this.votesByAccount(Txn.sender).value = clone(answerIds) this.voterCount.value += 1 } } @@ -228,7 +229,7 @@ export class VotingRoundApp extends arc4.Contract { for (const item of optionCounts) { totalOptions += item.native } - this.optionCounts.value = optionCounts.copy() + this.optionCounts.value = clone(optionCounts) this.totalOptions.value = totalOptions } diff --git a/examples/zk-whitelist/contract.algo.ts b/examples/zk-whitelist/contract.algo.ts index 3e041530..c152c205 100644 --- a/examples/zk-whitelist/contract.algo.ts +++ b/examples/zk-whitelist/contract.algo.ts @@ -6,6 +6,7 @@ import { assert, BigUint, Bytes, + clone, ensureBudget, Global, GlobalState, @@ -88,7 +89,7 @@ export default class ZkWhitelistContract extends arc4.Contract { .applicationCall({ appId: appId, fee: 0, - appArgs: [arc4.methodSelector('verify(byte[32][],byte[32][])bool'), proof.copy(), publicInputs.copy()], + appArgs: [arc4.methodSelector('verify(byte[32][],byte[32][])bool'), clone(proof), clone(publicInputs)], onCompletion: OnCompleteAction.NoOp, }) .submit().lastLog diff --git a/src/impl/clone.ts b/src/impl/clone.ts new file mode 100644 index 00000000..a78ab709 --- /dev/null +++ b/src/impl/clone.ts @@ -0,0 +1,11 @@ +import { getEncoder, toBytes } from './encoded-types' + +export function cloneImpl(typeInfoString: string, value: T): T { + if (value && typeof value === 'object' && 'copy' in value && typeof value.copy === 'function') { + return value.copy() as T + } + const bytes = toBytes(value) + const typeInfo = JSON.parse(typeInfoString) + const encoder = getEncoder(typeInfo) + return encoder(bytes, typeInfo) as T +} diff --git a/src/impl/encoded/arc4-impl.ts b/src/impl/encoded/arc4-impl.ts new file mode 100644 index 00000000..e69de29b diff --git a/src/impl/encoded/array-proxy.ts b/src/impl/encoded/array-proxy.ts new file mode 100644 index 00000000..e69de29b diff --git a/src/impl/encoded/containers.ts b/src/impl/encoded/containers.ts new file mode 100644 index 00000000..e69de29b diff --git a/src/impl/encoded/encoders.ts b/src/impl/encoded/encoders.ts new file mode 100644 index 00000000..e69de29b diff --git a/src/impl/encoded/helpers.ts b/src/impl/encoded/helpers.ts new file mode 100644 index 00000000..e69de29b diff --git a/src/impl/encoded/primitives.ts b/src/impl/encoded/primitives.ts new file mode 100644 index 00000000..e69de29b diff --git a/src/impl/encoded/types.ts b/src/impl/encoded/types.ts new file mode 100644 index 00000000..e69de29b diff --git a/src/impl/encoded/utils.ts b/src/impl/encoded/utils.ts new file mode 100644 index 00000000..e69de29b diff --git a/src/runtime-helpers.ts b/src/runtime-helpers.ts index 607d2d50..c2bce444 100644 --- a/src/runtime-helpers.ts +++ b/src/runtime-helpers.ts @@ -10,6 +10,7 @@ import { nameOfType, type DeliberateAny } from './typescript-helpers' import { flattenAsBytes } from './util' export { attachAbiMetadata } from './abi-metadata' +export { cloneImpl } from './impl/clone' export { emitImpl } from './impl/emit' export * from './impl/encoded-types' export { arc4EncodedLengthImpl, decodeArc4Impl, encodeArc4Impl } from './impl/encoded-types' diff --git a/src/test-transformer/visitors.ts b/src/test-transformer/visitors.ts index d987eb01..2e3dfc06 100644 --- a/src/test-transformer/visitors.ts +++ b/src/test-transformer/visitors.ts @@ -1,6 +1,7 @@ import { LoggingContext, ptypes, SourceLocation, TypeResolver } from '@algorandfoundation/puya-ts' import path from 'path' import ts from 'typescript' +import { CodeError } from '../errors' import type { TypeInfo } from '../impl/encoded-types' import { instanceOfAny } from '../typescript-helpers' import { nodeFactory } from './node-factory' @@ -160,17 +161,21 @@ class ExpressionVisitor { const stubbedFunctionName = this.stubbedFunctionName ?? tryGetStubbedFunctionName(updatedNode, this.helper) this.stubbedFunctionName = undefined let infoArg: TypeInfo | TypeInfo[] | undefined = info - if (isCallingEmit(stubbedFunctionName)) { - infoArg = this.helper.resolveTypeParameters(updatedNode).map(getGenericTypeInfo)[0] + + if ( + isCallingEmit(stubbedFunctionName) || + isCallingEncodeArc4(stubbedFunctionName) || + isCallingArc4EncodedLength(stubbedFunctionName) || + isCallingClone(stubbedFunctionName) + ) { + const sourceLocation = this.helper.sourceLocation(updatedNode) + infoArg = this.helper.resolveTypeParameters(updatedNode).map((t) => getGenericTypeInfo(t, sourceLocation))[0] } else if (isCallingDecodeArc4(stubbedFunctionName)) { - const sourceType = ptypes.ptypeToArc4EncodedType(type, this.helper.sourceLocation(node)) - const sourceTypeInfo = getGenericTypeInfo(sourceType) - const targetTypeInfo = getGenericTypeInfo(type) + const sourceLocation = this.helper.sourceLocation(updatedNode) + const sourceType = ptypes.ptypeToArc4EncodedType(type, sourceLocation) + const sourceTypeInfo = getGenericTypeInfo(sourceType, sourceLocation) + const targetTypeInfo = getGenericTypeInfo(type, sourceLocation) infoArg = [sourceTypeInfo, targetTypeInfo] - } else if (isCallingEncodeArc4(stubbedFunctionName)) { - infoArg = this.helper.resolveTypeParameters(updatedNode).map(getGenericTypeInfo)[0] - } else if (isCallingArc4EncodedLength(stubbedFunctionName)) { - infoArg = this.helper.resolveTypeParameters(updatedNode).map(getGenericTypeInfo)[0] } updatedNode = stubbedFunctionName @@ -378,18 +383,23 @@ const isArc4EncodedType = (type: ptypes.PType): boolean => type === ptypes.arc4StringType || type === ptypes.arc4BooleanType -const getGenericTypeInfo = (type: ptypes.PType): TypeInfo => { +const getGenericTypeInfo = (type: ptypes.PType, sourceLocation?: SourceLocation): TypeInfo => { + if (type instanceof ptypes.UnionPType) { + throw new CodeError( + `${sourceLocation}: Union types are not valid as a variable, parameter, return, or property type. Expression type is ${type.name}`, + ) + } let typeName = type?.name ?? type?.toString() ?? 'unknown' let genericArgs: TypeInfo[] | Record = [] if (instanceOfAny(type, ptypes.LocalStateType, ptypes.GlobalStateType, ptypes.BoxPType)) { - genericArgs.push(getGenericTypeInfo(type.contentType)) + genericArgs.push(getGenericTypeInfo(type.contentType, sourceLocation)) } else if (type instanceof ptypes.BoxMapPType) { - genericArgs.push(getGenericTypeInfo(type.keyType)) - genericArgs.push(getGenericTypeInfo(type.contentType)) + genericArgs.push(getGenericTypeInfo(type.keyType, sourceLocation)) + genericArgs.push(getGenericTypeInfo(type.contentType, sourceLocation)) } else if (instanceOfAny(type, ptypes.StaticArrayType, ptypes.DynamicArrayType, ptypes.ArrayPType)) { const entries = [] - entries.push(['elementType', getGenericTypeInfo(type.elementType)]) + entries.push(['elementType', getGenericTypeInfo(type.elementType, sourceLocation)]) if (instanceOfAny(type, ptypes.StaticArrayType)) { entries.push(['size', { name: type.arraySize.toString() }]) } @@ -402,15 +412,15 @@ const getGenericTypeInfo = (type: ptypes.PType): TypeInfo => { typeName = `Struct<${type.name}>` genericArgs = Object.fromEntries( Object.entries(type.fields) - .map(([key, value]) => [key, getGenericTypeInfo(value)]) + .map(([key, value]) => [key, getGenericTypeInfo(value, sourceLocation)]) .filter((x) => !!x), ) } else if (type instanceof ptypes.ARC4TupleType || type instanceof ptypes.TuplePType) { - genericArgs.push(...type.items.map(getGenericTypeInfo)) + genericArgs.push(...type.items.map((t) => getGenericTypeInfo(t, sourceLocation))) } else if (type instanceof ptypes.ObjectPType) { genericArgs = Object.fromEntries( Object.entries(type.properties) - .map(([key, value]) => [key, getGenericTypeInfo(value)]) + .map(([key, value]) => [key, getGenericTypeInfo(value, sourceLocation)]) .filter((x) => !!x), ) } @@ -429,14 +439,27 @@ const tryGetStubbedFunctionName = (node: ts.CallExpression, helper: VisitorHelpe : (node.expression as ts.Identifier) const functionName = tryGetAlgoTsSymbolName(identityExpression, helper) if (functionName === undefined) return undefined - const stubbedFunctionNames = ['interpretAsArc4', 'decodeArc4', 'encodeArc4', 'emit', 'methodSelector', 'arc4EncodedLength', 'abiCall'] + const stubbedFunctionNames = [ + 'interpretAsArc4', + 'decodeArc4', + 'encodeArc4', + 'emit', + 'methodSelector', + 'arc4EncodedLength', + 'abiCall', + 'clone', + ] if (stubbedFunctionNames.includes(functionName)) { + if (ts.isPropertyAccessExpression(node.expression)) { + const objectName = tryGetAlgoTsSymbolName(node.expression.expression, helper) + return objectName !== undefined ? functionName : undefined + } return functionName } if (['begin', 'next'].includes(functionName) && ts.isPropertyAccessExpression(node.expression)) { - const objectExpression = (node.expression as ts.PropertyAccessExpression).expression + const objectExpression = node.expression.expression const objectName = tryGetAlgoTsSymbolName(objectExpression, helper) if (objectName === 'itxnCompose') return functionName } @@ -458,3 +481,4 @@ const isCallingArc4EncodedLength = (functionName: string | undefined): boolean = const isCallingEmit = (functionName: string | undefined): boolean => 'emit' === (functionName ?? '') const isCallingMethodSelector = (functionName: string | undefined): boolean => 'methodSelector' === (functionName ?? '') const isCallingAbiCall = (functionName: string | undefined): boolean => ['abiCall', 'begin', 'next'].includes(functionName ?? '') +const isCallingClone = (functionName: string | undefined): boolean => 'clone' === (functionName ?? '') diff --git a/tests/arc4/dynamic-array.spec.ts b/tests/arc4/dynamic-array.spec.ts index f511f176..28a85406 100644 --- a/tests/arc4/dynamic-array.spec.ts +++ b/tests/arc4/dynamic-array.spec.ts @@ -1,4 +1,5 @@ import { getABIEncodedValue } from '@algorandfoundation/algokit-utils/types/app-arc56' +import { clone } from '@algorandfoundation/algorand-typescript' import { TestExecutionContext } from '@algorandfoundation/algorand-typescript-testing' import { Address, @@ -50,6 +51,9 @@ const addressDynamicArray = { concatABIValue() { return getABIEncodedValue([...this.nativeValues(), ...this.nativeValues()], this.abiTypeString, {}) }, + clone(array: DynamicArray
) { + return clone(array) + }, } const boolDynamicArray = { abiTypeString: 'bool[]', @@ -71,6 +75,9 @@ const boolDynamicArray = { concatABIValue() { return getABIEncodedValue([...this.nativeValues(), ...this.nativeValues()], this.abiTypeString, {}) }, + clone(array: DynamicArray) { + return clone(array) + }, } const uint256DynamicArray = { abiTypeString: 'uint256[]', @@ -92,6 +99,9 @@ const uint256DynamicArray = { concatABIValue() { return getABIEncodedValue([...this.nativeValues(), ...this.nativeValues()], this.abiTypeString, {}) }, + clone(array: DynamicArray>) { + return clone(array) + }, } const ufixednxmDynamicArray = { abiTypeString: 'ufixed256x16[]', @@ -124,6 +134,9 @@ const ufixednxmDynamicArray = { concatABIValue() { return getABIEncodedValue([...this.nativeValues(), ...this.nativeValues()], this.abiTypeString, {}) }, + clone(array: DynamicArray>) { + return clone(array) + }, } const stringDynamicArray = { abiTypeString: 'string[]', @@ -156,6 +169,9 @@ const stringDynamicArray = { concatABIValue() { return getABIEncodedValue([...this.nativeValues(), ...this.nativeValues()], this.abiTypeString, {}) }, + clone(array: DynamicArray) { + return clone(array) + }, } const boolDynamicArrayOfArray = { abiTypeString: 'bool[][]', @@ -179,6 +195,9 @@ const boolDynamicArrayOfArray = { concatABIValue() { return getABIEncodedValue([...this.nativeValues(), ...this.nativeValues()], this.abiTypeString, {}) }, + clone(array: DynamicArray>) { + return clone(array) + }, } const addressDynamicArrayOfArray = { abiTypeString: 'address[][]', @@ -202,6 +221,9 @@ const addressDynamicArrayOfArray = { concatABIValue() { return getABIEncodedValue([...this.nativeValues(), ...this.nativeValues()], this.abiTypeString, {}) }, + clone(array: DynamicArray>) { + return clone(array) + }, } const uint256DynamicArrayOfArray = { abiTypeString: 'uint256[][]', @@ -225,6 +247,9 @@ const uint256DynamicArrayOfArray = { concatABIValue() { return getABIEncodedValue([...this.nativeValues(), ...this.nativeValues()], this.abiTypeString, {}) }, + clone(array: DynamicArray>>) { + return clone(array) + }, } const uint256DynamicArrayOfStaticArray = { abiTypeString: 'uint256[10][]', @@ -250,6 +275,9 @@ const uint256DynamicArrayOfStaticArray = { concatABIValue() { return getABIEncodedValue([...this.nativeValues(), ...this.nativeValues()], this.abiTypeString, {}) }, + clone(array: DynamicArray, 10>>) { + return clone(array) + }, } const stringDynamicArrayOfArray = { abiTypeString: 'string[][]', @@ -273,6 +301,9 @@ const stringDynamicArrayOfArray = { concatABIValue() { return getABIEncodedValue([...this.nativeValues(), ...this.nativeValues()], this.abiTypeString, {}) }, + clone(array: DynamicArray>) { + return clone(array) + }, } const stringDynamicArrayOfArrayOfArray = { abiTypeString: 'string[][][]', @@ -301,6 +332,9 @@ const stringDynamicArrayOfArrayOfArray = { concatABIValue() { return getABIEncodedValue([...this.nativeValues(), ...this.nativeValues()], this.abiTypeString, {}) }, + clone(array: DynamicArray>>) { + return clone(array) + }, } const tupleDynamicArray = { abiTypeString: '(string[],(string[],string,uint256,address),bool,uint256[3])[]', @@ -354,6 +388,11 @@ const tupleDynamicArray = { concatABIValue() { return getABIEncodedValue([...this.nativeValues(), ...this.nativeValues()], this.abiTypeString, {}) }, + clone( + array: DynamicArray, Tuple<[DynamicArray, Str, UintN<256>, Address]>, Bool, StaticArray, 3>]>>, + ) { + return clone(array) + }, } class Swapped extends Struct<{ b: UintN<256> @@ -411,6 +450,9 @@ const structDynamicArray = { concatABIValue() { return getABIEncodedValue([...this.nativeValues(), ...this.nativeValues()], this.abiTypeString, {}) }, + clone(array: DynamicArray) { + return clone(array) + }, } const testDataArray = [ addressDynamicArray, @@ -433,7 +475,7 @@ describe('arc4.DynamicArray', () => { ctx.reset() }) - test.each(testDataArray)('should be able to get bytes representation', (data) => { + test.each([testDataArray])('should be able to get bytes representation', (data) => { const sdkResult = getABIEncodedValue(data.nativeValues(), data.abiTypeString, {}) const result = data.array().bytes expect(result).toEqual(sdkResult) @@ -442,7 +484,7 @@ describe('arc4.DynamicArray', () => { test.each(testDataArray)('copy dynamic array', (data) => { const sdkResult = getABIEncodedValue(data.nativeValues(), data.abiTypeString, {}) const original = data.array() - const copy = original.copy() + const copy = data.clone(original as DeliberateAny) const result = copy.bytes expect(copy.length).toEqual(original.length) expect(result).toEqual(sdkResult) @@ -475,7 +517,7 @@ describe('arc4.DynamicArray', () => { nativeValuesCopy[0] = nativeTemp const dynamicArray = data.array() - const dynamicArrayCopy = dynamicArray.copy() + const dynamicArrayCopy = data.clone(dynamicArray as DeliberateAny) const arrayTemp = dynamicArrayCopy.at(-1) dynamicArrayCopy[dynamicArrayCopy.length - 1] = dynamicArrayCopy[0] dynamicArrayCopy[0] = arrayTemp @@ -501,7 +543,7 @@ describe('arc4.DynamicArray', () => { nativeValuesCopy.push(nativeValues[0]) const dynamicArray = data.array() - const dynamicArrayCopy = dynamicArray.copy() + const dynamicArrayCopy = data.clone(dynamicArray as DeliberateAny) dynamicArrayCopy.push(dynamicArray.at(-1) as never, dynamicArray[0] as never) const sdkResult = getABIEncodedValue(nativeValuesCopy, data.abiTypeString, {}) @@ -536,7 +578,7 @@ describe('arc4.DynamicArray', () => { const nativeValue2 = nativeValuesCopy.pop() const dynamicArray = data.array() - const dynamicArrayCopy = dynamicArray.copy() + const dynamicArrayCopy = data.clone(dynamicArray as DeliberateAny) const value1 = dynamicArrayCopy.pop() const value2 = dynamicArrayCopy.pop() diff --git a/tests/arc4/encode-decode-arc4.spec.ts b/tests/arc4/encode-decode-arc4.spec.ts index d81b4363..d2e00266 100644 --- a/tests/arc4/encode-decode-arc4.spec.ts +++ b/tests/arc4/encode-decode-arc4.spec.ts @@ -123,7 +123,12 @@ const testData = [ }, { nativeValues() { - return { b: nativeNumber, c: nativeBool, d: nativeString, a: [nativeNumber, nativeBool, nativeBool] } + return { b: nativeNumber, c: nativeBool, d: nativeString, a: [nativeNumber, nativeBool, nativeBool] } as { + b: uint64 + c: boolean + d: string + a: [uint64, boolean, boolean] + } }, abiValues() { return { b: abiUint64, c: abiBool, d: abiString, a: new Tuple<[UintN<64>, Bool, Bool]>(abiUint64, abiBool, abiBool) } diff --git a/tests/arc4/static-array.spec.ts b/tests/arc4/static-array.spec.ts index efcdea70..3f09551c 100644 --- a/tests/arc4/static-array.spec.ts +++ b/tests/arc4/static-array.spec.ts @@ -1,5 +1,5 @@ import { getABIEncodedValue } from '@algorandfoundation/algokit-utils/types/app-arc56' -import { Bytes } from '@algorandfoundation/algorand-typescript' +import { Bytes, clone } from '@algorandfoundation/algorand-typescript' import { TestExecutionContext } from '@algorandfoundation/algorand-typescript-testing' import { Address, @@ -51,7 +51,11 @@ const addressStaticArray = { concatABIValue() { return getABIEncodedValue([...this.nativeValues(), ...this.nativeValues()], 'address[]', {}) }, + clone(original: StaticArray) { + return clone(original) + }, } + const boolStaticArray = { abiTypeString: 'bool[10]', nativeValues() { @@ -72,7 +76,11 @@ const boolStaticArray = { concatABIValue() { return getABIEncodedValue([...this.nativeValues(), ...this.nativeValues()], 'bool[]', {}) }, + clone(original: StaticArray) { + return clone(original) + }, } + const uint256StaticArray = { abiTypeString: 'uint256[10]', nativeValues() { @@ -93,7 +101,11 @@ const uint256StaticArray = { concatABIValue() { return getABIEncodedValue([...this.nativeValues(), ...this.nativeValues()], 'uint256[]', {}) }, + clone(original: StaticArray, 10>) { + return clone(original) + }, } + const ufixednxmStaticArray = { abiTypeString: 'ufixed256x16[10]', nativeValues() { @@ -125,7 +137,11 @@ const ufixednxmStaticArray = { concatABIValue() { return getABIEncodedValue([...this.nativeValues(), ...this.nativeValues()], 'ufixed256x16[]', {}) }, + clone(original: StaticArray, 10>) { + return clone(original) + }, } + const stringStaticArray = { abiTypeString: 'string[10]', nativeValues() { @@ -157,7 +173,11 @@ const stringStaticArray = { concatABIValue() { return getABIEncodedValue([...this.nativeValues(), ...this.nativeValues()], 'string[]', {}) }, + clone(original: StaticArray) { + return clone(original) + }, } + const addressStaticArrayOfArray = { abiTypeString: 'address[10][2]', nativeValues() { @@ -180,7 +200,11 @@ const addressStaticArrayOfArray = { concatABIValue() { return getABIEncodedValue([...this.nativeValues(), ...this.nativeValues()], 'address[10][]', {}) }, + clone(original: StaticArray, 2>) { + return clone(original) + }, } + const boolStaticArrayOfArray = { abiTypeString: 'bool[10][2]', nativeValues() { @@ -203,7 +227,11 @@ const boolStaticArrayOfArray = { concatABIValue() { return getABIEncodedValue([...this.nativeValues(), ...this.nativeValues()], 'bool[10][]', {}) }, + clone(original: StaticArray, 2>) { + return clone(original) + }, } + const uint256StaticArrayOfArray = { abiTypeString: 'uint256[10][2]', nativeValues() { @@ -226,7 +254,11 @@ const uint256StaticArrayOfArray = { concatABIValue() { return getABIEncodedValue([...this.nativeValues(), ...this.nativeValues()], 'uint256[10][]', {}) }, + clone(original: StaticArray, 10>, 2>) { + return clone(original) + }, } + const uint256StaticArrayOfDynamicArray = { abiTypeString: 'uint256[][2]', nativeValues() { @@ -247,7 +279,11 @@ const uint256StaticArrayOfDynamicArray = { concatABIValue() { return getABIEncodedValue([...this.nativeValues(), ...this.nativeValues()], 'uint256[][]', {}) }, + clone(original: StaticArray>, 2>) { + return clone(original) + }, } + const stringStaticArrayOfArray = { abiTypeString: 'string[10][2]', nativeValues() { @@ -270,7 +306,11 @@ const stringStaticArrayOfArray = { concatABIValue() { return getABIEncodedValue([...this.nativeValues(), ...this.nativeValues()], 'string[10][]', {}) }, + clone(original: StaticArray, 2>) { + return clone(original) + }, } + const stringStaticArrayOfArrayOfArray = { abiTypeString: 'string[10][3][2]', nativeValues() { @@ -299,7 +339,11 @@ const stringStaticArrayOfArrayOfArray = { concatABIValue() { return getABIEncodedValue([...this.nativeValues(), ...this.nativeValues()], 'string[10][3][]', {}) }, + clone(original: StaticArray, 3>, 2>) { + return clone(original) + }, } + const tupleStaticArray = { abiTypeString: '(string[],(string[],string,uint256,address),bool,uint256[3])[2]', nativeValues() { @@ -353,7 +397,16 @@ const tupleStaticArray = { {}, ) }, + clone( + original: StaticArray< + Tuple<[DynamicArray, Tuple<[DynamicArray, Str, UintN<256>, Address]>, Bool, StaticArray, 3>]>, + 2 + >, + ) { + return clone(original) + }, } + class Swapped extends Struct<{ b: UintN<256> c: Bool @@ -414,6 +467,9 @@ const structStaticArray = { {}, ) }, + clone(original: StaticArray) { + return clone(original) + }, } describe('arc4.StaticArray', () => { @@ -459,7 +515,7 @@ describe('arc4.StaticArray', () => { ])('copy static array', (data) => { const sdkResult = getABIEncodedValue(data.nativeValues(), data.abiTypeString, {}) const original = data.array() - const copy = original.copy() + const copy = data.clone(original as DeliberateAny) const result = copy.bytes expect(copy.length).toEqual(original.length) expect(result).toEqual(sdkResult) @@ -534,7 +590,7 @@ describe('arc4.StaticArray', () => { nativeValuesCopy[0] = nativeTemp const staticArray = data.array() - const staticArrayCopy = staticArray.copy() + const staticArrayCopy = data.clone(staticArray as DeliberateAny) const arrayTemp = staticArrayCopy.at(-1) staticArrayCopy[staticArrayCopy.length - 1] = staticArrayCopy[0] staticArrayCopy[0] = arrayTemp @@ -614,7 +670,7 @@ describe('arc4.StaticArray', () => { const sdkEncodedBytes = getABIEncodedValue(data.nativeValues(), data.abiTypeString, {}) const staticArray = data.create(Bytes(sdkEncodedBytes)) - const staticArrayCopy = staticArray.copy() + const staticArrayCopy = data.clone(staticArray as DeliberateAny) const arrayTemp = staticArrayCopy.at(-1) staticArrayCopy[staticArrayCopy.length - 1] = staticArrayCopy[0] staticArrayCopy[0] = arrayTemp diff --git a/tests/artifacts/arc4-abi-method/contract.algo.ts b/tests/artifacts/arc4-abi-method/contract.algo.ts index 72fed0cb..ef4ea35d 100644 --- a/tests/artifacts/arc4-abi-method/contract.algo.ts +++ b/tests/artifacts/arc4-abi-method/contract.algo.ts @@ -2,7 +2,7 @@ type UInt8Array = arc4.DynamicArray type MyAlias = arc4.UintN<128> import type { Account, Application, Asset } from '@algorandfoundation/algorand-typescript' -import { arc4, assert, gtxn, op, Txn } from '@algorandfoundation/algorand-typescript' +import { arc4, assert, clone, gtxn, op, Txn } from '@algorandfoundation/algorand-typescript' export class AnotherStruct extends arc4.Struct<{ one: arc4.UintN64 @@ -99,6 +99,6 @@ export class SignaturesContract extends arc4.Contract { assert(acc.balance === acc.minBalance + 1234) assert(five[0].native === 5) - return [struct1.anotherStruct.copy(), struct1.copy()] + return [clone(struct1.anotherStruct), clone(struct1)] } } diff --git a/tests/artifacts/arc4-primitive-ops/contract.algo.ts b/tests/artifacts/arc4-primitive-ops/contract.algo.ts index 7922b5d0..01e3e310 100644 --- a/tests/artifacts/arc4-primitive-ops/contract.algo.ts +++ b/tests/artifacts/arc4-primitive-ops/contract.algo.ts @@ -1,5 +1,5 @@ import type { bytes } from '@algorandfoundation/algorand-typescript' -import { arc4, BigUint, emit } from '@algorandfoundation/algorand-typescript' +import { arc4, BigUint, clone, emit } from '@algorandfoundation/algorand-typescript' import type { Bool, UFixedNxM } from '@algorandfoundation/algorand-typescript/arc4' import { Byte, Contract, interpretAsArc4, Str, UintN } from '@algorandfoundation/algorand-typescript/arc4' @@ -372,7 +372,7 @@ export class Arc4PrimitiveOpsContract extends Contract { const arc4_t = interpretAsArc4>(t) emit(new SwappedArc4({ m, n, o, p, q, r: arc4_r, s: arc4_s, t: arc4_t })) - emit('Swapped', a, b, c, d, e, f, g, h, m, n, o, p, q, arc4_r.copy(), arc4_s.copy(), arc4_t) + emit('Swapped', a, b, c, d, e, f, g, h, m, n, o, p, q, clone(arc4_r), clone(arc4_s), arc4_t) emit( 'Swapped(string,uint512,uint64,byte[],uint64,bool,byte[],string,uint64,uint256,ufixed32x8,ufixed256x16,bool,uint8[3],uint16[],(uint32,uint64,string))', a, @@ -388,8 +388,8 @@ export class Arc4PrimitiveOpsContract extends Contract { o, p, q, - arc4_r.copy(), - arc4_s.copy(), + clone(arc4_r), + clone(arc4_s), arc4_t, ) } diff --git a/tests/references/box-map.spec.ts b/tests/references/box-map.spec.ts index 4f04c488..0317d301 100644 --- a/tests/references/box-map.spec.ts +++ b/tests/references/box-map.spec.ts @@ -1,5 +1,5 @@ import type { biguint, bytes, uint64 } from '@algorandfoundation/algorand-typescript' -import { BigUint, BoxMap, Bytes, op, Uint64 } from '@algorandfoundation/algorand-typescript' +import { BigUint, BoxMap, Bytes, clone, op, Uint64 } from '@algorandfoundation/algorand-typescript' import { TestExecutionContext } from '@algorandfoundation/algorand-typescript-testing' import type { Bool, DynamicBytes, StaticArray, Tuple, UintN16 } from '@algorandfoundation/algorand-typescript/arc4' import { ARC4Encoded, DynamicArray, interpretAsArc4, Str, Struct, UintN64, UintN8 } from '@algorandfoundation/algorand-typescript/arc4' @@ -261,7 +261,7 @@ describe('BoxMap', () => { expect(boxMap(key).value.at(-1).native).toEqual(300) // setting bytes value through op should be reflected in the box value. - const copy = boxMap(key).value.copy() + const copy = clone(boxMap(key).value) copy[2] = new UintN64(400) expect(boxMap(key).value.at(-1).native).toEqual(300) diff --git a/tests/references/box.spec.ts b/tests/references/box.spec.ts index a481db68..b9205de2 100644 --- a/tests/references/box.spec.ts +++ b/tests/references/box.spec.ts @@ -1,5 +1,5 @@ import type { biguint, bytes, uint64 } from '@algorandfoundation/algorand-typescript' -import { BigUint, Box, Bytes, op, Uint64 } from '@algorandfoundation/algorand-typescript' +import { BigUint, Box, Bytes, clone, op, Uint64 } from '@algorandfoundation/algorand-typescript' import { TestExecutionContext } from '@algorandfoundation/algorand-typescript-testing' import type { DynamicBytes, UintN16 } from '@algorandfoundation/algorand-typescript/arc4' import { @@ -259,7 +259,7 @@ describe('Box', () => { expect(box.value.at(-1).native).toEqual(300) // setting bytes value through op should be reflected in the box value. - const copy = box.value.copy() + const copy = clone(box.value) copy[2] = new UintN64(400) expect(box.value.at(-1).native).toEqual(300) From 9d99af0219775d9dbc5adc39cd5b05d336d8efd8 Mon Sep 17 00:00:00 2001 From: Bobby Lat Date: Tue, 10 Jun 2025 17:04:30 +0800 Subject: [PATCH 16/68] feat: implement mutable object --- src/impl/encoded-types/encoded-types.ts | 16 +- src/impl/mutable-object.ts | 23 ++ src/runtime-helpers.ts | 2 +- src/test-transformer/node-factory.ts | 2 +- src/test-transformer/visitors.ts | 14 +- tests/mutable-object.spec.ts | 314 ++++++++++++++++++++++++ 6 files changed, 361 insertions(+), 10 deletions(-) create mode 100644 src/impl/mutable-object.ts create mode 100644 tests/mutable-object.spec.ts diff --git a/src/impl/encoded-types/encoded-types.ts b/src/impl/encoded-types/encoded-types.ts index 1c9c12c8..c785fdf7 100644 --- a/src/impl/encoded-types/encoded-types.ts +++ b/src/impl/encoded-types/encoded-types.ts @@ -37,6 +37,7 @@ import { uint8ArrayToNumber, } from '../../util' import { BytesBackedCls, Uint64BackedCls } from '../base' +import { MutableObjectImpl } from '../mutable-object' import type { StubBytesCompat } from '../primitives' import { BigUintCls, Bytes, BytesCls, getUint8Array, isBytes, Uint64Cls } from '../primitives' import { Account, AccountCls, ApplicationCls, AssetCls } from '../reference' @@ -1065,10 +1066,11 @@ export const getArc4Encoded = (value: DeliberateAny, sourceTypeInfoString?: stri const result = Object.values(value).reduce((acc: ARC4Encoded[], cur: DeliberateAny) => { return acc.concat(getArc4Encoded(cur)) }, []) - const genericArgs: TypeInfo[] = result.map((x) => (x as DeliberateAny).typeInfo) + const genericArgs: TypeInfo[] = value instanceof MutableObjectImpl ? [] : result.map((x) => (x as DeliberateAny).typeInfo) const typeInfo = { name: `Struct<${value.constructor.name}>`, - genericArgs: Object.fromEntries(Object.keys(value).map((x, i) => [x, genericArgs[i]])), + genericArgs: + value instanceof MutableObjectImpl ? value.genericArgs : Object.fromEntries(Object.keys(value).map((x, i) => [x, genericArgs[i]])), } return new StructImpl(typeInfo, Object.fromEntries(Object.keys(value).map((x, i) => [x, result[i]]))) } @@ -1101,6 +1103,15 @@ export const toBytes = (val: unknown): bytes => { throw new InternalError(`Invalid type for bytes: ${nameOfType(val)}`) } +const mutableObjectFromBytes: fromBytes> = ( + value: StubBytesCompat | Uint8Array, + typeInfo: string | TypeInfo, + prefix: 'none' | 'log' = 'none', +) => { + const s = StructImpl.fromBytesImpl(value, typeInfo, prefix) as StructImpl + return new MutableObjectImpl(typeInfo, s.native) +} + export const getEncoder = (typeInfo: TypeInfo): fromBytes => { const encoders: Record> = { account: AccountCls.fromBytes, @@ -1126,6 +1137,7 @@ export const getEncoder = (typeInfo: TypeInfo): fromBytes => { DynamicBytes: DynamicBytesImpl.fromBytesImpl, 'StaticBytes<.*>': StaticBytesImpl.fromBytesImpl, object: StructImpl.fromBytesImpl, + 'MutableObject(<.*>)?': mutableObjectFromBytes, } const encoder = Object.entries(encoders).find(([k, _]) => new RegExp(`^${k}$`, 'i').test(typeInfo.name))?.[1] diff --git a/src/impl/mutable-object.ts b/src/impl/mutable-object.ts new file mode 100644 index 00000000..2b7da32a --- /dev/null +++ b/src/impl/mutable-object.ts @@ -0,0 +1,23 @@ +import { MutableObject } from '@algorandfoundation/algorand-typescript' +import type { DeliberateAny } from '../typescript-helpers' +import type { TypeInfo } from './encoded-types' + +type MutableObjectConstraint = Record + +export class MutableObjectImpl extends (MutableObject as DeliberateAny) { + typeInfo: TypeInfo + genericArgs: Record + + constructor(typeInfo: TypeInfo | string, initial: T) { + super(initial) + this.typeInfo = typeof typeInfo === 'string' ? JSON.parse(typeInfo) : typeInfo + this.genericArgs = this.typeInfo.genericArgs as Record + Object.keys(this.genericArgs).forEach((key) => { + Object.defineProperty(this, key, { + value: initial[key], + writable: true, + enumerable: true, + }) + }) + } +} diff --git a/src/runtime-helpers.ts b/src/runtime-helpers.ts index c2bce444..6225b2d7 100644 --- a/src/runtime-helpers.ts +++ b/src/runtime-helpers.ts @@ -13,7 +13,7 @@ export { attachAbiMetadata } from './abi-metadata' export { cloneImpl } from './impl/clone' export { emitImpl } from './impl/emit' export * from './impl/encoded-types' -export { arc4EncodedLengthImpl, decodeArc4Impl, encodeArc4Impl } from './impl/encoded-types' +export { MutableObjectImpl } from './impl/mutable-object' export function switchableValue(x: unknown): bigint | string | boolean { if (typeof x === 'boolean') return x diff --git a/src/test-transformer/node-factory.ts b/src/test-transformer/node-factory.ts index 9d199d2c..6e9dae10 100644 --- a/src/test-transformer/node-factory.ts +++ b/src/test-transformer/node-factory.ts @@ -84,7 +84,7 @@ export const nodeFactory = { ) }, - instantiateARC4EncodedType(node: ts.NewExpression, typeInfo?: TypeInfo) { + instantiateEncodedType(node: ts.NewExpression, typeInfo?: TypeInfo) { const infoString = JSON.stringify(typeInfo) const classIdentifier = node.expression.getText().replace('arc4.', '') return factory.createNewExpression( diff --git a/src/test-transformer/visitors.ts b/src/test-transformer/visitors.ts index 2e3dfc06..f6659280 100644 --- a/src/test-transformer/visitors.ts +++ b/src/test-transformer/visitors.ts @@ -147,13 +147,13 @@ class ExpressionVisitor { const isGeneric = isGenericType(type) const needsToCaptureTypeInfo = isGeneric && isStateOrBoxType(type) - const isArc4Encoded = isArc4EncodedType(type) + const isArc4Encoded = isEncodedType(type) const info = isGeneric || isArc4Encoded ? getGenericTypeInfo(type) : undefined let updatedNode = node if (ts.isNewExpression(updatedNode)) { - if (isArc4EncodedType(type)) { - updatedNode = nodeFactory.instantiateARC4EncodedType(updatedNode, info) + if (isEncodedType(type)) { + updatedNode = nodeFactory.instantiateEncodedType(updatedNode, info) } } @@ -365,12 +365,13 @@ const isGenericType = (type: ptypes.PType): boolean => ptypes.UFixedNxMType, ptypes.UintNType, ptypes.TuplePType, + ptypes.MutableObjectType, ) const isStateOrBoxType = (type: ptypes.PType): boolean => instanceOfAny(type, ptypes.BoxMapPType, ptypes.BoxPType, ptypes.GlobalStateType, ptypes.LocalStateType) -const isArc4EncodedType = (type: ptypes.PType): boolean => +const isEncodedType = (type: ptypes.PType): boolean => instanceOfAny( type, ptypes.ARC4StructType, @@ -379,6 +380,7 @@ const isArc4EncodedType = (type: ptypes.PType): boolean => ptypes.StaticArrayType, ptypes.UFixedNxMType, ptypes.UintNType, + ptypes.MutableObjectType, ) || type === ptypes.arc4StringType || type === ptypes.arc4BooleanType @@ -408,8 +410,8 @@ const getGenericTypeInfo = (type: ptypes.PType, sourceLocation?: SourceLocation) genericArgs = { n: { name: type.n.toString() }, m: { name: type.m.toString() } } } else if (type instanceof ptypes.UintNType) { genericArgs.push({ name: type.n.toString() }) - } else if (type instanceof ptypes.ARC4StructType) { - typeName = `Struct<${type.name}>` + } else if (type instanceof ptypes.ARC4StructType || type instanceof ptypes.MutableObjectType) { + typeName = type instanceof ptypes.ARC4StructType ? `Struct<${type.name}>` : `MutableObject<${type.name}>` genericArgs = Object.fromEntries( Object.entries(type.fields) .map(([key, value]) => [key, getGenericTypeInfo(value, sourceLocation)]) diff --git a/tests/mutable-object.spec.ts b/tests/mutable-object.spec.ts new file mode 100644 index 00000000..9eb99692 --- /dev/null +++ b/tests/mutable-object.spec.ts @@ -0,0 +1,314 @@ +import { clone, MutableObject } from '@algorandfoundation/algorand-typescript' +import { Bool, DynamicArray, StaticArray, Str, Tuple, UintN } from '@algorandfoundation/algorand-typescript/arc4' +import { encodingUtil } from '@algorandfoundation/puya-ts' +import { describe, expect, it, test } from 'vitest' +import { AccountCls } from '../src/impl/reference' +import type { DeliberateAny } from '../src/typescript-helpers' + +const nativeString = 'hello' +const nativeNumber = 42 +const nativeBool = true + +const abiString = new Str('hello') +const abiUint64 = new UintN<64>(42) +const abiBool = new Bool(true) + +class Swapped1 extends MutableObject<{ + b: UintN<64> + c: Bool + d: Str + a: Tuple<[UintN<64>, Bool, Bool]> +}> {} + +class Swapped2 extends MutableObject<{ + b: UintN<64> + c: Bool + d: Str + a: Tuple<[Tuple<[UintN<64>, Bool, Bool]>, Tuple<[UintN<64>, Bool, Bool]>]> +}> {} + +class Swapped3 extends MutableObject<{ + b: UintN<64> + c: Bool + d: Str + a: Tuple<[DynamicArray, DynamicArray, Str, UintN<64>, Bool, StaticArray, 3>]> +}> {} + +class Swapped4 extends MutableObject<{ + b: UintN<64> + c: Bool + d: Str + a: Tuple<[Tuple<[Bool, DynamicArray, Str]>, UintN<64>, StaticArray, 3>]> +}> {} + +class Swapped5 extends MutableObject<{ + b: UintN<64> + c: Bool + d: Str + a: Tuple<[Tuple<[Bool, DynamicArray, Str]>, Tuple<[UintN<64>, StaticArray, 3>]>]> +}> {} + +class Swapped6 extends MutableObject<{ + b: UintN<64> + c: Bool + d: Str + a: Tuple<[Tuple<[Bool, Tuple<[DynamicArray, Str]>]>, Tuple<[UintN<64>, StaticArray, 3>]>]> +}> {} + +const testData = [ + { + abiTypeString: '(uint64,bool,string,(uint64,bool,bool))', + nativeValues() { + return [nativeNumber, nativeBool, nativeString, [nativeNumber, nativeBool, nativeBool]] + }, + abiValues() { + return { b: abiUint64, c: abiBool, d: abiString, a: new Tuple(abiUint64, abiBool, abiBool) } as Swapped1 + }, + create() { + return new Swapped1(this.abiValues()) + }, + clone(obj: Swapped1) { + return clone(obj) + }, + }, + { + abiTypeString: '(uint64,bool,string,((uint64,bool,bool),(uint64,bool,bool)))', + nativeValues() { + return [ + nativeNumber, + nativeBool, + nativeString, + [ + [nativeNumber, nativeBool, nativeBool], + [nativeNumber, nativeBool, nativeBool], + ], + ] + }, + abiValues() { + return { + b: abiUint64, + c: abiBool, + d: abiString, + a: new Tuple(new Tuple(abiUint64, abiBool, abiBool), new Tuple(abiUint64, abiBool, abiBool)), + } as Swapped2 + }, + create() { + return new Swapped2(this.abiValues()) + }, + clone(obj: Swapped2) { + return clone(obj) + }, + }, + { + abiTypeString: '(uint64,bool,string,(string[],string[],string,uint64,bool,uint64[3]))', + nativeValues() { + return [ + nativeNumber, + nativeBool, + nativeString, + [ + [nativeString, nativeString], + [nativeString, nativeString], + nativeString, + nativeNumber, + nativeBool, + [nativeNumber, nativeNumber, nativeNumber], + ], + ] + }, + abiValues() { + return { + b: abiUint64, + c: abiBool, + d: abiString, + a: new Tuple( + new DynamicArray(abiString, abiString), + new DynamicArray(abiString, abiString), + abiString, + abiUint64, + abiBool, + new StaticArray, 3>(abiUint64, abiUint64, abiUint64), + ), + } as Swapped3 + }, + create() { + return new Swapped3(this.abiValues()) + }, + clone(obj: Swapped3) { + return clone(obj) + }, + }, + { + abiTypeString: '(uint64,bool,string,((bool,string[],string),uint64,uint64[3]))', + nativeValues() { + return [ + nativeNumber, + nativeBool, + nativeString, + [[nativeBool, [nativeString, nativeString], nativeString], nativeNumber, [nativeNumber, nativeNumber, nativeNumber]], + ] + }, + abiValues() { + return { + b: abiUint64, + c: abiBool, + d: abiString, + a: new Tuple( + new Tuple(abiBool, new DynamicArray(abiString, abiString), abiString), + abiUint64, + new StaticArray, 3>(abiUint64, abiUint64, abiUint64), + ), + } as Swapped4 + }, + create() { + return new Swapped4(this.abiValues()) + }, + clone(obj: Swapped4) { + return clone(obj) + }, + }, + { + abiTypeString: '(uint64,bool,string,((bool,string[],string),(uint64,uint64[3])))', + nativeValues() { + return [ + nativeNumber, + nativeBool, + nativeString, + [ + [nativeBool, [nativeString, nativeString], nativeString], + [nativeNumber, [nativeNumber, nativeNumber, nativeNumber]], + ], + ] + }, + abiValues() { + return { + b: abiUint64, + c: abiBool, + d: abiString, + a: new Tuple( + new Tuple(abiBool, new DynamicArray(abiString, abiString), abiString), + new Tuple(abiUint64, new StaticArray, 3>(abiUint64, abiUint64, abiUint64)), + ), + } as Swapped5 + }, + create() { + return new Swapped5(this.abiValues()) + }, + clone(obj: Swapped5) { + return clone(obj) + }, + }, + { + abiTypeString: '(uint64,bool,string,((bool,(string[],string)),(uint64,uint64[3])))', + nativeValues() { + return [ + nativeNumber, + nativeBool, + nativeString, + [ + [nativeBool, [[nativeString, nativeString], nativeString]], + [nativeNumber, [nativeNumber, nativeNumber, nativeNumber]], + ], + ] + }, + abiValues() { + return { + b: abiUint64, + c: abiBool, + d: abiString, + a: new Tuple( + new Tuple(abiBool, new Tuple(new DynamicArray(abiString, abiString), abiString)), + new Tuple(abiUint64, new StaticArray, 3>(abiUint64, abiUint64, abiUint64)), + ), + } as Swapped6 + }, + create() { + return new Swapped6(this.abiValues()) + }, + clone(obj: Swapped6) { + return clone(obj) + }, + }, +] + +describe('mutable object', async () => { + test.each(testData)('create mutable object', async (data) => { + const nativeValues = data.nativeValues() + const result = data.create() + + let i = 0 + compareARC4AndABIValue(result.b, nativeValues[i++]) + compareARC4AndABIValue(result.c, nativeValues[i++]) + compareARC4AndABIValue(result.d, nativeValues[i++]) + compareARC4AndABIValue(result.a, nativeValues[i++]) + }) + + test.each(testData)('clone mutable object', async (data) => { + const result = data.create() + const result2 = data.clone(result as DeliberateAny) + + compare(result.b, result2.b) + compare(result.c, result2.c) + compare(result.d, result2.d) + compare(result.a, result2.a) + }) + + it('set item in mutable object', async () => { + const data = testData[5] + + const nativeValues = data.nativeValues() as DeliberateAny + nativeValues[0] = 43 + nativeValues[2] = 'world' + nativeValues[3][0][1][0][1] = 'hello, world' + nativeValues[3][0][1][0].push('test') + nativeValues[3][1][1][0] = 24 + + const abiValues = data.create() as Swapped6 + abiValues.b = new UintN<64>(43) + abiValues.d = new Str('world') + abiValues.a.at(0).at(1).at(0)[1] = new Str('hello, world') + abiValues.a.at(0).at(1).at(0).push(new Str('test')) + abiValues.a.at(1).at(1)[0] = new UintN<64>(24) + + let i = 0 + compareARC4AndABIValue(abiValues.b, nativeValues[i++]) + compareARC4AndABIValue(abiValues.c, nativeValues[i++]) + compareARC4AndABIValue(abiValues.d, nativeValues[i++]) + compareARC4AndABIValue(abiValues.a, nativeValues[i++]) + }) +}) + +const compareARC4AndABIValue = (arc4Value: DeliberateAny, nativeValue: DeliberateAny) => { + if (arc4Value instanceof StaticArray || arc4Value instanceof DynamicArray) { + for (let i = 0; i < arc4Value.length; i++) { + compareARC4AndABIValue(arc4Value[i], nativeValue[i]) + } + } else if (arc4Value instanceof Tuple) { + const tupleValues = arc4Value.native + for (let i = 0; i < arc4Value.length; i++) { + compareARC4AndABIValue(tupleValues[i], nativeValue[i]) + } + } else if (arc4Value.native !== undefined) { + if (arc4Value.native instanceof AccountCls) { + expect(arc4Value.native.bytes).toEqual(nativeValue) + } else { + expect(arc4Value.native).toEqual(nativeValue) + } + } else { + expect(arc4Value.bytes).toEqual(encodingUtil.bigIntToUint8Array(arc4Value, arc4Value.bytes.length)) + } +} + +const compare = (value1: DeliberateAny, value2: DeliberateAny) => { + if (value1 instanceof StaticArray || value1 instanceof DynamicArray) { + for (let i = 0; i < value1.length; i++) { + compare(value1[i], value2[i]) + } + } else if (value1 instanceof Tuple) { + for (let i = 0; i < value1.length; i++) { + compare(value1.at(i), value2.at(i)) + } + } else { + expect(value1.bytes).toEqual(value2.bytes) + } +} From 6a905f6bcc737633b22f67e2439f8cb595ead703 Mon Sep 17 00:00:00 2001 From: Bobby Lat Date: Fri, 27 Jun 2025 12:59:13 +0700 Subject: [PATCH 17/68] implement FixedArray and increase test coverage --- .env.template | 1 + package-lock.json | 253 ++++++++ package.json | 3 +- src/abi-metadata.ts | 37 +- src/impl/clone.ts | 2 +- src/impl/encoded-types/array-proxy.ts | 2 + src/impl/encoded-types/encoded-types.ts | 219 ++++++- src/impl/encoded-types/helpers.ts | 15 +- src/impl/encoded-types/index.ts | 2 + src/impl/encoded-types/utils.ts | 34 +- src/impl/encoded/arc4-impl.ts | 0 src/impl/encoded/array-proxy.ts | 0 src/impl/encoded/containers.ts | 0 src/impl/encoded/encoders.ts | 0 src/impl/encoded/helpers.ts | 0 src/impl/encoded/primitives.ts | 0 src/impl/encoded/types.ts | 0 src/impl/encoded/utils.ts | 0 src/impl/index.ts | 1 - src/impl/log.ts | 2 +- src/impl/mutable-array.ts | 122 ---- src/impl/mutable-object.ts | 23 - src/internal/index.ts | 1 - src/runtime-helpers.ts | 1 - src/test-transformer/visitors.ts | 57 +- tests/arc4/dynamic-array.spec.ts | 2 +- tests/arc4/emit.spec.ts | 2 +- tests/arc4/static-array.spec.ts | 2 +- .../arc4-primitive-ops/contract.algo.ts | 8 +- tests/artifacts/box-contract/contract.algo.ts | 2 +- tests/artifacts/state-ops/contract.algo.ts | 5 +- tests/fixed-array.spec.ts | 429 +++++++++++++ tests/global-state-arc4-values.spec.ts | 6 +- tests/local-state-arc4-values.spec.ts | 6 +- tests/mutable-object.spec.ts | 314 ---------- tests/native-mutable-array.spec.ts | 307 +++++++++ tests/native-mutable-object.spec.ts | 583 +++++++++++++++++ tests/native-readonly-array.spec.ts | 326 ++++++++++ tests/native-readonly-object.spec.ts | 593 ++++++++++++++++++ ...-array.spec.ts => reference-array.spec.ts} | 46 +- tests/references/box-map.spec.ts | 84 ++- tests/references/box.spec.ts | 69 +- tests/test-fixture.ts | 1 + vitest.setup.ts | 5 + 44 files changed, 2959 insertions(+), 606 deletions(-) create mode 100644 .env.template delete mode 100644 src/impl/encoded/arc4-impl.ts delete mode 100644 src/impl/encoded/array-proxy.ts delete mode 100644 src/impl/encoded/containers.ts delete mode 100644 src/impl/encoded/encoders.ts delete mode 100644 src/impl/encoded/helpers.ts delete mode 100644 src/impl/encoded/primitives.ts delete mode 100644 src/impl/encoded/types.ts delete mode 100644 src/impl/encoded/utils.ts delete mode 100644 src/impl/mutable-array.ts delete mode 100644 src/impl/mutable-object.ts create mode 100644 tests/fixed-array.spec.ts delete mode 100644 tests/mutable-object.spec.ts create mode 100644 tests/native-mutable-array.spec.ts create mode 100644 tests/native-mutable-object.spec.ts create mode 100644 tests/native-readonly-array.spec.ts create mode 100644 tests/native-readonly-object.spec.ts rename tests/{mutable-array.spec.ts => reference-array.spec.ts} (69%) diff --git a/.env.template b/.env.template new file mode 100644 index 00000000..25526520 --- /dev/null +++ b/.env.template @@ -0,0 +1 @@ +PUYA_PATH="" # e.g. '/home/parallels/.local/bin/puya' diff --git a/package-lock.json b/package-lock.json index caf02984..cd6ee879 100644 --- a/package-lock.json +++ b/package-lock.json @@ -20,6 +20,7 @@ "@algorandfoundation/algokit-utils": "^9.0.0", "@commitlint/cli": "^19.8.0", "@commitlint/config-conventional": "^19.8.0", + "@dotenvx/dotenvx": "^1.45.1", "@eslint/eslintrc": "3.3.1", "@eslint/js": "9.23.0", "@makerx/eslint-config": "4.2.0", @@ -485,6 +486,175 @@ "node": ">=v18" } }, + "node_modules/@dotenvx/dotenvx": { + "version": "1.45.1", + "resolved": "https://registry.npmjs.org/@dotenvx/dotenvx/-/dotenvx-1.45.1.tgz", + "integrity": "sha512-wKHPD+/NMMJVBPg3i98uD9jsURDy+Ck6RQRiWf39TlOAzC+Ge1FkmDk3sgeljYZxA3qF6E7SJmvRqC70XQuuVA==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "commander": "^11.1.0", + "dotenv": "^16.4.5", + "eciesjs": "^0.4.10", + "execa": "^5.1.1", + "fdir": "^6.2.0", + "ignore": "^5.3.0", + "object-treeify": "1.1.33", + "picomatch": "^4.0.2", + "which": "^4.0.0" + }, + "bin": { + "dotenvx": "src/cli/dotenvx.js", + "git-dotenvx": "src/cli/dotenvx.js" + }, + "funding": { + "url": "https://dotenvx.com" + } + }, + "node_modules/@dotenvx/dotenvx/node_modules/commander": { + "version": "11.1.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-11.1.0.tgz", + "integrity": "sha512-yPVavfyCcRhmorC7rWlkHn15b4wDVgVmBA7kV4QVBsF7kv/9TKJAbAXVTxvTnwP8HHKjRCJDClKbciiYS7p0DQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=16" + } + }, + "node_modules/@dotenvx/dotenvx/node_modules/execa": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/execa/-/execa-5.1.1.tgz", + "integrity": "sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==", + "dev": true, + "license": "MIT", + "dependencies": { + "cross-spawn": "^7.0.3", + "get-stream": "^6.0.0", + "human-signals": "^2.1.0", + "is-stream": "^2.0.0", + "merge-stream": "^2.0.0", + "npm-run-path": "^4.0.1", + "onetime": "^5.1.2", + "signal-exit": "^3.0.3", + "strip-final-newline": "^2.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sindresorhus/execa?sponsor=1" + } + }, + "node_modules/@dotenvx/dotenvx/node_modules/human-signals": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-2.1.0.tgz", + "integrity": "sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=10.17.0" + } + }, + "node_modules/@dotenvx/dotenvx/node_modules/is-stream": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", + "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@dotenvx/dotenvx/node_modules/mimic-fn": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", + "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/@dotenvx/dotenvx/node_modules/npm-run-path": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-4.0.1.tgz", + "integrity": "sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==", + "dev": true, + "license": "MIT", + "dependencies": { + "path-key": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/@dotenvx/dotenvx/node_modules/onetime": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz", + "integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==", + "dev": true, + "license": "MIT", + "dependencies": { + "mimic-fn": "^2.1.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@dotenvx/dotenvx/node_modules/signal-exit": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", + "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/@dotenvx/dotenvx/node_modules/strip-final-newline": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-2.0.0.tgz", + "integrity": "sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/@dotenvx/dotenvx/node_modules/which": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/which/-/which-4.0.0.tgz", + "integrity": "sha512-GlaYyEb07DPxYCKhKzplCWBJtvxZcZMrL+4UkrTSJHHPyZU4mYYTv3qaOe77H7EODLSSopAUFAc6W8U4yqvscg==", + "dev": true, + "license": "ISC", + "dependencies": { + "isexe": "^3.1.1" + }, + "bin": { + "node-which": "bin/which.js" + }, + "engines": { + "node": "^16.13.0 || >=18.0.0" + } + }, + "node_modules/@ecies/ciphers": { + "version": "0.2.3", + "resolved": "https://registry.npmjs.org/@ecies/ciphers/-/ciphers-0.2.3.tgz", + "integrity": "sha512-tapn6XhOueMwht3E2UzY0ZZjYokdaw9XtL9kEyjhQ/Fb9vL9xTFbOaI+fV0AWvTpYu4BNloC6getKW6NtSg4mA==", + "dev": true, + "license": "MIT", + "engines": { + "bun": ">=1", + "deno": ">=2", + "node": ">=16" + }, + "peerDependencies": { + "@noble/ciphers": "^1.0.0" + } + }, "node_modules/@esbuild/aix-ppc64": { "version": "0.25.0", "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.0.tgz", @@ -1364,6 +1534,48 @@ "dev": true, "license": "MIT" }, + "node_modules/@noble/ciphers": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/@noble/ciphers/-/ciphers-1.3.0.tgz", + "integrity": "sha512-2I0gnIVPtfnMw9ee9h1dJG7tp81+8Ob3OJb3Mv37rx5L40/b0i7djjCVvGOVqc9AEIQyvyu1i6ypKdFw8R8gQw==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^14.21.3 || >=16" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/@noble/curves": { + "version": "1.9.2", + "resolved": "https://registry.npmjs.org/@noble/curves/-/curves-1.9.2.tgz", + "integrity": "sha512-HxngEd2XUcg9xi20JkwlLCtYwfoFw4JGkuZpT+WlsPD4gB/cxkvTD8fSsoAnphGZhFdZYKeQIPCuFlWPm1uE0g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@noble/hashes": "1.8.0" + }, + "engines": { + "node": "^14.21.3 || >=16" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/@noble/hashes": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.8.0.tgz", + "integrity": "sha512-jCs9ldd7NwzpgXDIf6P3+NrHh9/sD6CQdxHyjQI+h/6rDNo88ypBxxz45UDuZHz9r3tNz7N/VInSVoVdtXEI4A==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^14.21.3 || >=16" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, "node_modules/@nodelib/fs.scandir": { "version": "2.1.5", "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", @@ -4475,6 +4687,19 @@ "node": ">=8" } }, + "node_modules/dotenv": { + "version": "16.6.1", + "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.6.1.tgz", + "integrity": "sha512-uBq4egWHTcTt33a72vpSG0z3HnPuIl6NqYcTrKEg2azoEyl2hpW0zqlxysq2pK9HlDIHyHyakeYaYnSAwd8bow==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://dotenvx.com" + } + }, "node_modules/dunder-proto": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", @@ -4539,6 +4764,24 @@ "integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==", "license": "MIT" }, + "node_modules/eciesjs": { + "version": "0.4.15", + "resolved": "https://registry.npmjs.org/eciesjs/-/eciesjs-0.4.15.tgz", + "integrity": "sha512-r6kEJXDKecVOCj2nLMuXK/FCPeurW33+3JRpfXVbjLja3XUYFfD9I/JBreH6sUyzcm3G/YQboBjMla6poKeSdA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@ecies/ciphers": "^0.2.3", + "@noble/ciphers": "^1.3.0", + "@noble/curves": "^1.9.1", + "@noble/hashes": "^1.8.0" + }, + "engines": { + "bun": ">=1", + "deno": ">=2", + "node": ">=16" + } + }, "node_modules/elliptic": { "version": "6.6.1", "resolved": "https://registry.npmjs.org/elliptic/-/elliptic-6.6.1.tgz", @@ -11014,6 +11257,16 @@ "node": ">= 0.4" } }, + "node_modules/object-treeify": { + "version": "1.1.33", + "resolved": "https://registry.npmjs.org/object-treeify/-/object-treeify-1.1.33.tgz", + "integrity": "sha512-EFVjAYfzWqWsBMRHPMAXLCDIJnpMhdWAqR7xG6M6a2cs6PMFpl/+Z20w9zDW4vkxOFfddegBKq9Rehd0bxWE7A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 10" + } + }, "node_modules/object.assign": { "version": "4.1.7", "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.7.tgz", diff --git a/package.json b/package.json index 3535528a..62078b9a 100644 --- a/package.json +++ b/package.json @@ -24,12 +24,13 @@ "script:documentation": "typedoc", "script:build-examples": "rollup --config examples/rollup.config.ts --configPlugin typescript", "script:compile-examples": "puya-ts --out-dir data ./examples/*/ --puya-path=/home/parallels/.local/bin/puya", - "script:refresh-test-artifacts": "puya-ts --out-dir data ./tests/artifacts/*/" + "script:refresh-test-artifacts": "puya-ts --out-dir data --output-awst --output-awst-json ./tests/artifacts/*/ --puya-path=/home/parallels/.local/bin/puya" }, "devDependencies": { "@algorandfoundation/algokit-utils": "^9.0.0", "@commitlint/cli": "^19.8.0", "@commitlint/config-conventional": "^19.8.0", + "@dotenvx/dotenvx": "^1.45.1", "@eslint/eslintrc": "3.3.1", "@eslint/js": "9.23.0", "@makerx/eslint-config": "4.2.0", diff --git a/src/abi-metadata.ts b/src/abi-metadata.ts index bc5f8564..61f6820a 100644 --- a/src/abi-metadata.ts +++ b/src/abi-metadata.ts @@ -4,7 +4,7 @@ import js_sha512 from 'js-sha512' import { ConventionalRouting } from './constants' import { Arc4MethodConfigSymbol, Contract } from './impl/contract' import type { TypeInfo } from './impl/encoded-types' -import { getArc4TypeName as getArc4TypeNameForARC4Encoded } from './impl/encoded-types' +import { getArc4TypeName } from './impl/encoded-types' import type { DeliberateAny } from './typescript-helpers' export interface AbiMetadata { @@ -82,41 +82,6 @@ export const getArc4Selector = (metadata: AbiMetadata): Uint8Array => { return new Uint8Array(hash.slice(0, 4)) } -const getArc4TypeName = (t: TypeInfo): string => { - const map: Record string)> = { - void: 'void', - account: 'account', - application: 'application', - asset: 'asset', - boolean: 'bool', - biguint: 'uint512', - bytes: 'byte[]', - string: 'string', - uint64: 'uint64', - OnCompleteAction: 'uint64', - TransactionType: 'uint64', - Transaction: 'txn', - PaymentTxn: 'pay', - KeyRegistrationTxn: 'keyreg', - AssetConfigTxn: 'acfg', - AssetTransferTxn: 'axfer', - AssetFreezeTxn: 'afrz', - ApplicationCallTxn: 'appl', - 'Tuple(<.*>)?': (t) => - `(${Object.values(t.genericArgs as Record) - .map(getArc4TypeName) - .join(',')})`, - } - const entry = Object.entries(map).find(([k, _]) => new RegExp(`^${k}$`, 'i').test(t.name))?.[1] - if (entry === undefined) { - return getArc4TypeNameForARC4Encoded(t) ?? t.name - } - if (entry instanceof Function) { - return entry(t) - } - return entry -} - /** * Get routing properties inferred by conventional naming * @param methodName The name of the method diff --git a/src/impl/clone.ts b/src/impl/clone.ts index a78ab709..95194875 100644 --- a/src/impl/clone.ts +++ b/src/impl/clone.ts @@ -4,7 +4,7 @@ export function cloneImpl(typeInfoString: string, value: T): T { if (value && typeof value === 'object' && 'copy' in value && typeof value.copy === 'function') { return value.copy() as T } - const bytes = toBytes(value) + const bytes = toBytes(value, typeInfoString) const typeInfo = JSON.parse(typeInfoString) const encoder = getEncoder(typeInfo) return encoder(bytes, typeInfo) as T diff --git a/src/impl/encoded-types/array-proxy.ts b/src/impl/encoded-types/array-proxy.ts index 713085d7..4bcc7d03 100644 --- a/src/impl/encoded-types/array-proxy.ts +++ b/src/impl/encoded-types/array-proxy.ts @@ -12,6 +12,8 @@ export const arrayProxyHandler = () => ({ return target.items[Symbol.iterator].bind(target.items) } else if (prop === 'entries') { return target.items.entries.bind(target.items) + } else if (prop === 'keys') { + return target.items.keys.bind(target.items) } else if (prop === 'at') { return (index: Uint64Compat): TItem => { return arrayUtil.arrayAt(target.items, index) diff --git a/src/impl/encoded-types/encoded-types.ts b/src/impl/encoded-types/encoded-types.ts index c785fdf7..507750b6 100644 --- a/src/impl/encoded-types/encoded-types.ts +++ b/src/impl/encoded-types/encoded-types.ts @@ -1,4 +1,5 @@ -import type { Account as AccountType, bytes, NTuple, StringCompat, uint64 } from '@algorandfoundation/algorand-typescript' +import type { Account as AccountType, bytes, NTuple, StringCompat, uint64, Uint64Compat } from '@algorandfoundation/algorand-typescript' +import { FixedArray, ReferenceArray } from '@algorandfoundation/algorand-typescript' import type { BitSize } from '@algorandfoundation/algorand-typescript/arc4' import { Address, @@ -30,6 +31,7 @@ import { asMaybeBigUintCls, asMaybeBytesCls, asMaybeUint64Cls, + asNumber, asUint64, asUint64Cls, asUint8Array, @@ -37,7 +39,6 @@ import { uint8ArrayToNumber, } from '../../util' import { BytesBackedCls, Uint64BackedCls } from '../base' -import { MutableObjectImpl } from '../mutable-object' import type { StubBytesCompat } from '../primitives' import { BigUintCls, Bytes, BytesCls, getUint8Array, isBytes, Uint64Cls } from '../primitives' import { Account, AccountCls, ApplicationCls, AssetCls } from '../reference' @@ -866,6 +867,110 @@ export class StaticBytesImpl extends StaticBytes extends ReferenceArray { + private _values: TItem[] + typeInfo: TypeInfo + + constructor(typeInfo: TypeInfo | string, ...items: TItem[]) { + super(...items) + this.typeInfo = typeof typeInfo === 'string' ? JSON.parse(typeInfo) : typeInfo + this._values = items + + return new Proxy(this, arrayProxyHandler()) as ReferenceArrayImpl + } + + /** + * Returns the current length of this array + */ + get length(): uint64 { + return this._values.length + } + + get items(): TItem[] { + return this._values + } + + setItem(index: number, value: TItem): void { + this.items[index] = value + } + + slice(start?: Uint64Compat, end?: Uint64Compat): ReferenceArray { + const startIndex = end === undefined ? 0 : asNumber(start ?? 0) + const endIndex = end === undefined ? asNumber(start ?? this._values.length) : asNumber(end) + return new ReferenceArrayImpl(this.typeInfo, ...this._values.slice(startIndex, endIndex)) + } + + /** + * Push a number of items into this array + * @param items The items to be added to this array + */ + push(...items: TItem[]): void { + this._values.push(...items) + } + + /** + * Pop a single item from this array + */ + pop(): TItem { + return this._values.pop()! + } + + copy(): ReferenceArray { + const bytesValue = toBytes(this) + return getEncoder>(this.typeInfo)(bytesValue, this.typeInfo) + } +} + +export class FixedArrayImpl extends FixedArray { + private _values: NTuple + private size: number + typeInfo: TypeInfo + private genericArgs: StaticArrayGenericArgs + + constructor(typeInfo: TypeInfo | string, ...items: TItem[] & { length: TLength }) { + super(...(items as DeliberateAny)) + this.typeInfo = typeof typeInfo === 'string' ? JSON.parse(typeInfo) : typeInfo + this.genericArgs = this.typeInfo.genericArgs as StaticArrayGenericArgs + this.size = parseInt(this.genericArgs.size.name, 10) + if (items.length) { + this._values = items as NTuple + } else { + const bytesValue = asBytes(new Uint8Array(getMaxLengthOfStaticContentType(this.typeInfo))) + this._values = ( + getEncoder>(this.typeInfo)(bytesValue, this.typeInfo) as FixedArrayImpl + ).items + } + return new Proxy(this, arrayProxyHandler()) as FixedArrayImpl + } + + concat(...others: (TItem | ConcatArray)[]): TItem[] { + return this.items.concat(...others) + } + + get length(): uint64 { + return this.size + } + + get items(): NTuple { + return this._values + } + + setItem(index: number, value: TItem): void { + this.items[index] = value + } + + slice(start?: Uint64Compat, end?: Uint64Compat): Array { + const startIndex = end === undefined ? 0 : asNumber(start ?? 0) + const endIndex = end === undefined ? asNumber(start ?? this._values.length) : asNumber(end) + return this._values.slice(startIndex, endIndex) + } + + copy(): FixedArray { + const bytesValue = toBytes(this) + return getEncoder>(this.typeInfo)(bytesValue, this.typeInfo) + } +} + const decode = (value: Uint8Array, childTypes: TypeInfo[]) => { let i = 0 let arrayIndex = 0 @@ -936,7 +1041,12 @@ const decode = (value: Uint8Array, childTypes: TypeInfo[]) => { const values: ARC4Encoded[] = [] childTypes.forEach((childType, index) => { - values.push(getEncoder(childType)(valuePartitions[index], childType)) + values.push( + getEncoder(childType)( + ['bytes', 'string'].includes(childType.name) ? valuePartitions[index].slice(2) : valuePartitions[index], + childType, + ), + ) }) return values } @@ -1048,13 +1158,30 @@ export const getArc4Encoded = (value: DeliberateAny, sourceTypeInfoString?: stri if (typeof value === 'string') { return new StrImpl({ name: 'Str' }, value) } - if (Array.isArray(value)) { - const result: ARC4Encoded[] = value.reduce((acc: ARC4Encoded[], cur: DeliberateAny) => { - return acc.concat(getArc4Encoded(cur)) - }, []) + if (Array.isArray(value) || value instanceof ReferenceArrayImpl || value instanceof FixedArrayImpl) { const sourceTypeInfo = sourceTypeInfoString ? JSON.parse(sourceTypeInfoString) : undefined + const sourceGenericArgs = ((value as DeliberateAny).typeInfo || sourceTypeInfo || {})?.genericArgs + const result: ARC4Encoded[] = (value instanceof ReferenceArrayImpl || value instanceof FixedArrayImpl ? value.items : value).reduce( + (acc: ARC4Encoded[], cur: DeliberateAny, currentIndex: number) => { + const elementTypeInfo = sourceGenericArgs?.elementType || sourceGenericArgs?.[currentIndex] + const elementTypeInfoString = elementTypeInfo ? JSON.stringify(elementTypeInfo) : undefined + return acc.concat(getArc4Encoded(cur, elementTypeInfoString)) + }, + [], + ) + const genericArgs: TypeInfo[] = result.map((x) => (x as DeliberateAny).typeInfo) - if (sourceTypeInfo?.name?.startsWith('Array')) { + if (value instanceof FixedArrayImpl) { + const typeInfo = { + name: `StaticArray<${genericArgs[0].name},${genericArgs.length}>`, + genericArgs: { elementType: genericArgs[0], size: { name: genericArgs.length.toString() } }, + } + return new StaticArrayImpl(typeInfo, ...(result as [ARC4Encoded, ...ARC4Encoded[]])) + } else if ( + sourceTypeInfo?.name?.startsWith('Array') || + sourceTypeInfo?.name?.startsWith('ReadonlyArray') || + value instanceof ReferenceArrayImpl + ) { const typeInfo = { name: `DynamicArray<${genericArgs[0].name}>`, genericArgs: { elementType: genericArgs[0] } } return new DynamicArrayImpl(typeInfo, ...(result as [ARC4Encoded, ...ARC4Encoded[]])) } else { @@ -1063,14 +1190,16 @@ export const getArc4Encoded = (value: DeliberateAny, sourceTypeInfoString?: stri } } if (typeof value === 'object') { - const result = Object.values(value).reduce((acc: ARC4Encoded[], cur: DeliberateAny) => { - return acc.concat(getArc4Encoded(cur)) + const sourceTypeInfo = sourceTypeInfoString ? JSON.parse(sourceTypeInfoString) : undefined + const propTypeInfos = (value.typeInfo || sourceTypeInfo || {}).genericArgs + const result = Object.entries(value).reduce((acc: ARC4Encoded[], [key, cur]: DeliberateAny) => { + const propTypeInfoString = propTypeInfos?.[key] ? JSON.stringify(propTypeInfos[key]) : undefined + return acc.concat(getArc4Encoded(cur, propTypeInfoString)) }, []) - const genericArgs: TypeInfo[] = value instanceof MutableObjectImpl ? [] : result.map((x) => (x as DeliberateAny).typeInfo) + const genericArgs: TypeInfo[] = result.map((x) => (x as DeliberateAny).typeInfo) const typeInfo = { name: `Struct<${value.constructor.name}>`, - genericArgs: - value instanceof MutableObjectImpl ? value.genericArgs : Object.fromEntries(Object.keys(value).map((x, i) => [x, genericArgs[i]])), + genericArgs: Object.fromEntries(Object.keys(value).map((x, i) => [x, genericArgs[i]])), } return new StructImpl(typeInfo, Object.fromEntries(Object.keys(value).map((x, i) => [x, result[i]]))) } @@ -1078,7 +1207,7 @@ export const getArc4Encoded = (value: DeliberateAny, sourceTypeInfoString?: stri throw new CodeError(`Unsupported type for encoding: ${typeof value}`) } -export const toBytes = (val: unknown): bytes => { +export const toBytes = (val: unknown, sourceTypeInfoString?: string): bytes => { const uint64Val = asMaybeUint64Cls(val, false) if (uint64Val !== undefined) { return uint64Val.toBytes().asAlgoTs() @@ -1098,21 +1227,54 @@ export const toBytes = (val: unknown): bytes => { return asUint64Cls(val.uint64).toBytes().asAlgoTs() } if (Array.isArray(val) || typeof val === 'object') { - return encodeArc4Impl(undefined, val) + return encodeArc4Impl(sourceTypeInfoString, val) } throw new InternalError(`Invalid type for bytes: ${nameOfType(val)}`) } -const mutableObjectFromBytes: fromBytes> = ( - value: StubBytesCompat | Uint8Array, - typeInfo: string | TypeInfo, - prefix: 'none' | 'log' = 'none', -) => { - const s = StructImpl.fromBytesImpl(value, typeInfo, prefix) as StructImpl - return new MutableObjectImpl(typeInfo, s.native) -} - export const getEncoder = (typeInfo: TypeInfo): fromBytes => { + const mutableTupleFromBytes = (value: StubBytesCompat | Uint8Array, typeInfo: string | TypeInfo, prefix: 'none' | 'log' = 'none') => { + const tuple = TupleImpl.fromBytesImpl(value, typeInfo, prefix) + return asNumber(tuple.bytes.length) ? tuple.native : ([] as unknown as typeof tuple.native) + } + const readonlyMutableTupleFromBytes = ( + value: StubBytesCompat | Uint8Array, + typeInfo: string | TypeInfo, + prefix: 'none' | 'log' = 'none', + ) => { + const result = mutableTupleFromBytes(value, typeInfo, prefix) + return result as Readonly + } + const mutableObjectFromBytes = (value: StubBytesCompat | Uint8Array, typeInfo: string | TypeInfo, prefix: 'none' | 'log' = 'none') => { + const struct = StructImpl.fromBytesImpl(value, typeInfo, prefix) + return asNumber(struct.bytes.length) ? struct.native : ({} as unknown as typeof struct.native) + } + const readonlyObjectFromBytes = (value: StubBytesCompat | Uint8Array, typeInfo: string | TypeInfo, prefix: 'none' | 'log' = 'none') => { + const result = mutableObjectFromBytes(value, typeInfo, prefix) + return result as Readonly + } + const arrayFromBytes = (value: StubBytesCompat | Uint8Array, typeInfo: string | TypeInfo, prefix: 'none' | 'log' = 'none') => { + const dynamicArray = DynamicArrayImpl.fromBytesImpl(value, typeInfo, prefix) + return asNumber(dynamicArray.bytes.length) ? dynamicArray.native : ([] as unknown as typeof dynamicArray.native) + } + const readonlyArrayFromBytes = (value: StubBytesCompat | Uint8Array, typeInfo: string | TypeInfo, prefix: 'none' | 'log' = 'none') => { + const result = arrayFromBytes(value, typeInfo, prefix) + return result as Readonly + } + const referenceArrayFromBytes = (value: StubBytesCompat | Uint8Array, typeInfo: string | TypeInfo, prefix: 'none' | 'log' = 'none') => { + const dynamicArray = DynamicArrayImpl.fromBytesImpl(value, typeInfo, prefix) + return new ReferenceArrayImpl( + typeInfo, + ...(asNumber(dynamicArray.bytes.length) ? dynamicArray.native : ([] as unknown as typeof dynamicArray.native)), + ) + } + const fixedArrayFromBytes = (value: StubBytesCompat | Uint8Array, typeInfo: string | TypeInfo, prefix: 'none' | 'log' = 'none') => { + const staticArray = StaticArrayImpl.fromBytesImpl(value, typeInfo, prefix) + return new FixedArrayImpl( + typeInfo, + ...(asNumber(staticArray.bytes.length) ? staticArray.native : ([] as unknown as typeof staticArray.native)), + ) + } const encoders: Record> = { account: AccountCls.fromBytes, application: ApplicationCls.fromBytes, @@ -1133,11 +1295,18 @@ export const getEncoder = (typeInfo: TypeInfo): fromBytes => { 'StaticArray<.*>': StaticArrayImpl.fromBytesImpl, 'DynamicArray<.*>': DynamicArrayImpl.fromBytesImpl, 'Tuple(<.*>)?': TupleImpl.fromBytesImpl, + 'ReadonlyTuple(<.*>)?': readonlyMutableTupleFromBytes, + 'MutableTuple(<.*>)?': mutableTupleFromBytes, 'Struct(<.*>)?': StructImpl.fromBytesImpl, DynamicBytes: DynamicBytesImpl.fromBytesImpl, 'StaticBytes<.*>': StaticBytesImpl.fromBytesImpl, object: StructImpl.fromBytesImpl, - 'MutableObject(<.*>)?': mutableObjectFromBytes, + 'Object<.*>': mutableObjectFromBytes, + 'ReadonlyObject<.*>': readonlyObjectFromBytes, + 'ReferenceArray<.*>': referenceArrayFromBytes, + 'FixedArray<.*>': fixedArrayFromBytes, + 'Array<.*>': arrayFromBytes, + 'ReadonlyArray<.*>': readonlyArrayFromBytes, } const encoder = Object.entries(encoders).find(([k, _]) => new RegExp(`^${k}$`, 'i').test(typeInfo.name))?.[1] diff --git a/src/impl/encoded-types/helpers.ts b/src/impl/encoded-types/helpers.ts index 46304598..ea7196a4 100644 --- a/src/impl/encoded-types/helpers.ts +++ b/src/impl/encoded-types/helpers.ts @@ -122,10 +122,21 @@ export const holdsDynamicLengthContent = (value: TypeInfo): boolean => { return ( itemTypeName === 'Str' || + itemTypeName === 'Array' || + itemTypeName === 'ReadonlyArray' || itemTypeName === 'DynamicArray' || + itemTypeName === 'ReferenceArray' || itemTypeName === 'DynamicBytes' || - (itemTypeName === 'StaticArray' && holdsDynamicLengthContent((value.genericArgs as StaticArrayGenericArgs).elementType)) || - ((itemTypeName === 'Tuple' || itemTypeName === 'Struct') && + itemTypeName === 'bytes' || + itemTypeName === 'string' || + ((itemTypeName === 'StaticArray' || itemTypeName === 'FixedArray') && + holdsDynamicLengthContent((value.genericArgs as StaticArrayGenericArgs).elementType)) || + ((itemTypeName === 'Tuple' || + itemTypeName === 'MutableTuple' || + itemTypeName === 'ReadonlyTuple' || + itemTypeName === 'Struct' || + itemTypeName === 'Object' || + itemTypeName === 'ReadonlyObject') && Object.values(value.genericArgs as Record).some(holdsDynamicLengthContent)) ) } diff --git a/src/impl/encoded-types/index.ts b/src/impl/encoded-types/index.ts index 455ba749..fdfd7460 100644 --- a/src/impl/encoded-types/index.ts +++ b/src/impl/encoded-types/index.ts @@ -13,6 +13,8 @@ export { encodeArc4Impl, getArc4Encoded, getEncoder, + FixedArrayImpl, + ReferenceArrayImpl, StaticArrayImpl, StaticBytesImpl, StrImpl, diff --git a/src/impl/encoded-types/utils.ts b/src/impl/encoded-types/utils.ts index bd2a2ebc..ef4b076e 100644 --- a/src/impl/encoded-types/utils.ts +++ b/src/impl/encoded-types/utils.ts @@ -61,10 +61,14 @@ export const getMaxLengthOfStaticContentType = (type: TypeInfo, asArc4Encoded: b case 'Address': case 'StaticBytes': case 'StaticArray': + case 'FixedArray': return getMaxBytesLengthForStaticArray(type as unknown as { genericArgs: StaticArrayGenericArgs }) case 'Tuple': - return getMaxBytesLengthForObjectType(type) + case 'ReadonlyTuple': + case 'MutableTuple': case 'Struct': + case 'Object': + case 'ReadonlyObject': return getMaxBytesLengthForObjectType(type) default: throw new CodeError(`unsupported type ${type.name}`) @@ -76,7 +80,27 @@ export const getArc4TypeName = (typeInfo: TypeInfo): string | undefined => { const genericArgs = Object.values(typeInfo.genericArgs as Record) return `(${genericArgs.map(getArc4TypeName).join(',')})` } - const map = { + const map: Record string)> = { + void: 'void', + account: 'account', + application: 'application', + asset: 'asset', + boolean: 'bool', + biguint: 'uint512', + bytes: 'byte[]', + string: 'string', + uint64: 'uint64', + OnCompleteAction: 'uint64', + TransactionType: 'uint64', + Transaction: 'txn', + PaymentTxn: 'pay', + KeyRegistrationTxn: 'keyreg', + AssetConfigTxn: 'acfg', + AssetTransferTxn: 'axfer', + AssetFreezeTxn: 'afrz', + ApplicationCallTxn: 'appl', + '(Readonly|Mutable)?Tuple(<.*>)?': getArc4TypeNameForObjectType, + Address: 'address', Bool: 'bool', Byte: 'byte', @@ -86,16 +110,16 @@ export const getArc4TypeName = (typeInfo: TypeInfo): string | undefined => { const genericArgs = t.genericArgs as uFixedNxMGenericArgs return `ufixed${genericArgs.n.name}x${genericArgs.m.name}` }, - 'StaticArray<.*>': (t: TypeInfo) => { + '(StaticArray|FixedArray)(<.*>)?': (t: TypeInfo) => { const genericArgs = t.genericArgs as StaticArrayGenericArgs return `${getArc4TypeName(genericArgs.elementType)}[${genericArgs.size.name}]` }, - 'DynamicArray<.*>': (t: TypeInfo) => { + '(Dynamic|Readonly)?Array<.*>': (t: TypeInfo) => { const genericArgs = t.genericArgs as DynamicArrayGenericArgs return `${getArc4TypeName(genericArgs.elementType)}[]` }, - 'Tuple(<.*>)?': getArc4TypeNameForObjectType, 'Struct(<.*>)?': getArc4TypeNameForObjectType, + '(Readonly)?Object(<.*>)?': getArc4TypeNameForObjectType, DynamicBytes: 'byte[]', 'StaticBytes<.*>': (t: TypeInfo) => { const genericArgs = t.genericArgs as StaticArrayGenericArgs diff --git a/src/impl/encoded/arc4-impl.ts b/src/impl/encoded/arc4-impl.ts deleted file mode 100644 index e69de29b..00000000 diff --git a/src/impl/encoded/array-proxy.ts b/src/impl/encoded/array-proxy.ts deleted file mode 100644 index e69de29b..00000000 diff --git a/src/impl/encoded/containers.ts b/src/impl/encoded/containers.ts deleted file mode 100644 index e69de29b..00000000 diff --git a/src/impl/encoded/encoders.ts b/src/impl/encoded/encoders.ts deleted file mode 100644 index e69de29b..00000000 diff --git a/src/impl/encoded/helpers.ts b/src/impl/encoded/helpers.ts deleted file mode 100644 index e69de29b..00000000 diff --git a/src/impl/encoded/primitives.ts b/src/impl/encoded/primitives.ts deleted file mode 100644 index e69de29b..00000000 diff --git a/src/impl/encoded/types.ts b/src/impl/encoded/types.ts deleted file mode 100644 index e69de29b..00000000 diff --git a/src/impl/encoded/utils.ts b/src/impl/encoded/utils.ts deleted file mode 100644 index e69de29b..00000000 diff --git a/src/impl/index.ts b/src/impl/index.ts index 4ae52bd6..a90600c6 100644 --- a/src/impl/index.ts +++ b/src/impl/index.ts @@ -16,4 +16,3 @@ export * from './pure' export { gloadBytes, gloadUint64, Scratch } from './scratch' export { gaid, Txn } from './txn' export { VoterParams } from './voter-params' -export { MutableArray } from './mutable-array' diff --git a/src/impl/log.ts b/src/impl/log.ts index 7864f77c..01beb4c4 100644 --- a/src/impl/log.ts +++ b/src/impl/log.ts @@ -5,5 +5,5 @@ import { toBytes } from './encoded-types' import type { StubBigUintCompat, StubBytesCompat, StubUint64Compat } from './primitives' export function log(...args: Array): void { - lazyContext.txn.appendLog(args.map(toBytes).reduce((left, right) => left.concat(right))) + lazyContext.txn.appendLog(args.map((a) => toBytes(a)).reduce((left, right) => left.concat(right))) } diff --git a/src/impl/mutable-array.ts b/src/impl/mutable-array.ts deleted file mode 100644 index e03a42d7..00000000 --- a/src/impl/mutable-array.ts +++ /dev/null @@ -1,122 +0,0 @@ -import type { uint64, Uint64Compat } from '@algorandfoundation/algorand-typescript' -import { AvmError } from '../errors' -import { asNumber } from '../util' - -export class MutableArray { - private _values: TItem[] - - constructor(...items: TItem[]) { - this._values = items - - return new Proxy(this, { - get(target, prop: PropertyKey) { - const idx = prop ? parseInt(prop.toString(), 10) : NaN - if (!isNaN(idx)) { - if (idx >= 0 && idx < target._values.length) return target._values[idx] - throw new AvmError('Index out of bounds') - } - return Reflect.get(target, prop) - }, - set(target, prop: PropertyKey, value: TItem) { - const idx = prop ? parseInt(prop.toString(), 10) : NaN - if (!isNaN(idx)) { - if (idx >= 0 && idx < target._values.length) { - target._values[idx] = value - return true - } - throw new AvmError('Index out of bounds') - } - - return Reflect.set(target, prop, value) - }, - }) - } - - /** - * Returns the current length of this array - */ - get length(): uint64 { - return this._values.length - } - - /** - * Returns the item at the given index. - * Negative indexes are taken from the end. - * @param index The index of the item to retrieve - */ - at(index: Uint64Compat): TItem { - return this._values[asNumber(index)] - } - - /** - * Create a new Dynamic array with all items from this array - * @internal Not supported yet - */ - slice(): MutableArray - /** - * Create a new MutableArray with all items up till `end`. - * Negative indexes are taken from the end. - * @param end An index in which to stop copying items. - * @internal Not supported yet - */ - slice(end: Uint64Compat): MutableArray - /** - * Create a new MutableArray with items from `start`, up until `end` - * Negative indexes are taken from the end. - * @param start An index in which to start copying items. - * @param end An index in which to stop copying items - * @internal Not supported yet - */ - slice(start: Uint64Compat, end: Uint64Compat): MutableArray - slice(start?: Uint64Compat, end?: Uint64Compat): MutableArray { - const startIndex = end === undefined ? 0 : asNumber(start ?? 0) - const endIndex = end === undefined ? asNumber(start ?? this._values.length) : asNumber(end) - return new MutableArray(...this._values.slice(startIndex, endIndex)) - } - - /** - * Returns an iterator for the items in this array - */ - [Symbol.iterator](): IterableIterator { - return this._values[Symbol.iterator]() - } - - /** - * Returns an iterator for a tuple of the indexes and items in this array - */ - entries(): IterableIterator { - return this._values.entries() - } - - /** - * Returns an iterator for the indexes in this array - */ - keys(): IterableIterator { - return this._values.keys() - } - - /** - * Get or set the item at the specified index. - * Negative indexes are not supported - */ - [index: uint64]: TItem - - /** - * Push a number of items into this array - * @param items The items to be added to this array - */ - push(...items: TItem[]): void { - this._values.push(...items) - } - - /** - * Pop a single item from this array - */ - pop(): TItem { - return this._values.pop()! - } - - copy(): MutableArray { - return new MutableArray(...this._values) - } -} diff --git a/src/impl/mutable-object.ts b/src/impl/mutable-object.ts deleted file mode 100644 index 2b7da32a..00000000 --- a/src/impl/mutable-object.ts +++ /dev/null @@ -1,23 +0,0 @@ -import { MutableObject } from '@algorandfoundation/algorand-typescript' -import type { DeliberateAny } from '../typescript-helpers' -import type { TypeInfo } from './encoded-types' - -type MutableObjectConstraint = Record - -export class MutableObjectImpl extends (MutableObject as DeliberateAny) { - typeInfo: TypeInfo - genericArgs: Record - - constructor(typeInfo: TypeInfo | string, initial: T) { - super(initial) - this.typeInfo = typeof typeInfo === 'string' ? JSON.parse(typeInfo) : typeInfo - this.genericArgs = this.typeInfo.genericArgs as Record - Object.keys(this.genericArgs).forEach((key) => { - Object.defineProperty(this, key, { - value: initial[key], - writable: true, - enumerable: true, - }) - }) - } -} diff --git a/src/internal/index.ts b/src/internal/index.ts index 77f493f9..1f97e32c 100644 --- a/src/internal/index.ts +++ b/src/internal/index.ts @@ -6,7 +6,6 @@ export { ensureBudgetImpl as ensureBudget } from '../impl/ensure-budget' export { Global } from '../impl/global' export { log } from '../impl/log' export { assertMatchImpl as assertMatch, matchImpl as match } from '../impl/match' -export { MutableArray } from '../impl/mutable-array' export { BigUint, Bytes, Uint64 } from '../impl/primitives' export { Account, Application, Asset } from '../impl/reference' export { Box, BoxMap, BoxRef, GlobalState, LocalState } from '../impl/state' diff --git a/src/runtime-helpers.ts b/src/runtime-helpers.ts index 6225b2d7..c7167848 100644 --- a/src/runtime-helpers.ts +++ b/src/runtime-helpers.ts @@ -13,7 +13,6 @@ export { attachAbiMetadata } from './abi-metadata' export { cloneImpl } from './impl/clone' export { emitImpl } from './impl/emit' export * from './impl/encoded-types' -export { MutableObjectImpl } from './impl/mutable-object' export function switchableValue(x: unknown): bigint | string | boolean { if (typeof x === 'boolean') return x diff --git a/src/test-transformer/visitors.ts b/src/test-transformer/visitors.ts index f6659280..9bbe498a 100644 --- a/src/test-transformer/visitors.ts +++ b/src/test-transformer/visitors.ts @@ -46,9 +46,18 @@ export class SourceFileVisitor { this.helper = { additionalStatements: [], resolveType(node: ts.Node): ptypes.PType { + let sourceLocation = undefined try { - return loggingContext.run(() => typeResolver.resolve(node, this.sourceLocation(node))) - } catch { + sourceLocation = this.sourceLocation(node) + return loggingContext.run(() => typeResolver.resolve(node, sourceLocation!)) + } catch (e) { + const err = e as Error + if ( + err.constructor.name === 'CodeError' && + !['Classes must extend', 'is not supported', 'Unable to reflect'].some((s) => err.message.includes(s)) + ) { + throw new Error(`${sourceLocation?.toString()}: ${err.message}`, { cause: err }) + } return ptypes.anyPType } }, @@ -364,8 +373,10 @@ const isGenericType = (type: ptypes.PType): boolean => ptypes.StaticArrayType, ptypes.UFixedNxMType, ptypes.UintNType, - ptypes.TuplePType, - ptypes.MutableObjectType, + ptypes.MutableTuplePType, + ptypes.ReadonlyTuplePType, + ptypes.MutableObjectPType, + ptypes.ImmutableObjectPType, ) const isStateOrBoxType = (type: ptypes.PType): boolean => @@ -380,7 +391,8 @@ const isEncodedType = (type: ptypes.PType): boolean => ptypes.StaticArrayType, ptypes.UFixedNxMType, ptypes.UintNType, - ptypes.MutableObjectType, + ptypes.ReferenceArrayType, + ptypes.FixedArrayPType, ) || type === ptypes.arc4StringType || type === ptypes.arc4BooleanType @@ -399,10 +411,20 @@ const getGenericTypeInfo = (type: ptypes.PType, sourceLocation?: SourceLocation) } else if (type instanceof ptypes.BoxMapPType) { genericArgs.push(getGenericTypeInfo(type.keyType, sourceLocation)) genericArgs.push(getGenericTypeInfo(type.contentType, sourceLocation)) - } else if (instanceOfAny(type, ptypes.StaticArrayType, ptypes.DynamicArrayType, ptypes.ArrayPType)) { + } else if ( + instanceOfAny( + type, + ptypes.StaticArrayType, + ptypes.DynamicArrayType, + ptypes.ArrayPType, + ptypes.ReadonlyArrayPType, + ptypes.ReferenceArrayType, + ptypes.FixedArrayPType, + ) + ) { const entries = [] entries.push(['elementType', getGenericTypeInfo(type.elementType, sourceLocation)]) - if (instanceOfAny(type, ptypes.StaticArrayType)) { + if (instanceOfAny(type, ptypes.StaticArrayType, ptypes.FixedArrayPType)) { entries.push(['size', { name: type.arraySize.toString() }]) } genericArgs = Object.fromEntries(entries) @@ -410,21 +432,32 @@ const getGenericTypeInfo = (type: ptypes.PType, sourceLocation?: SourceLocation) genericArgs = { n: { name: type.n.toString() }, m: { name: type.m.toString() } } } else if (type instanceof ptypes.UintNType) { genericArgs.push({ name: type.n.toString() }) - } else if (type instanceof ptypes.ARC4StructType || type instanceof ptypes.MutableObjectType) { - typeName = type instanceof ptypes.ARC4StructType ? `Struct<${type.name}>` : `MutableObject<${type.name}>` + } else if (type instanceof ptypes.ARC4StructType) { + typeName = `Struct<${type.name}>` genericArgs = Object.fromEntries( Object.entries(type.fields) .map(([key, value]) => [key, getGenericTypeInfo(value, sourceLocation)]) .filter((x) => !!x), ) - } else if (type instanceof ptypes.ARC4TupleType || type instanceof ptypes.TuplePType) { - genericArgs.push(...type.items.map((t) => getGenericTypeInfo(t, sourceLocation))) - } else if (type instanceof ptypes.ObjectPType) { + } else if (type instanceof ptypes.MutableObjectPType || type instanceof ptypes.ImmutableObjectPType) { + typeName = type instanceof ptypes.MutableObjectPType ? `Object<${type.name}>` : `ReadonlyObject<${type.name}>` genericArgs = Object.fromEntries( Object.entries(type.properties) .map(([key, value]) => [key, getGenericTypeInfo(value, sourceLocation)]) .filter((x) => !!x), ) + } else if ( + type instanceof ptypes.ARC4TupleType || + type instanceof ptypes.MutableTuplePType || + type instanceof ptypes.ReadonlyTuplePType + ) { + typeName = + type instanceof ptypes.MutableTuplePType + ? `MutableTuple<${type.name}>` + : type instanceof ptypes.ReadonlyTuplePType + ? `ReadonlyTuple<${type.name}>` + : type.name + genericArgs.push(...type.items.map((t) => getGenericTypeInfo(t, sourceLocation))) } const result: TypeInfo = { name: typeName } diff --git a/tests/arc4/dynamic-array.spec.ts b/tests/arc4/dynamic-array.spec.ts index 28a85406..54e93311 100644 --- a/tests/arc4/dynamic-array.spec.ts +++ b/tests/arc4/dynamic-array.spec.ts @@ -398,7 +398,7 @@ class Swapped extends Struct<{ b: UintN<256> c: Bool d: Str - a: Tuple<[DynamicArray, DynamicArray, Str, UintN<256>, Bool, StaticArray, 3>]> + a: Tuple, DynamicArray, Str, UintN<256>, Bool, StaticArray, 3>]> }> {} const structDynamicArray = { abiTypeString: '(uint256,bool,string,(string[],string[],string,uint256,bool,uint256[3]))[]', diff --git a/tests/arc4/emit.spec.ts b/tests/arc4/emit.spec.ts index 789886ea..0a4069c4 100644 --- a/tests/arc4/emit.spec.ts +++ b/tests/arc4/emit.spec.ts @@ -37,7 +37,7 @@ class SwappedArc4 extends arc4.Struct<{ q: arc4.Bool r: arc4.StaticArray s: arc4.DynamicArray - t: arc4.Tuple<[arc4.UintN32, arc4.UintN64, arc4.Str]> + t: arc4.Tuple }> {} describe('arc4.emit', async () => { diff --git a/tests/arc4/static-array.spec.ts b/tests/arc4/static-array.spec.ts index 3f09551c..5c342ea4 100644 --- a/tests/arc4/static-array.spec.ts +++ b/tests/arc4/static-array.spec.ts @@ -411,7 +411,7 @@ class Swapped extends Struct<{ b: UintN<256> c: Bool d: Str - a: Tuple<[DynamicArray, DynamicArray, Str, UintN<256>, Bool, StaticArray, 3>]> + a: Tuple, DynamicArray, Str, UintN<256>, Bool, StaticArray, 3>]> }> {} const structStaticArray = { abiTypeString: '(uint256,bool,string,(string[],string[],string,uint256,bool,uint256[3]))[2]', diff --git a/tests/artifacts/arc4-primitive-ops/contract.algo.ts b/tests/artifacts/arc4-primitive-ops/contract.algo.ts index 01e3e310..22c70b03 100644 --- a/tests/artifacts/arc4-primitive-ops/contract.algo.ts +++ b/tests/artifacts/arc4-primitive-ops/contract.algo.ts @@ -371,8 +371,8 @@ export class Arc4PrimitiveOpsContract extends Contract { const arc4_s = interpretAsArc4>(s) const arc4_t = interpretAsArc4>(t) - emit(new SwappedArc4({ m, n, o, p, q, r: arc4_r, s: arc4_s, t: arc4_t })) - emit('Swapped', a, b, c, d, e, f, g, h, m, n, o, p, q, clone(arc4_r), clone(arc4_s), arc4_t) + emit(new SwappedArc4({ m, n, o, p, q, r: clone(arc4_r), s: clone(arc4_s), t: clone(arc4_t) })) + emit('Swapped', a, b, c, d, e, f, g, h, m, n, o, p, q, arc4_r, arc4_s, arc4_t) emit( 'Swapped(string,uint512,uint64,byte[],uint64,bool,byte[],string,uint64,uint256,ufixed32x8,ufixed256x16,bool,uint8[3],uint16[],(uint32,uint64,string))', a, @@ -388,8 +388,8 @@ export class Arc4PrimitiveOpsContract extends Contract { o, p, q, - clone(arc4_r), - clone(arc4_s), + arc4_r, + arc4_s, arc4_t, ) } diff --git a/tests/artifacts/box-contract/contract.algo.ts b/tests/artifacts/box-contract/contract.algo.ts index 7c0208b7..45253fce 100644 --- a/tests/artifacts/box-contract/contract.algo.ts +++ b/tests/artifacts/box-contract/contract.algo.ts @@ -12,7 +12,7 @@ export class BoxContract extends arc4.Contract { } @arc4.abimethod() - public read_enums(): Tuple<[UintN64, UintN64]> { + public read_enums(): Tuple { assert(op.Box.get(Bytes('oca'))[0] === op.itob(this.oca.value)) assert(op.Box.get(Bytes('txn'))[0] === op.itob(this.txn.value)) diff --git a/tests/artifacts/state-ops/contract.algo.ts b/tests/artifacts/state-ops/contract.algo.ts index 364e86bf..d6dbe9a0 100644 --- a/tests/artifacts/state-ops/contract.algo.ts +++ b/tests/artifacts/state-ops/contract.algo.ts @@ -4,6 +4,7 @@ import { assert, BaseContract, Bytes, + clone, contract, Global, GlobalState, @@ -737,12 +738,12 @@ export class GlobalStateContract extends arc4.Contract { @arc4.abimethod() set_implicit_key_tuple(value: [uint64, bytes, boolean]) { - this.implicitKeyTuple.value = value + this.implicitKeyTuple.value = clone(value) } @arc4.abimethod() set_implicit_key_obj(value: { a: uint64; b: bytes; c: boolean }) { - this.implicitKeyObj.value = value + this.implicitKeyObj.value = clone(value) } // Setter methods for explicit key state variables diff --git a/tests/fixed-array.spec.ts b/tests/fixed-array.spec.ts new file mode 100644 index 00000000..a9358795 --- /dev/null +++ b/tests/fixed-array.spec.ts @@ -0,0 +1,429 @@ +import type { bytes, uint64 } from '@algorandfoundation/algorand-typescript' +import { + arc4, + assertMatch, + Box, + BoxMap, + Bytes, + clone, + Contract, + FixedArray, + Global, + GlobalState, + LocalState, + Uint64, +} from '@algorandfoundation/algorand-typescript' +import { TestExecutionContext } from '@algorandfoundation/algorand-typescript-testing' +import { methodSelector } from '@algorandfoundation/algorand-typescript/arc4' +import { describe, expect, it } from 'vitest' + +class TestContract extends Contract { + fixedArrayNative(a: FixedArray, b: string): readonly [FixedArray, string] { + return [new FixedArray(a[0], a[1]), b] + } + fixedArrayArc4(a: FixedArray, b: arc4.Str): readonly [FixedArray, arc4.Str] { + return [new FixedArray(a[0], a[1]), b] + } +} + +describe('FixedArray', () => { + describe('constructor', () => { + it('creates empty array when no arguments provided', () => { + const arr = new FixedArray() + expect(arr.length).toEqual(2) + }) + + it('creates array with initial values', () => { + const arr = new FixedArray(1, 2, 3) + expect(arr.length).toEqual(3) + expect(arr[0]).toEqual(1) + expect(arr[1]).toEqual(2) + expect(arr[2]).toEqual(3) + }) + }) + + describe('index access', () => { + it('throws on out of bounds access', () => { + const arr = new FixedArray(Uint64(1), Uint64(2)) + expect(() => arr[2]).toThrow('Index out of bounds') + expect(() => arr[-1]).toThrow('Index out of bounds') + }) + + it('allows setting values by index', () => { + const arr = new FixedArray(Uint64(1), Uint64(2)) + const index = Uint64(0) + arr[index] = 10 + expect(arr[index]).toEqual(10) + }) + }) + + describe('at()', () => { + it('returns value at positive index', () => { + const arr = new FixedArray(1, 2, 3) + expect(arr.at(Uint64(1))).toEqual(2) + }) + + it('returns value at negative index', () => { + const arr = new FixedArray(1, 2, 3) + expect(arr.at(-1)).toEqual(3) + }) + }) + + describe('slice()', () => { + it('returns full copy with no arguments', () => { + const arr = new FixedArray(1, 2, 3) + const sliced = arr.slice() + expect([...sliced]).toEqual([1, 2, 3]) + }) + + it('slices with end index', () => { + const arr = new FixedArray(1, 2, 3) + const sliced = arr.slice(Uint64(2)) + expect([...sliced]).toEqual([1, 2]) + }) + + it('slices with start and end index', () => { + const arr = new FixedArray(1, 2, 3, 4) + const sliced = arr.slice(Uint64(1), Uint64(3)) + expect([...sliced]).toEqual([2, 3]) + }) + }) + + describe('iteration', () => { + it('supports for...of iteration', () => { + const arr = new FixedArray(1, 2, 3) + const result: number[] = [] + for (const item of arr) { + result.push(item) + } + expect(result).toEqual([1, 2, 3]) + }) + + it('provides entries() iterator', () => { + const arr = new FixedArray('a', 'b') + const entries = [...arr.entries()] + expect(entries).toEqual([ + [0, 'a'], + [1, 'b'], + ]) + }) + + it('provides keys() iterator', () => { + const arr = new FixedArray('a', 'b') + const keys = [...arr.keys()] + expect(keys).toEqual([0, 1]) + }) + }) + + describe('copy()', () => { + it('creates a deep copy', () => { + const original = new FixedArray, 1>(new FixedArray('a', 'b', 'c')) + const copy = clone(original) + copy[0][0] = 'aa' + expect(original[0][0]).toEqual('a') + expect(copy[0][0]).toEqual('aa') + }) + }) + + describe('store in state', () => { + const ctx = new TestExecutionContext() + + it('stores fixed array in state', () => { + ctx.txn.createScope([ctx.any.txn.applicationCall()]).execute(() => { + const g1 = GlobalState({ key: 'g1', initialValue: new FixedArray(1, 2, 3) }) + assertMatch(g1.value, [1, 2, 3]) + + const l1 = LocalState>({ key: 'l1' }) + l1(Global.zeroAddress).value = new FixedArray(Uint64(4), Uint64(5), Uint64(6)) + assertMatch(l1(Global.zeroAddress).value, [4, 5, 6]) + + const b1 = Box>({ key: 'b1' }) + b1.value = new FixedArray() + assertMatch(b1.value, [0, 0, 0]) + + const b2 = BoxMap>({ keyPrefix: 'b2' }) + b2('key1').value = new FixedArray(7, 8, 9) + assertMatch(b2('key1').value, [7, 8, 9]) + }) + }) + }) + + describe('store arc4 value in fixed array', () => { + it('can store primitive arc4 values', () => { + const a1: FixedArray = new FixedArray(new arc4.UintN64(1), new arc4.UintN64(2), new arc4.UintN64(3)) + assertMatch(a1, [new arc4.UintN64(1), new arc4.UintN64(2), new arc4.UintN64(3)]) + + const a2 = clone(a1) + a2[0] = new arc4.UintN64(10) + assertMatch(a1, [new arc4.UintN64(1), new arc4.UintN64(2), new arc4.UintN64(3)]) + assertMatch(a2, [new arc4.UintN64(10), new arc4.UintN64(2), new arc4.UintN64(3)]) + + const a3 = new FixedArray() + assertMatch(a3, [new arc4.UintN64(0), new arc4.UintN64(0), new arc4.UintN64(0)]) + }) + + it('can store arc4 dynamic array', () => { + const a1: FixedArray, 1> = new FixedArray( + new arc4.DynamicArray(new arc4.UintN64(1), new arc4.UintN64(2), new arc4.UintN64(3)), + ) + assertMatch(a1[0], [new arc4.UintN64(1), new arc4.UintN64(2), new arc4.UintN64(3)]) + + const a2 = clone(a1) + a2[0][0] = new arc4.UintN64(10) + assertMatch(a1[0], [new arc4.UintN64(1), new arc4.UintN64(2), new arc4.UintN64(3)]) + assertMatch(a2[0], [new arc4.UintN64(10), new arc4.UintN64(2), new arc4.UintN64(3)]) + }) + + it('can store arc4 static array', () => { + const a1: FixedArray, 1> = new FixedArray( + new arc4.StaticArray(new arc4.UintN64(1), new arc4.UintN64(2), new arc4.UintN64(3)), + ) + assertMatch(a1[0], [new arc4.UintN64(1), new arc4.UintN64(2), new arc4.UintN64(3)]) + + const a2 = clone(a1) + a2[0][0] = new arc4.UintN64(10) + assertMatch(a1[0], [new arc4.UintN64(1), new arc4.UintN64(2), new arc4.UintN64(3)]) + assertMatch(a2[0], [new arc4.UintN64(10), new arc4.UintN64(2), new arc4.UintN64(3)]) + + const a3 = new FixedArray, 1>() + assertMatch(a3[0], [new arc4.UintN64(0), new arc4.UintN64(0), new arc4.UintN64(0)]) + }) + it('can store arc4 tuple', () => { + const a1: FixedArray, 1> = new FixedArray( + new arc4.Tuple(new arc4.UintN64(1), new arc4.Str('Hello')), + ) + assertMatch(a1[0].native, [new arc4.UintN64(1), new arc4.Str('Hello')]) + + const a2 = clone(a1) + a2[0] = new arc4.Tuple(new arc4.UintN64(10), new arc4.Str('hello')) + assertMatch(a1[0].native, [new arc4.UintN64(1), new arc4.Str('Hello')]) + assertMatch(a2[0].native, [new arc4.UintN64(10), new arc4.Str('hello')]) + + const a3 = new FixedArray, 1>() + assertMatch(a3[0].native, [new arc4.UintN64(0), new arc4.UintN64(0)]) + }) + }) + + describe('store fixed array in other collections', () => { + it('can be stored in object', () => { + const obj = { arr: new FixedArray(1, 2, 3), str: 'hello' } + assertMatch(obj.arr, [1, 2, 3]) + + const obj2 = clone(obj) + obj2.arr[0] = Uint64(10) + assertMatch(obj.arr, [1, 2, 3]) + assertMatch(obj2.arr, [10, 2, 3]) + + const obj3 = { arr: new FixedArray(), str: 'world' } + assertMatch(obj3, { arr: new FixedArray(Uint64(0), Uint64(0)), str: 'world' }) + }) + + it('can be stored in readonly object', () => { + const obj: Readonly<{ arr: FixedArray; str: string }> = { arr: new FixedArray(1, 2, 3), str: 'hello' } + assertMatch(obj.arr, [1, 2, 3]) + + const obj2 = clone(obj) + obj2.arr[0] = Uint64(10) + assertMatch(obj.arr, [1, 2, 3]) + assertMatch(obj2.arr, [10, 2, 3]) + + const obj3 = { arr: new FixedArray(), str: 'world' } as const + assertMatch(obj3, { arr: new FixedArray(Uint64(0), Uint64(0)), str: 'world' }) + }) + + it('can be stored in native array', () => { + const arr: FixedArray[] = [new FixedArray(1, 2, 3), new FixedArray(4, 5, 6)] + assertMatch(arr[0], [1, 2, 3]) + assertMatch(arr[1], [4, 5, 6]) + + const arr2 = clone(arr) + arr2[0][0] = Uint64(10) + assertMatch(arr[0], [1, 2, 3]) + assertMatch(arr2[0], [10, 2, 3]) + + const arr3: FixedArray[] = [] + assertMatch(arr3, []) + }) + + it('can be stored in native readonly array', () => { + const arr: ReadonlyArray> = [new FixedArray(1, 2, 3), new FixedArray(4, 5, 6)] + assertMatch(arr[0], [1, 2, 3]) + assertMatch(arr[1], [4, 5, 6]) + + const arr2 = clone(arr) + arr2[0][0] = Uint64(10) + assertMatch(arr[0], [1, 2, 3]) + assertMatch(arr2[0], [10, 2, 3]) + + const arr3: Readonly[]> = [] + assertMatch(arr3, []) + }) + + it('can be stored in native tuple', () => { + const a: [FixedArray, string] = [new FixedArray(1, 2, 3), 'hello'] + assertMatch(a[0], [1, 2, 3]) + + const b = clone(a) + b[0][0] = Uint64(10) + assertMatch(a[0], [1, 2, 3]) + assertMatch(b[0], [10, 2, 3]) + + const c: [FixedArray, string] = [new FixedArray(), 'hello'] + assertMatch(c[0], [0, 0, 0]) + }) + + it('can be stored in native readonly tuple', () => { + const a: readonly [FixedArray, string] = [new FixedArray(1, 2, 3), 'hello'] + assertMatch(a[0], [1, 2, 3]) + + const b = clone(a) + b[0][0] = Uint64(10) + assertMatch(a[0], [1, 2, 3]) + assertMatch(b[0], [10, 2, 3]) + + const c = [new FixedArray(), 'hello'] as const + assertMatch(c[0], [0, 0, 0]) + }) + }) + + describe('store other collections in fixed array', () => { + it('stores object in fixed array', () => { + type Point = { x: uint64; y: uint64 } + const a1 = new FixedArray({ x: Uint64(1), y: Uint64(2) }, { x: Uint64(3), y: Uint64(4) }, { x: Uint64(5), y: Uint64(6) }) + assertMatch(a1, [ + { x: 1, y: 2 }, + { x: 3, y: 4 }, + { x: 5, y: 6 }, + ]) + + const a2 = new FixedArray() + assertMatch(a2, [ + { x: 0, y: 0 }, + { x: 0, y: 0 }, + ]) + + const a3 = clone(a1) + a3[2] = a2.at(-1) + assertMatch(a3, [ + { x: 1, y: 2 }, + { x: 3, y: 4 }, + { x: 0, y: 0 }, + ]) + assertMatch(a1, [ + { x: 1, y: 2 }, + { x: 3, y: 4 }, + { x: 5, y: 6 }, + ]) + }) + + it('stores readonly object in fixed array', () => { + type Point = Readonly<{ x: uint64; y: uint64 }> + const a1 = new FixedArray({ x: Uint64(1), y: Uint64(2) }, { x: Uint64(3), y: Uint64(4) }, { x: Uint64(5), y: Uint64(6) }) + assertMatch(a1, [ + { x: 1, y: 2 }, + { x: 3, y: 4 }, + { x: 5, y: 6 }, + ]) + + const a2 = new FixedArray() + assertMatch(a2, [ + { x: 0, y: 0 }, + { x: 0, y: 0 }, + ]) + + const a3 = clone(a1) + a3[2] = a2.at(-1) + assertMatch(a3, [ + { x: 1, y: 2 }, + { x: 3, y: 4 }, + { x: 0, y: 0 }, + ]) + assertMatch(a1, [ + { x: 1, y: 2 }, + { x: 3, y: 4 }, + { x: 5, y: 6 }, + ]) + }) + + it('stores native array in fixed array', () => { + const a1 = new FixedArray([1], [2, 3], [4]) + assertMatch(a1, [[1], [2, 3], [4]]) + + const a2 = clone(a1) + a2[1][1] = a1[1][1] * 10 + assertMatch(a1, [[1], [2, 3], [4]]) + assertMatch(a2, [[1], [2, 30], [4]]) + }) + + it('stores native readonly array in fixed array', () => { + const a1 = new FixedArray, 3>([1], [2, 3], [4]) + assertMatch(a1, [[1], [2, 3], [4]]) + + const a2 = clone(a1) + a2[1] = [2, 30] + assertMatch(a1, [[1], [2, 3], [4]]) + assertMatch(a2, [[1], [2, 30], [4]]) + }) + + it('store native tuple in fixed array', () => { + const a1 = new FixedArray<[uint64, string, bytes], 2>([1, 'a', Bytes('x')], [2, 'b', Bytes('y')]) + assertMatch(a1, [ + [1, 'a', Bytes('x')], + [2, 'b', Bytes('y')], + ]) + + const a2 = clone(a1) + a2[1] = [20, 'hello', Bytes('world')] + assertMatch(a1, [ + [1, 'a', Bytes('x')], + [2, 'b', Bytes('y')], + ]) + assertMatch(a2, [ + [1, 'a', Bytes('x')], + [20, 'hello', Bytes('world')], + ]) + + const a3 = new FixedArray<[uint64, uint64], 2>() + assertMatch(a3, [ + [0, 0], + [0, 0], + ]) + }) + + it('store native readonly tuple in fixed array', () => { + const a1 = new FixedArray, 2>([1, 'a', Bytes('x')], [2, 'b', Bytes('y')]) + assertMatch(a1, [ + [1, 'a', Bytes('x')], + [2, 'b', Bytes('y')], + ]) + + const a2 = clone(a1) + a2[1] = [20, 'hello', Bytes('world')] + assertMatch(a1, [ + [1, 'a', Bytes('x')], + [2, 'b', Bytes('y')], + ]) + assertMatch(a2, [ + [1, 'a', Bytes('x')], + [20, 'hello', Bytes('world')], + ]) + + const a3 = new FixedArray() + assertMatch(a3, [ + [0, 0], + [0, 0], + ]) + }) + }) + + describe('method selector', () => { + it('should return correct method selector for fixed array method', () => { + expect(methodSelector(TestContract.prototype.fixedArrayNative)).toEqual( + methodSelector('fixedArrayNative(uint64[10],string)(uint64[2],string)'), + ) + expect(methodSelector(TestContract.prototype.fixedArrayArc4)).toEqual( + methodSelector('fixedArrayArc4(uint8[10],string)(uint8[2],string)'), + ) + }) + }) +}) diff --git a/tests/global-state-arc4-values.spec.ts b/tests/global-state-arc4-values.spec.ts index 12a6f45a..eec24bc4 100644 --- a/tests/global-state-arc4-values.spec.ts +++ b/tests/global-state-arc4-values.spec.ts @@ -31,7 +31,7 @@ describe('ARC4 AppGlobal values', async () => { ctx.reset() }) - const testData = ['_implicit_key', ''].flatMap((implicit) => [ + const testData: DeliberateAny[] = ['_implicit_key', ''].flatMap((implicit) => [ { nativeValue: 42, abiValue: new UintN<64>(42), @@ -106,6 +106,8 @@ describe('ARC4 AppGlobal values', async () => { expect(arc4Value.native).toEqual(expectedValue) }, }, + ]) + testData.push( { nativeValue: [21, asUint8Array(Bytes('Hello')), true], abiValue: [Uint64(21), Bytes('Hello'), true], @@ -122,7 +124,7 @@ describe('ARC4 AppGlobal values', async () => { expect(value).toEqual(expectedValue) }, }, - ]) + ) test.for(testData)('should be able to get arc4 state values', async (data, { appClientGlobalStateContract: appClient, testAccount }) => { ctx.defaultSender = Bytes.fromBase32(testAccount.addr.toString()) diff --git a/tests/local-state-arc4-values.spec.ts b/tests/local-state-arc4-values.spec.ts index 05be6e35..2f7c767e 100644 --- a/tests/local-state-arc4-values.spec.ts +++ b/tests/local-state-arc4-values.spec.ts @@ -32,7 +32,7 @@ describe('ARC4 AppLocal values', async () => { ctx.reset() }) - const testData = ['_implicit_key', ''].flatMap((implicit) => [ + const testData: DeliberateAny[] = ['_implicit_key', ''].flatMap((implicit) => [ { methodName: `get${implicit}_arc4_uintn64`, assert: (value: ARC4Encoded, expectedValue: DeliberateAny) => { @@ -93,6 +93,8 @@ describe('ARC4 AppLocal values', async () => { expect(arc4Value.native).toEqual(expectedValue) }, }, + ]) + testData.push( { methodName: `get_implicit_key_tuple`, assert: (value: DeliberateAny, expectedValue: DeliberateAny) => { @@ -105,7 +107,7 @@ describe('ARC4 AppLocal values', async () => { expect(value).toEqual(expectedValue) }, }, - ]) + ) test.for(testData)('should be able to get arc4 state values', async (data, { appClientLocalStateContract: appClient, testAccount }) => { const defaultSenderAccountAddress = Bytes.fromBase32(testAccount.addr.toString()) diff --git a/tests/mutable-object.spec.ts b/tests/mutable-object.spec.ts deleted file mode 100644 index 9eb99692..00000000 --- a/tests/mutable-object.spec.ts +++ /dev/null @@ -1,314 +0,0 @@ -import { clone, MutableObject } from '@algorandfoundation/algorand-typescript' -import { Bool, DynamicArray, StaticArray, Str, Tuple, UintN } from '@algorandfoundation/algorand-typescript/arc4' -import { encodingUtil } from '@algorandfoundation/puya-ts' -import { describe, expect, it, test } from 'vitest' -import { AccountCls } from '../src/impl/reference' -import type { DeliberateAny } from '../src/typescript-helpers' - -const nativeString = 'hello' -const nativeNumber = 42 -const nativeBool = true - -const abiString = new Str('hello') -const abiUint64 = new UintN<64>(42) -const abiBool = new Bool(true) - -class Swapped1 extends MutableObject<{ - b: UintN<64> - c: Bool - d: Str - a: Tuple<[UintN<64>, Bool, Bool]> -}> {} - -class Swapped2 extends MutableObject<{ - b: UintN<64> - c: Bool - d: Str - a: Tuple<[Tuple<[UintN<64>, Bool, Bool]>, Tuple<[UintN<64>, Bool, Bool]>]> -}> {} - -class Swapped3 extends MutableObject<{ - b: UintN<64> - c: Bool - d: Str - a: Tuple<[DynamicArray, DynamicArray, Str, UintN<64>, Bool, StaticArray, 3>]> -}> {} - -class Swapped4 extends MutableObject<{ - b: UintN<64> - c: Bool - d: Str - a: Tuple<[Tuple<[Bool, DynamicArray, Str]>, UintN<64>, StaticArray, 3>]> -}> {} - -class Swapped5 extends MutableObject<{ - b: UintN<64> - c: Bool - d: Str - a: Tuple<[Tuple<[Bool, DynamicArray, Str]>, Tuple<[UintN<64>, StaticArray, 3>]>]> -}> {} - -class Swapped6 extends MutableObject<{ - b: UintN<64> - c: Bool - d: Str - a: Tuple<[Tuple<[Bool, Tuple<[DynamicArray, Str]>]>, Tuple<[UintN<64>, StaticArray, 3>]>]> -}> {} - -const testData = [ - { - abiTypeString: '(uint64,bool,string,(uint64,bool,bool))', - nativeValues() { - return [nativeNumber, nativeBool, nativeString, [nativeNumber, nativeBool, nativeBool]] - }, - abiValues() { - return { b: abiUint64, c: abiBool, d: abiString, a: new Tuple(abiUint64, abiBool, abiBool) } as Swapped1 - }, - create() { - return new Swapped1(this.abiValues()) - }, - clone(obj: Swapped1) { - return clone(obj) - }, - }, - { - abiTypeString: '(uint64,bool,string,((uint64,bool,bool),(uint64,bool,bool)))', - nativeValues() { - return [ - nativeNumber, - nativeBool, - nativeString, - [ - [nativeNumber, nativeBool, nativeBool], - [nativeNumber, nativeBool, nativeBool], - ], - ] - }, - abiValues() { - return { - b: abiUint64, - c: abiBool, - d: abiString, - a: new Tuple(new Tuple(abiUint64, abiBool, abiBool), new Tuple(abiUint64, abiBool, abiBool)), - } as Swapped2 - }, - create() { - return new Swapped2(this.abiValues()) - }, - clone(obj: Swapped2) { - return clone(obj) - }, - }, - { - abiTypeString: '(uint64,bool,string,(string[],string[],string,uint64,bool,uint64[3]))', - nativeValues() { - return [ - nativeNumber, - nativeBool, - nativeString, - [ - [nativeString, nativeString], - [nativeString, nativeString], - nativeString, - nativeNumber, - nativeBool, - [nativeNumber, nativeNumber, nativeNumber], - ], - ] - }, - abiValues() { - return { - b: abiUint64, - c: abiBool, - d: abiString, - a: new Tuple( - new DynamicArray(abiString, abiString), - new DynamicArray(abiString, abiString), - abiString, - abiUint64, - abiBool, - new StaticArray, 3>(abiUint64, abiUint64, abiUint64), - ), - } as Swapped3 - }, - create() { - return new Swapped3(this.abiValues()) - }, - clone(obj: Swapped3) { - return clone(obj) - }, - }, - { - abiTypeString: '(uint64,bool,string,((bool,string[],string),uint64,uint64[3]))', - nativeValues() { - return [ - nativeNumber, - nativeBool, - nativeString, - [[nativeBool, [nativeString, nativeString], nativeString], nativeNumber, [nativeNumber, nativeNumber, nativeNumber]], - ] - }, - abiValues() { - return { - b: abiUint64, - c: abiBool, - d: abiString, - a: new Tuple( - new Tuple(abiBool, new DynamicArray(abiString, abiString), abiString), - abiUint64, - new StaticArray, 3>(abiUint64, abiUint64, abiUint64), - ), - } as Swapped4 - }, - create() { - return new Swapped4(this.abiValues()) - }, - clone(obj: Swapped4) { - return clone(obj) - }, - }, - { - abiTypeString: '(uint64,bool,string,((bool,string[],string),(uint64,uint64[3])))', - nativeValues() { - return [ - nativeNumber, - nativeBool, - nativeString, - [ - [nativeBool, [nativeString, nativeString], nativeString], - [nativeNumber, [nativeNumber, nativeNumber, nativeNumber]], - ], - ] - }, - abiValues() { - return { - b: abiUint64, - c: abiBool, - d: abiString, - a: new Tuple( - new Tuple(abiBool, new DynamicArray(abiString, abiString), abiString), - new Tuple(abiUint64, new StaticArray, 3>(abiUint64, abiUint64, abiUint64)), - ), - } as Swapped5 - }, - create() { - return new Swapped5(this.abiValues()) - }, - clone(obj: Swapped5) { - return clone(obj) - }, - }, - { - abiTypeString: '(uint64,bool,string,((bool,(string[],string)),(uint64,uint64[3])))', - nativeValues() { - return [ - nativeNumber, - nativeBool, - nativeString, - [ - [nativeBool, [[nativeString, nativeString], nativeString]], - [nativeNumber, [nativeNumber, nativeNumber, nativeNumber]], - ], - ] - }, - abiValues() { - return { - b: abiUint64, - c: abiBool, - d: abiString, - a: new Tuple( - new Tuple(abiBool, new Tuple(new DynamicArray(abiString, abiString), abiString)), - new Tuple(abiUint64, new StaticArray, 3>(abiUint64, abiUint64, abiUint64)), - ), - } as Swapped6 - }, - create() { - return new Swapped6(this.abiValues()) - }, - clone(obj: Swapped6) { - return clone(obj) - }, - }, -] - -describe('mutable object', async () => { - test.each(testData)('create mutable object', async (data) => { - const nativeValues = data.nativeValues() - const result = data.create() - - let i = 0 - compareARC4AndABIValue(result.b, nativeValues[i++]) - compareARC4AndABIValue(result.c, nativeValues[i++]) - compareARC4AndABIValue(result.d, nativeValues[i++]) - compareARC4AndABIValue(result.a, nativeValues[i++]) - }) - - test.each(testData)('clone mutable object', async (data) => { - const result = data.create() - const result2 = data.clone(result as DeliberateAny) - - compare(result.b, result2.b) - compare(result.c, result2.c) - compare(result.d, result2.d) - compare(result.a, result2.a) - }) - - it('set item in mutable object', async () => { - const data = testData[5] - - const nativeValues = data.nativeValues() as DeliberateAny - nativeValues[0] = 43 - nativeValues[2] = 'world' - nativeValues[3][0][1][0][1] = 'hello, world' - nativeValues[3][0][1][0].push('test') - nativeValues[3][1][1][0] = 24 - - const abiValues = data.create() as Swapped6 - abiValues.b = new UintN<64>(43) - abiValues.d = new Str('world') - abiValues.a.at(0).at(1).at(0)[1] = new Str('hello, world') - abiValues.a.at(0).at(1).at(0).push(new Str('test')) - abiValues.a.at(1).at(1)[0] = new UintN<64>(24) - - let i = 0 - compareARC4AndABIValue(abiValues.b, nativeValues[i++]) - compareARC4AndABIValue(abiValues.c, nativeValues[i++]) - compareARC4AndABIValue(abiValues.d, nativeValues[i++]) - compareARC4AndABIValue(abiValues.a, nativeValues[i++]) - }) -}) - -const compareARC4AndABIValue = (arc4Value: DeliberateAny, nativeValue: DeliberateAny) => { - if (arc4Value instanceof StaticArray || arc4Value instanceof DynamicArray) { - for (let i = 0; i < arc4Value.length; i++) { - compareARC4AndABIValue(arc4Value[i], nativeValue[i]) - } - } else if (arc4Value instanceof Tuple) { - const tupleValues = arc4Value.native - for (let i = 0; i < arc4Value.length; i++) { - compareARC4AndABIValue(tupleValues[i], nativeValue[i]) - } - } else if (arc4Value.native !== undefined) { - if (arc4Value.native instanceof AccountCls) { - expect(arc4Value.native.bytes).toEqual(nativeValue) - } else { - expect(arc4Value.native).toEqual(nativeValue) - } - } else { - expect(arc4Value.bytes).toEqual(encodingUtil.bigIntToUint8Array(arc4Value, arc4Value.bytes.length)) - } -} - -const compare = (value1: DeliberateAny, value2: DeliberateAny) => { - if (value1 instanceof StaticArray || value1 instanceof DynamicArray) { - for (let i = 0; i < value1.length; i++) { - compare(value1[i], value2[i]) - } - } else if (value1 instanceof Tuple) { - for (let i = 0; i < value1.length; i++) { - compare(value1.at(i), value2.at(i)) - } - } else { - expect(value1.bytes).toEqual(value2.bytes) - } -} diff --git a/tests/native-mutable-array.spec.ts b/tests/native-mutable-array.spec.ts new file mode 100644 index 00000000..18131ff5 --- /dev/null +++ b/tests/native-mutable-array.spec.ts @@ -0,0 +1,307 @@ +import type { bytes, uint64 } from '@algorandfoundation/algorand-typescript' +import { + arc4, + assertMatch, + Box, + BoxMap, + Bytes, + clone, + Contract, + FixedArray, + Global, + GlobalState, + LocalState, + Uint64, +} from '@algorandfoundation/algorand-typescript' +import { TestExecutionContext } from '@algorandfoundation/algorand-typescript-testing' +import { methodSelector } from '@algorandfoundation/algorand-typescript/arc4' +import { describe, expect, it } from 'vitest' + +class TestContract extends Contract { + nativeArrayMethod(a: uint64[], b: string): readonly [uint64[], string] { + return [a.slice(0, 2), b] + } + arc4ArrayMethod(a: arc4.UintN8[], b: arc4.Str): readonly [arc4.UintN8[], arc4.Str] { + return [a.slice(0, 2), b] + } +} + +describe('native mutable array', () => { + describe('copy()', () => { + it('creates a deep copy', () => { + const original: uint64[][] = [[1, 2, 3]] + const copy = clone(original) + assertMatch(original, copy) + + copy[0][0] = 10 + expect(original[0][0]).toEqual(1) + expect(copy[0][0]).toEqual(10) + }) + }) + + describe('store in state', () => { + const ctx = new TestExecutionContext() + + it('stores native array in state', () => { + ctx.txn.createScope([ctx.any.txn.applicationCall()]).execute(() => { + const g1 = GlobalState({ key: 'g1', initialValue: [1, 2, 3] as uint64[] }) + assertMatch(g1.value, [1, 2, 3]) + + const l1 = LocalState({ key: 'l1' }) + l1(Global.zeroAddress).value = [Uint64(4), Uint64(5), Uint64(6)] + assertMatch(l1(Global.zeroAddress).value, [4, 5, 6]) + + const b1 = Box({ key: 'b1' }) + b1.value = [100, 200, 300] + assertMatch(b1.value, [100, 200, 300]) + + const b2 = BoxMap({ keyPrefix: 'b2' }) + b2('key1').value = [7, 8, 9] + assertMatch(b2('key1').value, [7, 8, 9]) + }) + }) + }) + + describe('store arc4 value in native array', () => { + it('can store primitive arc4 values', () => { + const a1: arc4.UintN64[] = [new arc4.UintN64(1), new arc4.UintN64(2), new arc4.UintN64(3)] + assertMatch(a1, [new arc4.UintN64(1), new arc4.UintN64(2), new arc4.UintN64(3)]) + + const a2 = clone(a1) + a2[0] = new arc4.UintN64(10) + assertMatch(a1, [new arc4.UintN64(1), new arc4.UintN64(2), new arc4.UintN64(3)]) + assertMatch(a2, [new arc4.UintN64(10), new arc4.UintN64(2), new arc4.UintN64(3)]) + + const a3: arc4.UintN64[] = [] + assertMatch(a3, []) + }) + + it('can store arc4 dynamic array', () => { + const a1: arc4.DynamicArray[] = [new arc4.DynamicArray(new arc4.UintN64(1), new arc4.UintN64(2), new arc4.UintN64(3))] + assertMatch(a1[0], [new arc4.UintN64(1), new arc4.UintN64(2), new arc4.UintN64(3)]) + + const a2 = clone(a1) + a2[0][0] = new arc4.UintN64(10) + assertMatch(a1[0], [new arc4.UintN64(1), new arc4.UintN64(2), new arc4.UintN64(3)]) + assertMatch(a2[0], [new arc4.UintN64(10), new arc4.UintN64(2), new arc4.UintN64(3)]) + }) + + it('can store arc4 static array', () => { + const a1: arc4.StaticArray[] = [new arc4.StaticArray(new arc4.UintN64(1), new arc4.UintN64(2), new arc4.UintN64(3))] + assertMatch(a1[0], [new arc4.UintN64(1), new arc4.UintN64(2), new arc4.UintN64(3)]) + + const a2 = clone(a1) + a2[0][0] = new arc4.UintN64(10) + assertMatch(a1[0], [new arc4.UintN64(1), new arc4.UintN64(2), new arc4.UintN64(3)]) + assertMatch(a2[0], [new arc4.UintN64(10), new arc4.UintN64(2), new arc4.UintN64(3)]) + }) + + it('can store arc4 tuple', () => { + const a1: arc4.Tuple[] = [new arc4.Tuple(new arc4.UintN64(1), new arc4.Str('Hello'))] + assertMatch(a1[0].native, [new arc4.UintN64(1), new arc4.Str('Hello')]) + + const a2 = clone(a1) + a2[0] = new arc4.Tuple(new arc4.UintN64(10), new arc4.Str('hello')) + assertMatch(a1[0].native, [new arc4.UintN64(1), new arc4.Str('Hello')]) + assertMatch(a2[0].native, [new arc4.UintN64(10), new arc4.Str('hello')]) + }) + }) + + describe('store native array in other collections', () => { + it('can be stored in object', () => { + const obj = { arr: [1, 2, 3] as uint64[], str: 'hello' } + assertMatch(obj.arr, [1, 2, 3]) + + const obj2 = clone(obj) + obj2.arr[0] = Uint64(10) + assertMatch(obj.arr, [1, 2, 3]) + assertMatch(obj2.arr, [10, 2, 3]) + + const obj3 = { arr: [] as uint64[], str: 'world' } + assertMatch(obj3, { arr: [], str: 'world' }) + }) + + it('can be stored in readonly object', () => { + const obj: Readonly<{ arr: uint64[]; str: string }> = { arr: [1, 2, 3], str: 'hello' } + assertMatch(obj.arr, [1, 2, 3]) + + const obj2 = clone(obj) + obj2.arr[0] = Uint64(10) + assertMatch(obj.arr, [1, 2, 3]) + assertMatch(obj2.arr, [10, 2, 3]) + + const obj3 = { arr: [] as uint64[], str: 'world' } as const + assertMatch(obj3, { arr: [], str: 'world' }) + }) + + it('can be stored in FixedArray', () => { + const arr = new FixedArray([1, 2, 3], [4, 5, 6]) + assertMatch(arr[0], [1, 2, 3]) + assertMatch(arr[1], [4, 5, 6]) + + const arr2 = clone(arr) + arr2[0][0] = Uint64(10) + assertMatch(arr[0], [1, 2, 3]) + assertMatch(arr2[0], [10, 2, 3]) + }) + + it('can be stored in native tuple', () => { + const a: [uint64[], string] = [[1, 2, 3], 'hello'] + assertMatch(a[0], [1, 2, 3]) + + const b = clone(a) + b[0][0] = Uint64(10) + assertMatch(a[0], [1, 2, 3]) + assertMatch(b[0], [10, 2, 3]) + + const c: [uint64[], string] = [[], 'hello'] + assertMatch(c[0], []) + }) + + it('can be stored in native readonly tuple', () => { + const a: readonly [uint64[], string] = [[1, 2, 3], 'hello'] + assertMatch(a[0], [1, 2, 3]) + + const b = clone(a) + b[0][0] = Uint64(10) + assertMatch(a[0], [1, 2, 3]) + assertMatch(b[0], [10, 2, 3]) + + const c = [[] as uint64[], 'hello'] as const + assertMatch(c[0], []) + }) + }) + + describe('store other collections in native array', () => { + it('stores object in native array', () => { + type Point = { x: uint64; y: uint64 } + const a1: Point[] = [ + { x: Uint64(1), y: Uint64(2) }, + { x: Uint64(3), y: Uint64(4) }, + { x: Uint64(5), y: Uint64(6) }, + ] + assertMatch(a1, [ + { x: 1, y: 2 }, + { x: 3, y: 4 }, + { x: 5, y: 6 }, + ]) + + const a2: Point[] = [] + assertMatch(a2, []) + + const a3 = clone(a1) + a3[2] = { x: 0, y: 0 } + assertMatch(a3, [ + { x: 1, y: 2 }, + { x: 3, y: 4 }, + { x: 0, y: 0 }, + ]) + assertMatch(a1, [ + { x: 1, y: 2 }, + { x: 3, y: 4 }, + { x: 5, y: 6 }, + ]) + }) + + it('stores readonly object in native array', () => { + type Point = Readonly<{ x: uint64; y: uint64 }> + const a1: Point[] = [ + { x: Uint64(1), y: Uint64(2) }, + { x: Uint64(3), y: Uint64(4) }, + { x: Uint64(5), y: Uint64(6) }, + ] + assertMatch(a1, [ + { x: 1, y: 2 }, + { x: 3, y: 4 }, + { x: 5, y: 6 }, + ]) + + const a2: Point[] = [] + assertMatch(a2, []) + + const a3 = clone(a1) + a3[2] = { x: 0, y: 0 } + assertMatch(a3, [ + { x: 1, y: 2 }, + { x: 3, y: 4 }, + { x: 0, y: 0 }, + ]) + assertMatch(a1, [ + { x: 1, y: 2 }, + { x: 3, y: 4 }, + { x: 5, y: 6 }, + ]) + }) + + it('stores FixedArray in native array', () => { + const a1: FixedArray[] = [new FixedArray(1, 2), new FixedArray(3, 4)] + assertMatch(a1, [new FixedArray(1, 2), new FixedArray(3, 4)]) + + const a2 = clone(a1) + a2[1][1] = 40 + assertMatch(a1, [new FixedArray(Uint64(1), Uint64(2)), new FixedArray(Uint64(3), Uint64(4))]) + assertMatch(a2, [new FixedArray(Uint64(1), Uint64(2)), new FixedArray(Uint64(3), Uint64(40))]) + }) + + it('store native tuple in native array', () => { + const a1: [uint64, string, bytes][] = [ + [1, 'a', Bytes('x')], + [2, 'b', Bytes('y')], + ] + assertMatch(a1, [ + [1, 'a', Bytes('x')], + [2, 'b', Bytes('y')], + ]) + + const a2 = clone(a1) + a2[1] = [20, 'hello', Bytes('world')] + assertMatch(a1, [ + [1, 'a', Bytes('x')], + [2, 'b', Bytes('y')], + ]) + assertMatch(a2, [ + [1, 'a', Bytes('x')], + [20, 'hello', Bytes('world')], + ]) + + const a3: [uint64, uint64][] = [] + assertMatch(a3, []) + }) + + it('store native readonly tuple in native array', () => { + const a1: Readonly<[uint64, string, bytes]>[] = [ + [1, 'a', Bytes('x')], + [2, 'b', Bytes('y')], + ] + assertMatch(a1, [ + [1, 'a', Bytes('x')], + [2, 'b', Bytes('y')], + ]) + + const a2 = clone(a1) + a2[1] = [20, 'hello', Bytes('world')] + assertMatch(a1, [ + [1, 'a', Bytes('x')], + [2, 'b', Bytes('y')], + ]) + assertMatch(a2, [ + [1, 'a', Bytes('x')], + [20, 'hello', Bytes('world')], + ]) + + const a3: readonly [uint64, uint64][] = [] + assertMatch(a3, []) + }) + }) + + describe('method selector', () => { + it('should return correct method selector for native array method', () => { + expect(methodSelector(TestContract.prototype.nativeArrayMethod)).toEqual( + methodSelector('nativeArrayMethod(uint64[],string)(uint64[],string)'), + ) + expect(methodSelector(TestContract.prototype.arc4ArrayMethod)).toEqual( + methodSelector('arc4ArrayMethod(uint8[],string)(uint8[],string)'), + ) + }) + }) +}) diff --git a/tests/native-mutable-object.spec.ts b/tests/native-mutable-object.spec.ts new file mode 100644 index 00000000..82078809 --- /dev/null +++ b/tests/native-mutable-object.spec.ts @@ -0,0 +1,583 @@ +import type { bytes, uint64 } from '@algorandfoundation/algorand-typescript' +import { + arc4, + assertMatch, + Box, + BoxMap, + Bytes, + clone, + Contract, + FixedArray, + Global, + GlobalState, + LocalState, +} from '@algorandfoundation/algorand-typescript' +import { TestExecutionContext } from '@algorandfoundation/algorand-typescript-testing' +import { methodSelector } from '@algorandfoundation/algorand-typescript/arc4' +import { describe, expect, it } from 'vitest' + +type SimpleObj = { + a: uint64 + b: boolean + c: string + d: bytes +} + +type NestedObj = { + a: uint64 + b: boolean + c: string + d: { + x: uint64 + y: string + z: boolean + } +} + +type NestedReadonlyObj = { + a: uint64 + b: boolean + c: string + d: Readonly<{ + x: uint64 + y: string + z: boolean + }> +} + +type ArrayObj = { + a: uint64 + b: boolean + c: string + d: uint64[] +} + +type ReadonlyArrayObj = { + a: uint64 + b: boolean + c: string + d: readonly uint64[] +} + +type FixedArrayObj = { + a: uint64 + b: boolean + c: string + d: FixedArray +} + +type TupleObj = { + a: uint64 + b: boolean + c: string + d: [uint64, string, boolean] +} + +type ReadonlyTupleObj = { + a: uint64 + b: boolean + c: string + d: readonly [uint64, string, boolean] +} + +type DeepNestedObj = { + a: uint64 + b: { + x: { + p: uint64 + q: string + } + y: string[] + z: [uint64, boolean] + } + c: string +} + +type Arc4PrimitiveObj = { + a: arc4.UintN64 + b: arc4.Bool + c: arc4.Str + d: arc4.Byte +} + +type Arc4DynamicArrayObj = { + a: uint64 + b: arc4.DynamicArray + c: string +} + +type Arc4StaticArrayObj = { + a: uint64 + b: arc4.StaticArray + c: string +} + +type Arc4TupleObj = { + a: uint64 + b: arc4.Tuple + c: string +} + +class TestContract extends Contract { + nativeMutableObj(obj: SimpleObj): SimpleObj { + return obj + } + + arc4PrimitiveMutableObj(obj: Arc4PrimitiveObj): Arc4PrimitiveObj { + return obj + } +} + +describe('native mutable object', () => { + const ctx = new TestExecutionContext() + + describe('set properties', () => { + it('set properties in simple mutable object', () => { + const obj: SimpleObj = { a: 1, b: true, c: 'hello', d: Bytes('world') } + + const obj2 = clone(obj) + obj2.a = 42 + obj2.b = false + obj2.c = 'goodbye' + obj2.d = Bytes('universe') + + assertMatch(obj, { + a: 1, + b: true, + c: 'hello', + d: Bytes('world'), + }) + assertMatch(obj2, { + a: 42, + b: false, + c: 'goodbye', + d: Bytes('universe'), + }) + }) + + it('set properties in nested mutable object', () => { + const obj: NestedObj = { + a: 1, + b: true, + c: 'hello', + d: { x: 10, y: 'test', z: false }, + } + + const obj2 = clone(obj) + obj2.a = 42 + obj2.d.x = 100 + obj2.d.y = 'modified' + obj2.d.z = true + + assertMatch(obj, { + a: 1, + b: true, + c: 'hello', + d: { x: 10, y: 'test', z: false }, + }) + assertMatch(obj2, { + a: 42, + b: true, + c: 'hello', + d: { x: 100, y: 'modified', z: true }, + }) + }) + + it('set properties in nested readonly object', () => { + const obj: NestedReadonlyObj = { + a: 1, + b: true, + c: 'hello', + d: { x: 10, y: 'test', z: false }, + } + + const obj2 = clone(obj) + obj2.a = 42 + obj2.d = { + x: 100, + y: 'modified', + z: true, + } + + assertMatch(obj, { + a: 1, + b: true, + c: 'hello', + d: { x: 10, y: 'test', z: false }, + }) + assertMatch(obj2, { + a: 42, + b: true, + c: 'hello', + d: { x: 100, y: 'modified', z: true }, + }) + }) + + it('set array property in mutable object', () => { + const obj: ArrayObj = { a: 1, b: true, c: 'hello', d: [10, 20, 30] } + + const obj2 = clone(obj) + obj2.d[0] = 1 + obj2.d[1] = 2 + obj2.d.pop() + + assertMatch(obj.d, [10, 20, 30]) + assertMatch(obj2.d, [1, 2]) + }) + + it('set readonly array property in mutable object', () => { + const obj: ReadonlyArrayObj = { a: 1, b: true, c: 'hello', d: [10, 20, 30] } + + const obj2 = clone(obj) + obj2.d = [1, 2] + + assertMatch(obj.d, [10, 20, 30]) + assertMatch(obj2.d, [1, 2]) + }) + + it('set items in fixed array mutable object', () => { + const obj: FixedArrayObj = { + a: 1, + b: true, + c: 'hello', + d: new FixedArray(10, 20, 30), + } + + const obj2 = clone(obj) + obj2.d = new FixedArray(100, 20, 300) + + assertMatch(obj.d, [10, 20, 30]) + assertMatch(obj2.d, [100, 20, 300]) + }) + + it('set items in tuple mutable object', () => { + const obj: TupleObj = { + a: 1, + b: true, + c: 'hello', + d: [10, 'test', false] as [uint64, string, boolean], + } + + const obj2 = clone(obj) + obj2.d[0] = 100 + obj2.d[1] = 'modified' + obj2.d[2] = true + + assertMatch(obj.d, [10, 'test', false]) + assertMatch(obj2.d, [100, 'modified', true]) + }) + + it('set items in readonly tuple mutable object', () => { + const obj: ReadonlyTupleObj = { + a: 1, + b: true, + c: 'hello', + d: [10, 'test', false], + } + + const obj2 = clone(obj) + obj2.d = [100, 'modified', true] as [uint64, string, boolean] + + assertMatch(obj.d, [10, 'test', false]) + assertMatch(obj2.d, [100, 'modified', true]) + }) + + it('set items in deep nested mutable object', () => { + const obj: DeepNestedObj = { + a: 1, + b: { + x: { p: 10, q: 'test' }, + y: ['hello', 'world'], + z: [20, false] as [uint64, boolean], + }, + c: 'root', + } + const obj2 = clone(obj) + + obj2.a = 42 + obj2.b.x.p = 100 + obj2.b.x.q = 'modified' + obj2.b.y[0] = 'goodbye' + obj2.b.y.push('universe') + obj2.b.z[0] = 200 + obj2.b.z[1] = true + obj2.c = 'updated' + + assertMatch(obj, { + a: 1, + b: { + x: { p: 10, q: 'test' }, + y: ['hello', 'world'], + z: [20, false] as [uint64, boolean], + }, + c: 'root', + }) + assertMatch(obj2, { + a: 42, + b: { + x: { p: 100, q: 'modified' }, + y: ['goodbye', 'world', 'universe'], + z: [200, true] as [uint64, boolean], + }, + c: 'updated', + }) + }) + }) + + describe('store arc4 value', () => { + it('can store primitive arc4 values', () => { + const obj: Arc4PrimitiveObj = { + a: new arc4.UintN64(42), + b: new arc4.Bool(true), + c: new arc4.Str('hello'), + d: new arc4.Byte(125), + } + + const obj2 = clone(obj) + obj2.a = new arc4.UintN64(100) + obj2.b = new arc4.Bool(false) + obj2.c = new arc4.Str('world') + obj2.d = new arc4.Byte(42) + + assertMatch(obj, { + a: new arc4.UintN64(42), + b: new arc4.Bool(true), + c: new arc4.Str('hello'), + d: new arc4.Byte(125), + }) + assertMatch(obj2, { + a: new arc4.UintN64(100), + b: new arc4.Bool(false), + c: new arc4.Str('world'), + d: new arc4.Byte(42), + }) + }) + + it('can store arc4 dynamic array', () => { + const obj: Arc4DynamicArrayObj = { + a: 42, + b: new arc4.DynamicArray(new arc4.UintN64(10), new arc4.UintN64(20), new arc4.UintN64(30)), + c: 'test', + } + + const obj2 = clone(obj) + obj2.b[0] = new arc4.UintN64(100) + obj2.b.push(new arc4.UintN64(40)) + + assertMatch(obj.b, [new arc4.UintN64(10), new arc4.UintN64(20), new arc4.UintN64(30)]) + assertMatch(obj2.b, [new arc4.UintN64(100), new arc4.UintN64(20), new arc4.UintN64(30), new arc4.UintN64(40)]) + }) + + it('can store arc4 static array', () => { + const obj: Arc4StaticArrayObj = { + a: 42, + b: new arc4.StaticArray(new arc4.UintN64(10), new arc4.UintN64(20), new arc4.UintN64(30)), + c: 'test', + } + + const obj2 = clone(obj) + obj2.b[0] = new arc4.UintN64(100) + obj2.b[2] = new arc4.UintN64(300) + + assertMatch(obj.b, [new arc4.UintN64(10), new arc4.UintN64(20), new arc4.UintN64(30)]) + assertMatch(obj2.b, [new arc4.UintN64(100), new arc4.UintN64(20), new arc4.UintN64(300)]) + }) + + it('can store arc4 tuple', () => { + const obj: Arc4TupleObj = { + a: 42, + b: new arc4.Tuple(new arc4.UintN64(10), new arc4.Str('hello'), new arc4.Bool(true)), + c: 'test', + } + + const obj2 = clone(obj) + obj2.b = new arc4.Tuple(new arc4.UintN64(100), new arc4.Str('world'), new arc4.Bool(false)) + + assertMatch(obj.b.native, [new arc4.UintN64(10), new arc4.Str('hello'), new arc4.Bool(true)]) + assertMatch(obj2.b.native, [new arc4.UintN64(100), new arc4.Str('world'), new arc4.Bool(false)]) + }) + }) + + describe('store in state', () => { + it('stores mutable object in state', () => { + ctx.txn.createScope([ctx.any.txn.applicationCall()]).execute(() => { + const g1 = GlobalState({ key: 'g1', initialValue: { a: 1, b: true, c: 'hello', d: Bytes('world') } as SimpleObj }) + assertMatch(g1.value, { + a: 1, + b: true, + c: 'hello', + d: Bytes('world'), + }) + + const l1 = LocalState({ key: 'l1' }) + l1(Global.zeroAddress).value = { a: 42, b: false, c: 'test', d: Bytes('data') } as SimpleObj + assertMatch(l1(Global.zeroAddress).value, { + a: 42, + b: false, + c: 'test', + d: Bytes('data'), + }) + + const b1 = Box({ key: 'b1' }) + b1.value = { a: 100, b: true, c: 'box', d: Bytes('storage') } as SimpleObj + assertMatch(b1.value, { + a: 100, + b: true, + c: 'box', + d: Bytes('storage'), + }) + + const b2 = BoxMap({ keyPrefix: 'b2' }) + b2('key1').value = { a: 200, b: false, c: 'map', d: Bytes('value') } as SimpleObj + assertMatch(b2('key1').value, { + a: 200, + b: false, + c: 'map', + d: Bytes('value'), + }) + }) + }) + }) + + describe('store in collections', () => { + it('can be stored in native array', () => { + const arr: SimpleObj[] = [ + { a: 1, b: true, c: 'first', d: Bytes('one') }, + { a: 2, b: false, c: 'second', d: Bytes('two') }, + ] + + assertMatch(arr[0], { + a: 1, + b: true, + c: 'first', + d: Bytes('one'), + }) + assertMatch(arr[1], { + a: 2, + b: false, + c: 'second', + d: Bytes('two'), + }) + + const arr2 = clone(arr) + arr2[0].a = 10 + assertMatch(arr[0].a, 1) + assertMatch(arr2[0].a, 10) + }) + + it('can be stored in readonly native array', () => { + const arr: readonly SimpleObj[] = [ + { a: 1, b: true, c: 'first', d: Bytes('one') }, + { a: 2, b: false, c: 'second', d: Bytes('two') }, + ] + + assertMatch(arr[0], { + a: 1, + b: true, + c: 'first', + d: Bytes('one'), + }) + assertMatch(arr[1], { + a: 2, + b: false, + c: 'second', + d: Bytes('two'), + }) + + const arr2 = clone(arr) + arr2[0].a = 10 + assertMatch(arr[0].a, 1) + assertMatch(arr2[0].a, 10) + }) + + it('can be stored in fixed array', () => { + const arr = new FixedArray( + { a: 1, b: true, c: 'first', d: Bytes('one') }, + { a: 2, b: false, c: 'second', d: Bytes('two') }, + ) + + assertMatch(arr[0], { + a: 1, + b: true, + c: 'first', + d: Bytes('one'), + }) + assertMatch(arr[1], { + a: 2, + b: false, + c: 'second', + d: Bytes('two'), + }) + + const arr2 = clone(arr) + arr2[0].a = 10 + assertMatch(arr[0].a, 1) + assertMatch(arr2[0].a, 10) + }) + + it('can be stored in tuple', () => { + const tuple: [SimpleObj, string] = [{ a: 1, b: true, c: 'test', d: Bytes('data') }, 'metadata'] + + assertMatch(tuple[0], { + a: 1, + b: true, + c: 'test', + d: Bytes('data'), + }) + assertMatch(tuple[1], 'metadata') + + const tuple2 = clone(tuple) + tuple2[0].a = 10 + assertMatch(tuple[0], { + a: 1, + b: true, + c: 'test', + d: Bytes('data'), + }) + assertMatch(tuple2[0], { + a: 10, + b: true, + c: 'test', + d: Bytes('data'), + }) + }) + + it('can be stored in readonly tuple', () => { + const tuple: readonly [SimpleObj, string] = [{ a: 1, b: true, c: 'test', d: Bytes('data') }, 'metadata'] + + assertMatch(tuple[0], { + a: 1, + b: true, + c: 'test', + d: Bytes('data'), + }) + assertMatch(tuple[1], 'metadata') + + const tuple2 = clone(tuple) + tuple2[0].a = 10 + assertMatch(tuple[0], { + a: 1, + b: true, + c: 'test', + d: Bytes('data'), + }) + assertMatch(tuple2[0], { + a: 10, + b: true, + c: 'test', + d: Bytes('data'), + }) + }) + }) + + describe('method selector', () => { + it('should return correct method selector for mutable object method', () => { + expect(methodSelector(TestContract.prototype.nativeMutableObj)).toEqual( + methodSelector('nativeMutableObj((uint64,bool,string,byte[]))(uint64,bool,string,byte[])'), + ) + expect(methodSelector(TestContract.prototype.arc4PrimitiveMutableObj)).toEqual( + methodSelector('arc4PrimitiveMutableObj((uint64,bool,string,byte))(uint64,bool,string,byte)'), + ) + }) + }) +}) diff --git a/tests/native-readonly-array.spec.ts b/tests/native-readonly-array.spec.ts new file mode 100644 index 00000000..28d4deef --- /dev/null +++ b/tests/native-readonly-array.spec.ts @@ -0,0 +1,326 @@ +import type { bytes, uint64 } from '@algorandfoundation/algorand-typescript' +import { + arc4, + assertMatch, + Box, + BoxMap, + Bytes, + clone, + Contract, + FixedArray, + Global, + GlobalState, + LocalState, + Uint64, +} from '@algorandfoundation/algorand-typescript' +import { TestExecutionContext } from '@algorandfoundation/algorand-typescript-testing' +import { methodSelector } from '@algorandfoundation/algorand-typescript/arc4' +import { describe, expect, it } from 'vitest' + +class TestContract extends Contract { + nativeReadonlyArrayMethod(a: readonly uint64[], b: string): readonly [readonly uint64[], string] { + return [a.slice(0, 2), b] + } + arc4ReadonlyArrayMethod(a: readonly arc4.UintN8[], b: arc4.Str): readonly [readonly arc4.UintN8[], arc4.Str] { + return [a.slice(0, 2), b] + } +} + +describe('native readonly array', () => { + describe('readonly properties', () => { + it('access elements in readonly array', () => { + const arr: readonly uint64[] = [1, 2, 3] + + expect(arr[0]).toEqual(1) + expect(arr[1]).toEqual(2) + expect(arr[2]).toEqual(3) + expect(arr.length).toEqual(3) + + // Array is readonly - cannot modify elements + assertMatch(arr, [1, 2, 3]) + }) + + it('access elements with at() method', () => { + const arr: readonly uint64[] = [1, 2, 3] + + expect(arr.at(0)).toEqual(1) + expect(arr.at(-1)).toEqual(3) + }) + }) + + describe('copy()', () => { + it('creates a deep copy', () => { + const original: readonly uint64[][] = [[1, 2, 3]] + const copy = clone(original) + assertMatch(original, copy) + + // After cloning, the copy can be modified if it's not readonly + copy[0][0] = 10 + assertMatch(original[0], [1, 2, 3]) + assertMatch(copy[0], [10, 2, 3]) + }) + }) + + describe('store in state', () => { + const ctx = new TestExecutionContext() + + it('stores readonly array in state', () => { + ctx.txn.createScope([ctx.any.txn.applicationCall()]).execute(() => { + const g1 = GlobalState({ key: 'g1', initialValue: [1, 2, 3] as readonly uint64[] }) + assertMatch(g1.value, [1, 2, 3]) + + const l1 = LocalState>({ key: 'l1' }) + l1(Global.zeroAddress).value = [Uint64(4), Uint64(5), Uint64(6)] + assertMatch(l1(Global.zeroAddress).value, [4, 5, 6]) + + const b1 = Box({ key: 'b1' }) + b1.value = [100, 200, 300] + assertMatch(b1.value, [100, 200, 300]) + + const b2 = BoxMap({ keyPrefix: 'b2' }) + b2('key1').value = [7, 8, 9] + assertMatch(b2('key1').value, [7, 8, 9]) + }) + }) + }) + + describe('store arc4 value in readonly array', () => { + it('can store primitive arc4 values', () => { + const a1: readonly arc4.UintN64[] = [new arc4.UintN64(1), new arc4.UintN64(2), new arc4.UintN64(3)] + assertMatch(a1, [new arc4.UintN64(1), new arc4.UintN64(2), new arc4.UintN64(3)]) + + const a2 = clone(a1) + assertMatch(a1, [new arc4.UintN64(1), new arc4.UintN64(2), new arc4.UintN64(3)]) + assertMatch(a2, [new arc4.UintN64(1), new arc4.UintN64(2), new arc4.UintN64(3)]) + + const a3: readonly arc4.UintN64[] = [] + assertMatch(a3, []) + }) + + it('can store arc4 dynamic array', () => { + const a1: readonly arc4.DynamicArray[] = [ + new arc4.DynamicArray(new arc4.UintN64(1), new arc4.UintN64(2), new arc4.UintN64(3)), + ] + assertMatch(a1[0], [new arc4.UintN64(1), new arc4.UintN64(2), new arc4.UintN64(3)]) + + const a2 = clone(a1) + a2[0].push(new arc4.UintN64(4)) + assertMatch(a1[0], [new arc4.UintN64(1), new arc4.UintN64(2), new arc4.UintN64(3)]) + assertMatch(a2[0], [new arc4.UintN64(1), new arc4.UintN64(2), new arc4.UintN64(3), new arc4.UintN64(4)]) + }) + + it('can store arc4 static array', () => { + const a1: readonly arc4.StaticArray[] = [ + new arc4.StaticArray(new arc4.UintN64(1), new arc4.UintN64(2), new arc4.UintN64(3)), + ] + assertMatch(a1[0], [new arc4.UintN64(1), new arc4.UintN64(2), new arc4.UintN64(3)]) + + const a2 = clone(a1) + a2[0][0] = new arc4.UintN64(10) + assertMatch(a1[0], [new arc4.UintN64(1), new arc4.UintN64(2), new arc4.UintN64(3)]) + assertMatch(a2[0], [new arc4.UintN64(10), new arc4.UintN64(2), new arc4.UintN64(3)]) + }) + + it('can store arc4 tuple', () => { + const a1: readonly arc4.Tuple[] = [new arc4.Tuple(new arc4.UintN64(1), new arc4.Str('Hello'))] + assertMatch(a1[0].native, [new arc4.UintN64(1), new arc4.Str('Hello')]) + + const a2 = clone(a1) + assertMatch(a1[0].native, [new arc4.UintN64(1), new arc4.Str('Hello')]) + assertMatch(a2[0].native, [new arc4.UintN64(1), new arc4.Str('Hello')]) + }) + }) + + describe('store readonly array in other collections', () => { + it('can be stored in object', () => { + const obj = { arr: [1, 2, 3] as readonly uint64[], str: 'hello' } + assertMatch(obj.arr, [1, 2, 3]) + + const obj2 = clone(obj) + assertMatch(obj.arr, [1, 2, 3]) + assertMatch(obj2.arr, [1, 2, 3]) + + const obj3 = { arr: [] as readonly uint64[], str: 'world' } + assertMatch(obj3, { arr: [], str: 'world' }) + }) + + it('can be stored in readonly object', () => { + const obj: Readonly<{ arr: readonly uint64[]; str: string }> = { arr: [1, 2, 3], str: 'hello' } + assertMatch(obj.arr, [1, 2, 3]) + + const obj2 = clone(obj) + assertMatch(obj.arr, [1, 2, 3]) + assertMatch(obj2.arr, [1, 2, 3]) + + const obj3 = { arr: [] as readonly uint64[], str: 'world' } as const + assertMatch(obj3, { arr: [], str: 'world' }) + }) + + it('can be stored in FixedArray', () => { + const arr = new FixedArray([1, 2, 3], [4, 5, 6]) + assertMatch(arr[0], [1, 2, 3]) + assertMatch(arr[1], [4, 5, 6]) + + const arr2 = clone(arr) + assertMatch(arr[0], [1, 2, 3]) + assertMatch(arr2[0], [1, 2, 3]) + }) + + it('can be stored in native tuple', () => { + const a: [readonly uint64[], string] = [[1, 2, 3], 'hello'] + assertMatch(a[0], [1, 2, 3]) + + const b = clone(a) + assertMatch(a[0], [1, 2, 3]) + assertMatch(b[0], [1, 2, 3]) + + const c: [readonly uint64[], string] = [[], 'hello'] + assertMatch(c[0], []) + }) + + it('can be stored in native readonly tuple', () => { + const a: readonly [readonly uint64[], string] = [[1, 2, 3], 'hello'] + assertMatch(a[0], [1, 2, 3]) + + const b = clone(a) + assertMatch(a[0], [1, 2, 3]) + assertMatch(b[0], [1, 2, 3]) + + const c = [[] as readonly uint64[], 'hello'] as const + assertMatch(c[0], []) + }) + }) + + describe('store other collections in readonly array', () => { + it('stores object in readonly array', () => { + type Point = { x: uint64; y: uint64 } + const a1: readonly Point[] = [ + { x: Uint64(1), y: Uint64(2) }, + { x: Uint64(3), y: Uint64(4) }, + { x: Uint64(5), y: Uint64(6) }, + ] + assertMatch(a1, [ + { x: 1, y: 2 }, + { x: 3, y: 4 }, + { x: 5, y: 6 }, + ]) + + const a2: readonly Point[] = [] + assertMatch(a2, []) + + const a3 = clone(a1) + a3[0].x = Uint64(10) + a3[1].x = Uint64(30) + a3[2].x = Uint64(50) + assertMatch(a3, [ + { x: 10, y: 2 }, + { x: 30, y: 4 }, + { x: 50, y: 6 }, + ]) + assertMatch(a1, [ + { x: 1, y: 2 }, + { x: 3, y: 4 }, + { x: 5, y: 6 }, + ]) + }) + + it('stores readonly object in readonly array', () => { + type Point = Readonly<{ x: uint64; y: uint64 }> + const a1: readonly Point[] = [ + { x: Uint64(1), y: Uint64(2) }, + { x: Uint64(3), y: Uint64(4) }, + { x: Uint64(5), y: Uint64(6) }, + ] + assertMatch(a1, [ + { x: 1, y: 2 }, + { x: 3, y: 4 }, + { x: 5, y: 6 }, + ]) + + const a2: readonly Point[] = [] + assertMatch(a2, []) + + const a3 = clone(a1) + assertMatch(a3, [ + { x: 1, y: 2 }, + { x: 3, y: 4 }, + { x: 5, y: 6 }, + ]) + assertMatch(a1, [ + { x: 1, y: 2 }, + { x: 3, y: 4 }, + { x: 5, y: 6 }, + ]) + }) + + it('stores FixedArray in readonly array', () => { + const a1: readonly FixedArray[] = [new FixedArray(1, 2), new FixedArray(3, 4)] + assertMatch(a1, [new FixedArray(1, 2), new FixedArray(3, 4)]) + + const a2 = clone(a1) + a2[0][0] = Uint64(10) + a2[1][0] = Uint64(30) + assertMatch(a1, [new FixedArray(Uint64(1), Uint64(2)), new FixedArray(Uint64(3), Uint64(4))]) + assertMatch(a2, [new FixedArray(Uint64(10), Uint64(2)), new FixedArray(Uint64(30), Uint64(4))]) + }) + + it('store native tuple in readonly array', () => { + const a1: readonly [uint64, string, bytes][] = [ + [1, 'a', Bytes('x')], + [2, 'b', Bytes('y')], + ] + assertMatch(a1, [ + [1, 'a', Bytes('x')], + [2, 'b', Bytes('y')], + ]) + + const a2 = clone(a1) + assertMatch(a1, [ + [1, 'a', Bytes('x')], + [2, 'b', Bytes('y')], + ]) + assertMatch(a2, [ + [1, 'a', Bytes('x')], + [2, 'b', Bytes('y')], + ]) + + const a3: readonly [uint64, uint64][] = [] + assertMatch(a3, []) + }) + + it('store native readonly tuple in readonly array', () => { + const a1: readonly (readonly [uint64, string, bytes])[] = [ + [1, 'a', Bytes('x')], + [2, 'b', Bytes('y')], + ] + assertMatch(a1, [ + [1, 'a', Bytes('x')], + [2, 'b', Bytes('y')], + ]) + + const a2 = clone(a1) + assertMatch(a1, [ + [1, 'a', Bytes('x')], + [2, 'b', Bytes('y')], + ]) + assertMatch(a2, [ + [1, 'a', Bytes('x')], + [2, 'b', Bytes('y')], + ]) + + const a3: readonly (readonly [uint64, uint64])[] = [] + assertMatch(a3, []) + }) + }) + + describe('method selector', () => { + it('should return correct method selector for readonly array method', () => { + expect(methodSelector(TestContract.prototype.nativeReadonlyArrayMethod)).toEqual( + methodSelector('nativeReadonlyArrayMethod(uint64[],string)(uint64[],string)'), + ) + expect(methodSelector(TestContract.prototype.arc4ReadonlyArrayMethod)).toEqual( + methodSelector('arc4ReadonlyArrayMethod(uint8[],string)(uint8[],string)'), + ) + }) + }) +}) diff --git a/tests/native-readonly-object.spec.ts b/tests/native-readonly-object.spec.ts new file mode 100644 index 00000000..5d069108 --- /dev/null +++ b/tests/native-readonly-object.spec.ts @@ -0,0 +1,593 @@ +import type { bytes, uint64 } from '@algorandfoundation/algorand-typescript' +import { + arc4, + assertMatch, + Box, + BoxMap, + Bytes, + clone, + Contract, + FixedArray, + Global, + GlobalState, + LocalState, +} from '@algorandfoundation/algorand-typescript' +import { TestExecutionContext } from '@algorandfoundation/algorand-typescript-testing' +import { methodSelector } from '@algorandfoundation/algorand-typescript/arc4' +import { describe, expect, it } from 'vitest' + +type SimpleObj = Readonly<{ + a: uint64 + b: boolean + c: string + d: bytes +}> + +type NestedObj = Readonly<{ + a: uint64 + b: boolean + c: string + d: Readonly<{ + x: uint64 + y: string + z: boolean + }> +}> + +type ArrayObj = Readonly<{ + a: uint64 + b: boolean + c: string + d: uint64[] +}> + +type ReadonlyArrayObj = Readonly<{ + a: uint64 + b: boolean + c: string + d: readonly uint64[] +}> + +type FixedArrayReadonlyObj = Readonly<{ + a: uint64 + b: boolean + c: string + d: FixedArray +}> + +type TupleObj = Readonly<{ + a: uint64 + b: boolean + c: string + d: [uint64, string, boolean] +}> + +type ReadonlyTupleObj = Readonly<{ + a: uint64 + b: boolean + c: string + d: readonly [uint64, string, boolean] +}> + +type DeepNestedReadonlyObj = Readonly<{ + a: uint64 + b: Readonly<{ + x: Readonly<{ + p: uint64 + q: string + }> + y: readonly string[] + z: readonly [uint64, boolean] + }> + c: string +}> + +type Arc4PrimitiveReadonlyObj = Readonly<{ + a: arc4.UintN64 + b: arc4.Bool + c: arc4.Str + d: arc4.Byte +}> + +type Arc4DynamicArrayReadonlyObj = Readonly<{ + a: uint64 + b: arc4.DynamicArray + c: string +}> + +type Arc4StaticArrayReadonlyObj = Readonly<{ + a: uint64 + b: arc4.StaticArray + c: string +}> + +type Arc4TupleReadonlyObj = Readonly<{ + a: uint64 + b: arc4.Tuple + c: string +}> + +class TestContract extends Contract { + nativeReadonlyObj(obj: SimpleObj): SimpleObj { + return obj + } + + arc4PrimitiveReadonlyObj(obj: Arc4PrimitiveReadonlyObj): Arc4PrimitiveReadonlyObj { + return obj + } +} + +describe('native readonly object', () => { + const ctx = new TestExecutionContext() + + describe('readonly properties', () => { + it('access properties in simple readonly object', () => { + const obj: SimpleObj = { a: 1, b: true, c: 'hello', d: Bytes('world') } + + assertMatch(obj, { + a: 1, + b: true, + c: 'hello', + d: Bytes('world'), + }) + }) + + it('access properties in nested readonly object', () => { + const obj: NestedObj = { + a: 1, + b: true, + c: 'hello', + d: { x: 10, y: 'test', z: false }, + } + + assertMatch(obj, { + a: 1, + b: true, + c: 'hello', + d: { x: 10, y: 'test', z: false }, + }) + }) + + it('access readonly array property in readonly object', () => { + const obj: ReadonlyArrayObj = { a: 1, b: true, c: 'hello', d: [10, 20, 30] } + + assertMatch(obj, { + a: 1, + b: true, + c: 'hello', + d: [10, 20, 30], + }) + }) + + it('access array property in readonly object', () => { + const obj: ArrayObj = { a: 1, b: true, c: 'hello', d: [10, 20, 30] } + + const obj2 = clone(obj) + obj2.d[0] = 100 + obj2.d.pop() + + assertMatch(obj, { + a: 1, + b: true, + c: 'hello', + d: [10, 20, 30], + }) + assertMatch(obj2, { + a: 1, + b: true, + c: 'hello', + d: [100, 20], + }) + }) + + it('access fixed array property in readonly object', () => { + const obj: FixedArrayReadonlyObj = { + a: 1, + b: true, + c: 'hello', + d: new FixedArray(10, 20, 30), + } + + const obj2 = clone(obj) + obj2.d[0] = 100 + obj2.d[2] = 300 + + assertMatch(obj, { + a: 1, + b: true, + c: 'hello', + d: new FixedArray(10, 20, 30), + }) + + assertMatch(obj2, { + a: 1, + b: true, + c: 'hello', + d: new FixedArray(100, 20, 300), + }) + }) + + it('access tuple property in readonly object', () => { + const obj: TupleObj = { + a: 1, + b: true, + c: 'hello', + d: [10, 'test', false], + } + + const obj2 = clone(obj) + obj2.d[0] = 100 + obj2.d[2] = true + + assertMatch(obj, { + a: 1, + b: true, + c: 'hello', + d: [10, 'test', false], + }) + assertMatch(obj2, { + a: 1, + b: true, + c: 'hello', + d: [100, 'test', true], + }) + }) + + it('access readonly tuple property in readonly object', () => { + const obj: ReadonlyTupleObj = { + a: 1, + b: true, + c: 'hello', + d: [10, 'test', false], + } + + assertMatch(obj, { + a: 1, + b: true, + c: 'hello', + d: [10, 'test', false], + }) + }) + + it('access properties in deep nested readonly object', () => { + const obj: DeepNestedReadonlyObj = { + a: 1, + b: { + x: { p: 10, q: 'test' }, + y: ['hello', 'world'], + z: [20, false], + }, + c: 'root', + } + + assertMatch(obj, { + a: 1, + b: { + x: { p: 10, q: 'test' }, + y: ['hello', 'world'], + z: [20, false], + }, + c: 'root', + }) + }) + }) + + describe('clone readonly objects', () => { + it('clone simple readonly object', () => { + const original: SimpleObj = { a: 1, b: true, c: 'hello', d: Bytes('world') } + const cloned = clone(original) + + expect(cloned).toEqual(original) + expect(cloned).not.toBe(original) + }) + + it('clone nested readonly object', () => { + const original: NestedObj = { + a: 1, + b: true, + c: 'hello', + d: { x: 10, y: 'test', z: false }, + } + const cloned = clone(original) + + expect(cloned).toEqual(original) + expect(cloned).not.toBe(original) + expect(cloned.d).not.toBe(original.d) + }) + + it('clone readonly object with arrays', () => { + const original: ArrayObj = { a: 1, b: true, c: 'hello', d: [10, 20, 30] } + const cloned = clone(original) + + cloned.d[0] = 100 + + assertMatch(original, { + a: 1, + b: true, + c: 'hello', + d: [10, 20, 30], + }) + + assertMatch(cloned, { + a: 1, + b: true, + c: 'hello', + d: [100, 20, 30], + }) + }) + }) + + describe('store arc4 value', () => { + it('can store primitive arc4 values', () => { + const obj: Arc4PrimitiveReadonlyObj = { + a: new arc4.UintN64(42), + b: new arc4.Bool(true), + c: new arc4.Str('hello'), + d: new arc4.Byte(125), + } + + const obj2 = clone(obj) + + assertMatch(obj, { + a: new arc4.UintN64(42), + b: new arc4.Bool(true), + c: new arc4.Str('hello'), + d: new arc4.Byte(125), + }) + assertMatch(obj2, { + a: new arc4.UintN64(42), + b: new arc4.Bool(true), + c: new arc4.Str('hello'), + d: new arc4.Byte(125), + }) + }) + + it('can store arc4 dynamic array', () => { + const obj: Arc4DynamicArrayReadonlyObj = { + a: 42, + b: new arc4.DynamicArray(new arc4.UintN64(10), new arc4.UintN64(20), new arc4.UintN64(30)), + c: 'test', + } + + const obj2 = clone(obj) + obj2.b.push(new arc4.UintN64(40)) + + assertMatch(obj, { + a: 42, + b: new arc4.DynamicArray(new arc4.UintN64(10), new arc4.UintN64(20), new arc4.UintN64(30)), + c: 'test', + }) + + assertMatch(obj2, { + a: 42, + b: new arc4.DynamicArray(new arc4.UintN64(10), new arc4.UintN64(20), new arc4.UintN64(30), new arc4.UintN64(40)), + c: 'test', + }) + }) + + it('can store arc4 static array', () => { + const obj: Arc4StaticArrayReadonlyObj = { + a: 42, + b: new arc4.StaticArray(new arc4.UintN64(10), new arc4.UintN64(20), new arc4.UintN64(30)), + c: 'test', + } + + const obj2 = clone(obj) + obj2.b[0] = new arc4.UintN64(100) + obj2.b[2] = new arc4.UintN64(300) + + assertMatch(obj, { + a: 42, + b: new arc4.StaticArray(new arc4.UintN64(10), new arc4.UintN64(20), new arc4.UintN64(30)), + c: 'test', + }) + assertMatch(obj2, { + a: 42, + b: new arc4.StaticArray(new arc4.UintN64(100), new arc4.UintN64(20), new arc4.UintN64(300)), + c: 'test', + }) + }) + + it('can store arc4 tuple', () => { + const obj: Arc4TupleReadonlyObj = { + a: 42, + b: new arc4.Tuple(new arc4.UintN64(10), new arc4.Str('hello'), new arc4.Bool(true)), + c: 'test', + } + + const obj2 = clone(obj) + + assertMatch(obj, obj2) + }) + }) + + describe('store in state', () => { + it('stores readonly object in state', () => { + ctx.txn.createScope([ctx.any.txn.applicationCall()]).execute(() => { + const g1 = GlobalState({ key: 'g1', initialValue: { a: 1, b: true, c: 'hello', d: Bytes('world') } as SimpleObj }) + assertMatch(g1.value, { + a: 1, + b: true, + c: 'hello', + d: Bytes('world'), + }) + + const l1 = LocalState({ key: 'l1' }) + l1(Global.zeroAddress).value = { a: 42, b: false, c: 'test', d: Bytes('data') } as SimpleObj + assertMatch(l1(Global.zeroAddress).value, { + a: 42, + b: false, + c: 'test', + d: Bytes('data'), + }) + + const b1 = Box({ key: 'b1' }) + b1.value = { a: 100, b: true, c: 'box', d: Bytes('storage') } as SimpleObj + assertMatch(b1.value, { + a: 100, + b: true, + c: 'box', + d: Bytes('storage'), + }) + + const b2 = BoxMap({ keyPrefix: 'b2' }) + b2('key1').value = { a: 200, b: false, c: 'map', d: Bytes('value') } as SimpleObj + assertMatch(b2('key1').value, { + a: 200, + b: false, + c: 'map', + d: Bytes('value'), + }) + }) + }) + }) + + describe('store in collections', () => { + it('can be stored in native array', () => { + const arr: SimpleObj[] = [ + { a: 1, b: true, c: 'first', d: Bytes('one') }, + { a: 2, b: false, c: 'second', d: Bytes('two') }, + ] + + assertMatch(arr[0], { + a: 1, + b: true, + c: 'first', + d: Bytes('one'), + }) + assertMatch(arr[1], { + a: 2, + b: false, + c: 'second', + d: Bytes('two'), + }) + + const arr2 = clone(arr) + arr2[1] = { a: 3, b: true, c: 'third', d: Bytes('three') } + assertMatch(arr[0], arr2[0]) + assertMatch(arr2[1], { + a: 3, + b: true, + c: 'third', + d: Bytes('three'), + }) + }) + + it('can be stored in readonly native array', () => { + const arr: readonly SimpleObj[] = [ + { a: 1, b: true, c: 'first', d: Bytes('one') }, + { a: 2, b: false, c: 'second', d: Bytes('two') }, + ] + + assertMatch(arr[0], { + a: 1, + b: true, + c: 'first', + d: Bytes('one'), + }) + assertMatch(arr[1], { + a: 2, + b: false, + c: 'second', + d: Bytes('two'), + }) + + const arr2 = clone(arr) + assertMatch(arr[0], arr2[0]) + assertMatch(arr[1], arr2[1]) + }) + + it('can be stored in fixed array', () => { + const arr = new FixedArray( + { a: 1, b: true, c: 'first', d: Bytes('one') }, + { a: 2, b: false, c: 'second', d: Bytes('two') }, + ) + + assertMatch(arr[0], { + a: 1, + b: true, + c: 'first', + d: Bytes('one'), + }) + assertMatch(arr[1], { + a: 2, + b: false, + c: 'second', + d: Bytes('two'), + }) + + const arr2 = clone(arr) + assertMatch(arr[0], arr2[0]) + assertMatch(arr[1], arr2[1]) + }) + + it('can be stored in tuple', () => { + const tuple: [SimpleObj, string] = [{ a: 1, b: true, c: 'test', d: Bytes('data') }, 'metadata'] + + assertMatch(tuple[0], { + a: 1, + b: true, + c: 'test', + d: Bytes('data'), + }) + assertMatch(tuple[1], 'metadata') + + const tuple2 = clone(tuple) + tuple2[0] = { a: 2, b: false, c: 'hello', d: Bytes('world') } + assertMatch(tuple, [ + { + a: 1, + b: true, + c: 'test', + d: Bytes('data'), + }, + 'metadata', + ]) + assertMatch(tuple2, [ + { + a: 2, + b: false, + c: 'hello', + d: Bytes('world'), + }, + ]) + }) + + it('can be stored in readonly tuple', () => { + const tuple: readonly [SimpleObj, string] = [{ a: 1, b: true, c: 'test', d: Bytes('data') }, 'metadata'] + + assertMatch(tuple[0], { + a: 1, + b: true, + c: 'test', + d: Bytes('data'), + }) + assertMatch(tuple[1], 'metadata') + + const tuple2 = clone(tuple) + assertMatch(tuple[0], { + a: 1, + b: true, + c: 'test', + d: Bytes('data'), + }) + assertMatch(tuple2[0], { + a: 1, + b: true, + c: 'test', + d: Bytes('data'), + }) + }) + }) + + describe('method selector', () => { + it('should return correct method selector for readonly object method', () => { + expect(methodSelector(TestContract.prototype.nativeReadonlyObj)).toEqual( + methodSelector('nativeReadonlyObj((uint64,bool,string,byte[]))(uint64,bool,string,byte[])'), + ) + expect(methodSelector(TestContract.prototype.arc4PrimitiveReadonlyObj)).toEqual( + methodSelector('arc4PrimitiveReadonlyObj((uint64,bool,string,byte))(uint64,bool,string,byte)'), + ) + }) + }) +}) diff --git a/tests/mutable-array.spec.ts b/tests/reference-array.spec.ts similarity index 69% rename from tests/mutable-array.spec.ts rename to tests/reference-array.spec.ts index 543fb6a0..4295c3b3 100644 --- a/tests/mutable-array.spec.ts +++ b/tests/reference-array.spec.ts @@ -1,17 +1,17 @@ -import type { uint64 } from '@algorandfoundation/algorand-typescript' +import { clone, ReferenceArray, type uint64 } from '@algorandfoundation/algorand-typescript' import { describe, expect, it } from 'vitest' -import { MutableArray } from '../src/impl' + import { Uint64 } from '../src/impl/primitives' -describe('MutableArray', () => { +describe('ReferenceArray', () => { describe('constructor', () => { it('creates empty array when no arguments provided', () => { - const arr = new MutableArray() + const arr = new ReferenceArray() expect(arr.length).toEqual(0) }) it('creates array with initial values', () => { - const arr = new MutableArray(Uint64(1), Uint64(2), Uint64(3)) + const arr = new ReferenceArray(Uint64(1), Uint64(2), Uint64(3)) expect(arr.length).toEqual(3) expect(arr[0]).toEqual(1) expect(arr[1]).toEqual(2) @@ -21,13 +21,13 @@ describe('MutableArray', () => { describe('index access', () => { it('throws on out of bounds access', () => { - const arr = new MutableArray(1, 2) + const arr = new ReferenceArray(1, 2) expect(() => arr[2]).toThrow('Index out of bounds') expect(() => arr[-1]).toThrow('Index out of bounds') }) it('allows setting values by index', () => { - const arr = new MutableArray(1, 2) + const arr = new ReferenceArray(1, 2) const index = Uint64(0) arr[index] = 10 expect(arr[index]).toEqual(10) @@ -36,31 +36,31 @@ describe('MutableArray', () => { describe('at()', () => { it('returns value at positive index', () => { - const arr = new MutableArray(1, 2, 3) + const arr = new ReferenceArray(1, 2, 3) expect(arr.at(Uint64(1))).toEqual(2) }) it('returns value at negative index', () => { - const arr = new MutableArray(1, 2, 3) - expect(() => arr.at(-1)).toThrow('Uint64 overflow or underflow') + const arr = new ReferenceArray(1, 2, 3) + expect(arr.at(-1)).toEqual(3) }) }) describe('slice()', () => { it('returns full copy with no arguments', () => { - const arr = new MutableArray(1, 2, 3) + const arr = new ReferenceArray(1, 2, 3) const sliced = arr.slice() expect([...sliced]).toEqual([1, 2, 3]) }) it('slices with end index', () => { - const arr = new MutableArray(1, 2, 3) + const arr = new ReferenceArray(1, 2, 3) const sliced = arr.slice(Uint64(2)) expect([...sliced]).toEqual([1, 2]) }) it('slices with start and end index', () => { - const arr = new MutableArray(1, 2, 3, 4) + const arr = new ReferenceArray(1, 2, 3, 4) const sliced = arr.slice(Uint64(1), Uint64(3)) expect([...sliced]).toEqual([2, 3]) }) @@ -68,13 +68,13 @@ describe('MutableArray', () => { describe('push() and pop()', () => { it('pushes items to end of array', () => { - const arr = new MutableArray(1) + const arr = new ReferenceArray(1) arr.push(2, 3) expect([...arr]).toEqual([1, 2, 3]) }) it('pops item from end of array', () => { - const arr = new MutableArray(1, 2, 3) + const arr = new ReferenceArray(1, 2, 3) const popped = arr.pop() expect(popped).toEqual(3) expect([...arr]).toEqual([1, 2]) @@ -83,7 +83,7 @@ describe('MutableArray', () => { describe('iteration', () => { it('supports for...of iteration', () => { - const arr = new MutableArray(1, 2, 3) + const arr = new ReferenceArray(1, 2, 3) const result: number[] = [] for (const item of arr) { result.push(item) @@ -92,7 +92,7 @@ describe('MutableArray', () => { }) it('provides entries() iterator', () => { - const arr = new MutableArray('a', 'b') + const arr = new ReferenceArray('a', 'b') const entries = [...arr.entries()] expect(entries).toEqual([ [0, 'a'], @@ -101,7 +101,7 @@ describe('MutableArray', () => { }) it('provides keys() iterator', () => { - const arr = new MutableArray('a', 'b') + const arr = new ReferenceArray('a', 'b') const keys = [...arr.keys()] expect(keys).toEqual([0, 1]) }) @@ -109,11 +109,11 @@ describe('MutableArray', () => { describe('copy()', () => { it('creates a deep copy', () => { - const original = new MutableArray(1, 2, 3) - const copy = original.copy() - copy[0] = 10 - expect(original[0]).toEqual(1) - expect(copy[0]).toEqual(10) + const original = new ReferenceArray>(new ReferenceArray(1, 2, 3)) + const copy = clone(original) + copy[0][0] = 10 + expect(original[0][0]).toEqual(1) + expect(copy[0][0]).toEqual(10) }) }) }) diff --git a/tests/references/box-map.spec.ts b/tests/references/box-map.spec.ts index 0317d301..fb63895c 100644 --- a/tests/references/box-map.spec.ts +++ b/tests/references/box-map.spec.ts @@ -1,8 +1,18 @@ import type { biguint, bytes, uint64 } from '@algorandfoundation/algorand-typescript' import { BigUint, BoxMap, Bytes, clone, op, Uint64 } from '@algorandfoundation/algorand-typescript' import { TestExecutionContext } from '@algorandfoundation/algorand-typescript-testing' -import type { Bool, DynamicBytes, StaticArray, Tuple, UintN16 } from '@algorandfoundation/algorand-typescript/arc4' -import { ARC4Encoded, DynamicArray, interpretAsArc4, Str, Struct, UintN64, UintN8 } from '@algorandfoundation/algorand-typescript/arc4' +import type { StaticArray, UintN16 } from '@algorandfoundation/algorand-typescript/arc4' +import { + ARC4Encoded, + Bool, + DynamicArray, + DynamicBytes, + interpretAsArc4, + Str, + Struct, + UintN64, + UintN8, +} from '@algorandfoundation/algorand-typescript/arc4' import { afterEach, describe, expect, it, test } from 'vitest' import { MAX_UINT64 } from '../../src/constants' import { toBytes } from '../../src/impl/encoded-types' @@ -11,6 +21,8 @@ import { asBytes } from '../../src/util' const BOX_NOT_CREATED_ERROR = 'Box has not been created' class MyStruct extends Struct<{ a: Str; b: DynamicBytes; c: Bool }> {} +type MyObject = { a: string; b: bytes; c: boolean } +type MyArc4Object = { a: Str; b: DynamicBytes; c: Bool } describe('BoxMap', () => { const ctx = new TestExecutionContext() @@ -104,7 +116,7 @@ describe('BoxMap', () => { key: new Str('TTest'), value: ['hello', Bytes('world'), true] as const, newValue: ['world', Bytes('hello'), false] as const, - emptyValue: interpretAsArc4>(Bytes('')), + emptyValue: [], withBoxContext: (test: (boxMap: BoxMap) => void) => { ctx.txn.createScope([ctx.any.txn.applicationCall()]).execute(() => { const boxMap = BoxMap({ keyPrefix }) @@ -112,6 +124,18 @@ describe('BoxMap', () => { }) }, }, + { + key: new Str('TTest'), + value: ['hello', Bytes('world'), true] as const, + newValue: ['world', Bytes('hello'), false] as const, + emptyValue: [], + withBoxContext: (test: (boxMap: BoxMap) => void) => { + ctx.txn.createScope([ctx.any.txn.applicationCall()]).execute(() => { + const boxMap = BoxMap({ keyPrefix }) + test(boxMap) + }) + }, + }, { key: new Str('OTest'), value: { a: 'hello', b: Bytes('world'), c: true } as unknown as MyStruct, @@ -126,12 +150,48 @@ describe('BoxMap', () => { }, { key: { x: Uint64(21), y: Uint64(42) }, - value: { a: 'hello', b: Bytes('world'), c: true } as unknown as MyStruct, - newValue: { a: 'world', b: Bytes('hello'), c: false } as unknown as MyStruct, - emptyValue: interpretAsArc4(Bytes('')), - withBoxContext: (test: (boxMap: BoxMap<{ x: uint64; y: uint64 }, MyStruct>) => void) => { + value: { a: 'hello', b: Bytes('world'), c: true }, + newValue: { a: 'world', b: Bytes('hello'), c: false }, + emptyValue: {}, + withBoxContext: (test: (boxMap: BoxMap<{ x: uint64; y: uint64 }, MyObject>) => void) => { + ctx.txn.createScope([ctx.any.txn.applicationCall()]).execute(() => { + const boxMap = BoxMap<{ x: uint64; y: uint64 }, MyObject>({ keyPrefix }) + test(boxMap) + }) + }, + }, + { + key: { x: Uint64(21), y: Uint64(42) }, + value: { a: 'hello', b: Bytes('world'), c: true }, + newValue: { a: 'world', b: Bytes('hello'), c: false }, + emptyValue: {}, + withBoxContext: (test: (boxMap: BoxMap<{ x: uint64; y: uint64 }, Readonly>) => void) => { + ctx.txn.createScope([ctx.any.txn.applicationCall()]).execute(() => { + const boxMap = BoxMap<{ x: uint64; y: uint64 }, Readonly>({ keyPrefix }) + test(boxMap) + }) + }, + }, + { + key: { x: Uint64(21), y: Uint64(42) }, + value: { a: new Str('hello'), b: new DynamicBytes('world'), c: new Bool(true) }, + newValue: { a: new Str('world'), b: new DynamicBytes('hello'), c: new Bool(false) }, + emptyValue: {}, + withBoxContext: (test: (boxMap: BoxMap<{ x: uint64; y: uint64 }, MyArc4Object>) => void) => { + ctx.txn.createScope([ctx.any.txn.applicationCall()]).execute(() => { + const boxMap = BoxMap<{ x: uint64; y: uint64 }, MyArc4Object>({ keyPrefix }) + test(boxMap) + }) + }, + }, + { + key: { x: Uint64(21), y: Uint64(42) }, + value: { a: new Str('hello'), b: new DynamicBytes('world'), c: new Bool(true) }, + newValue: { a: new Str('world'), b: new DynamicBytes('hello'), c: new Bool(false) }, + emptyValue: {}, + withBoxContext: (test: (boxMap: BoxMap<{ x: uint64; y: uint64 }, Readonly>) => void) => { ctx.txn.createScope([ctx.any.txn.applicationCall()]).execute(() => { - const boxMap = BoxMap<{ x: uint64; y: uint64 }, MyStruct>({ keyPrefix }) + const boxMap = BoxMap<{ x: uint64; y: uint64 }, Readonly>({ keyPrefix }) test(boxMap) }) }, @@ -225,7 +285,7 @@ describe('BoxMap', () => { withBoxContext((boxMap) => { boxMap(key as never).value = value if (value instanceof ARC4Encoded) { - expect((boxMap(key as never).value as ARC4Encoded).bytes).toEqual(value.bytes) + expect((boxMap(key as never).value as unknown as ARC4Encoded).bytes).toEqual(value.bytes) } else { expect(boxMap(key as never).value).toEqual(value) } @@ -237,11 +297,11 @@ describe('BoxMap', () => { expect(opContent).toEqual(newBytesValue) if (newValue instanceof ARC4Encoded) { - expect((boxMap(key as never).value as ARC4Encoded).bytes).toEqual(newValue.bytes) + expect((boxMap(key as never).value as unknown as ARC4Encoded).bytes).toEqual(newValue.bytes) } else if (boxMap(key as never).value instanceof ARC4Encoded) { - expect((boxMap(key as never).value as ARC4Encoded).bytes).toEqual(newBytesValue) + expect((boxMap(key as never).value as unknown as ARC4Encoded).bytes).toEqual(newBytesValue) } else { - expect(boxMap(key as never).value).toEqual(newValue) + expect(toBytes(boxMap(key as never).value)).toEqual(newBytesValue) } }) }) diff --git a/tests/references/box.spec.ts b/tests/references/box.spec.ts index b9205de2..d11a06fc 100644 --- a/tests/references/box.spec.ts +++ b/tests/references/box.spec.ts @@ -1,15 +1,15 @@ import type { biguint, bytes, uint64 } from '@algorandfoundation/algorand-typescript' import { BigUint, Box, Bytes, clone, op, Uint64 } from '@algorandfoundation/algorand-typescript' import { TestExecutionContext } from '@algorandfoundation/algorand-typescript-testing' -import type { DynamicBytes, UintN16 } from '@algorandfoundation/algorand-typescript/arc4' +import type { UintN16 } from '@algorandfoundation/algorand-typescript/arc4' import { ARC4Encoded, Bool, DynamicArray, + DynamicBytes, interpretAsArc4, StaticArray, Str, - Struct, Tuple, UintN32, UintN64, @@ -24,7 +24,8 @@ import { BoxContract } from '../artifacts/box-contract/contract.algo' const BOX_NOT_CREATED_ERROR = 'Box has not been created' -class MyStruct extends Struct<{ a: Str; b: DynamicBytes; c: Bool }> {} +type MyObject = { a: string; b: bytes; c: boolean } +type MyArc4Object = { a: Str; b: DynamicBytes; c: Bool } describe('Box', () => { const ctx = new TestExecutionContext() @@ -110,7 +111,7 @@ describe('Box', () => { { value: ['hello', Bytes('world'), true] as const, newValue: ['world', Bytes('hello'), false] as const, - emptyValue: interpretAsArc4>(Bytes('')), + emptyValue: [], withBoxContext: (test: (boxMap: Box) => void) => { ctx.txn.createScope([ctx.any.txn.applicationCall()]).execute(() => { const boxMap = Box({ key }) @@ -118,13 +119,57 @@ describe('Box', () => { }) }, }, + { + value: ['hello', Bytes('world'), true] as const, + newValue: ['world', Bytes('hello'), false] as const, + emptyValue: [], + withBoxContext: (test: (boxMap: Box<[string, bytes, boolean]>) => void) => { + ctx.txn.createScope([ctx.any.txn.applicationCall()]).execute(() => { + const boxMap = Box<[string, bytes, boolean]>({ key }) + test(boxMap) + }) + }, + }, + { + value: { a: 'hello', b: Bytes('world'), c: true }, + newValue: { a: 'world', b: Bytes('hello'), c: false }, + emptyValue: {}, + withBoxContext: (test: (boxMap: Box) => void) => { + ctx.txn.createScope([ctx.any.txn.applicationCall()]).execute(() => { + const boxMap = Box({ key }) + test(boxMap) + }) + }, + }, { value: { a: 'hello', b: Bytes('world'), c: true }, newValue: { a: 'world', b: Bytes('hello'), c: false }, - emptyValue: interpretAsArc4(Bytes('')), - withBoxContext: (test: (boxMap: Box<{ a: string; b: bytes; c: boolean }>) => void) => { + emptyValue: {}, + withBoxContext: (test: (boxMap: Box>) => void) => { ctx.txn.createScope([ctx.any.txn.applicationCall()]).execute(() => { - const boxMap = Box<{ a: string; b: bytes; c: boolean }>({ key }) + const boxMap = Box>({ key }) + test(boxMap) + }) + }, + }, + { + value: { a: new Str('hello'), b: new DynamicBytes('world'), c: new Bool(true) }, + newValue: { a: new Str('world'), b: new DynamicBytes('hello'), c: new Bool(false) }, + emptyValue: {}, + withBoxContext: (test: (boxMap: Box) => void) => { + ctx.txn.createScope([ctx.any.txn.applicationCall()]).execute(() => { + const boxMap = Box({ key }) + test(boxMap) + }) + }, + }, + { + value: { a: new Str('hello'), b: new DynamicBytes('world'), c: new Bool(true) }, + newValue: { a: new Str('world'), b: new DynamicBytes('hello'), c: new Bool(false) }, + emptyValue: {}, + withBoxContext: (test: (boxMap: Box>) => void) => { + ctx.txn.createScope([ctx.any.txn.applicationCall()]).execute(() => { + const boxMap = Box>({ key }) test(boxMap) }) }, @@ -240,7 +285,7 @@ describe('Box', () => { } else if (box.value instanceof ARC4Encoded) { expect(box.value.bytes).toEqual(newBytesValue) } else { - expect(box.value).toEqual(newValue) + expect(toBytes(box.value)).toEqual(newBytesValue) } }) }) @@ -322,7 +367,7 @@ describe('Box', () => { const boxArc4Bool = Box({ key: 'arc4b' }) const boxUint = Box({ key: 'b' }) const boxStaticArray = Box>({ key: 'c' }) - const boxTuple = Box>({ key: 'e' }) + const boxTuple = Box>({ key: 'e' }) const errorMessage = 'attempt to box_put wrong size' boxBool.create({ size: 9 }) @@ -362,7 +407,7 @@ describe('Box', () => { const boxArc4Bool = Box({ key: 'arc4b' }) const boxUint = Box({ key: 'b' }) const boxStaticArray = Box>({ key: 'c' }) - const boxTuple = Box>({ key: 'e' }) + const boxTuple = Box>({ key: 'e' }) boxBool.create() expect(boxBool.length).toEqual(8) @@ -407,7 +452,7 @@ describe('Box', () => { const boxStr = Box({ key: 'a' }) const boxStaticArray = Box, 10>>({ key: 'c' }) const boxDynamicArray = Box>({ key: 'd' }) - const boxTuple = Box>({ key: 'e' }) + const boxTuple = Box>({ key: 'e' }) boxStr.create({ size: 2 }) boxStr.value = 'hello' @@ -443,7 +488,7 @@ describe('Box', () => { const boxStr = Box({ key: 'a' }) const boxStaticArray = Box, 10>>({ key: 'c' }) const boxDynamicArray = Box>({ key: 'd' }) - const boxTuple = Box>({ key: 'e' }) + const boxTuple = Box>({ key: 'e' }) boxStr.create({ size: 200 }) boxStr.value = 'hello' diff --git a/tests/test-fixture.ts b/tests/test-fixture.ts index 6a7de7d9..0cbfde86 100644 --- a/tests/test-fixture.ts +++ b/tests/test-fixture.ts @@ -246,6 +246,7 @@ async function compilePath( dryRun: false, logLevel: 'error' as Parameters[0]['logLevel'], skipVersionCheck: true, + customPuyaPath: process.env.PUYA_PATH || undefined, outputSsaIr: false, outputOptimizationIr: false, diff --git a/vitest.setup.ts b/vitest.setup.ts index 8a1a3940..ed602a0a 100644 --- a/vitest.setup.ts +++ b/vitest.setup.ts @@ -1,6 +1,11 @@ +// to setup mock env variables, copy contents of tests/.env.template to tests/.env and update them as needed +import * as dotenv from '@dotenvx/dotenvx' +import path from 'path' import { beforeAll, expect } from 'vitest' import { addEqualityTesters } from './src/set-up' +dotenv.config({ path: path.join(__dirname, '.env') }) + beforeAll(() => { addEqualityTesters({ expect }) }) From ba72eb4169e15bd59b28d8996a967ebf6bb4152e Mon Sep 17 00:00:00 2001 From: Bobby Lat Date: Mon, 14 Jul 2025 14:03:27 +0700 Subject: [PATCH 18/68] add encode decode tests for native arrays and objects --- src/impl/encoded-types/encoded-types.ts | 5 +- src/impl/encoded-types/helpers.ts | 7 +- src/impl/match.ts | 3 + tests/arc4/encode-decode-arc4.spec.ts | 8 +- tests/fixed-array.spec.ts | 131 ++++++++++++++- tests/native-mutable-array.spec.ts | 172 ++++++++++++++++++- tests/native-mutable-object.spec.ts | 211 +++++++++++++++++++++++- tests/native-readonly-array.spec.ts | 165 +++++++++++++++++- tests/native-readonly-object.spec.ts | 211 +++++++++++++++++++++++- 9 files changed, 901 insertions(+), 12 deletions(-) diff --git a/src/impl/encoded-types/encoded-types.ts b/src/impl/encoded-types/encoded-types.ts index 507750b6..bdc4b820 100644 --- a/src/impl/encoded-types/encoded-types.ts +++ b/src/impl/encoded-types/encoded-types.ts @@ -290,7 +290,7 @@ export class BoolImpl extends Bool { } get bytes(): bytes { - return Bytes(this.value) + return Bytes(this.value?.length ? this.value : new Uint8Array([0])) } static fromBytesImpl(value: StubBytesCompat | Uint8Array, typeInfo: string | TypeInfo, prefix: 'none' | 'log' = 'none'): BoolImpl { @@ -1182,7 +1182,8 @@ export const getArc4Encoded = (value: DeliberateAny, sourceTypeInfoString?: stri sourceTypeInfo?.name?.startsWith('ReadonlyArray') || value instanceof ReferenceArrayImpl ) { - const typeInfo = { name: `DynamicArray<${genericArgs[0].name}>`, genericArgs: { elementType: genericArgs[0] } } + const elementType = genericArgs[0] ?? sourceTypeInfo.genericArgs?.elementType + const typeInfo = { name: `DynamicArray<${elementType.name}>`, genericArgs: { elementType } } return new DynamicArrayImpl(typeInfo, ...(result as [ARC4Encoded, ...ARC4Encoded[]])) } else { const typeInfo = { name: `Tuple<[${genericArgs.map((x) => x.name).join(',')}]>`, genericArgs } diff --git a/src/impl/encoded-types/helpers.ts b/src/impl/encoded-types/helpers.ts index ea7196a4..fa7b720f 100644 --- a/src/impl/encoded-types/helpers.ts +++ b/src/impl/encoded-types/helpers.ts @@ -67,7 +67,12 @@ export const getNativeValue = (value: DeliberateAny, targetTypeInfo: TypeInfo | return Object.fromEntries( Object.entries(native).map(([key, value], index) => [ key, - getNativeValue(value, (targetTypeInfo?.genericArgs as TypeInfo[])?.[index]), + getNativeValue( + value, + Array.isArray(targetTypeInfo?.genericArgs) + ? (targetTypeInfo?.genericArgs as TypeInfo[])?.[index] + : ((targetTypeInfo?.genericArgs as Record)?.[key] as TypeInfo), + ), ]), ) } diff --git a/src/impl/match.ts b/src/impl/match.ts index ca553c95..628bfd20 100644 --- a/src/impl/match.ts +++ b/src/impl/match.ts @@ -3,6 +3,7 @@ import { ARC4Encoded } from '@algorandfoundation/algorand-typescript/arc4' import type { DeliberateAny } from '../typescript-helpers' import { asBytes, asMaybeBigUintCls, assert } from '../util' import { BytesBackedCls, Uint64BackedCls } from './base' +import { FixedArrayImpl } from './encoded-types/encoded-types' import type { StubBytesCompat, Uint64Cls } from './primitives' import { BytesCls } from './primitives' @@ -42,6 +43,8 @@ export const matchImpl: typeof match = (subject, test): boolean => { return (subject as unknown as ARC4Encoded).bytes.equals(test.bytes) } else if (Array.isArray(test)) { return test.map((x, i) => matchImpl((subject as DeliberateAny)[i], x as DeliberateAny)).every((x) => x) + } else if (test instanceof FixedArrayImpl) { + return test.items.map((x, i) => matchImpl((subject as DeliberateAny[])[i], x as DeliberateAny)).every((x) => x) } else if (typeof test === 'object') { return Object.entries(test!) .map(([k, v]) => matchImpl((subject as DeliberateAny)[k], v as DeliberateAny)) diff --git a/tests/arc4/encode-decode-arc4.spec.ts b/tests/arc4/encode-decode-arc4.spec.ts index d2e00266..3d361337 100644 --- a/tests/arc4/encode-decode-arc4.spec.ts +++ b/tests/arc4/encode-decode-arc4.spec.ts @@ -1,5 +1,5 @@ import type { biguint, bytes, uint64 } from '@algorandfoundation/algorand-typescript' -import { Bytes } from '@algorandfoundation/algorand-typescript' +import { assertMatch, Bytes } from '@algorandfoundation/algorand-typescript' import type { StaticBytes, UFixedNxM } from '@algorandfoundation/algorand-typescript/arc4' import { Address, @@ -168,7 +168,7 @@ describe('decodeArc4', () => { ...encodingUtil.utf8ToUint8Array('hello world'), ]), ) - const e = { a: 50n, b: new Uint8Array([1, 2, 3, 4, 5]) } + const e = { a: new UintN64(50n), b: new DynamicBytes(asBytes(new Uint8Array([1, 2, 3, 4, 5]))) } const eBytes = asBytes(new Uint8Array([...encodingUtil.bigIntToUint8Array(50n, 8), 0, 10, 0, 5, 1, 2, 3, 4, 5])) const f = new Address(Bytes.fromHex(`${'00'.repeat(31)}ff`)) const fBytes = Bytes.fromHex(`${'00'.repeat(31)}ff`) @@ -176,7 +176,7 @@ describe('decodeArc4', () => { expect(decodeArc4(bBytes)).toEqual(b) expect(decodeArc4(cBytes)).toEqual(c) expect(decodeArc4(dBytes)).toEqual(d) - expect(decodeArc4(eBytes)).toEqual(e) + assertMatch(decodeArc4(eBytes), e) const lenPrefix = itob(1).slice(6, 8) const offsetHeader = itob(2).slice(6, 8) @@ -184,7 +184,7 @@ describe('decodeArc4', () => { expect(decodeArc4(lenPrefix.concat(bBytes))).toEqual([b]) expect(decodeArc4(lenPrefix.concat(cBytes))).toEqual([c]) expect(decodeArc4(Bytes`${lenPrefix}${offsetHeader}${dBytes}`)).toEqual([d]) - expect(decodeArc4(Bytes`${lenPrefix}${offsetHeader}${eBytes}`)).toEqual([e]) + assertMatch(decodeArc4(Bytes`${lenPrefix}${offsetHeader}${eBytes}`), [e]) expect(JSON.stringify(decodeArc4(Bytes`${lenPrefix}${fBytes}`))).toEqual(JSON.stringify([f])) }) }) diff --git a/tests/fixed-array.spec.ts b/tests/fixed-array.spec.ts index a9358795..5da3dd70 100644 --- a/tests/fixed-array.spec.ts +++ b/tests/fixed-array.spec.ts @@ -14,7 +14,7 @@ import { Uint64, } from '@algorandfoundation/algorand-typescript' import { TestExecutionContext } from '@algorandfoundation/algorand-typescript-testing' -import { methodSelector } from '@algorandfoundation/algorand-typescript/arc4' +import { decodeArc4, encodeArc4, interpretAsArc4, methodSelector } from '@algorandfoundation/algorand-typescript/arc4' import { describe, expect, it } from 'vitest' class TestContract extends Contract { @@ -426,4 +426,133 @@ describe('FixedArray', () => { ) }) }) + + describe('decode and encode', () => { + it('should decode and encode native uint64 fixed array', () => { + const arr = new FixedArray(10, 20, 30) + const encoded = encodeArc4(arr) + const interpreted = interpretAsArc4>(encoded) + const decoded = decodeArc4>(encoded) + + assertMatch(interpreted.length, arr.length) + for (let i = 0; i < arr.length; i++) { + assertMatch(interpreted[i].native, arr[i]) + } + assertMatch(decoded, arr) + }) + + it('should decode and encode string fixed array', () => { + const arr = new FixedArray('hello', 'world') + const encoded = encodeArc4(arr) + const interpreted = interpretAsArc4>(encoded) + const decoded = decodeArc4>(encoded) + + assertMatch(interpreted.length, arr.length) + for (let i = 0; i < arr.length; i++) { + assertMatch(interpreted[i].native, arr[i]) + } + assertMatch(decoded, arr) + }) + + it('should decode and encode boolean fixed array', () => { + const arr = new FixedArray(true, false, true, false, true, false, true, false, true, false) + const encoded = encodeArc4(arr) + const interpreted = interpretAsArc4>(encoded) + const decoded = decodeArc4>(encoded) + + assertMatch(interpreted.length, arr.length) + for (let i = 0; i < arr.length; i++) { + assertMatch(interpreted[i].native, arr[i]) + } + assertMatch(decoded, arr) + }) + + it('should decode and encode bytes fixed array', () => { + const arr = new FixedArray(Bytes('hello'), Bytes('world')) + const encoded = encodeArc4(arr) + const interpreted = interpretAsArc4>(encoded) + const decoded = decodeArc4>(encoded) + + assertMatch(interpreted.length, arr.length) + for (let i = 0; i < arr.length; i++) { + assertMatch(interpreted[i].native, arr[i]) + } + assertMatch(decoded, arr) + }) + + it('should decode and encode nested fixed array', () => { + const arr = new FixedArray, 2>(new FixedArray(1, 2), new FixedArray(3, 4)) + const encoded = encodeArc4(arr) + const interpreted = interpretAsArc4, 2>>(encoded) + const decoded = decodeArc4, 2>>(encoded) + + assertMatch(interpreted.length, arr.length) + for (let i = 0; i < arr.length; i++) { + assertMatch(interpreted[i].length, arr[i].length) + for (let j = 0; j < arr[i].length; j++) { + assertMatch(interpreted[i][j].native, arr[i][j]) + } + } + assertMatch(decoded, arr) + }) + + it('should decode and encode fixed array with native arrays', () => { + const arr = new FixedArray([1, 2, 3], [4, 5]) + const encoded = encodeArc4(arr) + const interpreted = interpretAsArc4, 2>>(encoded) + const decoded = decodeArc4>(encoded) + + assertMatch(interpreted.length, arr.length) + for (let i = 0; i < arr.length; i++) { + assertMatch(interpreted[i].length, arr[i].length) + for (let j = 0; j < arr[i].length; j++) { + assertMatch(interpreted[i][j].native, arr[i][j]) + } + } + assertMatch(decoded, arr) + }) + + it('should decode and encode fixed array with tuples', () => { + const arr = new FixedArray<[uint64, string], 2>([10, 'first'], [20, 'second']) + const encoded = encodeArc4(arr) + const interpreted = interpretAsArc4, 2>>(encoded) + const decoded = decodeArc4>(encoded) + + assertMatch(interpreted.length, arr.length) + for (let i = 0; i < arr.length; i++) { + assertMatch(interpreted[i].native[0].native, arr[i][0]) + assertMatch(interpreted[i].native[1].native, arr[i][1]) + } + assertMatch(decoded, arr) + }) + + it('should decode and encode fixed array with objects', () => { + type Point = { x: uint64; y: uint64 } + const arr = new FixedArray({ x: 1, y: 2 }, { x: 3, y: 4 }) + class ObjStruct extends arc4.Struct<{ x: arc4.UintN64; y: arc4.UintN64 }> {} + const encoded = encodeArc4(arr) + const interpreted = interpretAsArc4>(encoded) + const decoded = decodeArc4>(encoded) + + assertMatch(interpreted.length, arr.length) + for (let i = 0; i < arr.length; i++) { + assertMatch(interpreted[i].x.native, arr[i].x) + assertMatch(interpreted[i].y.native, arr[i].y) + } + assertMatch(decoded, arr) + }) + + it('should decode and encode arc4 fixed array', () => { + const arr = new FixedArray(new arc4.UintN64(100), new arc4.UintN64(200), new arc4.UintN64(300)) + const encoded = encodeArc4(arr) + const interpreted = interpretAsArc4>(encoded) + const decoded = decodeArc4>(encoded) + + assertMatch(interpreted.length, arr.length) + for (let i = 0; i < arr.length; i++) { + assertMatch(interpreted[i], arr[i]) + } + assertMatch(decoded, arr) + }) + }) }) diff --git a/tests/native-mutable-array.spec.ts b/tests/native-mutable-array.spec.ts index 18131ff5..76d5bb0b 100644 --- a/tests/native-mutable-array.spec.ts +++ b/tests/native-mutable-array.spec.ts @@ -14,7 +14,7 @@ import { Uint64, } from '@algorandfoundation/algorand-typescript' import { TestExecutionContext } from '@algorandfoundation/algorand-typescript-testing' -import { methodSelector } from '@algorandfoundation/algorand-typescript/arc4' +import { decodeArc4, encodeArc4, interpretAsArc4, methodSelector } from '@algorandfoundation/algorand-typescript/arc4' import { describe, expect, it } from 'vitest' class TestContract extends Contract { @@ -304,4 +304,174 @@ describe('native mutable array', () => { ) }) }) + + describe('decode and encode', () => { + it('should decode and encode mutable uint64 array', () => { + const arr: uint64[] = [10, 20, 30, 40] + const encoded = encodeArc4(arr) + const interpreted = interpretAsArc4>(encoded) + const decoded = decodeArc4(encoded) + + assertMatch(interpreted.length, arr.length) + for (let i = 0; i < arr.length; i++) { + assertMatch(interpreted[i].native, arr[i]) + } + assertMatch(decoded, arr) + }) + + it('should decode and encode mutable string array', () => { + const arr: string[] = ['hello', 'world', 'test', 'mutable'] + const encoded = encodeArc4(arr) + const interpreted = interpretAsArc4>(encoded) + const decoded = decodeArc4(encoded) + + assertMatch(interpreted.length, arr.length) + for (let i = 0; i < arr.length; i++) { + assertMatch(interpreted[i].native, arr[i]) + } + assertMatch(decoded, arr) + }) + + it('should decode and encode mutable boolean array', () => { + const arr: boolean[] = [true, false, true, false, true, true, false, true, false, true] + const encoded = encodeArc4(arr) + const interpreted = interpretAsArc4>(encoded) + const decoded = decodeArc4(encoded) + + assertMatch(interpreted.length, arr.length) + for (let i = 0; i < arr.length; i++) { + assertMatch(interpreted[i].native, arr[i]) + } + assertMatch(decoded, arr) + }) + + it('should decode and encode mutable bytes array', () => { + const arr: bytes[] = [Bytes('hello'), Bytes('world'), Bytes('test'), Bytes('data')] + const encoded = encodeArc4(arr) + const interpreted = interpretAsArc4>(encoded) + const decoded = decodeArc4(encoded) + + assertMatch(interpreted.length, arr.length) + for (let i = 0; i < arr.length; i++) { + assertMatch(interpreted[i].native, arr[i]) + } + assertMatch(decoded, arr) + }) + + it('should decode and encode mutable nested array', () => { + const arr: uint64[][] = [[1, 2], [3, 4, 5], [6], [7, 8, 9, 10]] + const encoded = encodeArc4(arr) + const interpreted = interpretAsArc4>>(encoded) + const decoded = decodeArc4(encoded) + + assertMatch(interpreted.length, arr.length) + for (let i = 0; i < arr.length; i++) { + assertMatch(interpreted[i].length, arr[i].length) + for (let j = 0; j < arr[i].length; j++) { + assertMatch(interpreted[i][j].native, arr[i][j]) + } + } + assertMatch(decoded, arr) + }) + + it('should decode and encode mutable array with FixedArrays', () => { + const arr: FixedArray[] = [ + new FixedArray(1, 2), + new FixedArray(3, 4), + new FixedArray(5, 6), + ] + const encoded = encodeArc4(arr) + const interpreted = interpretAsArc4>>(encoded) + const decoded = decodeArc4[]>(encoded) + + assertMatch(interpreted.length, arr.length) + for (let i = 0; i < arr.length; i++) { + assertMatch(interpreted[i].length, arr[i].length) + for (let j = 0; j < arr[i].length; j++) { + assertMatch(interpreted[i][j].native, arr[i][j]) + } + } + assertMatch(decoded, arr) + }) + + it('should decode and encode mutable array with tuples', () => { + const arr: [uint64, string][] = [ + [10, 'first'], + [20, 'second'], + [30, 'third'], + ] + const encoded = encodeArc4(arr) + const interpreted = interpretAsArc4>>(encoded) + const decoded = decodeArc4<[uint64, string][]>(encoded) + + assertMatch(interpreted.length, arr.length) + for (let i = 0; i < arr.length; i++) { + assertMatch(interpreted[i].native[0].native, arr[i][0]) + assertMatch(interpreted[i].native[1].native, arr[i][1]) + } + assertMatch(decoded, arr) + }) + + it('should decode and encode mutable array with objects', () => { + type Point = { x: uint64; y: uint64 } + const arr: Point[] = [ + { x: 1, y: 2 }, + { x: 3, y: 4 }, + { x: 5, y: 6 }, + ] + class PointStruct extends arc4.Struct<{ x: arc4.UintN64; y: arc4.UintN64 }> {} + const encoded = encodeArc4(arr) + const interpreted = interpretAsArc4>(encoded) + const decoded = decodeArc4(encoded) + + assertMatch(interpreted.length, arr.length) + for (let i = 0; i < arr.length; i++) { + assertMatch(interpreted[i].x.native, arr[i].x) + assertMatch(interpreted[i].y.native, arr[i].y) + } + assertMatch(decoded, arr) + }) + + it('should decode and encode mutable arc4 array', () => { + const arr: arc4.UintN64[] = [new arc4.UintN64(100), new arc4.UintN64(200), new arc4.UintN64(300), new arc4.UintN64(400)] + const encoded = encodeArc4(arr) + const interpreted = interpretAsArc4>(encoded) + const decoded = decodeArc4(encoded) + + assertMatch(interpreted.length, arr.length) + for (let i = 0; i < arr.length; i++) { + assertMatch(interpreted[i], arr[i]) + } + assertMatch(decoded, arr) + }) + + it('should decode and encode empty mutable array', () => { + const arr: uint64[] = [] + const encoded = encodeArc4(arr) + const interpreted = interpretAsArc4>(encoded) + const decoded = decodeArc4(encoded) + + assertMatch(interpreted.length, 0) + assertMatch(decoded, []) + }) + + it('should decode and encode after array mutation', () => { + const arr: uint64[] = [1, 2, 3] + + // Mutate the array + arr.push(4, 5) + arr[0] = 10 + + const encoded = encodeArc4(arr) + const interpreted = interpretAsArc4>(encoded) + const decoded = decodeArc4(encoded) + + const expected = [10, 2, 3, 4, 5] + assertMatch(interpreted.length, expected.length) + for (let i = 0; i < expected.length; i++) { + assertMatch(interpreted[i].native, expected[i]) + } + assertMatch(decoded, expected) + }) + }) }) diff --git a/tests/native-mutable-object.spec.ts b/tests/native-mutable-object.spec.ts index 82078809..e86ef61a 100644 --- a/tests/native-mutable-object.spec.ts +++ b/tests/native-mutable-object.spec.ts @@ -13,7 +13,7 @@ import { LocalState, } from '@algorandfoundation/algorand-typescript' import { TestExecutionContext } from '@algorandfoundation/algorand-typescript-testing' -import { methodSelector } from '@algorandfoundation/algorand-typescript/arc4' +import { decodeArc4, encodeArc4, interpretAsArc4, methodSelector } from '@algorandfoundation/algorand-typescript/arc4' import { describe, expect, it } from 'vitest' type SimpleObj = { @@ -580,4 +580,213 @@ describe('native mutable object', () => { ) }) }) + + describe('decode and encode', () => { + it('should decode and encode simple mutable object', () => { + class SimpleObjStruct extends arc4.Struct<{ + a: arc4.UintN64 + b: arc4.Bool + c: arc4.Str + d: arc4.DynamicBytes + }> {} + const obj: SimpleObj = { a: 1, b: true, c: 'hello', d: Bytes('world') } + const encoded = encodeArc4(obj) + const interpreted = interpretAsArc4(encoded) + const decoded = decodeArc4(encoded) + + assertMatch(interpreted.a.native, obj.a) + assertMatch(interpreted.b.native, obj.b) + assertMatch(interpreted.c.native, obj.c) + assertMatch(interpreted.d.native, obj.d) + assertMatch(decoded, obj) + }) + + it('should decode and encode nested mutable object', () => { + class ObjectStruct extends arc4.Struct<{ + x: arc4.UintN64 + y: arc4.Str + z: arc4.Bool + }> {} + class NestedObjStruct extends arc4.Struct<{ + a: arc4.UintN64 + b: arc4.Bool + c: arc4.Str + d: ObjectStruct + }> {} + + const obj: NestedObj = { + a: 42, + b: false, + c: 'nested', + d: { x: 100, y: 'inner', z: true }, + } + + const encoded = encodeArc4(obj) + const interpreted = interpretAsArc4(encoded) + const decoded = decodeArc4(encoded) + + assertMatch(interpreted.a.native, obj.a) + assertMatch(interpreted.b.native, obj.b) + assertMatch(interpreted.c.native, obj.c) + assertMatch(interpreted.d.x.native, obj.d.x) + assertMatch(interpreted.d.y.native, obj.d.y) + assertMatch(interpreted.d.z.native, obj.d.z) + assertMatch(decoded, obj) + }) + + it('should decode and encode array object', () => { + class ArrayObjStruct extends arc4.Struct<{ + a: arc4.UintN64 + b: arc4.Bool + c: arc4.Str + d: arc4.DynamicArray + }> {} + + const obj: ArrayObj = { + a: 123, + b: true, + c: 'array test', + d: [10, 20, 30, 40], + } + + const encoded = encodeArc4(obj) + const interpreted = interpretAsArc4(encoded) + const decoded = decodeArc4(encoded) + + assertMatch(interpreted.a.native, obj.a) + assertMatch(interpreted.b.native, obj.b) + assertMatch(interpreted.c.native, obj.c) + assertMatch(interpreted.d.length, obj.d.length) + for (let i = 0; i < obj.d.length; i++) { + assertMatch(interpreted.d[i].native, obj.d[i]) + } + assertMatch(decoded, obj) + }) + + it('should decode and encode fixed array object', () => { + class FixedArrayObjStruct extends arc4.Struct<{ + a: arc4.UintN64 + b: arc4.Bool + c: arc4.Str + d: arc4.StaticArray + }> {} + + const obj: FixedArrayObj = { + a: 456, + b: false, + c: 'fixed array', + d: new FixedArray(5, 10, 15), + } + + const encoded = encodeArc4(obj) + const interpreted = interpretAsArc4(encoded) + const decoded = decodeArc4(encoded) + + assertMatch(interpreted.a.native, obj.a) + assertMatch(interpreted.b.native, obj.b) + assertMatch(interpreted.c.native, obj.c) + assertMatch(interpreted.d.length, obj.d.length) + for (let i = 0; i < obj.d.length; i++) { + assertMatch(interpreted.d[i].native, obj.d[i]) + } + assertMatch(decoded, obj) + }) + + it('should decode and encode tuple object', () => { + class TupleObjStruct extends arc4.Struct<{ + a: arc4.UintN64 + b: arc4.Bool + c: arc4.Str + d: arc4.Tuple<[arc4.UintN64, arc4.Str, arc4.Bool]> + }> {} + + const obj: TupleObj = { + a: 789, + b: true, + c: 'tuple test', + d: [99, 'tuple string', false], + } + + const encoded = encodeArc4(obj) + const interpreted = interpretAsArc4(encoded) + const decoded = decodeArc4(encoded) + + assertMatch(interpreted.a.native, obj.a) + assertMatch(interpreted.b.native, obj.b) + assertMatch(interpreted.c.native, obj.c) + assertMatch(interpreted.d.native[0].native, obj.d[0]) + assertMatch(interpreted.d.native[1].native, obj.d[1]) + assertMatch(interpreted.d.native[2].native, obj.d[2]) + assertMatch(decoded, obj) + }) + + it('should decode and encode arc4 primitive object', () => { + class Arc4PrimitiveObjStruct extends arc4.Struct<{ + a: arc4.UintN64 + b: arc4.Bool + c: arc4.Str + d: arc4.Byte + }> {} + + const obj: Arc4PrimitiveObj = { + a: new arc4.UintN64(999), + b: new arc4.Bool(false), + c: new arc4.Str('arc4 test'), + d: new arc4.Byte(255), + } + + const encoded = encodeArc4(obj) + const interpreted = interpretAsArc4(encoded) + const decoded = decodeArc4(encoded) + + assertMatch(interpreted.a, obj.a) + assertMatch(interpreted.b, obj.b) + assertMatch(interpreted.c, obj.c) + assertMatch(interpreted.d, obj.d) + assertMatch(decoded, obj) + }) + + it('should decode and encode deep nested object', () => { + class ObjStruct extends arc4.Struct<{ + p: arc4.UintN64 + q: arc4.Str + }> {} + class NestedObjStruct extends arc4.Struct<{ + x: ObjStruct + y: arc4.DynamicArray + z: arc4.Tuple<[arc4.UintN64, arc4.Bool]> + }> {} + class DeepNestedObjStruct extends arc4.Struct<{ + a: arc4.UintN64 + b: NestedObjStruct + c: arc4.Str + }> {} + + const obj: DeepNestedObj = { + a: 555, + b: { + x: { p: 111, q: 'deep' }, + y: ['first', 'second', 'third'], + z: [222, true], + }, + c: 'deep test', + } + + const encoded = encodeArc4(obj) + const interpreted = interpretAsArc4(encoded) + const decoded = decodeArc4(encoded) + + assertMatch(interpreted.a.native, obj.a) + assertMatch(interpreted.b.x.p.native, obj.b.x.p) + assertMatch(interpreted.b.x.q.native, obj.b.x.q) + assertMatch(interpreted.b.y.length, obj.b.y.length) + for (let i = 0; i < obj.b.y.length; i++) { + assertMatch(interpreted.b.y[i].native, obj.b.y[i]) + } + assertMatch(interpreted.b.z.native[0].native, obj.b.z[0]) + assertMatch(interpreted.b.z.native[1].native, obj.b.z[1]) + assertMatch(interpreted.c.native, obj.c) + assertMatch(decoded, obj) + }) + }) }) diff --git a/tests/native-readonly-array.spec.ts b/tests/native-readonly-array.spec.ts index 28d4deef..8f7836b0 100644 --- a/tests/native-readonly-array.spec.ts +++ b/tests/native-readonly-array.spec.ts @@ -14,7 +14,7 @@ import { Uint64, } from '@algorandfoundation/algorand-typescript' import { TestExecutionContext } from '@algorandfoundation/algorand-typescript-testing' -import { methodSelector } from '@algorandfoundation/algorand-typescript/arc4' +import { decodeArc4, encodeArc4, interpretAsArc4, methodSelector } from '@algorandfoundation/algorand-typescript/arc4' import { describe, expect, it } from 'vitest' class TestContract extends Contract { @@ -46,6 +46,18 @@ describe('native readonly array', () => { expect(arr.at(0)).toEqual(1) expect(arr.at(-1)).toEqual(3) }) + + it('update element with with() method', () => { + const arr: readonly uint64[] = [1, 2, 3] + + let updatedArr = arr.with(0, Uint64(10)) + assertMatch(updatedArr, [10, 2, 3]) + + updatedArr = arr.with(-1, Uint64(30)) + assertMatch(updatedArr, [1, 2, 30]) + + assertMatch(arr, [1, 2, 3]) + }) }) describe('copy()', () => { @@ -323,4 +335,155 @@ describe('native readonly array', () => { ) }) }) + + describe('decode and encode', () => { + it('should decode and encode readonly uint64 array', () => { + const arr: readonly uint64[] = [10, 20, 30, 40] + const encoded = encodeArc4(arr) + const interpreted = interpretAsArc4>(encoded) + const decoded = decodeArc4(encoded) + + assertMatch(interpreted.length, arr.length) + for (let i = 0; i < arr.length; i++) { + assertMatch(interpreted[i].native, arr[i]) + } + assertMatch(decoded, arr) + }) + + it('should decode and encode readonly string array', () => { + const arr: readonly string[] = ['hello', 'world', 'test'] + const encoded = encodeArc4(arr) + const interpreted = interpretAsArc4>(encoded) + const decoded = decodeArc4(encoded) + + assertMatch(interpreted.length, arr.length) + for (let i = 0; i < arr.length; i++) { + assertMatch(interpreted[i].native, arr[i]) + } + assertMatch(decoded, arr) + }) + + it('should decode and encode readonly boolean array', () => { + const arr: readonly boolean[] = [true, false, true, false, true, true, false, true, false, true] + const encoded = encodeArc4(arr) + const interpreted = interpretAsArc4>(encoded) + const decoded = decodeArc4(encoded) + + assertMatch(interpreted.length, arr.length) + for (let i = 0; i < arr.length; i++) { + assertMatch(interpreted[i].native, arr[i]) + } + assertMatch(decoded, arr) + }) + + it('should decode and encode readonly bytes array', () => { + const arr: readonly bytes[] = [Bytes('hello'), Bytes('world'), Bytes('test')] + const encoded = encodeArc4(arr) + const interpreted = interpretAsArc4>(encoded) + const decoded = decodeArc4(encoded) + + assertMatch(interpreted.length, arr.length) + for (let i = 0; i < arr.length; i++) { + assertMatch(interpreted[i].native, arr[i]) + } + assertMatch(decoded, arr) + }) + + it('should decode and encode readonly nested array', () => { + const arr: readonly (readonly uint64[])[] = [[1, 2], [3, 4, 5], [6]] + const encoded = encodeArc4(arr) + const interpreted = interpretAsArc4>>(encoded) + const decoded = decodeArc4(encoded) + + assertMatch(interpreted.length, arr.length) + for (let i = 0; i < arr.length; i++) { + assertMatch(interpreted[i].length, arr[i].length) + for (let j = 0; j < arr[i].length; j++) { + assertMatch(interpreted[i][j].native, arr[i][j]) + } + } + assertMatch(decoded, arr) + }) + + it('should decode and encode readonly array with FixedArrays', () => { + const arr: readonly FixedArray[] = [ + new FixedArray(1, 2), + new FixedArray(3, 4), + new FixedArray(5, 6), + ] + const encoded = encodeArc4(arr) + const interpreted = interpretAsArc4>>(encoded) + const decoded = decodeArc4[]>(encoded) + + assertMatch(interpreted.length, arr.length) + for (let i = 0; i < arr.length; i++) { + assertMatch(interpreted[i].length, arr[i].length) + for (let j = 0; j < arr[i].length; j++) { + assertMatch(interpreted[i][j].native, arr[i][j]) + } + } + assertMatch(decoded, arr) + }) + + it('should decode and encode readonly array with tuples', () => { + const arr: readonly (readonly [uint64, string])[] = [ + [10, 'first'], + [20, 'second'], + [30, 'third'], + ] + const encoded = encodeArc4(arr) + const interpreted = interpretAsArc4>>(encoded) + const decoded = decodeArc4(encoded) + + assertMatch(interpreted.length, arr.length) + for (let i = 0; i < arr.length; i++) { + assertMatch(interpreted[i].native[0].native, arr[i][0]) + assertMatch(interpreted[i].native[1].native, arr[i][1]) + } + assertMatch(decoded, arr) + }) + + it('should decode and encode readonly array with objects', () => { + type Point = Readonly<{ x: uint64; y: uint64 }> + const arr: readonly Point[] = [ + { x: 1, y: 2 }, + { x: 3, y: 4 }, + { x: 5, y: 6 }, + ] + class PointStruct extends arc4.Struct<{ x: arc4.UintN64; y: arc4.UintN64 }> {} + const encoded = encodeArc4(arr) + const interpreted = interpretAsArc4>(encoded) + const decoded = decodeArc4(encoded) + + assertMatch(interpreted.length, arr.length) + for (let i = 0; i < arr.length; i++) { + assertMatch(interpreted[i].x.native, arr[i].x) + assertMatch(interpreted[i].y.native, arr[i].y) + } + assertMatch(decoded, arr) + }) + + it('should decode and encode readonly arc4 array', () => { + const arr: readonly arc4.UintN64[] = [new arc4.UintN64(100), new arc4.UintN64(200), new arc4.UintN64(300)] + const encoded = encodeArc4(arr) + const interpreted = interpretAsArc4>(encoded) + const decoded = decodeArc4(encoded) + + assertMatch(interpreted.length, arr.length) + for (let i = 0; i < arr.length; i++) { + assertMatch(interpreted[i], arr[i]) + } + assertMatch(decoded, arr) + }) + + it('should decode and encode empty readonly array', () => { + const arr: readonly uint64[] = [] + const encoded = encodeArc4(arr) + const interpreted = interpretAsArc4>(encoded) + const decoded = decodeArc4(encoded) + + assertMatch(interpreted.length, 0) + assertMatch(decoded, []) + }) + }) }) diff --git a/tests/native-readonly-object.spec.ts b/tests/native-readonly-object.spec.ts index 5d069108..328df000 100644 --- a/tests/native-readonly-object.spec.ts +++ b/tests/native-readonly-object.spec.ts @@ -13,7 +13,7 @@ import { LocalState, } from '@algorandfoundation/algorand-typescript' import { TestExecutionContext } from '@algorandfoundation/algorand-typescript-testing' -import { methodSelector } from '@algorandfoundation/algorand-typescript/arc4' +import { decodeArc4, encodeArc4, interpretAsArc4, methodSelector } from '@algorandfoundation/algorand-typescript/arc4' import { describe, expect, it } from 'vitest' type SimpleObj = Readonly<{ @@ -590,4 +590,213 @@ describe('native readonly object', () => { ) }) }) + + describe('decode and encode', () => { + it('should decode and encode simple mutable object', () => { + class SimpleObjStruct extends arc4.Struct<{ + a: arc4.UintN64 + b: arc4.Bool + c: arc4.Str + d: arc4.DynamicBytes + }> {} + const obj: SimpleObj = { a: 1, b: true, c: 'hello', d: Bytes('world') } + const encoded = encodeArc4(obj) + const interpreted = interpretAsArc4(encoded) + const decoded = decodeArc4(encoded) + + assertMatch(interpreted.a.native, obj.a) + assertMatch(interpreted.b.native, obj.b) + assertMatch(interpreted.c.native, obj.c) + assertMatch(interpreted.d.native, obj.d) + assertMatch(decoded, obj) + }) + + it('should decode and encode nested mutable object', () => { + class ObjectStruct extends arc4.Struct<{ + x: arc4.UintN64 + y: arc4.Str + z: arc4.Bool + }> {} + class NestedObjStruct extends arc4.Struct<{ + a: arc4.UintN64 + b: arc4.Bool + c: arc4.Str + d: ObjectStruct + }> {} + + const obj: NestedObj = { + a: 42, + b: false, + c: 'nested', + d: { x: 100, y: 'inner', z: true }, + } + + const encoded = encodeArc4(obj) + const interpreted = interpretAsArc4(encoded) + const decoded = decodeArc4(encoded) + + assertMatch(interpreted.a.native, obj.a) + assertMatch(interpreted.b.native, obj.b) + assertMatch(interpreted.c.native, obj.c) + assertMatch(interpreted.d.x.native, obj.d.x) + assertMatch(interpreted.d.y.native, obj.d.y) + assertMatch(interpreted.d.z.native, obj.d.z) + assertMatch(decoded, obj) + }) + + it('should decode and encode array object', () => { + class ArrayObjStruct extends arc4.Struct<{ + a: arc4.UintN64 + b: arc4.Bool + c: arc4.Str + d: arc4.DynamicArray + }> {} + + const obj: ArrayObj = { + a: 123, + b: true, + c: 'array test', + d: [10, 20, 30, 40], + } + + const encoded = encodeArc4(obj) + const interpreted = interpretAsArc4(encoded) + const decoded = decodeArc4(encoded) + + assertMatch(interpreted.a.native, obj.a) + assertMatch(interpreted.b.native, obj.b) + assertMatch(interpreted.c.native, obj.c) + assertMatch(interpreted.d.length, obj.d.length) + for (let i = 0; i < obj.d.length; i++) { + assertMatch(interpreted.d[i].native, obj.d[i]) + } + assertMatch(decoded, obj) + }) + + it('should decode and encode fixed array object', () => { + class FixedArrayObjStruct extends arc4.Struct<{ + a: arc4.UintN64 + b: arc4.Bool + c: arc4.Str + d: arc4.StaticArray + }> {} + + const obj: FixedArrayReadonlyObj = { + a: 456, + b: false, + c: 'fixed array', + d: new FixedArray(5, 10, 15), + } + + const encoded = encodeArc4(obj) + const interpreted = interpretAsArc4(encoded) + const decoded = decodeArc4(encoded) + + assertMatch(interpreted.a.native, obj.a) + assertMatch(interpreted.b.native, obj.b) + assertMatch(interpreted.c.native, obj.c) + assertMatch(interpreted.d.length, obj.d.length) + for (let i = 0; i < obj.d.length; i++) { + assertMatch(interpreted.d[i].native, obj.d[i]) + } + assertMatch(decoded, obj) + }) + + it('should decode and encode tuple object', () => { + class TupleObjStruct extends arc4.Struct<{ + a: arc4.UintN64 + b: arc4.Bool + c: arc4.Str + d: arc4.Tuple<[arc4.UintN64, arc4.Str, arc4.Bool]> + }> {} + + const obj: TupleObj = { + a: 789, + b: true, + c: 'tuple test', + d: [99, 'tuple string', false], + } + + const encoded = encodeArc4(obj) + const interpreted = interpretAsArc4(encoded) + const decoded = decodeArc4(encoded) + + assertMatch(interpreted.a.native, obj.a) + assertMatch(interpreted.b.native, obj.b) + assertMatch(interpreted.c.native, obj.c) + assertMatch(interpreted.d.native[0].native, obj.d[0]) + assertMatch(interpreted.d.native[1].native, obj.d[1]) + assertMatch(interpreted.d.native[2].native, obj.d[2]) + assertMatch(decoded, obj) + }) + + it('should decode and encode arc4 primitive object', () => { + class Arc4PrimitiveObjStruct extends arc4.Struct<{ + a: arc4.UintN64 + b: arc4.Bool + c: arc4.Str + d: arc4.Byte + }> {} + + const obj: Arc4PrimitiveReadonlyObj = { + a: new arc4.UintN64(999), + b: new arc4.Bool(false), + c: new arc4.Str('arc4 test'), + d: new arc4.Byte(255), + } + + const encoded = encodeArc4(obj) + const interpreted = interpretAsArc4(encoded) + const decoded = decodeArc4(encoded) + + assertMatch(interpreted.a, obj.a) + assertMatch(interpreted.b, obj.b) + assertMatch(interpreted.c, obj.c) + assertMatch(interpreted.d, obj.d) + assertMatch(decoded, obj) + }) + + it('should decode and encode deep nested object', () => { + class ObjStruct extends arc4.Struct<{ + p: arc4.UintN64 + q: arc4.Str + }> {} + class NestedObjStruct extends arc4.Struct<{ + x: ObjStruct + y: arc4.DynamicArray + z: arc4.Tuple<[arc4.UintN64, arc4.Bool]> + }> {} + class DeepNestedObjStruct extends arc4.Struct<{ + a: arc4.UintN64 + b: NestedObjStruct + c: arc4.Str + }> {} + + const obj: DeepNestedReadonlyObj = { + a: 555, + b: { + x: { p: 111, q: 'deep' }, + y: ['first', 'second', 'third'], + z: [222, true], + }, + c: 'deep test', + } + + const encoded = encodeArc4(obj) + const interpreted = interpretAsArc4(encoded) + const decoded = decodeArc4(encoded) + + assertMatch(interpreted.a.native, obj.a) + assertMatch(interpreted.b.x.p.native, obj.b.x.p) + assertMatch(interpreted.b.x.q.native, obj.b.x.q) + assertMatch(interpreted.b.y.length, obj.b.y.length) + for (let i = 0; i < obj.b.y.length; i++) { + assertMatch(interpreted.b.y[i].native, obj.b.y[i]) + } + assertMatch(interpreted.b.z.native[0].native, obj.b.z[0]) + assertMatch(interpreted.b.z.native[1].native, obj.b.z[1]) + assertMatch(interpreted.c.native, obj.c) + assertMatch(decoded, obj) + }) + }) }) From 13e22f412be91590f74684ca15136485d8542111 Mon Sep 17 00:00:00 2001 From: Bobby Lat Date: Tue, 15 Jul 2025 13:11:10 +0700 Subject: [PATCH 19/68] fix assert match implementation to compare array lengths --- src/impl/match.ts | 8 +++++--- tests/match.spec.ts | 20 +++++++++++--------- tests/native-readonly-object.spec.ts | 1 + 3 files changed, 17 insertions(+), 12 deletions(-) diff --git a/src/impl/match.ts b/src/impl/match.ts index 628bfd20..c25a053c 100644 --- a/src/impl/match.ts +++ b/src/impl/match.ts @@ -42,7 +42,10 @@ export const matchImpl: typeof match = (subject, test): boolean => { } else if (test instanceof ARC4Encoded) { return (subject as unknown as ARC4Encoded).bytes.equals(test.bytes) } else if (Array.isArray(test)) { - return test.map((x, i) => matchImpl((subject as DeliberateAny)[i], x as DeliberateAny)).every((x) => x) + return ( + (subject as DeliberateAny).length === test.length && + test.map((x, i) => matchImpl((subject as DeliberateAny)[i], x as DeliberateAny)).every((x) => x) + ) } else if (test instanceof FixedArrayImpl) { return test.items.map((x, i) => matchImpl((subject as DeliberateAny[])[i], x as DeliberateAny)).every((x) => x) } else if (typeof test === 'object') { @@ -53,10 +56,9 @@ export const matchImpl: typeof match = (subject, test): boolean => { return false } -export const assertMatchImpl: typeof assertMatch = (subject, test, message): boolean => { +export const assertMatchImpl: typeof assertMatch = (subject, test, message): void => { const isMatching = matchImpl(subject, test) assert(isMatching, message) - return isMatching } const getBigIntValue = (x: unknown) => { diff --git a/tests/match.spec.ts b/tests/match.spec.ts index 2ced7493..a5fb9afa 100644 --- a/tests/match.spec.ts +++ b/tests/match.spec.ts @@ -52,6 +52,7 @@ describe('match', () => { { subject: MAX_UINT512, test: { not: MAX_UINT512 }, expected: false }, { subject: { a: 42 }, test: { a: { not: 3 } }, expected: true }, { subject: { a: 42 }, test: { a: { not: 42 } }, expected: false }, + { subject: [Uint64(1), Uint64(2), Uint64(3)], test: [Uint64(1), { lessThanEq: Uint64(2) }], expected: false }, ] const account1 = ctx.any.account() @@ -109,10 +110,10 @@ describe('match', () => { { subject: { a: 'hello', b: 42, c: arc4Str1 }, test: { c: differentArc4Str }, expected: false }, { subject: { a: 'hello', b: 42, c: arc4Str1 }, test: { not: { c: differentArc4Str } }, expected: true }, { subject: ['hello', 42, arc4Str1], test: ['hello', { lessThanEq: 42 }, sameArc4Str], expected: true }, - { subject: ['hello', 42, arc4Str1], test: ['hello'], expected: true }, - { subject: ['hello', 42, arc4Str1], test: { not: ['hello'] }, expected: false }, - { subject: ['hello', 42, arc4Str1], test: ['world'], expected: false }, - { subject: ['hello', 42, arc4Str1], test: { not: ['world'] }, expected: true }, + { subject: ['hello', 42, arc4Str1], test: ['hello'], expected: false }, + { subject: ['hello', 42, arc4Str1], test: [{ not: 'hello' }, 42, sameArc4Str], expected: false }, + { subject: ['hello', 42, arc4Str1], test: ['world', 42, sameArc4Str], expected: false }, + { subject: ['hello', 42, arc4Str1], test: [{ not: 'world' }, 42, sameArc4Str], expected: true }, { subject: { x: 43 }, test: { not: { x: 3 } }, expected: true }, { subject: { x: 43 }, test: { x: { not: 3 } }, expected: true }, { subject: { x: 43 }, test: { not: { x: { not: 3 } } }, expected: false }, @@ -125,17 +126,18 @@ describe('match', () => { test.each(testData)('should be able to match %s', (data) => { const { subject, test, expected } = data - expect(match(subject, test)).toBe(expected) + const result = match(subject, test) + expect(result).toBe(expected) }) test.each(numericTestData.filter((x) => x.expected))('should be able to assert match numeric data %s', (data) => { - const { subject, test, expected } = data - expect(assertMatch(subject, test)).toBe(expected) + const { subject, test } = data + expect(() => assertMatch(subject, test)).not.toThrow() }) test.each(testData.filter((x) => x.expected))('should be able to assert match %s', (data) => { - const { subject, test, expected } = data - expect(match(subject, test)).toBe(expected) + const { subject, test } = data + expect(() => assertMatch(subject, test)).not.toThrow() }) test.each(numericTestData.filter((x) => !x.expected))('should throw exception when assert match fails for numeric data %s', (data) => { diff --git a/tests/native-readonly-object.spec.ts b/tests/native-readonly-object.spec.ts index 328df000..e87ea5a7 100644 --- a/tests/native-readonly-object.spec.ts +++ b/tests/native-readonly-object.spec.ts @@ -550,6 +550,7 @@ describe('native readonly object', () => { c: 'hello', d: Bytes('world'), }, + 'metadata', ]) }) From 518d70e8099fcefdcd9fa9ae09f68f0f8ce440b6 Mon Sep 17 00:00:00 2001 From: Bobby Lat Date: Thu, 24 Jul 2025 12:19:52 +0700 Subject: [PATCH 20/68] upgrade algorand-typescript version and rename UintN, UFixedNxM --- .../arc4/classes/Arc4ValueGenerator.md | 62 +- docs/coverage.md | 15 +- docs/testing-guide/arc4-types.md | 36 +- examples/arc4-simple-voting/contract.algo.ts | 4 +- examples/marketplace/contract.algo.ts | 54 +- examples/marketplace/contract.spec.ts | 86 +- examples/voting/contract.algo.ts | 2 +- examples/voting/contract.spec.ts | 10 +- package-lock.json | 1275 ++++++++--------- package.json | 11 +- src/impl/encoded-types/encoded-types.ts | 58 +- src/impl/encoded-types/index.ts | 6 +- src/impl/encoded-types/types.ts | 2 +- src/impl/encoded-types/utils.ts | 14 +- src/subcontexts/contract-context.ts | 10 +- src/test-transformer/visitors.ts | 6 +- src/value-generators/arc4.ts | 58 +- tests/arc4/dynamic-array.spec.ts | 88 +- tests/arc4/emit.spec.ts | 28 +- tests/arc4/encode-decode-arc4.spec.ts | 49 +- tests/arc4/method-selector.spec.ts | 34 +- tests/arc4/static-array.spec.ts | 90 +- tests/arc4/struct.spec.ts | 44 +- tests/arc4/tuple.spec.ts | 84 +- tests/arc4/ufixednxm.spec.ts | 86 +- tests/arc4/uintn.spec.ts | 80 +- tests/arc4/zero-constructor.spec.ts | 14 +- .../arc4-abi-method/contract.algo.ts | 12 +- .../arc4-primitive-ops/contract.algo.ts | 178 +-- tests/artifacts/box-contract/contract.algo.ts | 7 +- .../created-app-asset/contract.algo.ts | 4 +- .../artifacts/primitive-ops/contract.algo.ts | 26 +- tests/artifacts/state-ops/contract.algo.ts | 50 +- tests/fixed-array.spec.ts | 76 +- tests/global-state-arc4-values.spec.ts | 20 +- tests/local-state-arc4-values.spec.ts | 16 +- tests/log.spec.ts | 27 +- tests/native-mutable-array.spec.ts | 64 +- tests/native-mutable-object.spec.ts | 72 +- tests/native-readonly-array.spec.ts | 62 +- tests/native-readonly-object.spec.ts | 62 +- tests/references/box-map.spec.ts | 35 +- tests/references/box.spec.ts | 155 +- tests/state-op-codes.spec.ts | 4 +- 44 files changed, 1529 insertions(+), 1647 deletions(-) diff --git a/docs/code/value-generators/arc4/classes/Arc4ValueGenerator.md b/docs/code/value-generators/arc4/classes/Arc4ValueGenerator.md index e2666dd9..de12e9bb 100644 --- a/docs/code/value-generators/arc4/classes/Arc4ValueGenerator.md +++ b/docs/code/value-generators/arc4/classes/Arc4ValueGenerator.md @@ -1,6 +1,6 @@ [**@algorandfoundation/algorand-typescript-testing**](../../../README.md) -*** +--- [@algorandfoundation/algorand-typescript-testing](../../../README.md) / [value-generators/arc4](../README.md) / Arc4ValueGenerator @@ -33,7 +33,7 @@ Generate a random Algorand address. `Address` -*** +--- ### dynamicBytes() @@ -53,7 +53,7 @@ Generate a random dynamic bytes of size `n` bits. `DynamicBytes` -*** +--- ### str() @@ -73,15 +73,15 @@ Generate a random dynamic string of size `n` bits. `Str` -*** +--- ### uintN128() -> **uintN128**(`minValue`, `maxValue`): `UintN128` +> **uintN128**(`minValue`, `maxValue`): `Uint128` Defined in: [src/value-generators/arc4.ts:67](https://github.com/algorandfoundation/algorand-typescript-testing/blob/main/src/value-generators/arc4.ts#L67) -Generate a random UintN128 within the specified range. +Generate a random Uint128 within the specified range. #### Parameters @@ -95,17 +95,17 @@ Generate a random UintN128 within the specified range. #### Returns -`UintN128` +`Uint128` -*** +--- ### uintN16() -> **uintN16**(`minValue`, `maxValue`): `UintN16` +> **uintN16**(`minValue`, `maxValue`): `Uint16` Defined in: [src/value-generators/arc4.ts:37](https://github.com/algorandfoundation/algorand-typescript-testing/blob/main/src/value-generators/arc4.ts#L37) -Generate a random UintN16 within the specified range. +Generate a random Uint16 within the specified range. #### Parameters @@ -119,17 +119,17 @@ Generate a random UintN16 within the specified range. #### Returns -`UintN16` +`Uint16` -*** +--- ### uintN256() -> **uintN256**(`minValue`, `maxValue`): `UintN256` +> **uintN256**(`minValue`, `maxValue`): `Uint256` Defined in: [src/value-generators/arc4.ts:77](https://github.com/algorandfoundation/algorand-typescript-testing/blob/main/src/value-generators/arc4.ts#L77) -Generate a random UintN256 within the specified range. +Generate a random Uint256 within the specified range. #### Parameters @@ -143,17 +143,17 @@ Generate a random UintN256 within the specified range. #### Returns -`UintN256` +`Uint256` -*** +--- ### uintN32() -> **uintN32**(`minValue`, `maxValue`): `UintN32` +> **uintN32**(`minValue`, `maxValue`): `Uint32` Defined in: [src/value-generators/arc4.ts:47](https://github.com/algorandfoundation/algorand-typescript-testing/blob/main/src/value-generators/arc4.ts#L47) -Generate a random UintN32 within the specified range. +Generate a random Uint32 within the specified range. #### Parameters @@ -167,17 +167,17 @@ Generate a random UintN32 within the specified range. #### Returns -`UintN32` +`Uint32` -*** +--- ### uintN512() -> **uintN512**(`minValue`, `maxValue`): `UintN`\<`512`\> +> **uintN512**(`minValue`, `maxValue`): `Uint`\<`512`\> Defined in: [src/value-generators/arc4.ts:87](https://github.com/algorandfoundation/algorand-typescript-testing/blob/main/src/value-generators/arc4.ts#L87) -Generate a random UintN512 within the specified range. +Generate a random Uint512 within the specified range. #### Parameters @@ -191,17 +191,17 @@ Generate a random UintN512 within the specified range. #### Returns -`UintN`\<`512`\> +`Uint`\<`512`\> -*** +--- ### uintN64() -> **uintN64**(`minValue`, `maxValue`): `UintN64` +> **uintN64**(`minValue`, `maxValue`): `Uint64` Defined in: [src/value-generators/arc4.ts:57](https://github.com/algorandfoundation/algorand-typescript-testing/blob/main/src/value-generators/arc4.ts#L57) -Generate a random UintN64 within the specified range. +Generate a random Uint64 within the specified range. #### Parameters @@ -215,17 +215,17 @@ Generate a random UintN64 within the specified range. #### Returns -`UintN64` +`Uint64` -*** +--- ### uintN8() -> **uintN8**(`minValue`, `maxValue`): `UintN8` +> **uintN8**(`minValue`, `maxValue`): `Uint8` Defined in: [src/value-generators/arc4.ts:27](https://github.com/algorandfoundation/algorand-typescript-testing/blob/main/src/value-generators/arc4.ts#L27) -Generate a random UintN8 within the specified range. +Generate a random Uint8 within the specified range. #### Parameters @@ -239,4 +239,4 @@ Generate a random UintN8 within the specified range. #### Returns -`UintN8` +`Uint8` diff --git a/docs/coverage.md b/docs/coverage.md index 9bd57f45..d8807861 100644 --- a/docs/coverage.md +++ b/docs/coverage.md @@ -54,15 +54,14 @@ See which `algorand-typescript` stubs are implemented by the `algorand-typescrip | arc4.Str | Native | | arc4.Struct | Native | | arc4.Tuple | Native | -| arc4.UFixedNxM | Native | +| arc4.UFixed | Native | | arc4.UintN | Native | -| arc4.UintN128 | Native | -| arc4.UintN16 | Native | -| arc4.UintN256 | Native | -| arc4.UintN32 | Native | -| arc4.UintN64 | Native | -| arc4.UintN8 | Native | -| arc4.UIntN | Native | +| arc4.Uint128 | Native | +| arc4.Uint16 | Native | +| arc4.Uint256 | Native | +| arc4.Uint32 | Native | +| arc4.Uint64 | Native | +| arc4.Uint8 | Native | | arc4.abimethod | Emulated | | arc4.methodSelector | Native | | arc4.baremethod | Emulated | diff --git a/docs/testing-guide/arc4-types.md b/docs/testing-guide/arc4-types.md index badddb8d..d3397347 100644 --- a/docs/testing-guide/arc4-types.md +++ b/docs/testing-guide/arc4-types.md @@ -22,28 +22,28 @@ const ctx = new TestExecutionContext() ```ts // Integer types -const uint8Value = new arc4.UintN8(255) -const uint16Value = new arc4.UintN16(65535) -const uint32Value = new arc4.UintN32(4294967295) -const uint64Value = new arc4.UintN64(18446744073709551615n) +const uint8Value = new arc4.Uint8(255) +const uint16Value = new arc4.Uint16(65535) +const uint32Value = new arc4.Uint32(4294967295) +const uint64Value = new arc4.Uint64(18446744073709551615n) // Generate a random unsigned arc4 integer with default range -const uint8 = ctx.any.arc4.uintN8() -const uint16 = ctx.any.arc4.uintN16() -const uint32 = ctx.any.arc4.uintN32() -const uint64 = ctx.any.arc4.uintN64() -const biguint128 = ctx.any.arc4.uintN128() -const biguint256 = ctx.any.arc4.uintN256() -const biguint512 = ctx.any.arc4.uintN512() +const uint8 = ctx.any.arc4.uint8() +const uint16 = ctx.any.arc4.uint16() +const uint32 = ctx.any.arc4.uint32() +const uint64 = ctx.any.arc4.uint64() +const biguint128 = ctx.any.arc4.uint128() +const biguint256 = ctx.any.arc4.uint256() +const biguint512 = ctx.any.arc4.uint512() // Generate a random unsigned arc4 integer with specified range -const uint8Custom = ctx.any.arc4.uintN8(10, 100) -const uint16Custom = ctx.any.arc4.uintN16(1000, 5000) -const uint32Custom = ctx.any.arc4.uintN32(100000, 1000000) -const uint64Custom = ctx.any.arc4.uintN64(1000000000, 10000000000) -const biguint128Custom = ctx.any.arc4.uintN128(1000000000000000, 10000000000000000n) -const biguint256Custom = ctx.any.arc4.uintN256(1000000000000000000000000n, 10000000000000000000000000n) -const biguint512Custom = ctx.any.arc4.uintN512(10000000000000000000000000000000000n, 10000000000000000000000000000000000n) +const uint8Custom = ctx.any.arc4.uint8(10, 100) +const uint16Custom = ctx.any.arc4.uint16(1000, 5000) +const uint32Custom = ctx.any.arc4.uint32(100000, 1000000) +const uint64Custom = ctx.any.arc4.uint64(1000000000, 10000000000) +const biguint128Custom = ctx.any.arc4.uint128(1000000000000000, 10000000000000000n) +const biguint256Custom = ctx.any.arc4.uint256(1000000000000000000000000n, 10000000000000000000000000n) +const biguint512Custom = ctx.any.arc4.uint512(10000000000000000000000000000000000n, 10000000000000000000000000000000000n) ``` ## Address diff --git a/examples/arc4-simple-voting/contract.algo.ts b/examples/arc4-simple-voting/contract.algo.ts index 5dd7587b..aada9956 100644 --- a/examples/arc4-simple-voting/contract.algo.ts +++ b/examples/arc4-simple-voting/contract.algo.ts @@ -26,8 +26,8 @@ export default class VotingContract extends arc4.Contract { } @arc4.abimethod({ readonly: true }) - public getVotes(): arc4.UintN64 { - return new arc4.UintN64(this.votes.value) + public getVotes(): arc4.Uint64 { + return new arc4.Uint64(this.votes.value) } public clearStateProgram(): boolean { diff --git a/examples/marketplace/contract.algo.ts b/examples/marketplace/contract.algo.ts index d31d1cd3..a998d12c 100644 --- a/examples/marketplace/contract.algo.ts +++ b/examples/marketplace/contract.algo.ts @@ -3,16 +3,16 @@ import { arc4, assert, BoxMap, clone, Global, itxn, op, Txn } from '@algorandfou export class ListingKey extends arc4.Struct<{ owner: arc4.Address - asset: arc4.UintN64 - nonce: arc4.UintN64 + asset: arc4.Uint64 + nonce: arc4.Uint64 }> {} export class ListingValue extends arc4.Struct<{ - deposited: arc4.UintN64 - unitaryPrice: arc4.UintN64 + deposited: arc4.Uint64 + unitaryPrice: arc4.Uint64 bidder: arc4.Address - bid: arc4.UintN64 - bidUnitaryPrice: arc4.UintN64 + bid: arc4.Uint64 + bidUnitaryPrice: arc4.Uint64 }> {} export default class DigitalMarketplace extends arc4.Contract { @@ -74,14 +74,14 @@ export default class DigitalMarketplace extends arc4.Contract { } @arc4.abimethod() - firstDeposit(mbrPay: gtxn.PaymentTxn, xfer: gtxn.AssetTransferTxn, unitaryPrice: arc4.UintN64, nonce: arc4.UintN64) { + firstDeposit(mbrPay: gtxn.PaymentTxn, xfer: gtxn.AssetTransferTxn, unitaryPrice: arc4.Uint64, nonce: arc4.Uint64) { assert(mbrPay.sender === Txn.sender) assert(mbrPay.receiver === Global.currentApplicationAddress) assert(mbrPay.amount === this.listingsBoxMbr()) const key = new ListingKey({ owner: new arc4.Address(Txn.sender), - asset: new arc4.UintN64(xfer.xferAsset.id), + asset: new arc4.Uint64(xfer.xferAsset.id), nonce: nonce, }) assert(!this.listings(key).exists) @@ -91,19 +91,19 @@ export default class DigitalMarketplace extends arc4.Contract { assert(xfer.assetAmount > 0) this.listings(key).value = new ListingValue({ - deposited: new arc4.UintN64(xfer.assetAmount), + deposited: new arc4.Uint64(xfer.assetAmount), unitaryPrice: unitaryPrice, bidder: new arc4.Address(), - bid: new arc4.UintN64(), - bidUnitaryPrice: new arc4.UintN64(), + bid: new arc4.Uint64(), + bidUnitaryPrice: new arc4.Uint64(), }) } @arc4.abimethod() - deposit(xfer: gtxn.AssetTransferTxn, nonce: arc4.UintN64) { + deposit(xfer: gtxn.AssetTransferTxn, nonce: arc4.Uint64) { const key = new ListingKey({ owner: new arc4.Address(Txn.sender), - asset: new arc4.UintN64(xfer.xferAsset.id), + asset: new arc4.Uint64(xfer.xferAsset.id), nonce: nonce, }) @@ -117,15 +117,15 @@ export default class DigitalMarketplace extends arc4.Contract { bidUnitaryPrice: existing.bidUnitaryPrice, bidder: existing.bidder, unitaryPrice: existing.unitaryPrice, - deposited: new arc4.UintN64(existing.deposited.native + xfer.assetAmount), + deposited: new arc4.Uint64(existing.deposited.native + xfer.assetAmount), }) } @arc4.abimethod() - setPrice(asset: Asset, nonce: arc4.UintN64, unitaryPrice: arc4.UintN64) { + setPrice(asset: Asset, nonce: arc4.Uint64, unitaryPrice: arc4.Uint64) { const key = new ListingKey({ owner: new arc4.Address(Txn.sender), - asset: new arc4.UintN64(asset.id), + asset: new arc4.Uint64(asset.id), nonce: nonce, }) @@ -140,10 +140,10 @@ export default class DigitalMarketplace extends arc4.Contract { } @arc4.abimethod() - buy(owner: arc4.Address, asset: Asset, nonce: arc4.UintN64, buyPay: gtxn.PaymentTxn, quantity: uint64) { + buy(owner: arc4.Address, asset: Asset, nonce: arc4.Uint64, buyPay: gtxn.PaymentTxn, quantity: uint64) { const key = new ListingKey({ owner: owner, - asset: new arc4.UintN64(asset.id), + asset: new arc4.Uint64(asset.id), nonce: nonce, }) @@ -160,7 +160,7 @@ export default class DigitalMarketplace extends arc4.Contract { bidUnitaryPrice: listing.bidUnitaryPrice, bidder: listing.bidder, unitaryPrice: listing.unitaryPrice, - deposited: new arc4.UintN64(listing.deposited.native - quantity), + deposited: new arc4.Uint64(listing.deposited.native - quantity), }) itxn @@ -173,10 +173,10 @@ export default class DigitalMarketplace extends arc4.Contract { } @arc4.abimethod() - withdraw(asset: Asset, nonce: arc4.UintN64) { + withdraw(asset: Asset, nonce: arc4.Uint64) { const key = new ListingKey({ owner: new arc4.Address(Txn.sender), - asset: new arc4.UintN64(asset.id), + asset: new arc4.Uint64(asset.id), nonce: nonce, }) @@ -200,8 +200,8 @@ export default class DigitalMarketplace extends arc4.Contract { } @arc4.abimethod() - bid(owner: arc4.Address, asset: Asset, nonce: arc4.UintN64, bidPay: gtxn.PaymentTxn, quantity: arc4.UintN64, unitaryPrice: arc4.UintN64) { - const key = new ListingKey({ owner, asset: new arc4.UintN64(asset.id), nonce }) + bid(owner: arc4.Address, asset: Asset, nonce: arc4.Uint64, bidPay: gtxn.PaymentTxn, quantity: arc4.Uint64, unitaryPrice: arc4.Uint64) { + const key = new ListingKey({ owner, asset: new arc4.Uint64(asset.id), nonce }) const listing = clone(this.listings(key).value) if (listing.bidder !== new arc4.Address()) { @@ -228,8 +228,8 @@ export default class DigitalMarketplace extends arc4.Contract { } @arc4.abimethod() - acceptBid(asset: Asset, nonce: arc4.UintN64) { - const key = new ListingKey({ owner: new arc4.Address(Txn.sender), asset: new arc4.UintN64(asset.id), nonce }) + acceptBid(asset: Asset, nonce: arc4.Uint64) { + const key = new ListingKey({ owner: new arc4.Address(Txn.sender), asset: new arc4.Uint64(asset.id), nonce }) const listing = clone(this.listings(key).value) assert(listing.bidder !== new arc4.Address()) @@ -252,8 +252,8 @@ export default class DigitalMarketplace extends arc4.Contract { bidder: listing.bidder, bidUnitaryPrice: listing.bidUnitaryPrice, unitaryPrice: listing.unitaryPrice, - deposited: new arc4.UintN64(listing.deposited.native - minQuantity), - bid: new arc4.UintN64(listing.bid.native - minQuantity), + deposited: new arc4.Uint64(listing.deposited.native - minQuantity), + bid: new arc4.Uint64(listing.bid.native - minQuantity), }) } } diff --git a/examples/marketplace/contract.spec.ts b/examples/marketplace/contract.spec.ts index 8bd14347..3ca80e03 100644 --- a/examples/marketplace/contract.spec.ts +++ b/examples/marketplace/contract.spec.ts @@ -16,7 +16,7 @@ describe('DigitalMarketplace', () => { test('first deposit', () => { const contract = ctx.contract.create(DigitalMarketplace) const testAsset = ctx.any.asset({ decimals: TEST_DECIMALS }) - const testNonce = ctx.any.arc4.uintN64() + const testNonce = ctx.any.arc4.uint64() // Arrange const testApp = ctx.ledger.getApplicationForContract(contract) @@ -29,14 +29,14 @@ describe('DigitalMarketplace', () => { assetReceiver: testApp.address, assetAmount: 10, }), - ctx.any.arc4.uintN64(), + ctx.any.arc4.uint64(), testNonce, ) // Assert const listingKey = new ListingKey({ owner: new arc4.Address(ctx.defaultSender), - asset: new arc4.UintN64(testAsset.id), + asset: new arc4.Uint64(testAsset.id), nonce: testNonce, }) const listingValue = interpretAsArc4(Bytes(ctx.ledger.getBox(contract, Bytes('listings').concat(listingKey.bytes)))) @@ -46,21 +46,21 @@ describe('DigitalMarketplace', () => { test('deposit', () => { const contract = ctx.contract.create(DigitalMarketplace) const testAsset = ctx.any.asset({ decimals: TEST_DECIMALS }) - const testNonce = ctx.any.arc4.uintN64() + const testNonce = ctx.any.arc4.uint64() // Arrange const testApp = ctx.ledger.getApplicationForContract(contract) const listingKey = new ListingKey({ owner: new arc4.Address(ctx.defaultSender), - asset: new arc4.UintN64(testAsset.id), + asset: new arc4.Uint64(testAsset.id), nonce: testNonce, }) contract.listings(listingKey).value = new ListingValue({ - deposited: new arc4.UintN64(10), - unitaryPrice: new arc4.UintN64(10), + deposited: new arc4.Uint64(10), + unitaryPrice: new arc4.Uint64(10), bidder: new arc4.Address(ctx.defaultSender), - bid: new arc4.UintN64(10), - bidUnitaryPrice: new arc4.UintN64(10), + bid: new arc4.Uint64(10), + bidUnitaryPrice: new arc4.Uint64(10), }) // Act @@ -80,20 +80,20 @@ describe('DigitalMarketplace', () => { test('setPrice', () => { const contract = ctx.contract.create(DigitalMarketplace) const testAsset = ctx.any.asset({ decimals: TEST_DECIMALS }) - const testNonce = ctx.any.arc4.uintN64() + const testNonce = ctx.any.arc4.uint64() // Arrange - const testUnitaryPrice = ctx.any.arc4.uintN64() + const testUnitaryPrice = ctx.any.arc4.uint64() const listingKey = new ListingKey({ owner: new arc4.Address(ctx.defaultSender), - asset: new arc4.UintN64(testAsset.id), + asset: new arc4.Uint64(testAsset.id), nonce: testNonce, }) contract.listings(listingKey).value = new ListingValue({ - deposited: new arc4.UintN64(10), - unitaryPrice: new arc4.UintN64(10), + deposited: new arc4.Uint64(10), + unitaryPrice: new arc4.Uint64(10), bidder: new arc4.Address(ctx.defaultSender), - bid: new arc4.UintN64(10), - bidUnitaryPrice: new arc4.UintN64(10), + bid: new arc4.Uint64(10), + bidUnitaryPrice: new arc4.Uint64(10), }) // Act @@ -107,20 +107,20 @@ describe('DigitalMarketplace', () => { test('buy', () => { const contract = ctx.contract.create(DigitalMarketplace) const testAsset = ctx.any.asset({ decimals: TEST_DECIMALS }) - const testNonce = ctx.any.arc4.uintN64() + const testNonce = ctx.any.arc4.uint64() // Arrange const testOwner = new arc4.Address(ctx.defaultSender) - const testUnitaryPrice = ctx.any.arc4.uintN64(0, 10000000n) - const initialDeposit = ctx.any.arc4.uintN64() - const testBuyQuantity = ctx.any.arc4.uintN64(0, 1000000n) + const testUnitaryPrice = ctx.any.arc4.uint64(0, 10000000n) + const initialDeposit = ctx.any.arc4.uint64() + const testBuyQuantity = ctx.any.arc4.uint64(0, 1000000n) - const listingKey = new ListingKey({ owner: testOwner, asset: new arc4.UintN64(testAsset.id), nonce: testNonce }) + const listingKey = new ListingKey({ owner: testOwner, asset: new arc4.Uint64(testAsset.id), nonce: testNonce }) contract.listings(listingKey).value = new ListingValue({ deposited: initialDeposit, unitaryPrice: testUnitaryPrice, bidder: new arc4.Address(), - bid: new arc4.UintN64(0), - bidUnitaryPrice: new arc4.UintN64(0), + bid: new arc4.Uint64(0), + bidUnitaryPrice: new arc4.Uint64(0), }) // Act @@ -144,22 +144,22 @@ describe('DigitalMarketplace', () => { test('withdraw', () => { const contract = ctx.contract.create(DigitalMarketplace) const testAsset = ctx.any.asset({ decimals: TEST_DECIMALS }) - const testNonce = ctx.any.arc4.uintN64() + const testNonce = ctx.any.arc4.uint64() // Arrange const testOwner = new arc4.Address(ctx.defaultSender) - const initialDeposit = ctx.any.arc4.uintN64(1) - const testUnitaryPrice = ctx.any.arc4.uintN64() + const initialDeposit = ctx.any.arc4.uint64(1) + const testUnitaryPrice = ctx.any.arc4.uint64() const listingsBoxMbr = contract.listingsBoxMbr() - const listingKey = new ListingKey({ owner: testOwner, asset: new arc4.UintN64(testAsset.id), nonce: testNonce }) + const listingKey = new ListingKey({ owner: testOwner, asset: new arc4.Uint64(testAsset.id), nonce: testNonce }) contract.listings(listingKey).value = new ListingValue({ deposited: initialDeposit, unitaryPrice: testUnitaryPrice, bidder: new arc4.Address(), - bid: new arc4.UintN64(0), - bidUnitaryPrice: new arc4.UintN64(0), + bid: new arc4.Uint64(0), + bidUnitaryPrice: new arc4.Uint64(0), }) // Act @@ -182,26 +182,26 @@ describe('DigitalMarketplace', () => { test('bid', () => { const contract = ctx.contract.create(DigitalMarketplace) const testAsset = ctx.any.asset({ decimals: TEST_DECIMALS }) - const testNonce = ctx.any.arc4.uintN64() + const testNonce = ctx.any.arc4.uint64() // Arrange const app = ctx.ledger.getApplicationForContract(contract) const owner = new arc4.Address(ctx.defaultSender) - const initialPrice = ctx.any.arc4.uintN64(0, 10000000n) - const initialDeposit = ctx.any.arc4.uintN64(0, 1000000n) + const initialPrice = ctx.any.arc4.uint64(0, 10000000n) + const initialDeposit = ctx.any.arc4.uint64(0, 1000000n) - const listingKey = new ListingKey({ owner, asset: new arc4.UintN64(testAsset.id), nonce: testNonce }) + const listingKey = new ListingKey({ owner, asset: new arc4.Uint64(testAsset.id), nonce: testNonce }) contract.listings(listingKey).value = new ListingValue({ deposited: initialDeposit, unitaryPrice: initialPrice, bidder: new arc4.Address(), - bid: new arc4.UintN64(0), - bidUnitaryPrice: new arc4.UintN64(0), + bid: new arc4.Uint64(0), + bidUnitaryPrice: new arc4.Uint64(0), }) const bidder = ctx.any.account() - const bidQuantity = ctx.any.arc4.uintN64(0, BigInt(initialDeposit.native)) - const bidPrice = ctx.any.arc4.uintN64(initialPrice.native + 1, 10000000n) + const bidQuantity = ctx.any.arc4.uint64(0, BigInt(initialDeposit.native)) + const bidPrice = ctx.any.arc4.uint64(initialPrice.native + 1, 10000000n) const bidAmount = contract.quantityPrice(bidQuantity.native, bidPrice.native, testAsset.decimals) // Act @@ -230,22 +230,22 @@ describe('DigitalMarketplace', () => { test('acceptBid', () => { const contract = ctx.contract.create(DigitalMarketplace) const testAsset = ctx.any.asset({ decimals: TEST_DECIMALS }) - const testNonce = ctx.any.arc4.uintN64() + const testNonce = ctx.any.arc4.uint64() // Arrange const owner = ctx.defaultSender - const initialDeposit = ctx.any.arc4.uintN64(1, 10000000n) - const bidQuantity = ctx.any.arc4.uintN64(0, BigInt(initialDeposit.native)) - const bidPrice = ctx.any.arc4.uintN64(0, 10000000n) + const initialDeposit = ctx.any.arc4.uint64(1, 10000000n) + const bidQuantity = ctx.any.arc4.uint64(0, BigInt(initialDeposit.native)) + const bidPrice = ctx.any.arc4.uint64(0, 10000000n) const bidder = ctx.any.account() const listingKey = new ListingKey({ owner: new arc4.Address(owner), - asset: new arc4.UintN64(testAsset.id), + asset: new arc4.Uint64(testAsset.id), nonce: testNonce, }) contract.listings(listingKey).value = new ListingValue({ deposited: initialDeposit, - unitaryPrice: ctx.any.arc4.uintN64(), + unitaryPrice: ctx.any.arc4.uint64(), bidder: new arc4.Address(bidder), bid: bidQuantity, bidUnitaryPrice: bidPrice, diff --git a/examples/voting/contract.algo.ts b/examples/voting/contract.algo.ts index 5b0faace..7d379334 100644 --- a/examples/voting/contract.algo.ts +++ b/examples/voting/contract.algo.ts @@ -20,7 +20,7 @@ import { urange, } from '@algorandfoundation/algorand-typescript' -type VoteIndexArray = arc4.DynamicArray> +type VoteIndexArray = arc4.DynamicArray> const VOTE_INDEX_BYTES: uint64 = 1 const VOTE_COUNT_BYTES: uint64 = 8 diff --git a/examples/voting/contract.spec.ts b/examples/voting/contract.spec.ts index ae1fd33b..41d9ef4c 100644 --- a/examples/voting/contract.spec.ts +++ b/examples/voting/contract.spec.ts @@ -1,6 +1,6 @@ import { Bytes, Uint64 } from '@algorandfoundation/algorand-typescript' import { TestExecutionContext, toExternalValue } from '@algorandfoundation/algorand-typescript-testing' -import { DynamicArray, UintN8 } from '@algorandfoundation/algorand-typescript/arc4' +import { DynamicArray, Uint8 } from '@algorandfoundation/algorand-typescript/arc4' import nacl from 'tweetnacl' import { afterEach, describe, expect, it } from 'vitest' import { VotingRoundApp } from './contract.algo' @@ -20,10 +20,10 @@ describe('VotingRoundApp', () => { const metadataIpfsCid = ctx.any.string(16) const startTime = ctx.any.uint64(Date.now() - 10_000, Date.now()) const endTime = ctx.any.uint64(Date.now() + 10_000, Date.now() + 100_000) - const optionCounts = new DynamicArray( + const optionCounts = new DynamicArray( ...Array(13) .fill(0) - .map(() => new UintN8(2)), + .map(() => new Uint8(2)), ) const quorum = ctx.any.uint64() const nftImageUrl = ctx.any.string(64) @@ -68,10 +68,10 @@ describe('VotingRoundApp', () => { const account = ctx.any.account() const signature = nacl.sign.detached(toExternalValue(account.bytes), keyPair.secretKey) - const answerIds = new DynamicArray( + const answerIds = new DynamicArray( ...Array(13) .fill(0) - .map(() => new UintN8(Math.ceil(Math.random() * 10) % 2)), + .map(() => new Uint8(Math.ceil(Math.random() * 10) % 2)), ) ctx.txn.createScope([ctx.any.txn.applicationCall({ appId: app, sender: account })]).execute(() => { diff --git a/package-lock.json b/package-lock.json index cd6ee879..4318234a 100644 --- a/package-lock.json +++ b/package-lock.json @@ -8,8 +8,8 @@ "name": "@algorandfoundation/algorand-typescript-testing", "version": "1.0.0", "dependencies": { - "@algorandfoundation/algorand-typescript": "1.0.0-alpha.61", - "@algorandfoundation/puya-ts": "1.0.0-alpha.61", + "@algorandfoundation/algorand-typescript": "1.0.0-alpha.64", + "@algorandfoundation/puya-ts": "1.0.0-alpha.64", "elliptic": "^6.6.1", "js-sha256": "^0.11.0", "js-sha3": "^0.9.3", @@ -35,11 +35,11 @@ "@types/node": "22.13.14", "@typescript-eslint/eslint-plugin": "8.28.0", "@typescript-eslint/parser": "8.28.0", - "@vitest/coverage-v8": "3.1.3", + "@vitest/coverage-v8": "3.2.4", "better-npm-audit": "3.11.0", "conventional-changelog-conventionalcommits": "^8.0.0", "copyfiles": "2.4.1", - "eslint": "9.23.0", + "eslint": "9.31.0", "eslint-config-prettier": "^10.1.1", "eslint-plugin-prettier": "^5.2.5", "glob": "^11.0.1", @@ -54,7 +54,7 @@ "typedoc-plugin-markdown": "^4.6.0", "typescript": "^5.8.2", "upath": "^2.0.1", - "vitest": "3.1.3" + "vitest": "3.2.4" } }, "node_modules/@algorandfoundation/algokit-utils": { @@ -74,40 +74,40 @@ } }, "node_modules/@algorandfoundation/algorand-typescript": { - "version": "1.0.0-alpha.61", - "resolved": "https://registry.npmjs.org/@algorandfoundation/algorand-typescript/-/algorand-typescript-1.0.0-alpha.61.tgz", - "integrity": "sha512-8TyZ9A9M8BClcrx46IlcFlPsnkD01y9lWmAX05xX/bSh9UHFkZanvY0j9DoHh3M/E6czjrPrkTa7rWwbWgF3JA==", + "version": "1.0.0-alpha.64", + "resolved": "https://registry.npmjs.org/@algorandfoundation/algorand-typescript/-/algorand-typescript-1.0.0-alpha.64.tgz", + "integrity": "sha512-DgB6qNMQH/sWr2E3t0mz+rKPskJTZvUm4iuExaQ6VX3y5tzpAQI8VbuIeKLS7yVN4oVXUa9WY7w86OyJ7GmNXQ==", "peerDependencies": { "tslib": "^2.6.2" } }, "node_modules/@algorandfoundation/puya-ts": { - "version": "1.0.0-alpha.61", - "resolved": "https://registry.npmjs.org/@algorandfoundation/puya-ts/-/puya-ts-1.0.0-alpha.61.tgz", - "integrity": "sha512-/38wr4GljyQ5mVJZP9pro+E6uU2mK+kbEWNui+q1KyMnRX/CkXBBw+ZMB1/4XCUqN0xW6cKAPZRZrGCtMSvoLg==", + "version": "1.0.0-alpha.64", + "resolved": "https://registry.npmjs.org/@algorandfoundation/puya-ts/-/puya-ts-1.0.0-alpha.64.tgz", + "integrity": "sha512-cW5pBwJh4nz7OxGXgwbNLK0zc2nqGlbfrZVyBdrOPH6RVaE8Ao6T2zOm1RmlOzngSZZhq86EBFOkQ36FP06LTw==", "hasInstallScript": true, "license": "MIT", "dependencies": { - "@algorandfoundation/algorand-typescript": "1.0.0-alpha.61", + "@algorandfoundation/algorand-typescript": "1.0.0-alpha.64", "arcsecond": "^5.0.0", "argparse": "^2.0.1", "chalk": "^5.4.1", "change-case": "^5.4.4", "cross-spawn": "7.0.6", - "glob": "^11.0.1", - "minimatch": "^10.0.1", + "glob": "^11.0.3", + "minimatch": "^10.0.3", "polytype": "^0.17.0", "rxjs": "^7.8.2", "signal-exit": "^4.1.0", "tar": "^7.4.3", "tslib": "^2.8.1", - "typescript": "^5.8.2", + "typescript": "^5.8.3", "upath": "^2.0.1", "vscode-languageserver": "^9.0.1", "vscode-languageserver-textdocument": "^1.0.12", "vscode-uri": "^3.1.0", "which": "^5.0.0", - "zod": "^3.24.2" + "zod": "^4.0.5" }, "bin": { "download-puya-binary": "bin/download-puya-binary.mjs", @@ -1110,9 +1110,9 @@ } }, "node_modules/@eslint/config-array": { - "version": "0.19.2", - "resolved": "https://registry.npmjs.org/@eslint/config-array/-/config-array-0.19.2.tgz", - "integrity": "sha512-GNKqxfHG2ySmJOBSHg7LxeUx4xpuCoFjacmlCoYWEbaPXLwvfIjixRI12xCQZeULksQb23uiA8F40w5TojpV7w==", + "version": "0.21.0", + "resolved": "https://registry.npmjs.org/@eslint/config-array/-/config-array-0.21.0.tgz", + "integrity": "sha512-ENIdc4iLu0d93HeYirvKmrzshzofPw6VkZRKQGe9Nv46ZnWUzcF1xV01dcvEg/1wXUR61OmmlSfyeyO7EvjLxQ==", "dev": true, "license": "Apache-2.0", "dependencies": { @@ -1125,9 +1125,9 @@ } }, "node_modules/@eslint/config-array/node_modules/brace-expansion": { - "version": "1.1.11", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", - "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "version": "1.1.12", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", + "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", "dev": true, "license": "MIT", "dependencies": { @@ -1149,9 +1149,9 @@ } }, "node_modules/@eslint/config-helpers": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/@eslint/config-helpers/-/config-helpers-0.2.0.tgz", - "integrity": "sha512-yJLLmLexii32mGrhW29qvU3QBVTu0GUmEf/J4XsBtVhp4JkIUFN/BjWqTF63yRvGApIDpZm5fa97LtYtINmfeQ==", + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/@eslint/config-helpers/-/config-helpers-0.3.0.tgz", + "integrity": "sha512-ViuymvFmcJi04qdZeDc2whTHryouGcDlaxPqarTD0ZE10ISpxGUVZGZDx4w01upyIynL3iu6IXH2bS1NhclQMw==", "dev": true, "license": "Apache-2.0", "engines": { @@ -1159,9 +1159,9 @@ } }, "node_modules/@eslint/core": { - "version": "0.12.0", - "resolved": "https://registry.npmjs.org/@eslint/core/-/core-0.12.0.tgz", - "integrity": "sha512-cmrR6pytBuSMTaBweKoGMwu3EiHiEC+DoyupPmlZ0HxBJBtIxwe+j/E4XPIKNx+Q74c8lXKPwYawBf5glsTkHg==", + "version": "0.15.1", + "resolved": "https://registry.npmjs.org/@eslint/core/-/core-0.15.1.tgz", + "integrity": "sha512-bkOp+iumZCCbt1K1CmWf0R9pM5yKpDv+ZXtvSyQpudrI9kuFLp+bM2WOPXImuD/ceQuaa8f5pj93Y7zyECIGNA==", "dev": true, "license": "Apache-2.0", "dependencies": { @@ -1213,9 +1213,9 @@ } }, "node_modules/@eslint/eslintrc/node_modules/brace-expansion": { - "version": "1.1.11", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", - "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "version": "1.1.12", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", + "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", "dev": true, "license": "MIT", "dependencies": { @@ -1264,13 +1264,13 @@ } }, "node_modules/@eslint/plugin-kit": { - "version": "0.2.7", - "resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.2.7.tgz", - "integrity": "sha512-JubJ5B2pJ4k4yGxaNLdbjrnk9d/iDz6/q8wOilpIowd6PJPgaxCuHBnBszq7Ce2TyMrywm5r4PnKm6V3iiZF+g==", + "version": "0.3.4", + "resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.3.4.tgz", + "integrity": "sha512-Ul5l+lHEcw3L5+k8POx6r74mxEYKG5kOb6Xpy2gCRW6zweT6TEhAf8vhxGgjhqrd/VO/Dirhsb+1hNpD1ue9hw==", "dev": true, "license": "Apache-2.0", "dependencies": { - "@eslint/core": "^0.12.0", + "@eslint/core": "^0.15.1", "levn": "^0.4.1" }, "engines": { @@ -1355,6 +1355,27 @@ "url": "https://github.com/sponsors/nzakas" } }, + "node_modules/@isaacs/balanced-match": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/@isaacs/balanced-match/-/balanced-match-4.0.1.tgz", + "integrity": "sha512-yzMTt9lEb8Gv7zRioUilSglI0c0smZ9k5D65677DLWLtWJaXIS3CqcGyUFByYKlnUj6TkjLVs54fBl6+TiGQDQ==", + "license": "MIT", + "engines": { + "node": "20 || >=22" + } + }, + "node_modules/@isaacs/brace-expansion": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/@isaacs/brace-expansion/-/brace-expansion-5.0.0.tgz", + "integrity": "sha512-ZT55BDLV0yv0RBm2czMiZ+SqCGO7AvmOM3G/w2xhVPH+te0aKgFjmBvGlL1dH+ql2tgGO3MVrbb3jCKyvpgnxA==", + "license": "MIT", + "dependencies": { + "@isaacs/balanced-match": "^4.0.1" + }, + "engines": { + "node": "20 || >=22" + } + }, "node_modules/@isaacs/cliui": { "version": "8.0.2", "resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz", @@ -1962,9 +1983,9 @@ } }, "node_modules/@rollup/rollup-android-arm-eabi": { - "version": "4.38.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.38.0.tgz", - "integrity": "sha512-ldomqc4/jDZu/xpYU+aRxo3V4mGCV9HeTgUBANI3oIQMOL+SsxB+S2lxMpkFp5UamSS3XuTMQVbsS24R4J4Qjg==", + "version": "4.45.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.45.1.tgz", + "integrity": "sha512-NEySIFvMY0ZQO+utJkgoMiCAjMrGvnbDLHvcmlA33UXJpYBCvlBEbMMtV837uCkS+plG2umfhn0T5mMAxGrlRA==", "cpu": [ "arm" ], @@ -1976,9 +1997,9 @@ ] }, "node_modules/@rollup/rollup-android-arm64": { - "version": "4.38.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.38.0.tgz", - "integrity": "sha512-VUsgcy4GhhT7rokwzYQP+aV9XnSLkkhlEJ0St8pbasuWO/vwphhZQxYEKUP3ayeCYLhk6gEtacRpYP/cj3GjyQ==", + "version": "4.45.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.45.1.tgz", + "integrity": "sha512-ujQ+sMXJkg4LRJaYreaVx7Z/VMgBBd89wGS4qMrdtfUFZ+TSY5Rs9asgjitLwzeIbhwdEhyj29zhst3L1lKsRQ==", "cpu": [ "arm64" ], @@ -1990,9 +2011,9 @@ ] }, "node_modules/@rollup/rollup-darwin-arm64": { - "version": "4.38.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.38.0.tgz", - "integrity": "sha512-buA17AYXlW9Rn091sWMq1xGUvWQFOH4N1rqUxGJtEQzhChxWjldGCCup7r/wUnaI6Au8sKXpoh0xg58a7cgcpg==", + "version": "4.45.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.45.1.tgz", + "integrity": "sha512-FSncqHvqTm3lC6Y13xncsdOYfxGSLnP+73k815EfNmpewPs+EyM49haPS105Rh4aF5mJKywk9X0ogzLXZzN9lA==", "cpu": [ "arm64" ], @@ -2004,9 +2025,9 @@ ] }, "node_modules/@rollup/rollup-darwin-x64": { - "version": "4.38.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.38.0.tgz", - "integrity": "sha512-Mgcmc78AjunP1SKXl624vVBOF2bzwNWFPMP4fpOu05vS0amnLcX8gHIge7q/lDAHy3T2HeR0TqrriZDQS2Woeg==", + "version": "4.45.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.45.1.tgz", + "integrity": "sha512-2/vVn/husP5XI7Fsf/RlhDaQJ7x9zjvC81anIVbr4b/f0xtSmXQTFcGIQ/B1cXIYM6h2nAhJkdMHTnD7OtQ9Og==", "cpu": [ "x64" ], @@ -2018,9 +2039,9 @@ ] }, "node_modules/@rollup/rollup-freebsd-arm64": { - "version": "4.38.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.38.0.tgz", - "integrity": "sha512-zzJACgjLbQTsscxWqvrEQAEh28hqhebpRz5q/uUd1T7VTwUNZ4VIXQt5hE7ncs0GrF+s7d3S4on4TiXUY8KoQA==", + "version": "4.45.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.45.1.tgz", + "integrity": "sha512-4g1kaDxQItZsrkVTdYQ0bxu4ZIQ32cotoQbmsAnW1jAE4XCMbcBPDirX5fyUzdhVCKgPcrwWuucI8yrVRBw2+g==", "cpu": [ "arm64" ], @@ -2032,9 +2053,9 @@ ] }, "node_modules/@rollup/rollup-freebsd-x64": { - "version": "4.38.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.38.0.tgz", - "integrity": "sha512-hCY/KAeYMCyDpEE4pTETam0XZS4/5GXzlLgpi5f0IaPExw9kuB+PDTOTLuPtM10TlRG0U9OSmXJ+Wq9J39LvAg==", + "version": "4.45.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.45.1.tgz", + "integrity": "sha512-L/6JsfiL74i3uK1Ti2ZFSNsp5NMiM4/kbbGEcOCps99aZx3g8SJMO1/9Y0n/qKlWZfn6sScf98lEOUe2mBvW9A==", "cpu": [ "x64" ], @@ -2046,9 +2067,9 @@ ] }, "node_modules/@rollup/rollup-linux-arm-gnueabihf": { - "version": "4.38.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.38.0.tgz", - "integrity": "sha512-mimPH43mHl4JdOTD7bUMFhBdrg6f9HzMTOEnzRmXbOZqjijCw8LA5z8uL6LCjxSa67H2xiLFvvO67PT05PRKGg==", + "version": "4.45.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.45.1.tgz", + "integrity": "sha512-RkdOTu2jK7brlu+ZwjMIZfdV2sSYHK2qR08FUWcIoqJC2eywHbXr0L8T/pONFwkGukQqERDheaGTeedG+rra6Q==", "cpu": [ "arm" ], @@ -2060,9 +2081,9 @@ ] }, "node_modules/@rollup/rollup-linux-arm-musleabihf": { - "version": "4.38.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.38.0.tgz", - "integrity": "sha512-tPiJtiOoNuIH8XGG8sWoMMkAMm98PUwlriOFCCbZGc9WCax+GLeVRhmaxjJtz6WxrPKACgrwoZ5ia/uapq3ZVg==", + "version": "4.45.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.45.1.tgz", + "integrity": "sha512-3kJ8pgfBt6CIIr1o+HQA7OZ9mp/zDk3ctekGl9qn/pRBgrRgfwiffaUmqioUGN9hv0OHv2gxmvdKOkARCtRb8Q==", "cpu": [ "arm" ], @@ -2074,9 +2095,9 @@ ] }, "node_modules/@rollup/rollup-linux-arm64-gnu": { - "version": "4.38.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.38.0.tgz", - "integrity": "sha512-wZco59rIVuB0tjQS0CSHTTUcEde+pXQWugZVxWaQFdQQ1VYub/sTrNdY76D1MKdN2NB48JDuGABP6o6fqos8mA==", + "version": "4.45.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.45.1.tgz", + "integrity": "sha512-k3dOKCfIVixWjG7OXTCOmDfJj3vbdhN0QYEqB+OuGArOChek22hn7Uy5A/gTDNAcCy5v2YcXRJ/Qcnm4/ma1xw==", "cpu": [ "arm64" ], @@ -2088,9 +2109,9 @@ ] }, "node_modules/@rollup/rollup-linux-arm64-musl": { - "version": "4.38.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.38.0.tgz", - "integrity": "sha512-fQgqwKmW0REM4LomQ+87PP8w8xvU9LZfeLBKybeli+0yHT7VKILINzFEuggvnV9M3x1Ed4gUBmGUzCo/ikmFbQ==", + "version": "4.45.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.45.1.tgz", + "integrity": "sha512-PmI1vxQetnM58ZmDFl9/Uk2lpBBby6B6rF4muJc65uZbxCs0EA7hhKCk2PKlmZKuyVSHAyIw3+/SiuMLxKxWog==", "cpu": [ "arm64" ], @@ -2102,9 +2123,9 @@ ] }, "node_modules/@rollup/rollup-linux-loongarch64-gnu": { - "version": "4.38.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loongarch64-gnu/-/rollup-linux-loongarch64-gnu-4.38.0.tgz", - "integrity": "sha512-hz5oqQLXTB3SbXpfkKHKXLdIp02/w3M+ajp8p4yWOWwQRtHWiEOCKtc9U+YXahrwdk+3qHdFMDWR5k+4dIlddg==", + "version": "4.45.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loongarch64-gnu/-/rollup-linux-loongarch64-gnu-4.45.1.tgz", + "integrity": "sha512-9UmI0VzGmNJ28ibHW2GpE2nF0PBQqsyiS4kcJ5vK+wuwGnV5RlqdczVocDSUfGX/Na7/XINRVoUgJyFIgipoRg==", "cpu": [ "loong64" ], @@ -2116,9 +2137,9 @@ ] }, "node_modules/@rollup/rollup-linux-powerpc64le-gnu": { - "version": "4.38.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.38.0.tgz", - "integrity": "sha512-NXqygK/dTSibQ+0pzxsL3r4Xl8oPqVoWbZV9niqOnIHV/J92fe65pOir0xjkUZDRSPyFRvu+4YOpJF9BZHQImw==", + "version": "4.45.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.45.1.tgz", + "integrity": "sha512-7nR2KY8oEOUTD3pBAxIBBbZr0U7U+R9HDTPNy+5nVVHDXI4ikYniH1oxQz9VoB5PbBU1CZuDGHkLJkd3zLMWsg==", "cpu": [ "ppc64" ], @@ -2130,9 +2151,9 @@ ] }, "node_modules/@rollup/rollup-linux-riscv64-gnu": { - "version": "4.38.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.38.0.tgz", - "integrity": "sha512-GEAIabR1uFyvf/jW/5jfu8gjM06/4kZ1W+j1nWTSSB3w6moZEBm7iBtzwQ3a1Pxos2F7Gz+58aVEnZHU295QTg==", + "version": "4.45.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.45.1.tgz", + "integrity": "sha512-nlcl3jgUultKROfZijKjRQLUu9Ma0PeNv/VFHkZiKbXTBQXhpytS8CIj5/NfBeECZtY2FJQubm6ltIxm/ftxpw==", "cpu": [ "riscv64" ], @@ -2144,9 +2165,9 @@ ] }, "node_modules/@rollup/rollup-linux-riscv64-musl": { - "version": "4.38.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.38.0.tgz", - "integrity": "sha512-9EYTX+Gus2EGPbfs+fh7l95wVADtSQyYw4DfSBcYdUEAmP2lqSZY0Y17yX/3m5VKGGJ4UmIH5LHLkMJft3bYoA==", + "version": "4.45.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.45.1.tgz", + "integrity": "sha512-HJV65KLS51rW0VY6rvZkiieiBnurSzpzore1bMKAhunQiECPuxsROvyeaot/tcK3A3aGnI+qTHqisrpSgQrpgA==", "cpu": [ "riscv64" ], @@ -2158,9 +2179,9 @@ ] }, "node_modules/@rollup/rollup-linux-s390x-gnu": { - "version": "4.38.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.38.0.tgz", - "integrity": "sha512-Mpp6+Z5VhB9VDk7RwZXoG2qMdERm3Jw07RNlXHE0bOnEeX+l7Fy4bg+NxfyN15ruuY3/7Vrbpm75J9QHFqj5+Q==", + "version": "4.45.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.45.1.tgz", + "integrity": "sha512-NITBOCv3Qqc6hhwFt7jLV78VEO/il4YcBzoMGGNxznLgRQf43VQDae0aAzKiBeEPIxnDrACiMgbqjuihx08OOw==", "cpu": [ "s390x" ], @@ -2172,9 +2193,9 @@ ] }, "node_modules/@rollup/rollup-linux-x64-gnu": { - "version": "4.38.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.38.0.tgz", - "integrity": "sha512-vPvNgFlZRAgO7rwncMeE0+8c4Hmc+qixnp00/Uv3ht2x7KYrJ6ERVd3/R0nUtlE6/hu7/HiiNHJ/rP6knRFt1w==", + "version": "4.45.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.45.1.tgz", + "integrity": "sha512-+E/lYl6qu1zqgPEnTrs4WysQtvc/Sh4fC2nByfFExqgYrqkKWp1tWIbe+ELhixnenSpBbLXNi6vbEEJ8M7fiHw==", "cpu": [ "x64" ], @@ -2186,9 +2207,9 @@ ] }, "node_modules/@rollup/rollup-linux-x64-musl": { - "version": "4.38.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.38.0.tgz", - "integrity": "sha512-q5Zv+goWvQUGCaL7fU8NuTw8aydIL/C9abAVGCzRReuj5h30TPx4LumBtAidrVOtXnlB+RZkBtExMsfqkMfb8g==", + "version": "4.45.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.45.1.tgz", + "integrity": "sha512-a6WIAp89p3kpNoYStITT9RbTbTnqarU7D8N8F2CV+4Cl9fwCOZraLVuVFvlpsW0SbIiYtEnhCZBPLoNdRkjQFw==", "cpu": [ "x64" ], @@ -2200,9 +2221,9 @@ ] }, "node_modules/@rollup/rollup-win32-arm64-msvc": { - "version": "4.38.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.38.0.tgz", - "integrity": "sha512-u/Jbm1BU89Vftqyqbmxdq14nBaQjQX1HhmsdBWqSdGClNaKwhjsg5TpW+5Ibs1mb8Es9wJiMdl86BcmtUVXNZg==", + "version": "4.45.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.45.1.tgz", + "integrity": "sha512-T5Bi/NS3fQiJeYdGvRpTAP5P02kqSOpqiopwhj0uaXB6nzs5JVi2XMJb18JUSKhCOX8+UE1UKQufyD6Or48dJg==", "cpu": [ "arm64" ], @@ -2214,9 +2235,9 @@ ] }, "node_modules/@rollup/rollup-win32-ia32-msvc": { - "version": "4.38.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.38.0.tgz", - "integrity": "sha512-mqu4PzTrlpNHHbu5qleGvXJoGgHpChBlrBx/mEhTPpnAL1ZAYFlvHD7rLK839LLKQzqEQMFJfGrrOHItN4ZQqA==", + "version": "4.45.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.45.1.tgz", + "integrity": "sha512-lxV2Pako3ujjuUe9jiU3/s7KSrDfH6IgTSQOnDWr9aJ92YsFd7EurmClK0ly/t8dzMkDtd04g60WX6yl0sGfdw==", "cpu": [ "ia32" ], @@ -2228,9 +2249,9 @@ ] }, "node_modules/@rollup/rollup-win32-x64-msvc": { - "version": "4.38.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.38.0.tgz", - "integrity": "sha512-jjqy3uWlecfB98Psxb5cD6Fny9Fupv9LrDSPTQZUROqjvZmcCqNu4UMl7qqhlUUGpwiAkotj6GYu4SZdcr/nLw==", + "version": "4.45.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.45.1.tgz", + "integrity": "sha512-M/fKi4sasCdM8i0aWJjCSFm2qEnYRR8AMLG2kxp6wD13+tMGA4Z1tVAuHkNRjud5SW2EM3naLuK35w9twvf6aA==", "cpu": [ "x64" ], @@ -2607,6 +2628,16 @@ "@types/node": "*" } }, + "node_modules/@types/chai": { + "version": "5.2.2", + "resolved": "https://registry.npmjs.org/@types/chai/-/chai-5.2.2.tgz", + "integrity": "sha512-8kB30R7Hwqf40JPiKhVzodJs2Qc1ZJ5zuT3uzw5Hq/dhNCl3G3l83jfpdI1e20BP348+fV7VIL/+FxaXkqBmWg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/deep-eql": "*" + } + }, "node_modules/@types/conventional-commits-parser": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/@types/conventional-commits-parser/-/conventional-commits-parser-5.0.1.tgz", @@ -2617,6 +2648,13 @@ "@types/node": "*" } }, + "node_modules/@types/deep-eql": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/@types/deep-eql/-/deep-eql-4.0.2.tgz", + "integrity": "sha512-c9h9dVVMigMPc4bwTvC5dxqtqJZwQPePsWjPlpSOnojbor6pGqdk541lfA7AqFQr5pB1BRdq0juY9db81BwyFw==", + "dev": true, + "license": "MIT" + }, "node_modules/@types/elliptic": { "version": "6.4.18", "resolved": "https://registry.npmjs.org/@types/elliptic/-/elliptic-6.4.18.tgz", @@ -2628,9 +2666,9 @@ } }, "node_modules/@types/estree": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.7.tgz", - "integrity": "sha512-w28IoSUCJpidD/TGviZwwMJckNESJZXFu7NBZ5YJ4mEUnNraUn9Pm8HSZm/jDF1pDWYKspWE7oVphigUPRakIQ==", + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz", + "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==", "dev": true, "license": "MIT" }, @@ -2892,15 +2930,16 @@ } }, "node_modules/@vitest/coverage-v8": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/@vitest/coverage-v8/-/coverage-v8-3.1.3.tgz", - "integrity": "sha512-cj76U5gXCl3g88KSnf80kof6+6w+K4BjOflCl7t6yRJPDuCrHtVu0SgNYOUARJOL5TI8RScDbm5x4s1/P9bvpw==", + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/@vitest/coverage-v8/-/coverage-v8-3.2.4.tgz", + "integrity": "sha512-EyF9SXU6kS5Ku/U82E259WSnvg6c8KTjppUncuNdm5QHpe17mwREHnjDzozC8x9MZ0xfBUFSaLkRv4TMA75ALQ==", "dev": true, "license": "MIT", "dependencies": { "@ampproject/remapping": "^2.3.0", "@bcoe/v8-coverage": "^1.0.2", - "debug": "^4.4.0", + "ast-v8-to-istanbul": "^0.3.3", + "debug": "^4.4.1", "istanbul-lib-coverage": "^3.2.2", "istanbul-lib-report": "^3.0.1", "istanbul-lib-source-maps": "^5.0.6", @@ -2915,8 +2954,8 @@ "url": "https://opencollective.com/vitest" }, "peerDependencies": { - "@vitest/browser": "3.1.3", - "vitest": "3.1.3" + "@vitest/browser": "3.2.4", + "vitest": "3.2.4" }, "peerDependenciesMeta": { "@vitest/browser": { @@ -2925,14 +2964,15 @@ } }, "node_modules/@vitest/expect": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-3.1.3.tgz", - "integrity": "sha512-7FTQQuuLKmN1Ig/h+h/GO+44Q1IlglPlR2es4ab7Yvfx+Uk5xsv+Ykk+MEt/M2Yn/xGmzaLKxGw2lgy2bwuYqg==", + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-3.2.4.tgz", + "integrity": "sha512-Io0yyORnB6sikFlt8QW5K7slY4OjqNX9jmJQ02QDda8lyM6B5oNgVWoSoKPac8/kgnCUzuHQKrSLtu/uOqqrig==", "dev": true, "license": "MIT", "dependencies": { - "@vitest/spy": "3.1.3", - "@vitest/utils": "3.1.3", + "@types/chai": "^5.2.2", + "@vitest/spy": "3.2.4", + "@vitest/utils": "3.2.4", "chai": "^5.2.0", "tinyrainbow": "^2.0.0" }, @@ -2941,13 +2981,13 @@ } }, "node_modules/@vitest/mocker": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/@vitest/mocker/-/mocker-3.1.3.tgz", - "integrity": "sha512-PJbLjonJK82uCWHjzgBJZuR7zmAOrSvKk1QBxrennDIgtH4uK0TB1PvYmc0XBCigxxtiAVPfWtAdy4lpz8SQGQ==", + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/@vitest/mocker/-/mocker-3.2.4.tgz", + "integrity": "sha512-46ryTE9RZO/rfDd7pEqFl7etuyzekzEhUbTW3BvmeO/BcCMEgq59BKhek3dXDWgAj4oMK6OZi+vRr1wPW6qjEQ==", "dev": true, "license": "MIT", "dependencies": { - "@vitest/spy": "3.1.3", + "@vitest/spy": "3.2.4", "estree-walker": "^3.0.3", "magic-string": "^0.30.17" }, @@ -2956,7 +2996,7 @@ }, "peerDependencies": { "msw": "^2.4.9", - "vite": "^5.0.0 || ^6.0.0" + "vite": "^5.0.0 || ^6.0.0 || ^7.0.0-0" }, "peerDependenciesMeta": { "msw": { @@ -2978,9 +3018,9 @@ } }, "node_modules/@vitest/pretty-format": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-3.1.3.tgz", - "integrity": "sha512-i6FDiBeJUGLDKADw2Gb01UtUNb12yyXAqC/mmRWuYl+m/U9GS7s8us5ONmGkGpUUo7/iAYzI2ePVfOZTYvUifA==", + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-3.2.4.tgz", + "integrity": "sha512-IVNZik8IVRJRTr9fxlitMKeJeXFFFN0JaB9PHPGQ8NKQbGpfjlTx9zO4RefN8gp7eqjNy8nyK3NZmBzOPeIxtA==", "dev": true, "license": "MIT", "dependencies": { @@ -2991,27 +3031,28 @@ } }, "node_modules/@vitest/runner": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-3.1.3.tgz", - "integrity": "sha512-Tae+ogtlNfFei5DggOsSUvkIaSuVywujMj6HzR97AHK6XK8i3BuVyIifWAm/sE3a15lF5RH9yQIrbXYuo0IFyA==", + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-3.2.4.tgz", + "integrity": "sha512-oukfKT9Mk41LreEW09vt45f8wx7DordoWUZMYdY/cyAk7w5TWkTRCNZYF7sX7n2wB7jyGAl74OxgwhPgKaqDMQ==", "dev": true, "license": "MIT", "dependencies": { - "@vitest/utils": "3.1.3", - "pathe": "^2.0.3" + "@vitest/utils": "3.2.4", + "pathe": "^2.0.3", + "strip-literal": "^3.0.0" }, "funding": { "url": "https://opencollective.com/vitest" } }, "node_modules/@vitest/snapshot": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-3.1.3.tgz", - "integrity": "sha512-XVa5OPNTYUsyqG9skuUkFzAeFnEzDp8hQu7kZ0N25B1+6KjGm4hWLtURyBbsIAOekfWQ7Wuz/N/XXzgYO3deWQ==", + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-3.2.4.tgz", + "integrity": "sha512-dEYtS7qQP2CjU27QBC5oUOxLE/v5eLkGqPE0ZKEIDGMs4vKWe7IjgLOeauHsR0D5YuuycGRO5oSRXnwnmA78fQ==", "dev": true, "license": "MIT", "dependencies": { - "@vitest/pretty-format": "3.1.3", + "@vitest/pretty-format": "3.2.4", "magic-string": "^0.30.17", "pathe": "^2.0.3" }, @@ -3020,27 +3061,27 @@ } }, "node_modules/@vitest/spy": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-3.1.3.tgz", - "integrity": "sha512-x6w+ctOEmEXdWaa6TO4ilb7l9DxPR5bwEb6hILKuxfU1NqWT2mpJD9NJN7t3OTfxmVlOMrvtoFJGdgyzZ605lQ==", + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-3.2.4.tgz", + "integrity": "sha512-vAfasCOe6AIK70iP5UD11Ac4siNUNJ9i/9PZ3NKx07sG6sUxeag1LWdNrMWeKKYBLlzuK+Gn65Yd5nyL6ds+nw==", "dev": true, "license": "MIT", "dependencies": { - "tinyspy": "^3.0.2" + "tinyspy": "^4.0.3" }, "funding": { "url": "https://opencollective.com/vitest" } }, "node_modules/@vitest/utils": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-3.1.3.tgz", - "integrity": "sha512-2Ltrpht4OmHO9+c/nmHtF09HWiyWdworqnHIwjfvDyWjuwKbdkcS9AnhsDn+8E2RM4x++foD1/tNuLPVvWG1Rg==", + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-3.2.4.tgz", + "integrity": "sha512-fB2V0JFrQSMsCo9HiSq3Ezpdv4iYaXRG1Sx8edX3MwxfyNn83mKiGzOcH+Fkxt4MHxr3y42fQi1oeAInqgX2QA==", "dev": true, "license": "MIT", "dependencies": { - "@vitest/pretty-format": "3.1.3", - "loupe": "^3.1.3", + "@vitest/pretty-format": "3.2.4", + "loupe": "^3.1.4", "tinyrainbow": "^2.0.0" }, "funding": { @@ -3048,9 +3089,9 @@ } }, "node_modules/acorn": { - "version": "8.14.1", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.14.1.tgz", - "integrity": "sha512-OvQ/2pUDKmgfCg++xsTX1wGxfTaszcHVcTctW4UJB4hibJx2HXxxO5UmVgyjMa+ZDsiaf5wWLXYpRWMmBI0QHg==", + "version": "8.15.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz", + "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==", "dev": true, "license": "MIT", "bin": { @@ -3293,6 +3334,35 @@ "node": ">=12" } }, + "node_modules/ast-v8-to-istanbul": { + "version": "0.3.3", + "resolved": "https://registry.npmjs.org/ast-v8-to-istanbul/-/ast-v8-to-istanbul-0.3.3.tgz", + "integrity": "sha512-MuXMrSLVVoA6sYN/6Hke18vMzrT4TZNbZIj/hvh0fnYFpO+/kFXcLIaiPwXXWaQUPg4yJD8fj+lfJ7/1EBconw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/trace-mapping": "^0.3.25", + "estree-walker": "^3.0.3", + "js-tokens": "^9.0.1" + } + }, + "node_modules/ast-v8-to-istanbul/node_modules/estree-walker": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-3.0.3.tgz", + "integrity": "sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "^1.0.0" + } + }, + "node_modules/ast-v8-to-istanbul/node_modules/js-tokens": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-9.0.1.tgz", + "integrity": "sha512-mxa9E9ITFOt0ban3j6L5MpjwegGz6lBQmM1IJkWeBZGcMxto50+eWdjC/52xDbS2vy0k7vIMK0Fe2wfL9OQSpQ==", + "dev": true, + "license": "MIT" + }, "node_modules/astral-regex": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/astral-regex/-/astral-regex-2.0.0.tgz", @@ -3418,9 +3488,9 @@ "license": "MIT" }, "node_modules/brace-expansion": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", - "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz", + "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==", "license": "MIT", "dependencies": { "balanced-match": "^1.0.0" @@ -3541,9 +3611,9 @@ } }, "node_modules/chai": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/chai/-/chai-5.2.0.tgz", - "integrity": "sha512-mCuXncKXk5iCLhfhwTc0izo0gtEmpz5CtG2y8GiOINBlMVS6v8TMRc5TaLWKS6692m9+dVVfzgeVxR5UxWHTYw==", + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/chai/-/chai-5.2.1.tgz", + "integrity": "sha512-5nFxhUrX0PqtyogoYOA8IPswy5sZFTOsBFl/9bNsmDLgsxYTzSZQJDPppDnZPTQbzSEm0hqGjWPzRemQCYbD6A==", "dev": true, "license": "MIT", "dependencies": { @@ -3554,7 +3624,7 @@ "pathval": "^2.0.0" }, "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/chalk": { @@ -4211,9 +4281,9 @@ } }, "node_modules/copyfiles/node_modules/brace-expansion": { - "version": "1.1.11", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", - "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "version": "1.1.12", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", + "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", "dev": true, "license": "MIT", "dependencies": { @@ -4561,9 +4631,9 @@ "license": "MIT" }, "node_modules/debug": { - "version": "4.4.0", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.0.tgz", - "integrity": "sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA==", + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.1.tgz", + "integrity": "sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ==", "dev": true, "license": "MIT", "dependencies": { @@ -5177,20 +5247,20 @@ } }, "node_modules/eslint": { - "version": "9.23.0", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.23.0.tgz", - "integrity": "sha512-jV7AbNoFPAY1EkFYpLq5bslU9NLNO8xnEeQXwErNibVryjk67wHVmddTBilc5srIttJDBrB0eMHKZBFbSIABCw==", + "version": "9.31.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.31.0.tgz", + "integrity": "sha512-QldCVh/ztyKJJZLr4jXNUByx3gR+TDYZCRXEktiZoUR3PGy4qCmSbkxcIle8GEwGpb5JBZazlaJ/CxLidXdEbQ==", "dev": true, "license": "MIT", "dependencies": { "@eslint-community/eslint-utils": "^4.2.0", "@eslint-community/regexpp": "^4.12.1", - "@eslint/config-array": "^0.19.2", - "@eslint/config-helpers": "^0.2.0", - "@eslint/core": "^0.12.0", + "@eslint/config-array": "^0.21.0", + "@eslint/config-helpers": "^0.3.0", + "@eslint/core": "^0.15.0", "@eslint/eslintrc": "^3.3.1", - "@eslint/js": "9.23.0", - "@eslint/plugin-kit": "^0.2.7", + "@eslint/js": "9.31.0", + "@eslint/plugin-kit": "^0.3.1", "@humanfs/node": "^0.16.6", "@humanwhocodes/module-importer": "^1.0.1", "@humanwhocodes/retry": "^0.4.2", @@ -5201,9 +5271,9 @@ "cross-spawn": "^7.0.6", "debug": "^4.3.2", "escape-string-regexp": "^4.0.0", - "eslint-scope": "^8.3.0", - "eslint-visitor-keys": "^4.2.0", - "espree": "^10.3.0", + "eslint-scope": "^8.4.0", + "eslint-visitor-keys": "^4.2.1", + "espree": "^10.4.0", "esquery": "^1.5.0", "esutils": "^2.0.2", "fast-deep-equal": "^3.1.3", @@ -5282,9 +5352,9 @@ } }, "node_modules/eslint-scope": { - "version": "8.3.0", - "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-8.3.0.tgz", - "integrity": "sha512-pUNxi75F8MJ/GdeKtVLSbYg4ZI34J6C0C7sbL4YOp2exGwen7ZsuBqKzUhXd0qMQ362yET3z+uPwKeg/0C2XCQ==", + "version": "8.4.0", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-8.4.0.tgz", + "integrity": "sha512-sNXOfKCn74rt8RICKMvJS7XKV/Xk9kA7DyJr8mJik3S7Cwgy3qlkkmyS2uQB3jiJg6VNdZd/pDBJu0nvG2NlTg==", "dev": true, "license": "BSD-2-Clause", "dependencies": { @@ -5311,6 +5381,19 @@ "url": "https://opencollective.com/eslint" } }, + "node_modules/eslint/node_modules/@eslint/js": { + "version": "9.31.0", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.31.0.tgz", + "integrity": "sha512-LOm5OVt7D4qiKCqoiPbA7LWmI+tbw1VbTUowBcUMgQSuM6poJufkFkYDcQpo5KfgD39TnNySV26QjOh7VFpSyw==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://eslint.org/donate" + } + }, "node_modules/eslint/node_modules/ajv": { "version": "6.12.6", "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", @@ -5345,9 +5428,9 @@ } }, "node_modules/eslint/node_modules/brace-expansion": { - "version": "1.1.11", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", - "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "version": "1.1.12", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", + "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", "dev": true, "license": "MIT", "dependencies": { @@ -5393,9 +5476,9 @@ "license": "MIT" }, "node_modules/eslint/node_modules/eslint-visitor-keys": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.0.tgz", - "integrity": "sha512-UyLnSehNt62FFhSwjZlHmeokpRK59rcz29j+F1/aDgbkbRTk7wIc9XzdoasMUbRNKDM0qQt/+BJ4BrpFeABemw==", + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.1.tgz", + "integrity": "sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ==", "dev": true, "license": "Apache-2.0", "engines": { @@ -5514,15 +5597,15 @@ } }, "node_modules/espree": { - "version": "10.3.0", - "resolved": "https://registry.npmjs.org/espree/-/espree-10.3.0.tgz", - "integrity": "sha512-0QYC8b24HWY8zjRnDTL6RiHfDbAWn63qb4LMj1Z4b076A4une81+z03Kg7l7mn/48PUTqoLptSXez8oknU8Clg==", + "version": "10.4.0", + "resolved": "https://registry.npmjs.org/espree/-/espree-10.4.0.tgz", + "integrity": "sha512-j6PAQ2uUr79PZhBjP5C5fhl8e39FmRnOjsD5lGnWrFU8i2G776tBK7+nP8KuQUTTyAZUwfQqXAgrVH5MbH9CYQ==", "dev": true, "license": "BSD-2-Clause", "dependencies": { - "acorn": "^8.14.0", + "acorn": "^8.15.0", "acorn-jsx": "^5.3.2", - "eslint-visitor-keys": "^4.2.0" + "eslint-visitor-keys": "^4.2.1" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -5532,9 +5615,9 @@ } }, "node_modules/espree/node_modules/eslint-visitor-keys": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.0.tgz", - "integrity": "sha512-UyLnSehNt62FFhSwjZlHmeokpRK59rcz29j+F1/aDgbkbRTk7wIc9XzdoasMUbRNKDM0qQt/+BJ4BrpFeABemw==", + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.1.tgz", + "integrity": "sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ==", "dev": true, "license": "Apache-2.0", "engines": { @@ -5754,9 +5837,9 @@ } }, "node_modules/fdir": { - "version": "6.4.4", - "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.4.4.tgz", - "integrity": "sha512-1NZP+GK4GfuAv3PqKvxQRDMjdSRZjnkq7KfhlNrCNNlZ0ygQFpebfrnfnq/W7fpUnAv9aGWmY1zKx7FYL3gwhg==", + "version": "6.4.6", + "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.4.6.tgz", + "integrity": "sha512-hiFoqpyZcfNm1yc4u8oWCf9A2c4D3QjCrks3zmoVKVxpQRzmPNar1hUJcBG2RQHvEVGDN+Jm81ZheVLAQMK6+w==", "dev": true, "license": "MIT", "peerDependencies": { @@ -6183,14 +6266,14 @@ } }, "node_modules/glob": { - "version": "11.0.1", - "resolved": "https://registry.npmjs.org/glob/-/glob-11.0.1.tgz", - "integrity": "sha512-zrQDm8XPnYEKawJScsnM0QzobJxlT/kHOOlRTio8IH/GrmxRE5fjllkzdaHclIuNjUQTJYH2xHNIGfdpJkDJUw==", + "version": "11.0.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-11.0.3.tgz", + "integrity": "sha512-2Nim7dha1KVkaiF4q6Dj+ngPPMdfvLJEOpZk/jKiUAkqKebpGAWQXAq9z1xu9HKu5lWfqw/FASuccEjyznjPaA==", "license": "ISC", "dependencies": { - "foreground-child": "^3.1.0", - "jackspeak": "^4.0.1", - "minimatch": "^10.0.0", + "foreground-child": "^3.3.1", + "jackspeak": "^4.1.1", + "minimatch": "^10.0.3", "minipass": "^7.1.2", "package-json-from-dist": "^1.0.0", "path-scurry": "^2.0.0" @@ -7321,9 +7404,9 @@ } }, "node_modules/jackspeak": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-4.1.0.tgz", - "integrity": "sha512-9DDdhb5j6cpeitCbvLO7n7J4IxnbM6hoF6O1g4HQ5TfhvvKN8ywDM7668ZhMHRqVmxqhps/F6syWK2KcPxYlkw==", + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-4.1.1.tgz", + "integrity": "sha512-zptv57P3GpL+O0I7VdMJNBZCu+BPHVQUk55Ft8/QCJjTVxrnJHuVuX/0Bl2A6/+2oyR/ZMEuFKwmzqqZ/U5nPQ==", "license": "BlueOak-1.0.0", "dependencies": { "@isaacs/cliui": "^8.0.2" @@ -7680,9 +7763,9 @@ "license": "MIT" }, "node_modules/loupe": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/loupe/-/loupe-3.1.3.tgz", - "integrity": "sha512-kkIp7XSkP78ZxJEsSxW3712C6teJVoeHHwgo9zJ380de7IYyJ2ISlxojcH2pC5OFLewESmnRi/+XCDIEEVyoug==", + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/loupe/-/loupe-3.2.0.tgz", + "integrity": "sha512-2NCfZcT5VGVNX9mSZIxLRkEAegDGBpuQZBy13desuHeVORmBDyAET4TkJr4SjqQy3A8JDofMN6LpkK8Xcm/dlw==", "dev": true, "license": "MIT" }, @@ -7918,12 +8001,12 @@ "license": "MIT" }, "node_modules/minimatch": { - "version": "10.0.1", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.0.1.tgz", - "integrity": "sha512-ethXTt3SGGR+95gudmqJ1eNhRO7eGEGIgYA9vnPatK4/etz2MEVDno5GMCibdMTuBMyElzIlgxMna3K94XDIDQ==", + "version": "10.0.3", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.0.3.tgz", + "integrity": "sha512-IPZ167aShDZZUMdRk66cyQAW3qr0WzbHkPdMYa8bzZhlHhO3jALbKdxcaak7W9FfT2rZNpQuUu4Od7ILEpXSaw==", "license": "ISC", "dependencies": { - "brace-expansion": "^2.0.1" + "@isaacs/brace-expansion": "^5.0.0" }, "engines": { "node": "20 || >=22" @@ -8187,9 +8270,9 @@ } }, "node_modules/npm": { - "version": "10.9.1", - "resolved": "https://registry.npmjs.org/npm/-/npm-10.9.1.tgz", - "integrity": "sha512-yJUw03xLqjiv1D52oHeoS5qmOEC5hkJlhP1cWlSrCgshuxWVyFEEK3M3hLC0NwbTaklLTYrhoIanYsuNP5WUKg==", + "version": "10.9.3", + "resolved": "https://registry.npmjs.org/npm/-/npm-10.9.3.tgz", + "integrity": "sha512-6Eh1u5Q+kIVXeA8e7l2c/HpnFFcwrkt37xDMujD5be1gloWa9p6j3Fsv3mByXXmqJHy+2cElRMML8opNT7xIJQ==", "bundleDependencies": [ "@isaacs/string-locale-compare", "@npmcli/arborist", @@ -8271,37 +8354,37 @@ ], "dependencies": { "@isaacs/string-locale-compare": "^1.1.0", - "@npmcli/arborist": "^8.0.0", + "@npmcli/arborist": "^8.0.1", "@npmcli/config": "^9.0.0", "@npmcli/fs": "^4.0.0", - "@npmcli/map-workspaces": "^4.0.1", - "@npmcli/package-json": "^6.0.1", + "@npmcli/map-workspaces": "^4.0.2", + "@npmcli/package-json": "^6.2.0", "@npmcli/promise-spawn": "^8.0.2", - "@npmcli/redact": "^3.0.0", - "@npmcli/run-script": "^9.0.1", - "@sigstore/tuf": "^3.0.0", - "abbrev": "^3.0.0", + "@npmcli/redact": "^3.2.2", + "@npmcli/run-script": "^9.1.0", + "@sigstore/tuf": "^3.1.1", + "abbrev": "^3.0.1", "archy": "~1.0.0", "cacache": "^19.0.1", - "chalk": "^5.3.0", - "ci-info": "^4.1.0", + "chalk": "^5.4.1", + "ci-info": "^4.2.0", "cli-columns": "^4.0.0", "fastest-levenshtein": "^1.0.16", "fs-minipass": "^3.0.3", "glob": "^10.4.5", "graceful-fs": "^4.2.11", - "hosted-git-info": "^8.0.2", + "hosted-git-info": "^8.1.0", "ini": "^5.0.0", - "init-package-json": "^7.0.1", - "is-cidr": "^5.1.0", + "init-package-json": "^7.0.2", + "is-cidr": "^5.1.1", "json-parse-even-better-errors": "^4.0.0", "libnpmaccess": "^9.0.0", - "libnpmdiff": "^7.0.0", - "libnpmexec": "^9.0.0", - "libnpmfund": "^6.0.0", + "libnpmdiff": "^7.0.1", + "libnpmexec": "^9.0.1", + "libnpmfund": "^6.0.1", "libnpmhook": "^11.0.0", "libnpmorg": "^7.0.0", - "libnpmpack": "^8.0.0", + "libnpmpack": "^8.0.1", "libnpmpublish": "^10.0.1", "libnpmsearch": "^8.0.0", "libnpmteam": "^7.0.0", @@ -8311,23 +8394,23 @@ "minipass": "^7.1.1", "minipass-pipeline": "^1.2.4", "ms": "^2.1.2", - "node-gyp": "^10.2.0", - "nopt": "^8.0.0", + "node-gyp": "^11.2.0", + "nopt": "^8.1.0", "normalize-package-data": "^7.0.0", "npm-audit-report": "^6.0.0", "npm-install-checks": "^7.1.1", - "npm-package-arg": "^12.0.0", + "npm-package-arg": "^12.0.2", "npm-pick-manifest": "^10.0.0", "npm-profile": "^11.0.1", "npm-registry-fetch": "^18.0.2", "npm-user-validate": "^3.0.0", - "p-map": "^4.0.0", + "p-map": "^7.0.3", "pacote": "^19.0.1", "parse-conflict-json": "^4.0.0", "proc-log": "^5.0.0", "qrcode-terminal": "^0.12.0", - "read": "^4.0.0", - "semver": "^7.6.3", + "read": "^4.1.0", + "semver": "^7.7.2", "spdx-expression-parse": "^4.0.0", "ssri": "^12.0.0", "supports-color": "^9.4.0", @@ -8335,7 +8418,7 @@ "text-table": "~0.2.0", "tiny-relative-date": "^1.3.0", "treeverse": "^3.0.0", - "validate-npm-package-name": "^6.0.0", + "validate-npm-package-name": "^6.0.1", "which": "^5.0.0", "write-file-atomic": "^6.0.0" }, @@ -8374,9 +8457,9 @@ } }, "node_modules/npm-run-all/node_modules/brace-expansion": { - "version": "1.1.11", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", - "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "version": "1.1.12", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", + "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", "dev": true, "license": "MIT", "dependencies": { @@ -8590,7 +8673,7 @@ } }, "node_modules/npm/node_modules/@npmcli/arborist": { - "version": "8.0.0", + "version": "8.0.1", "dev": true, "inBundle": true, "license": "ISC", @@ -8670,7 +8753,7 @@ } }, "node_modules/npm/node_modules/@npmcli/git": { - "version": "6.0.1", + "version": "6.0.3", "dev": true, "inBundle": true, "license": "ISC", @@ -8680,7 +8763,6 @@ "lru-cache": "^10.0.1", "npm-pick-manifest": "^10.0.0", "proc-log": "^5.0.0", - "promise-inflight": "^1.0.1", "promise-retry": "^2.0.1", "semver": "^7.3.5", "which": "^5.0.0" @@ -8706,7 +8788,7 @@ } }, "node_modules/npm/node_modules/@npmcli/map-workspaces": { - "version": "4.0.1", + "version": "4.0.2", "dev": true, "inBundle": true, "license": "ISC", @@ -8786,7 +8868,7 @@ } }, "node_modules/npm/node_modules/@npmcli/package-json": { - "version": "6.0.1", + "version": "6.2.0", "dev": true, "inBundle": true, "license": "ISC", @@ -8795,9 +8877,9 @@ "glob": "^10.2.2", "hosted-git-info": "^8.0.0", "json-parse-even-better-errors": "^4.0.0", - "normalize-package-data": "^7.0.0", "proc-log": "^5.0.0", - "semver": "^7.5.3" + "semver": "^7.5.3", + "validate-npm-package-license": "^3.0.4" }, "engines": { "node": "^18.17.0 || >=20.5.0" @@ -8816,19 +8898,19 @@ } }, "node_modules/npm/node_modules/@npmcli/query": { - "version": "4.0.0", + "version": "4.0.1", "dev": true, "inBundle": true, "license": "ISC", "dependencies": { - "postcss-selector-parser": "^6.1.2" + "postcss-selector-parser": "^7.0.0" }, "engines": { "node": "^18.17.0 || >=20.5.0" } }, "node_modules/npm/node_modules/@npmcli/redact": { - "version": "3.0.0", + "version": "3.2.2", "dev": true, "inBundle": true, "license": "ISC", @@ -8837,7 +8919,7 @@ } }, "node_modules/npm/node_modules/@npmcli/run-script": { - "version": "9.0.1", + "version": "9.1.0", "dev": true, "inBundle": true, "license": "ISC", @@ -8845,7 +8927,7 @@ "@npmcli/node-gyp": "^4.0.0", "@npmcli/package-json": "^6.0.0", "@npmcli/promise-spawn": "^8.0.0", - "node-gyp": "^10.0.0", + "node-gyp": "^11.0.0", "proc-log": "^5.0.0", "which": "^5.0.0" }, @@ -8864,21 +8946,21 @@ } }, "node_modules/npm/node_modules/@sigstore/protobuf-specs": { - "version": "0.3.2", + "version": "0.4.3", "dev": true, "inBundle": true, "license": "Apache-2.0", "engines": { - "node": "^16.14.0 || >=18.0.0" + "node": "^18.17.0 || >=20.5.0" } }, "node_modules/npm/node_modules/@sigstore/tuf": { - "version": "3.0.0", + "version": "3.1.1", "dev": true, "inBundle": true, "license": "Apache-2.0", "dependencies": { - "@sigstore/protobuf-specs": "^0.3.2", + "@sigstore/protobuf-specs": "^0.4.1", "tuf-js": "^3.0.1" }, "engines": { @@ -8895,7 +8977,7 @@ } }, "node_modules/npm/node_modules/abbrev": { - "version": "3.0.0", + "version": "3.0.1", "dev": true, "inBundle": true, "license": "ISC", @@ -8904,30 +8986,14 @@ } }, "node_modules/npm/node_modules/agent-base": { - "version": "7.1.1", + "version": "7.1.3", "dev": true, "inBundle": true, "license": "MIT", - "dependencies": { - "debug": "^4.3.4" - }, "engines": { "node": ">= 14" } }, - "node_modules/npm/node_modules/aggregate-error": { - "version": "3.1.0", - "dev": true, - "inBundle": true, - "license": "MIT", - "dependencies": { - "clean-stack": "^2.0.0", - "indent-string": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, "node_modules/npm/node_modules/ansi-regex": { "version": "5.0.1", "dev": true, @@ -8996,7 +9062,7 @@ } }, "node_modules/npm/node_modules/brace-expansion": { - "version": "2.0.1", + "version": "2.0.2", "dev": true, "inBundle": true, "license": "MIT", @@ -9036,19 +9102,6 @@ "node": ">=18" } }, - "node_modules/npm/node_modules/cacache/node_modules/minizlib": { - "version": "3.0.1", - "dev": true, - "inBundle": true, - "license": "MIT", - "dependencies": { - "minipass": "^7.0.4", - "rimraf": "^5.0.5" - }, - "engines": { - "node": ">= 18" - } - }, "node_modules/npm/node_modules/cacache/node_modules/mkdirp": { "version": "3.0.1", "dev": true, @@ -9064,18 +9117,6 @@ "url": "https://github.com/sponsors/isaacs" } }, - "node_modules/npm/node_modules/cacache/node_modules/p-map": { - "version": "7.0.2", - "dev": true, - "inBundle": true, - "license": "MIT", - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/npm/node_modules/cacache/node_modules/tar": { "version": "7.4.3", "dev": true, @@ -9103,7 +9144,7 @@ } }, "node_modules/npm/node_modules/chalk": { - "version": "5.3.0", + "version": "5.4.1", "dev": true, "inBundle": true, "license": "MIT", @@ -9124,7 +9165,7 @@ } }, "node_modules/npm/node_modules/ci-info": { - "version": "4.1.0", + "version": "4.2.0", "dev": true, "funding": [ { @@ -9139,7 +9180,7 @@ } }, "node_modules/npm/node_modules/cidr-regex": { - "version": "4.1.1", + "version": "4.1.3", "dev": true, "inBundle": true, "license": "BSD-2-Clause", @@ -9150,15 +9191,6 @@ "node": ">=14" } }, - "node_modules/npm/node_modules/clean-stack": { - "version": "2.2.0", - "dev": true, - "inBundle": true, - "license": "MIT", - "engines": { - "node": ">=6" - } - }, "node_modules/npm/node_modules/cli-columns": { "version": "4.0.0", "dev": true, @@ -9247,7 +9279,7 @@ } }, "node_modules/npm/node_modules/debug": { - "version": "4.3.7", + "version": "4.4.1", "dev": true, "inBundle": true, "license": "MIT", @@ -9310,7 +9342,7 @@ "license": "MIT" }, "node_modules/npm/node_modules/exponential-backoff": { - "version": "3.1.1", + "version": "3.1.2", "dev": true, "inBundle": true, "license": "Apache-2.0" @@ -9325,12 +9357,12 @@ } }, "node_modules/npm/node_modules/foreground-child": { - "version": "3.3.0", + "version": "3.3.1", "dev": true, "inBundle": true, "license": "ISC", "dependencies": { - "cross-spawn": "^7.0.0", + "cross-spawn": "^7.0.6", "signal-exit": "^4.0.1" }, "engines": { @@ -9379,7 +9411,7 @@ "license": "ISC" }, "node_modules/npm/node_modules/hosted-git-info": { - "version": "8.0.2", + "version": "8.1.0", "dev": true, "inBundle": true, "license": "ISC", @@ -9391,7 +9423,7 @@ } }, "node_modules/npm/node_modules/http-cache-semantics": { - "version": "4.1.1", + "version": "4.2.0", "dev": true, "inBundle": true, "license": "BSD-2-Clause" @@ -9410,12 +9442,12 @@ } }, "node_modules/npm/node_modules/https-proxy-agent": { - "version": "7.0.5", + "version": "7.0.6", "dev": true, "inBundle": true, "license": "MIT", "dependencies": { - "agent-base": "^7.0.2", + "agent-base": "^7.1.2", "debug": "4" }, "engines": { @@ -9456,15 +9488,6 @@ "node": ">=0.8.19" } }, - "node_modules/npm/node_modules/indent-string": { - "version": "4.0.0", - "dev": true, - "inBundle": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, "node_modules/npm/node_modules/ini": { "version": "5.0.0", "dev": true, @@ -9475,7 +9498,7 @@ } }, "node_modules/npm/node_modules/init-package-json": { - "version": "7.0.1", + "version": "7.0.2", "dev": true, "inBundle": true, "license": "ISC", @@ -9518,7 +9541,7 @@ } }, "node_modules/npm/node_modules/is-cidr": { - "version": "5.1.0", + "version": "5.1.1", "dev": true, "inBundle": true, "license": "BSD-2-Clause", @@ -9538,12 +9561,6 @@ "node": ">=8" } }, - "node_modules/npm/node_modules/is-lambda": { - "version": "1.0.1", - "dev": true, - "inBundle": true, - "license": "MIT" - }, "node_modules/npm/node_modules/isexe": { "version": "2.0.0", "dev": true, @@ -9624,12 +9641,12 @@ } }, "node_modules/npm/node_modules/libnpmdiff": { - "version": "7.0.0", + "version": "7.0.1", "dev": true, "inBundle": true, "license": "ISC", "dependencies": { - "@npmcli/arborist": "^8.0.0", + "@npmcli/arborist": "^8.0.1", "@npmcli/installed-package-contents": "^3.0.0", "binary-extensions": "^2.3.0", "diff": "^5.1.0", @@ -9643,12 +9660,12 @@ } }, "node_modules/npm/node_modules/libnpmexec": { - "version": "9.0.0", + "version": "9.0.1", "dev": true, "inBundle": true, "license": "ISC", "dependencies": { - "@npmcli/arborist": "^8.0.0", + "@npmcli/arborist": "^8.0.1", "@npmcli/run-script": "^9.0.1", "ci-info": "^4.0.0", "npm-package-arg": "^12.0.0", @@ -9664,12 +9681,12 @@ } }, "node_modules/npm/node_modules/libnpmfund": { - "version": "6.0.0", + "version": "6.0.1", "dev": true, "inBundle": true, "license": "ISC", "dependencies": { - "@npmcli/arborist": "^8.0.0" + "@npmcli/arborist": "^8.0.1" }, "engines": { "node": "^18.17.0 || >=20.5.0" @@ -9702,12 +9719,12 @@ } }, "node_modules/npm/node_modules/libnpmpack": { - "version": "8.0.0", + "version": "8.0.1", "dev": true, "inBundle": true, "license": "ISC", "dependencies": { - "@npmcli/arborist": "^8.0.0", + "@npmcli/arborist": "^8.0.1", "@npmcli/run-script": "^9.0.1", "npm-package-arg": "^12.0.0", "pacote": "^19.0.0" @@ -9850,7 +9867,7 @@ } }, "node_modules/npm/node_modules/minipass-fetch": { - "version": "4.0.0", + "version": "4.0.1", "dev": true, "inBundle": true, "license": "MIT", @@ -9866,19 +9883,6 @@ "encoding": "^0.1.13" } }, - "node_modules/npm/node_modules/minipass-fetch/node_modules/minizlib": { - "version": "3.0.1", - "dev": true, - "inBundle": true, - "license": "MIT", - "dependencies": { - "minipass": "^7.0.4", - "rimraf": "^5.0.5" - }, - "engines": { - "node": ">= 18" - } - }, "node_modules/npm/node_modules/minipass-flush": { "version": "1.0.5", "dev": true, @@ -9952,28 +9956,15 @@ } }, "node_modules/npm/node_modules/minizlib": { - "version": "2.1.2", + "version": "3.0.2", "dev": true, "inBundle": true, "license": "MIT", "dependencies": { - "minipass": "^3.0.0", - "yallist": "^4.0.0" + "minipass": "^7.1.2" }, "engines": { - "node": ">= 8" - } - }, - "node_modules/npm/node_modules/minizlib/node_modules/minipass": { - "version": "3.3.6", - "dev": true, - "inBundle": true, - "license": "ISC", - "dependencies": { - "yallist": "^4.0.0" - }, - "engines": { - "node": ">=8" + "node": ">= 18" } }, "node_modules/npm/node_modules/mkdirp": { @@ -10003,230 +9994,87 @@ "node": "^18.17.0 || >=20.5.0" } }, - "node_modules/npm/node_modules/negotiator": { - "version": "0.6.4", - "dev": true, - "inBundle": true, - "license": "MIT", - "engines": { - "node": ">= 0.6" - } - }, "node_modules/npm/node_modules/node-gyp": { - "version": "10.2.0", + "version": "11.2.0", "dev": true, "inBundle": true, "license": "MIT", "dependencies": { "env-paths": "^2.2.0", "exponential-backoff": "^3.1.1", - "glob": "^10.3.10", "graceful-fs": "^4.2.6", - "make-fetch-happen": "^13.0.0", - "nopt": "^7.0.0", - "proc-log": "^4.1.0", + "make-fetch-happen": "^14.0.3", + "nopt": "^8.0.0", + "proc-log": "^5.0.0", "semver": "^7.3.5", - "tar": "^6.2.1", - "which": "^4.0.0" + "tar": "^7.4.3", + "tinyglobby": "^0.2.12", + "which": "^5.0.0" }, "bin": { "node-gyp": "bin/node-gyp.js" }, "engines": { - "node": "^16.14.0 || >=18.0.0" - } - }, - "node_modules/npm/node_modules/node-gyp/node_modules/@npmcli/agent": { - "version": "2.2.2", - "dev": true, - "inBundle": true, - "license": "ISC", - "dependencies": { - "agent-base": "^7.1.0", - "http-proxy-agent": "^7.0.0", - "https-proxy-agent": "^7.0.1", - "lru-cache": "^10.0.1", - "socks-proxy-agent": "^8.0.3" - }, - "engines": { - "node": "^16.14.0 || >=18.0.0" - } - }, - "node_modules/npm/node_modules/node-gyp/node_modules/@npmcli/fs": { - "version": "3.1.1", - "dev": true, - "inBundle": true, - "license": "ISC", - "dependencies": { - "semver": "^7.3.5" - }, - "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0" - } - }, - "node_modules/npm/node_modules/node-gyp/node_modules/abbrev": { - "version": "2.0.0", - "dev": true, - "inBundle": true, - "license": "ISC", - "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0" - } - }, - "node_modules/npm/node_modules/node-gyp/node_modules/cacache": { - "version": "18.0.4", - "dev": true, - "inBundle": true, - "license": "ISC", - "dependencies": { - "@npmcli/fs": "^3.1.0", - "fs-minipass": "^3.0.0", - "glob": "^10.2.2", - "lru-cache": "^10.0.1", - "minipass": "^7.0.3", - "minipass-collect": "^2.0.1", - "minipass-flush": "^1.0.5", - "minipass-pipeline": "^1.2.4", - "p-map": "^4.0.0", - "ssri": "^10.0.0", - "tar": "^6.1.11", - "unique-filename": "^3.0.0" - }, - "engines": { - "node": "^16.14.0 || >=18.0.0" - } - }, - "node_modules/npm/node_modules/node-gyp/node_modules/isexe": { - "version": "3.1.1", - "dev": true, - "inBundle": true, - "license": "ISC", - "engines": { - "node": ">=16" + "node": "^18.17.0 || >=20.5.0" } }, - "node_modules/npm/node_modules/node-gyp/node_modules/make-fetch-happen": { - "version": "13.0.1", + "node_modules/npm/node_modules/node-gyp/node_modules/chownr": { + "version": "3.0.0", "dev": true, "inBundle": true, - "license": "ISC", - "dependencies": { - "@npmcli/agent": "^2.0.0", - "cacache": "^18.0.0", - "http-cache-semantics": "^4.1.1", - "is-lambda": "^1.0.1", - "minipass": "^7.0.2", - "minipass-fetch": "^3.0.0", - "minipass-flush": "^1.0.5", - "minipass-pipeline": "^1.2.4", - "negotiator": "^0.6.3", - "proc-log": "^4.2.0", - "promise-retry": "^2.0.1", - "ssri": "^10.0.0" - }, + "license": "BlueOak-1.0.0", "engines": { - "node": "^16.14.0 || >=18.0.0" + "node": ">=18" } }, - "node_modules/npm/node_modules/node-gyp/node_modules/minipass-fetch": { - "version": "3.0.5", + "node_modules/npm/node_modules/node-gyp/node_modules/mkdirp": { + "version": "3.0.1", "dev": true, "inBundle": true, "license": "MIT", - "dependencies": { - "minipass": "^7.0.3", - "minipass-sized": "^1.0.3", - "minizlib": "^2.1.2" - }, - "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0" - }, - "optionalDependencies": { - "encoding": "^0.1.13" - } - }, - "node_modules/npm/node_modules/node-gyp/node_modules/nopt": { - "version": "7.2.1", - "dev": true, - "inBundle": true, - "license": "ISC", - "dependencies": { - "abbrev": "^2.0.0" - }, "bin": { - "nopt": "bin/nopt.js" - }, - "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0" - } - }, - "node_modules/npm/node_modules/node-gyp/node_modules/proc-log": { - "version": "4.2.0", - "dev": true, - "inBundle": true, - "license": "ISC", - "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0" - } - }, - "node_modules/npm/node_modules/node-gyp/node_modules/ssri": { - "version": "10.0.6", - "dev": true, - "inBundle": true, - "license": "ISC", - "dependencies": { - "minipass": "^7.0.3" + "mkdirp": "dist/cjs/src/bin.js" }, "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0" - } - }, - "node_modules/npm/node_modules/node-gyp/node_modules/unique-filename": { - "version": "3.0.0", - "dev": true, - "inBundle": true, - "license": "ISC", - "dependencies": { - "unique-slug": "^4.0.0" + "node": ">=10" }, - "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + "funding": { + "url": "https://github.com/sponsors/isaacs" } }, - "node_modules/npm/node_modules/node-gyp/node_modules/unique-slug": { - "version": "4.0.0", + "node_modules/npm/node_modules/node-gyp/node_modules/tar": { + "version": "7.4.3", "dev": true, "inBundle": true, "license": "ISC", "dependencies": { - "imurmurhash": "^0.1.4" + "@isaacs/fs-minipass": "^4.0.0", + "chownr": "^3.0.0", + "minipass": "^7.1.2", + "minizlib": "^3.0.1", + "mkdirp": "^3.0.1", + "yallist": "^5.0.0" }, "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + "node": ">=18" } }, - "node_modules/npm/node_modules/node-gyp/node_modules/which": { - "version": "4.0.0", + "node_modules/npm/node_modules/node-gyp/node_modules/yallist": { + "version": "5.0.0", "dev": true, "inBundle": true, - "license": "ISC", - "dependencies": { - "isexe": "^3.1.1" - }, - "bin": { - "node-which": "bin/which.js" - }, + "license": "BlueOak-1.0.0", "engines": { - "node": "^16.13.0 || >=18.0.0" + "node": ">=18" } }, "node_modules/npm/node_modules/nopt": { - "version": "8.0.0", + "version": "8.1.0", "dev": true, "inBundle": true, "license": "ISC", "dependencies": { - "abbrev": "^2.0.0" + "abbrev": "^3.0.0" }, "bin": { "nopt": "bin/nopt.js" @@ -10235,15 +10083,6 @@ "node": "^18.17.0 || >=20.5.0" } }, - "node_modules/npm/node_modules/nopt/node_modules/abbrev": { - "version": "2.0.0", - "dev": true, - "inBundle": true, - "license": "ISC", - "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0" - } - }, "node_modules/npm/node_modules/normalize-package-data": { "version": "7.0.0", "dev": true, @@ -10301,7 +10140,7 @@ } }, "node_modules/npm/node_modules/npm-package-arg": { - "version": "12.0.0", + "version": "12.0.2", "dev": true, "inBundle": true, "license": "ISC", @@ -10374,19 +10213,6 @@ "node": "^18.17.0 || >=20.5.0" } }, - "node_modules/npm/node_modules/npm-registry-fetch/node_modules/minizlib": { - "version": "3.0.1", - "dev": true, - "inBundle": true, - "license": "MIT", - "dependencies": { - "minipass": "^7.0.4", - "rimraf": "^5.0.5" - }, - "engines": { - "node": ">= 18" - } - }, "node_modules/npm/node_modules/npm-user-validate": { "version": "3.0.0", "dev": true, @@ -10397,15 +10223,12 @@ } }, "node_modules/npm/node_modules/p-map": { - "version": "4.0.0", + "version": "7.0.3", "dev": true, "inBundle": true, "license": "MIT", - "dependencies": { - "aggregate-error": "^3.0.0" - }, "engines": { - "node": ">=10" + "node": ">=18" }, "funding": { "url": "https://github.com/sponsors/sindresorhus" @@ -10488,7 +10311,7 @@ } }, "node_modules/npm/node_modules/postcss-selector-parser": { - "version": "6.1.2", + "version": "7.1.0", "dev": true, "inBundle": true, "license": "MIT", @@ -10536,12 +10359,6 @@ "url": "https://github.com/sponsors/isaacs" } }, - "node_modules/npm/node_modules/promise-inflight": { - "version": "1.0.1", - "dev": true, - "inBundle": true, - "license": "ISC" - }, "node_modules/npm/node_modules/promise-retry": { "version": "2.0.1", "dev": true, @@ -10576,7 +10393,7 @@ } }, "node_modules/npm/node_modules/read": { - "version": "4.0.0", + "version": "4.1.0", "dev": true, "inBundle": true, "license": "ISC", @@ -10618,21 +10435,6 @@ "node": ">= 4" } }, - "node_modules/npm/node_modules/rimraf": { - "version": "5.0.10", - "dev": true, - "inBundle": true, - "license": "ISC", - "dependencies": { - "glob": "^10.3.7" - }, - "bin": { - "rimraf": "dist/esm/bin.mjs" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, "node_modules/npm/node_modules/safer-buffer": { "version": "2.1.2", "dev": true, @@ -10641,7 +10443,7 @@ "optional": true }, "node_modules/npm/node_modules/semver": { - "version": "7.6.3", + "version": "7.7.2", "dev": true, "inBundle": true, "license": "ISC", @@ -10686,29 +10488,29 @@ } }, "node_modules/npm/node_modules/sigstore": { - "version": "3.0.0", + "version": "3.1.0", "dev": true, "inBundle": true, "license": "Apache-2.0", "dependencies": { - "@sigstore/bundle": "^3.0.0", + "@sigstore/bundle": "^3.1.0", "@sigstore/core": "^2.0.0", - "@sigstore/protobuf-specs": "^0.3.2", - "@sigstore/sign": "^3.0.0", - "@sigstore/tuf": "^3.0.0", - "@sigstore/verify": "^2.0.0" + "@sigstore/protobuf-specs": "^0.4.0", + "@sigstore/sign": "^3.1.0", + "@sigstore/tuf": "^3.1.0", + "@sigstore/verify": "^2.1.0" }, "engines": { "node": "^18.17.0 || >=20.5.0" } }, "node_modules/npm/node_modules/sigstore/node_modules/@sigstore/bundle": { - "version": "3.0.0", + "version": "3.1.0", "dev": true, "inBundle": true, "license": "Apache-2.0", "dependencies": { - "@sigstore/protobuf-specs": "^0.3.2" + "@sigstore/protobuf-specs": "^0.4.0" }, "engines": { "node": "^18.17.0 || >=20.5.0" @@ -10724,15 +10526,15 @@ } }, "node_modules/npm/node_modules/sigstore/node_modules/@sigstore/sign": { - "version": "3.0.0", + "version": "3.1.0", "dev": true, "inBundle": true, "license": "Apache-2.0", "dependencies": { - "@sigstore/bundle": "^3.0.0", + "@sigstore/bundle": "^3.1.0", "@sigstore/core": "^2.0.0", - "@sigstore/protobuf-specs": "^0.3.2", - "make-fetch-happen": "^14.0.1", + "@sigstore/protobuf-specs": "^0.4.0", + "make-fetch-happen": "^14.0.2", "proc-log": "^5.0.0", "promise-retry": "^2.0.1" }, @@ -10741,14 +10543,14 @@ } }, "node_modules/npm/node_modules/sigstore/node_modules/@sigstore/verify": { - "version": "2.0.0", + "version": "2.1.1", "dev": true, "inBundle": true, "license": "Apache-2.0", "dependencies": { - "@sigstore/bundle": "^3.0.0", + "@sigstore/bundle": "^3.1.0", "@sigstore/core": "^2.0.0", - "@sigstore/protobuf-specs": "^0.3.2" + "@sigstore/protobuf-specs": "^0.4.1" }, "engines": { "node": "^18.17.0 || >=20.5.0" @@ -10765,7 +10567,7 @@ } }, "node_modules/npm/node_modules/socks": { - "version": "2.8.3", + "version": "2.8.5", "dev": true, "inBundle": true, "license": "MIT", @@ -10779,12 +10581,12 @@ } }, "node_modules/npm/node_modules/socks-proxy-agent": { - "version": "8.0.4", + "version": "8.0.5", "dev": true, "inBundle": true, "license": "MIT", "dependencies": { - "agent-base": "^7.1.1", + "agent-base": "^7.1.2", "debug": "^4.3.4", "socks": "^2.8.3" }, @@ -10829,7 +10631,7 @@ } }, "node_modules/npm/node_modules/spdx-license-ids": { - "version": "3.0.20", + "version": "3.0.21", "dev": true, "inBundle": true, "license": "CC0-1.0" @@ -10968,6 +10770,31 @@ "node": ">=8" } }, + "node_modules/npm/node_modules/tar/node_modules/minizlib": { + "version": "2.1.2", + "dev": true, + "inBundle": true, + "license": "MIT", + "dependencies": { + "minipass": "^3.0.0", + "yallist": "^4.0.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/npm/node_modules/tar/node_modules/minizlib/node_modules/minipass": { + "version": "3.3.6", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/npm/node_modules/text-table": { "version": "0.2.0", "dev": true, @@ -10980,6 +10807,48 @@ "inBundle": true, "license": "MIT" }, + "node_modules/npm/node_modules/tinyglobby": { + "version": "0.2.14", + "dev": true, + "inBundle": true, + "license": "MIT", + "dependencies": { + "fdir": "^6.4.4", + "picomatch": "^4.0.2" + }, + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/SuperchupuDev" + } + }, + "node_modules/npm/node_modules/tinyglobby/node_modules/fdir": { + "version": "6.4.6", + "dev": true, + "inBundle": true, + "license": "MIT", + "peerDependencies": { + "picomatch": "^3 || ^4" + }, + "peerDependenciesMeta": { + "picomatch": { + "optional": true + } + } + }, + "node_modules/npm/node_modules/tinyglobby/node_modules/picomatch": { + "version": "4.0.2", + "dev": true, + "inBundle": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, "node_modules/npm/node_modules/treeverse": { "version": "3.0.0", "dev": true, @@ -11067,7 +10936,7 @@ } }, "node_modules/npm/node_modules/validate-npm-package-name": { - "version": "6.0.0", + "version": "6.0.1", "dev": true, "inBundle": true, "license": "ISC", @@ -11605,9 +11474,9 @@ "license": "MIT" }, "node_modules/pathval": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/pathval/-/pathval-2.0.0.tgz", - "integrity": "sha512-vE7JKRyES09KiunauX7nd2Q9/L7lhok4smP9RZTDeD4MVs72Dp2qNFVz39Nz5a0FVEW0BJR6C0DYrq6unoziZA==", + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/pathval/-/pathval-2.0.1.tgz", + "integrity": "sha512-//nshmD55c46FuFw26xV/xFAaB5HF9Xdap7HJBBnrKdAd6/GxDBaNA1870O79+9ueg61cZLSVc+OaFlfmObYVQ==", "dev": true, "license": "MIT", "engines": { @@ -11754,9 +11623,9 @@ } }, "node_modules/postcss": { - "version": "8.5.3", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.3.tgz", - "integrity": "sha512-dle9A3yYxlBSrt8Fu+IpjGT8SY8hN0mlaA6GY8t0P5PjIOZemULz/E2Bnm/2dcUOena75OTNkHI76uZBNUUq3A==", + "version": "8.5.6", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.6.tgz", + "integrity": "sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==", "dev": true, "funding": [ { @@ -11774,7 +11643,7 @@ ], "license": "MIT", "dependencies": { - "nanoid": "^3.3.8", + "nanoid": "^3.3.11", "picocolors": "^1.1.1", "source-map-js": "^1.2.1" }, @@ -12194,13 +12063,13 @@ } }, "node_modules/rollup": { - "version": "4.38.0", - "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.38.0.tgz", - "integrity": "sha512-5SsIRtJy9bf1ErAOiFMFzl64Ex9X5V7bnJ+WlFMb+zmP459OSWCEG7b0ERZ+PEU7xPt4OG3RHbrp1LJlXxYTrw==", + "version": "4.45.1", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.45.1.tgz", + "integrity": "sha512-4iya7Jb76fVpQyLoiVpzUrsjQ12r3dM7fIVz+4NwoYvZOShknRmiv+iu9CClZml5ZLGb0XMcYLutK6w9tgxHDw==", "dev": true, "license": "MIT", "dependencies": { - "@types/estree": "1.0.7" + "@types/estree": "1.0.8" }, "bin": { "rollup": "dist/bin/rollup" @@ -12210,26 +12079,26 @@ "npm": ">=8.0.0" }, "optionalDependencies": { - "@rollup/rollup-android-arm-eabi": "4.38.0", - "@rollup/rollup-android-arm64": "4.38.0", - "@rollup/rollup-darwin-arm64": "4.38.0", - "@rollup/rollup-darwin-x64": "4.38.0", - "@rollup/rollup-freebsd-arm64": "4.38.0", - "@rollup/rollup-freebsd-x64": "4.38.0", - "@rollup/rollup-linux-arm-gnueabihf": "4.38.0", - "@rollup/rollup-linux-arm-musleabihf": "4.38.0", - "@rollup/rollup-linux-arm64-gnu": "4.38.0", - "@rollup/rollup-linux-arm64-musl": "4.38.0", - "@rollup/rollup-linux-loongarch64-gnu": "4.38.0", - "@rollup/rollup-linux-powerpc64le-gnu": "4.38.0", - "@rollup/rollup-linux-riscv64-gnu": "4.38.0", - "@rollup/rollup-linux-riscv64-musl": "4.38.0", - "@rollup/rollup-linux-s390x-gnu": "4.38.0", - "@rollup/rollup-linux-x64-gnu": "4.38.0", - "@rollup/rollup-linux-x64-musl": "4.38.0", - "@rollup/rollup-win32-arm64-msvc": "4.38.0", - "@rollup/rollup-win32-ia32-msvc": "4.38.0", - "@rollup/rollup-win32-x64-msvc": "4.38.0", + "@rollup/rollup-android-arm-eabi": "4.45.1", + "@rollup/rollup-android-arm64": "4.45.1", + "@rollup/rollup-darwin-arm64": "4.45.1", + "@rollup/rollup-darwin-x64": "4.45.1", + "@rollup/rollup-freebsd-arm64": "4.45.1", + "@rollup/rollup-freebsd-x64": "4.45.1", + "@rollup/rollup-linux-arm-gnueabihf": "4.45.1", + "@rollup/rollup-linux-arm-musleabihf": "4.45.1", + "@rollup/rollup-linux-arm64-gnu": "4.45.1", + "@rollup/rollup-linux-arm64-musl": "4.45.1", + "@rollup/rollup-linux-loongarch64-gnu": "4.45.1", + "@rollup/rollup-linux-powerpc64le-gnu": "4.45.1", + "@rollup/rollup-linux-riscv64-gnu": "4.45.1", + "@rollup/rollup-linux-riscv64-musl": "4.45.1", + "@rollup/rollup-linux-s390x-gnu": "4.45.1", + "@rollup/rollup-linux-x64-gnu": "4.45.1", + "@rollup/rollup-linux-x64-musl": "4.45.1", + "@rollup/rollup-win32-arm64-msvc": "4.45.1", + "@rollup/rollup-win32-ia32-msvc": "4.45.1", + "@rollup/rollup-win32-x64-msvc": "4.45.1", "fsevents": "~2.3.2" } }, @@ -13130,6 +12999,26 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/strip-literal": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/strip-literal/-/strip-literal-3.0.0.tgz", + "integrity": "sha512-TcccoMhJOM3OebGhSBEmp3UZ2SfDMZUEBdRA/9ynfLi8yYajyWX3JiXArcJt4Umh4vISpspkQIY8ZZoCqjbviA==", + "dev": true, + "license": "MIT", + "dependencies": { + "js-tokens": "^9.0.1" + }, + "funding": { + "url": "https://github.com/sponsors/antfu" + } + }, + "node_modules/strip-literal/node_modules/js-tokens": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-9.0.1.tgz", + "integrity": "sha512-mxa9E9ITFOt0ban3j6L5MpjwegGz6lBQmM1IJkWeBZGcMxto50+eWdjC/52xDbS2vy0k7vIMK0Fe2wfL9OQSpQ==", + "dev": true, + "license": "MIT" + }, "node_modules/super-regex": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/super-regex/-/super-regex-1.0.0.tgz", @@ -13566,9 +13455,9 @@ "license": "MIT" }, "node_modules/tinyglobby": { - "version": "0.2.13", - "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.13.tgz", - "integrity": "sha512-mEwzpUgrLySlveBwEVDMKk5B57bhLPYovRfPAXD5gA/98Opn0rCDj3GtLwFvCvH5RK9uPCExUROW5NjDwvqkxw==", + "version": "0.2.14", + "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.14.tgz", + "integrity": "sha512-tX5e7OM1HnYr2+a2C/4V0htOcSQcoSTH9KgJnVvNm5zm/cyEWKJ7j7YutsH9CxMdtOkkLFy2AHrMci9IM8IPZQ==", "dev": true, "license": "MIT", "dependencies": { @@ -13583,9 +13472,9 @@ } }, "node_modules/tinypool": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/tinypool/-/tinypool-1.0.2.tgz", - "integrity": "sha512-al6n+QEANGFOMf/dmUMsuS5/r9B06uwlyNjZZql/zv8J7ybHCgoihBNORZCY2mzUuAnomQa2JdhyHKzZxPCrFA==", + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/tinypool/-/tinypool-1.1.1.tgz", + "integrity": "sha512-Zba82s87IFq9A9XmjiX5uZA/ARWDrB03OHlq+Vw1fSdt0I+4/Kutwy8BP4Y/y/aORMo61FQ0vIb5j44vSo5Pkg==", "dev": true, "license": "MIT", "engines": { @@ -13603,9 +13492,9 @@ } }, "node_modules/tinyspy": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/tinyspy/-/tinyspy-3.0.2.tgz", - "integrity": "sha512-n1cw8k1k0x4pgA2+9XrOkFydTerNcJ1zWCO5Nn9scWHTD+5tp8dghT2x1uduQePZTZgd3Tupf+x9BxJjeJi77Q==", + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/tinyspy/-/tinyspy-4.0.3.tgz", + "integrity": "sha512-t2T/WLB2WRgZ9EpE4jgPJ9w+i66UZfDc8wHh0xrwiRNN+UwH98GIJkTeZqX9rg0i0ptwzqW+uYeIF0T4F8LR7A==", "dev": true, "license": "MIT", "engines": { @@ -13841,9 +13730,9 @@ } }, "node_modules/typescript": { - "version": "5.8.2", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.8.2.tgz", - "integrity": "sha512-aJn6wq13/afZp/jT9QZmwEjDqqvSGp1VT5GVg+f/t6/oVyrgXM6BY1h9BRh/O5p3PlUPAe+WuiEZOmb/49RqoQ==", + "version": "5.8.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.8.3.tgz", + "integrity": "sha512-p1diW6TqL9L07nNxvRMM7hMMw4c5XOo/1ibL4aAIGmSAt9slTE1Xgw5KWuof2uTOvCg9BY7ZRi+GaF+7sfgPeQ==", "license": "Apache-2.0", "bin": { "tsc": "bin/tsc", @@ -14015,24 +13904,24 @@ } }, "node_modules/vite": { - "version": "6.3.5", - "resolved": "https://registry.npmjs.org/vite/-/vite-6.3.5.tgz", - "integrity": "sha512-cZn6NDFE7wdTpINgs++ZJ4N49W2vRp8LCKrn3Ob1kYNtOo21vfDoaV5GzBfLU4MovSAB8uNRm4jgzVQZ+mBzPQ==", + "version": "7.0.5", + "resolved": "https://registry.npmjs.org/vite/-/vite-7.0.5.tgz", + "integrity": "sha512-1mncVwJxy2C9ThLwz0+2GKZyEXuC3MyWtAAlNftlZZXZDP3AJt5FmwcMit/IGGaNZ8ZOB2BNO/HFUB+CpN0NQw==", "dev": true, "license": "MIT", "dependencies": { "esbuild": "^0.25.0", - "fdir": "^6.4.4", + "fdir": "^6.4.6", "picomatch": "^4.0.2", - "postcss": "^8.5.3", - "rollup": "^4.34.9", - "tinyglobby": "^0.2.13" + "postcss": "^8.5.6", + "rollup": "^4.40.0", + "tinyglobby": "^0.2.14" }, "bin": { "vite": "bin/vite.js" }, "engines": { - "node": "^18.0.0 || ^20.0.0 || >=22.0.0" + "node": "^20.19.0 || >=22.12.0" }, "funding": { "url": "https://github.com/vitejs/vite?sponsor=1" @@ -14041,14 +13930,14 @@ "fsevents": "~2.3.3" }, "peerDependencies": { - "@types/node": "^18.0.0 || ^20.0.0 || >=22.0.0", + "@types/node": "^20.19.0 || >=22.12.0", "jiti": ">=1.21.0", - "less": "*", + "less": "^4.0.0", "lightningcss": "^1.21.0", - "sass": "*", - "sass-embedded": "*", - "stylus": "*", - "sugarss": "*", + "sass": "^1.70.0", + "sass-embedded": "^1.70.0", + "stylus": ">=0.54.8", + "sugarss": "^5.0.0", "terser": "^5.16.0", "tsx": "^4.8.1", "yaml": "^2.4.2" @@ -14090,17 +13979,17 @@ } }, "node_modules/vite-node": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/vite-node/-/vite-node-3.1.3.tgz", - "integrity": "sha512-uHV4plJ2IxCl4u1up1FQRrqclylKAogbtBfOTwcuJ28xFi+89PZ57BRh+naIRvH70HPwxy5QHYzg1OrEaC7AbA==", + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/vite-node/-/vite-node-3.2.4.tgz", + "integrity": "sha512-EbKSKh+bh1E1IFxeO0pg1n4dvoOTt0UDiXMd/qn++r98+jPO1xtJilvXldeuQ8giIB5IkpjCgMleHMNEsGH6pg==", "dev": true, "license": "MIT", "dependencies": { "cac": "^6.7.14", - "debug": "^4.4.0", + "debug": "^4.4.1", "es-module-lexer": "^1.7.0", "pathe": "^2.0.3", - "vite": "^5.0.0 || ^6.0.0" + "vite": "^5.0.0 || ^6.0.0 || ^7.0.0-0" }, "bin": { "vite-node": "vite-node.mjs" @@ -14113,32 +14002,34 @@ } }, "node_modules/vitest": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/vitest/-/vitest-3.1.3.tgz", - "integrity": "sha512-188iM4hAHQ0km23TN/adso1q5hhwKqUpv+Sd6p5sOuh6FhQnRNW3IsiIpvxqahtBabsJ2SLZgmGSpcYK4wQYJw==", + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/vitest/-/vitest-3.2.4.tgz", + "integrity": "sha512-LUCP5ev3GURDysTWiP47wRRUpLKMOfPh+yKTx3kVIEiu5KOMeqzpnYNsKyOoVrULivR8tLcks4+lga33Whn90A==", "dev": true, "license": "MIT", "dependencies": { - "@vitest/expect": "3.1.3", - "@vitest/mocker": "3.1.3", - "@vitest/pretty-format": "^3.1.3", - "@vitest/runner": "3.1.3", - "@vitest/snapshot": "3.1.3", - "@vitest/spy": "3.1.3", - "@vitest/utils": "3.1.3", + "@types/chai": "^5.2.2", + "@vitest/expect": "3.2.4", + "@vitest/mocker": "3.2.4", + "@vitest/pretty-format": "^3.2.4", + "@vitest/runner": "3.2.4", + "@vitest/snapshot": "3.2.4", + "@vitest/spy": "3.2.4", + "@vitest/utils": "3.2.4", "chai": "^5.2.0", - "debug": "^4.4.0", + "debug": "^4.4.1", "expect-type": "^1.2.1", "magic-string": "^0.30.17", "pathe": "^2.0.3", + "picomatch": "^4.0.2", "std-env": "^3.9.0", "tinybench": "^2.9.0", "tinyexec": "^0.3.2", - "tinyglobby": "^0.2.13", - "tinypool": "^1.0.2", + "tinyglobby": "^0.2.14", + "tinypool": "^1.1.1", "tinyrainbow": "^2.0.0", - "vite": "^5.0.0 || ^6.0.0", - "vite-node": "3.1.3", + "vite": "^5.0.0 || ^6.0.0 || ^7.0.0-0", + "vite-node": "3.2.4", "why-is-node-running": "^2.3.0" }, "bin": { @@ -14154,8 +14045,8 @@ "@edge-runtime/vm": "*", "@types/debug": "^4.1.12", "@types/node": "^18.0.0 || ^20.0.0 || >=22.0.0", - "@vitest/browser": "3.1.3", - "@vitest/ui": "3.1.3", + "@vitest/browser": "3.2.4", + "@vitest/ui": "3.2.4", "happy-dom": "*", "jsdom": "*" }, @@ -14656,9 +14547,9 @@ } }, "node_modules/zod": { - "version": "3.24.2", - "resolved": "https://registry.npmjs.org/zod/-/zod-3.24.2.tgz", - "integrity": "sha512-lY7CDW43ECgW9u1TcT3IoXHflywfVqDYze4waEz812jR/bZ8FHDsl7pFQoSZTz5N+2NqRXs8GBwnAwo3ZNxqhQ==", + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/zod/-/zod-4.0.8.tgz", + "integrity": "sha512-+MSh9cZU9r3QKlHqrgHMTSr3QwMGv4PLfR0M4N/sYWV5/x67HgXEhIGObdBkpnX8G78pTgWnIrBL2lZcNJOtfg==", "license": "MIT", "funding": { "url": "https://github.com/sponsors/colinhacks" diff --git a/package.json b/package.json index 62078b9a..6c263ed1 100644 --- a/package.json +++ b/package.json @@ -45,11 +45,11 @@ "@types/node": "22.13.14", "@typescript-eslint/eslint-plugin": "8.28.0", "@typescript-eslint/parser": "8.28.0", - "@vitest/coverage-v8": "3.1.3", + "@vitest/coverage-v8": "3.2.4", "better-npm-audit": "3.11.0", "conventional-changelog-conventionalcommits": "^8.0.0", "copyfiles": "2.4.1", - "eslint": "9.23.0", + "eslint": "9.31.0", "eslint-config-prettier": "^10.1.1", "eslint-plugin-prettier": "^5.2.5", "glob": "^11.0.1", @@ -64,11 +64,11 @@ "typedoc-plugin-markdown": "^4.6.0", "typescript": "^5.8.2", "upath": "^2.0.1", - "vitest": "3.1.3" + "vitest": "3.2.4" }, "dependencies": { - "@algorandfoundation/algorand-typescript": "1.0.0-alpha.61", - "@algorandfoundation/puya-ts": "1.0.0-alpha.61", + "@algorandfoundation/algorand-typescript": "1.0.0-alpha.64", + "@algorandfoundation/puya-ts": "1.0.0-alpha.64", "elliptic": "^6.6.1", "js-sha256": "^0.11.0", "js-sha3": "^0.9.3", @@ -77,7 +77,6 @@ }, "overrides": { "cross-spawn": "7.0.6", - "npm": "10.9.1", "esbuild": "0.25.0" } } diff --git a/src/impl/encoded-types/encoded-types.ts b/src/impl/encoded-types/encoded-types.ts index bdc4b820..b22ff87c 100644 --- a/src/impl/encoded-types/encoded-types.ts +++ b/src/impl/encoded-types/encoded-types.ts @@ -13,8 +13,8 @@ import { Str, Struct, Tuple, - UFixedNxM, - UintN, + UFixed, + Uint, } from '@algorandfoundation/algorand-typescript/arc4' import { encodingUtil } from '@algorandfoundation/puya-ts' import assert from 'assert' @@ -74,11 +74,11 @@ import type { StaticArrayGenericArgs, StructConstraint, TypeInfo, - uFixedNxMGenericArgs, + uFixedGenericArgs, } from './types' import { getMaxLengthOfStaticContentType } from './utils' -export class UintNImpl extends UintN { +export class UintImpl extends Uint { private value: Uint8Array private bitSize: N typeInfo: TypeInfo @@ -86,7 +86,7 @@ export class UintNImpl extends UintN { constructor(typeInfo: TypeInfo | string, v?: CompatForArc4Int) { super() this.typeInfo = typeof typeInfo === 'string' ? JSON.parse(typeInfo) : typeInfo - this.bitSize = UintNImpl.getMaxBitsLength(this.typeInfo) as N + this.bitSize = UintImpl.getMaxBitsLength(this.typeInfo) as N assert(validBitSizes.includes(this.bitSize), `Invalid bit size ${this.bitSize}`) @@ -99,7 +99,7 @@ export class UintNImpl extends UintN { get native() { const bigIntValue = encodingUtil.uint8ArrayToBigInt(this.value) - return (this.bitSize <= UINT64_SIZE ? asUint64(bigIntValue) : asBigUint(bigIntValue)) as UintN['native'] + return (this.bitSize <= UINT64_SIZE ? asUint64(bigIntValue) : asBigUint(bigIntValue)) as Uint['native'] } get bytes(): bytes { @@ -107,7 +107,7 @@ export class UintNImpl extends UintN { } equals(other: this): boolean { - if (!(other instanceof UintNImpl) || JSON.stringify(this.typeInfo) !== JSON.stringify(other.typeInfo)) { + if (!(other instanceof UintImpl) || JSON.stringify(this.typeInfo) !== JSON.stringify(other.typeInfo)) { throw new CodeError(`Expected expression of type ${this.typeInfo.name}, got ${other.typeInfo.name}`) } return this.bytes.equals(other.bytes) @@ -117,13 +117,13 @@ export class UintNImpl extends UintN { value: StubBytesCompat | Uint8Array, typeInfo: string | TypeInfo, prefix: 'none' | 'log' = 'none', - ): UintNImpl { + ): UintImpl { let bytesValue = asBytesCls(value) if (prefix === 'log') { assert(bytesValue.slice(0, 4).equals(ABI_RETURN_VALUE_LOG_PREFIX), 'ABI return prefix not found') bytesValue = bytesValue.slice(4) } - const result = new UintNImpl(typeInfo) + const result = new UintImpl(typeInfo) result.value = asUint8Array(bytesValue) return result } @@ -133,7 +133,7 @@ export class UintNImpl extends UintN { } } -export class UFixedNxMImpl extends UFixedNxM { +export class UFixedImpl extends UFixed { private value: Uint8Array private bitSize: N private precision: M @@ -142,8 +142,8 @@ export class UFixedNxMImpl extends UFixedNx constructor(typeInfo: TypeInfo | string, v?: `${number}.${number}`) { super(v) this.typeInfo = typeof typeInfo === 'string' ? JSON.parse(typeInfo) : typeInfo - const genericArgs = this.typeInfo.genericArgs as uFixedNxMGenericArgs - this.bitSize = UFixedNxMImpl.getMaxBitsLength(this.typeInfo) as N + const genericArgs = this.typeInfo.genericArgs as uFixedGenericArgs + this.bitSize = UFixedImpl.getMaxBitsLength(this.typeInfo) as N this.precision = parseInt(genericArgs.m.name, 10) as M const trimmedValue = trimTrailingDecimalZeros(v ?? '0.0') @@ -158,7 +158,7 @@ export class UFixedNxMImpl extends UFixedNx get native() { const bigIntValue = encodingUtil.uint8ArrayToBigInt(this.value) - return (this.bitSize <= UINT64_SIZE ? asUint64(bigIntValue) : asBigUint(bigIntValue)) as UFixedNxM['native'] + return (this.bitSize <= UINT64_SIZE ? asUint64(bigIntValue) : asBigUint(bigIntValue)) as UFixed['native'] } get bytes(): bytes { @@ -166,7 +166,7 @@ export class UFixedNxMImpl extends UFixedNx } equals(other: this): boolean { - if (!(other instanceof UFixedNxMImpl) || JSON.stringify(this.typeInfo) !== JSON.stringify(other.typeInfo)) { + if (!(other instanceof UFixedImpl) || JSON.stringify(this.typeInfo) !== JSON.stringify(other.typeInfo)) { throw new CodeError(`Expected expression of type ${this.typeInfo.name}, got ${other.typeInfo.name}`) } return this.bytes.equals(other.bytes) @@ -176,31 +176,31 @@ export class UFixedNxMImpl extends UFixedNx value: StubBytesCompat | Uint8Array, typeInfo: string | TypeInfo, prefix: 'none' | 'log' = 'none', - ): UFixedNxM { + ): UFixed { let bytesValue = asBytesCls(value) if (prefix === 'log') { assert(bytesValue.slice(0, 4).equals(ABI_RETURN_VALUE_LOG_PREFIX), 'ABI return prefix not found') bytesValue = bytesValue.slice(4) } - const result = new UFixedNxMImpl(typeInfo, '0.0') + const result = new UFixedImpl(typeInfo, '0.0') result.value = asUint8Array(bytesValue) return result } static getMaxBitsLength(typeInfo: TypeInfo): BitSize { - const genericArgs = typeInfo.genericArgs as uFixedNxMGenericArgs + const genericArgs = typeInfo.genericArgs as uFixedGenericArgs return parseInt(genericArgs.n.name, 10) as BitSize } } export class ByteImpl extends Byte { typeInfo: TypeInfo - private value: UintNImpl<8> + private value: UintImpl<8> constructor(typeInfo: TypeInfo | string, v?: CompatForArc4Int<8>) { super(v) this.typeInfo = typeof typeInfo === 'string' ? JSON.parse(typeInfo) : typeInfo - this.value = new UintNImpl<8>(typeInfo, v) + this.value = new UintImpl<8>(typeInfo, v) } get native() { @@ -219,14 +219,14 @@ export class ByteImpl extends Byte { } static fromBytesImpl(value: StubBytesCompat | Uint8Array, typeInfo: string | TypeInfo, prefix: 'none' | 'log' = 'none'): ByteImpl { - const uintNValue = UintNImpl.fromBytesImpl(value, typeInfo, prefix) as UintNImpl<8> + const uintNValue = UintImpl.fromBytesImpl(value, typeInfo, prefix) as UintImpl<8> const result = new ByteImpl(typeInfo) result.value = uintNValue return result } static getMaxBitsLength(typeInfo: TypeInfo): BitSize { - return UintNImpl.getMaxBitsLength(typeInfo) + return UintImpl.getMaxBitsLength(typeInfo) } } @@ -1127,27 +1127,27 @@ export const getArc4Encoded = (value: DeliberateAny, sourceTypeInfoString?: stri } if (value instanceof AccountCls) { const index = (lazyContext.activeGroup.activeTransaction as ApplicationCallTransaction).apat.indexOf(value) - return new UintNImpl({ name: 'UintN<64>', genericArgs: [{ name: '64' }] }, asBigInt(index)) + return new UintImpl({ name: 'Uint<64>', genericArgs: [{ name: '64' }] }, asBigInt(index)) } if (value instanceof AssetCls) { const index = (lazyContext.activeGroup.activeTransaction as ApplicationCallTransaction).apas.indexOf(value) - return new UintNImpl({ name: 'UintN<64>', genericArgs: [{ name: '64' }] }, asBigInt(index)) + return new UintImpl({ name: 'Uint<64>', genericArgs: [{ name: '64' }] }, asBigInt(index)) } if (value instanceof ApplicationCls) { const index = (lazyContext.activeGroup.activeTransaction as ApplicationCallTransaction).apfa.indexOf(value) - return new UintNImpl({ name: 'UintN<64>', genericArgs: [{ name: '64' }] }, asBigInt(index)) + return new UintImpl({ name: 'Uint<64>', genericArgs: [{ name: '64' }] }, asBigInt(index)) } if (typeof value === 'boolean') { return new BoolImpl({ name: 'Bool' }, value) } if (value instanceof Uint64Cls || typeof value === 'number') { - return new UintNImpl({ name: 'UintN<64>', genericArgs: [{ name: '64' }] }, asBigInt(value)) + return new UintImpl({ name: 'Uint<64>', genericArgs: [{ name: '64' }] }, asBigInt(value)) } if (value instanceof BigUintCls) { - return new UintNImpl({ name: 'UintN<512>', genericArgs: [{ name: '512' }] }, value.asBigInt()) + return new UintImpl({ name: 'Uint<512>', genericArgs: [{ name: '512' }] }, value.asBigInt()) } if (typeof value === 'bigint') { - return new UintNImpl({ name: 'UintN<512>', genericArgs: [{ name: '512' }] }, value) + return new UintImpl({ name: 'Uint<512>', genericArgs: [{ name: '512' }] }, value) } if (value instanceof BytesCls) { return new DynamicBytesImpl( @@ -1291,8 +1291,8 @@ export const getEncoder = (typeInfo: TypeInfo): fromBytes => { Bool: BoolImpl.fromBytesImpl, Byte: ByteImpl.fromBytesImpl, Str: StrImpl.fromBytesImpl, - 'UintN<.*>': UintNImpl.fromBytesImpl, - 'UFixedNxM<.*>': UFixedNxMImpl.fromBytesImpl, + 'Uint<.*>': UintImpl.fromBytesImpl, + 'UFixed<.*>': UFixedImpl.fromBytesImpl, 'StaticArray<.*>': StaticArrayImpl.fromBytesImpl, 'DynamicArray<.*>': DynamicArrayImpl.fromBytesImpl, 'Tuple(<.*>)?': TupleImpl.fromBytesImpl, diff --git a/src/impl/encoded-types/index.ts b/src/impl/encoded-types/index.ts index fdfd7460..9dce036a 100644 --- a/src/impl/encoded-types/index.ts +++ b/src/impl/encoded-types/index.ts @@ -11,9 +11,9 @@ export { DynamicArrayImpl, DynamicBytesImpl, encodeArc4Impl, + FixedArrayImpl, getArc4Encoded, getEncoder, - FixedArrayImpl, ReferenceArrayImpl, StaticArrayImpl, StaticBytesImpl, @@ -21,8 +21,8 @@ export { StructImpl, toBytes, TupleImpl, - UFixedNxMImpl, - UintNImpl, + UFixedImpl, + UintImpl, } from './encoded-types' export { TypeInfo } from './types' export { arc4EncodedLengthImpl, getArc4TypeName, getMaxLengthOfStaticContentType, minLengthForType } from './utils' diff --git a/src/impl/encoded-types/types.ts b/src/impl/encoded-types/types.ts index e374d39a..bee383b5 100644 --- a/src/impl/encoded-types/types.ts +++ b/src/impl/encoded-types/types.ts @@ -4,7 +4,7 @@ import type { ARC4Encoded, BitSize } from '@algorandfoundation/algorand-typescri import type { StubBytesCompat } from '../primitives' export type CompatForArc4Int = N extends 8 | 16 | 24 | 32 | 40 | 48 | 56 | 64 ? Uint64Compat : BigUintCompat -export type uFixedNxMGenericArgs = { n: TypeInfo; m: TypeInfo } +export type uFixedGenericArgs = { n: TypeInfo; m: TypeInfo } export type StaticArrayGenericArgs = { elementType: TypeInfo; size: TypeInfo } export type DynamicArrayGenericArgs = { elementType: TypeInfo } export type StructConstraint = Record diff --git a/src/impl/encoded-types/utils.ts b/src/impl/encoded-types/utils.ts index ef4b076e..d07c9b79 100644 --- a/src/impl/encoded-types/utils.ts +++ b/src/impl/encoded-types/utils.ts @@ -2,7 +2,7 @@ import type { uint64 } from '@algorandfoundation/algorand-typescript' import { BITS_IN_BYTE, UINT512_SIZE, UINT64_SIZE } from '../../constants' import { CodeError } from '../../errors' import { findBoolTypes, trimGenericTypeName } from './helpers' -import type { DynamicArrayGenericArgs, StaticArrayGenericArgs, TypeInfo, uFixedNxMGenericArgs } from './types' +import type { DynamicArrayGenericArgs, StaticArrayGenericArgs, TypeInfo, uFixedGenericArgs } from './types' export const getMaxLengthOfStaticContentType = (type: TypeInfo, asArc4Encoded: boolean = true): number => { const getMaxBytesLengthForStaticArray = (typeInfo: { genericArgs: StaticArrayGenericArgs }) => { @@ -54,10 +54,10 @@ export const getMaxLengthOfStaticContentType = (type: TypeInfo, asArc4Encoded: b case 'Bool': return 1 case 'Byte': - case 'UintN': + case 'Uint': return parseInt((type.genericArgs as TypeInfo[])![0].name, 10) / BITS_IN_BYTE - case 'UFixedNxM': - return parseInt((type.genericArgs as uFixedNxMGenericArgs).n.name, 10) / BITS_IN_BYTE + case 'UFixed': + return parseInt((type.genericArgs as uFixedGenericArgs).n.name, 10) / BITS_IN_BYTE case 'Address': case 'StaticBytes': case 'StaticArray': @@ -105,9 +105,9 @@ export const getArc4TypeName = (typeInfo: TypeInfo): string | undefined => { Bool: 'bool', Byte: 'byte', Str: 'string', - 'UintN<.*>': (t: TypeInfo) => `uint${getMaxLengthOfStaticContentType(t) * BITS_IN_BYTE}`, - 'UFixedNxM<.*>': (t: TypeInfo) => { - const genericArgs = t.genericArgs as uFixedNxMGenericArgs + 'Uint<.*>': (t: TypeInfo) => `uint${getMaxLengthOfStaticContentType(t) * BITS_IN_BYTE}`, + 'UFixed<.*>': (t: TypeInfo) => { + const genericArgs = t.genericArgs as uFixedGenericArgs return `ufixed${genericArgs.n.name}x${genericArgs.m.name}` }, '(StaticArray|FixedArray)(<.*>)?': (t: TypeInfo) => { diff --git a/src/subcontexts/contract-context.ts b/src/subcontexts/contract-context.ts index 48725393..c1280571 100644 --- a/src/subcontexts/contract-context.ts +++ b/src/subcontexts/contract-context.ts @@ -15,7 +15,7 @@ import { lazyContext } from '../context-helpers/internal-context' import { CodeError } from '../errors' import { BaseContract, ContractOptionsSymbol } from '../impl/base-contract' import { Contract } from '../impl/contract' -import { getArc4Encoded, UintNImpl, type TypeInfo } from '../impl/encoded-types' +import { getArc4Encoded, UintImpl, type TypeInfo } from '../impl/encoded-types' import { Bytes } from '../impl/primitives' import { AccountCls, ApplicationCls, AssetCls } from '../impl/reference' import { BoxCls, BoxMapCls, BoxRefCls, GlobalStateCls } from '../impl/state' @@ -87,7 +87,7 @@ const extractStates = (contract: BaseContract, contractOptions: ContractOptionsP return states } -const getUintN8Impl = (value: number) => new UintNImpl({ name: 'UintN<8>', genericArgs: [{ name: '8' }] }, value) +const getUint8Impl = (value: number) => new UintImpl({ name: 'Uint<8>', genericArgs: [{ name: '8' }] }, value) /** @ignore */ export const extractArraysFromArgs = (app: Application, methodSelector: Uint8Array, args: DeliberateAny[]) => { @@ -101,13 +101,13 @@ export const extractArraysFromArgs = (app: Application, methodSelector: Uint8Arr if (isTransaction(arg)) { transactions.push(arg) } else if (arg instanceof AccountCls) { - appArgs.push(getUintN8Impl(accounts.length)) + appArgs.push(getUint8Impl(accounts.length)) accounts.push(arg as Account) } else if (arg instanceof ApplicationCls) { - appArgs.push(getUintN8Impl(apps.length)) + appArgs.push(getUint8Impl(apps.length)) apps.push(arg as Application) } else if (arg instanceof AssetCls) { - appArgs.push(getUintN8Impl(assets.length)) + appArgs.push(getUint8Impl(assets.length)) assets.push(arg as Asset) } else if (arg !== undefined) { appArgs.push(getArc4Encoded(arg)) diff --git a/src/test-transformer/visitors.ts b/src/test-transformer/visitors.ts index 9bbe498a..f22c20b1 100644 --- a/src/test-transformer/visitors.ts +++ b/src/test-transformer/visitors.ts @@ -95,7 +95,7 @@ export class SourceFileVisitor { } // capture generic type info for variable initialising outside class and function declarations - // e.g. `const x = new UintN<32>(42) + // e.g. `const x = new Uint<32>(42) if (ts.isVariableDeclaration(node) && node.initializer) { return new VariableInitializerVisitor(this.context, this.helper, node).result() } @@ -278,8 +278,8 @@ class FunctionOrMethodVisitor { * capture generic type info in test functions and swap arc4 types with implementation; e.g. * ``` * it('should work', () => { - * expect(() => new UintN<32>(2 ** 32)).toThrowError(`expected value <= ${2 ** 32 - 1}`) - * expect(UintN.fromBytes>('').bytes).toEqual(Bytes()) + * expect(() => new Uint<32>(2 ** 32)).toThrowError(`expected value <= ${2 ** 32 - 1}`) + * expect(Uint.fromBytes>('').bytes).toEqual(Bytes()) * }) * ``` */ diff --git a/src/value-generators/arc4.ts b/src/value-generators/arc4.ts index 2eeb2767..7ede41b0 100644 --- a/src/value-generators/arc4.ts +++ b/src/value-generators/arc4.ts @@ -1,6 +1,6 @@ import type { arc4 } from '@algorandfoundation/algorand-typescript' import { BITS_IN_BYTE, MAX_UINT128, MAX_UINT16, MAX_UINT256, MAX_UINT32, MAX_UINT512, MAX_UINT64, MAX_UINT8 } from '../constants' -import { AddressImpl, DynamicBytesImpl, StrImpl, UintNImpl } from '../impl/encoded-types' +import { AddressImpl, DynamicBytesImpl, StrImpl, UintImpl } from '../impl/encoded-types' import { getRandomBigInt, getRandomBytes } from '../util' import { AvmValueGenerator } from './avm' @@ -19,73 +19,73 @@ export class Arc4ValueGenerator { } /** - * Generate a random UintN8 within the specified range. + * Generate a random Uint8 within the specified range. * @param minValue: Minimum value (inclusive). Defaults to 0. * @param maxValue: Maximum value (inclusive). Defaults to 2 ** 8 - 1. - * @returns: A random UintN8 value. + * @returns: A random Uint8 value. * */ - uintN8(minValue: number | bigint = 0, maxValue: number | bigint = MAX_UINT8): arc4.UintN8 { - return new UintNImpl({ name: 'UintN', genericArgs: [{ name: '8' }] }, getRandomBigInt(minValue, maxValue)) as arc4.UintN8 + uint8(minValue: number | bigint = 0, maxValue: number | bigint = MAX_UINT8): arc4.Uint8 { + return new UintImpl({ name: 'Uint', genericArgs: [{ name: '8' }] }, getRandomBigInt(minValue, maxValue)) as arc4.Uint8 } /** - * Generate a random UintN16 within the specified range. + * Generate a random Uint16 within the specified range. * @param minValue: Minimum value (inclusive). Defaults to 0. * @param maxValue: Maximum value (inclusive). Defaults to 2 ** 16 - 1. - * @returns: A random UintN16 value. + * @returns: A random Uint16 value. * */ - uintN16(minValue: number | bigint = 0, maxValue: number | bigint = MAX_UINT16): arc4.UintN16 { - return new UintNImpl({ name: 'UintN', genericArgs: [{ name: '16' }] }, getRandomBigInt(minValue, maxValue)) as arc4.UintN16 + uint16(minValue: number | bigint = 0, maxValue: number | bigint = MAX_UINT16): arc4.Uint16 { + return new UintImpl({ name: 'Uint', genericArgs: [{ name: '16' }] }, getRandomBigInt(minValue, maxValue)) as arc4.Uint16 } /** - * Generate a random UintN32 within the specified range. + * Generate a random Uint32 within the specified range. * @param minValue: Minimum value (inclusive). Defaults to 0. * @param maxValue: Maximum value (inclusive). Defaults to 2 ** 32 - 1. - * @returns: A random UintN32 value. + * @returns: A random Uint32 value. * */ - uintN32(minValue: number | bigint = 0, maxValue: number | bigint = MAX_UINT32): arc4.UintN32 { - return new UintNImpl({ name: 'UintN', genericArgs: [{ name: '32' }] }, getRandomBigInt(minValue, maxValue)) as arc4.UintN32 + uint32(minValue: number | bigint = 0, maxValue: number | bigint = MAX_UINT32): arc4.Uint32 { + return new UintImpl({ name: 'Uint', genericArgs: [{ name: '32' }] }, getRandomBigInt(minValue, maxValue)) as arc4.Uint32 } /** - * Generate a random UintN64 within the specified range. + * Generate a random Uint64 within the specified range. * @param minValue: Minimum value (inclusive). Defaults to 0. * @param maxValue: Maximum value (inclusive). Defaults to 2n ** 64n - 1n. - * @returns: A random UintN64 value. + * @returns: A random Uint64 value. * */ - uintN64(minValue: number | bigint = 0, maxValue: number | bigint = MAX_UINT64): arc4.UintN64 { - return new UintNImpl({ name: 'UintN', genericArgs: [{ name: '64' }] }, getRandomBigInt(minValue, maxValue)) as arc4.UintN64 + uint64(minValue: number | bigint = 0, maxValue: number | bigint = MAX_UINT64): arc4.Uint64 { + return new UintImpl({ name: 'Uint', genericArgs: [{ name: '64' }] }, getRandomBigInt(minValue, maxValue)) as arc4.Uint64 } /** - * Generate a random UintN128 within the specified range. + * Generate a random Uint128 within the specified range. * @param minValue: Minimum value (inclusive). Defaults to 0. * @param maxValue: Maximum value (inclusive). Defaults to 2n ** 128n - 1n. - * @returns: A random UintN128 value. + * @returns: A random Uint128 value. * */ - uintN128(minValue: number | bigint = 0, maxValue: number | bigint = MAX_UINT128): arc4.UintN128 { - return new UintNImpl({ name: 'UintN', genericArgs: [{ name: '128' }] }, getRandomBigInt(minValue, maxValue)) as arc4.UintN128 + uint128(minValue: number | bigint = 0, maxValue: number | bigint = MAX_UINT128): arc4.Uint128 { + return new UintImpl({ name: 'Uint', genericArgs: [{ name: '128' }] }, getRandomBigInt(minValue, maxValue)) as arc4.Uint128 } /** - * Generate a random UintN256 within the specified range. + * Generate a random Uint256 within the specified range. * @param minValue: Minimum value (inclusive). Defaults to 0. * @param maxValue: Maximum value (inclusive). Defaults to 2n ** 256n - 1n. - * @returns: A random UintN256 value. + * @returns: A random Uint256 value. * */ - uintN256(minValue: number | bigint = 0, maxValue: number | bigint = MAX_UINT256): arc4.UintN256 { - return new UintNImpl({ name: 'UintN', genericArgs: [{ name: '256' }] }, getRandomBigInt(minValue, maxValue)) as arc4.UintN256 + uint256(minValue: number | bigint = 0, maxValue: number | bigint = MAX_UINT256): arc4.Uint256 { + return new UintImpl({ name: 'Uint', genericArgs: [{ name: '256' }] }, getRandomBigInt(minValue, maxValue)) as arc4.Uint256 } /** - * Generate a random UintN512 within the specified range. + * Generate a random Uint512 within the specified range. * @param minValue: Minimum value (inclusive). Defaults to 0. * @param maxValue: Maximum value (inclusive). Defaults to 2n ** 512n - 1n. - * @returns: A random UintN512 value. + * @returns: A random Uint512 value. * */ - uintN512(minValue: number | bigint = 0, maxValue: number | bigint = MAX_UINT512): arc4.UintN<512> { - return new UintNImpl({ name: 'UintN', genericArgs: [{ name: '512' }] }, getRandomBigInt(minValue, maxValue)) as arc4.UintN<512> + uint512(minValue: number | bigint = 0, maxValue: number | bigint = MAX_UINT512): arc4.Uint<512> { + return new UintImpl({ name: 'Uint', genericArgs: [{ name: '512' }] }, getRandomBigInt(minValue, maxValue)) as arc4.Uint<512> } /** diff --git a/tests/arc4/dynamic-array.spec.ts b/tests/arc4/dynamic-array.spec.ts index 54e93311..4b139514 100644 --- a/tests/arc4/dynamic-array.spec.ts +++ b/tests/arc4/dynamic-array.spec.ts @@ -10,8 +10,8 @@ import { Str, Struct, Tuple, - UFixedNxM, - UintN, + UFixed, + Uint, } from '@algorandfoundation/algorand-typescript/arc4' import { encodingUtil } from '@algorandfoundation/puya-ts' import { afterEach, describe, expect, it, test } from 'vitest' @@ -85,13 +85,13 @@ const uint256DynamicArray = { return [0n, 1n, 2n, 3n, 2n ** 8n, 2n ** 16n, 2n ** 32n, 2n ** 64n, 2n ** 128n, 2n ** 256n - 1n] }, abiValues() { - return this.nativeValues().map((v) => new UintN<256>(v)) + return this.nativeValues().map((v) => new Uint<256>(v)) }, array(isEmpty = false) { - return isEmpty ? new DynamicArray>() : new DynamicArray>(...this.abiValues()) + return isEmpty ? new DynamicArray>() : new DynamicArray>(...this.abiValues()) }, create(value: StubBytesCompat) { - return interpretAsArc4>>(asBytes(value)) + return interpretAsArc4>>(asBytes(value)) }, concat() { return this.array().concat(this.array()) @@ -99,34 +99,34 @@ const uint256DynamicArray = { concatABIValue() { return getABIEncodedValue([...this.nativeValues(), ...this.nativeValues()], this.abiTypeString, {}) }, - clone(array: DynamicArray>) { + clone(array: DynamicArray>) { return clone(array) }, } -const ufixednxmDynamicArray = { +const ufixedDynamicArray = { abiTypeString: 'ufixed256x16[]', nativeValues() { return this.abiValues().map((v) => v.native.valueOf()) }, abiValues() { return [ - new UFixedNxM<256, 16>('0.0'), - new UFixedNxM<256, 16>('1.0'), - new UFixedNxM<256, 16>('2.0'), - new UFixedNxM<256, 16>('3.0'), - new UFixedNxM<256, 16>('255.0'), - new UFixedNxM<256, 16>('65536.0'), - new UFixedNxM<256, 16>('4294967295.0'), - new UFixedNxM<256, 16>('1844.6744073709551616'), - new UFixedNxM<256, 16>('340282366920938463463374.607431768211456'), - new UFixedNxM<256, 16>('11579208923731619542357098500868790785326998466564056403945758.4007913129639935'), + new UFixed<256, 16>('0.0'), + new UFixed<256, 16>('1.0'), + new UFixed<256, 16>('2.0'), + new UFixed<256, 16>('3.0'), + new UFixed<256, 16>('255.0'), + new UFixed<256, 16>('65536.0'), + new UFixed<256, 16>('4294967295.0'), + new UFixed<256, 16>('1844.6744073709551616'), + new UFixed<256, 16>('340282366920938463463374.607431768211456'), + new UFixed<256, 16>('11579208923731619542357098500868790785326998466564056403945758.4007913129639935'), ] }, array(isEmpty = false) { - return isEmpty ? new DynamicArray>() : new DynamicArray>(...this.abiValues()) + return isEmpty ? new DynamicArray>() : new DynamicArray>(...this.abiValues()) }, create(value: StubBytesCompat) { - return interpretAsArc4>>(asBytes(value)) + return interpretAsArc4>>(asBytes(value)) }, concat() { return this.array().concat(this.array()) @@ -134,7 +134,7 @@ const ufixednxmDynamicArray = { concatABIValue() { return getABIEncodedValue([...this.nativeValues(), ...this.nativeValues()], this.abiTypeString, {}) }, - clone(array: DynamicArray>) { + clone(array: DynamicArray>) { return clone(array) }, } @@ -231,15 +231,15 @@ const uint256DynamicArrayOfArray = { return [uint256DynamicArray.nativeValues(), uint256DynamicArray.nativeValues().reverse()] }, abiValues() { - return this.nativeValues().map((a) => new DynamicArray>(...a.map((v) => new UintN<256>(v)))) + return this.nativeValues().map((a) => new DynamicArray>(...a.map((v) => new Uint<256>(v)))) }, array(isEmpty = false) { return isEmpty - ? new DynamicArray>>() - : new DynamicArray>>(...this.abiValues().map((a) => new DynamicArray>(...a))) + ? new DynamicArray>>() + : new DynamicArray>>(...this.abiValues().map((a) => new DynamicArray>(...a))) }, create(value: StubBytesCompat) { - return interpretAsArc4>>>(asBytes(value)) + return interpretAsArc4>>>(asBytes(value)) }, concat() { return this.array().concat(this.array()) @@ -247,7 +247,7 @@ const uint256DynamicArrayOfArray = { concatABIValue() { return getABIEncodedValue([...this.nativeValues(), ...this.nativeValues()], this.abiTypeString, {}) }, - clone(array: DynamicArray>>) { + clone(array: DynamicArray>>) { return clone(array) }, } @@ -257,17 +257,17 @@ const uint256DynamicArrayOfStaticArray = { return [uint256DynamicArray.nativeValues(), uint256DynamicArray.nativeValues().reverse()] }, abiValues() { - return this.nativeValues().map((a) => new StaticArray, 10>(...(a.map((v) => new UintN<256>(v)) as []))) + return this.nativeValues().map((a) => new StaticArray, 10>(...(a.map((v) => new Uint<256>(v)) as []))) }, array(isEmpty = false) { return isEmpty - ? new DynamicArray, 10>>() - : new DynamicArray, 10>>( - ...this.abiValues().map((a) => new StaticArray, 10>(...(a as DeliberateAny))), + ? new DynamicArray, 10>>() + : new DynamicArray, 10>>( + ...this.abiValues().map((a) => new StaticArray, 10>(...(a as DeliberateAny))), ) }, create(value: StubBytesCompat) { - return interpretAsArc4, 10>>>(asBytes(value)) + return interpretAsArc4, 10>>>(asBytes(value)) }, concat() { return this.array().concat(this.array()) @@ -275,7 +275,7 @@ const uint256DynamicArrayOfStaticArray = { concatABIValue() { return getABIEncodedValue([...this.nativeValues(), ...this.nativeValues()], this.abiTypeString, {}) }, - clone(array: DynamicArray, 10>>) { + clone(array: DynamicArray, 10>>) { return clone(array) }, } @@ -356,30 +356,28 @@ const tupleDynamicArray = { new Tuple( ...[ new DynamicArray(...stringDynamicArray.abiValues().slice(0, 2)), - new Tuple<[DynamicArray, Str, UintN<256>, Address]>( + new Tuple<[DynamicArray, Str, Uint<256>, Address]>( new DynamicArray(...stringDynamicArray.abiValues().slice(6, 8)), stringDynamicArray.abiValues()[9], uint256DynamicArray.abiValues()[4], addressDynamicArray.abiValues()[5], ), boolDynamicArray.abiValues()[3], - new StaticArray, 3>(...(uint256DynamicArray.abiValues().slice(4, 7) as [])), + new StaticArray, 3>(...(uint256DynamicArray.abiValues().slice(4, 7) as [])), ], ), ) }, array(isEmpty = false) { return isEmpty - ? new DynamicArray< - Tuple<[DynamicArray, Tuple<[DynamicArray, Str, UintN<256>, Address]>, Bool, StaticArray, 3>]> - >() - : new DynamicArray< - Tuple<[DynamicArray, Tuple<[DynamicArray, Str, UintN<256>, Address]>, Bool, StaticArray, 3>]> - >(...this.abiValues()) + ? new DynamicArray, Tuple<[DynamicArray, Str, Uint<256>, Address]>, Bool, StaticArray, 3>]>>() + : new DynamicArray, Tuple<[DynamicArray, Str, Uint<256>, Address]>, Bool, StaticArray, 3>]>>( + ...this.abiValues(), + ) }, create(value: StubBytesCompat) { return interpretAsArc4< - DynamicArray, Tuple<[DynamicArray, Str, UintN<256>, Address]>, Bool, StaticArray, 3>]>> + DynamicArray, Tuple<[DynamicArray, Str, Uint<256>, Address]>, Bool, StaticArray, 3>]>> >(asBytes(value)) }, concat() { @@ -389,16 +387,16 @@ const tupleDynamicArray = { return getABIEncodedValue([...this.nativeValues(), ...this.nativeValues()], this.abiTypeString, {}) }, clone( - array: DynamicArray, Tuple<[DynamicArray, Str, UintN<256>, Address]>, Bool, StaticArray, 3>]>>, + array: DynamicArray, Tuple<[DynamicArray, Str, Uint<256>, Address]>, Bool, StaticArray, 3>]>>, ) { return clone(array) }, } class Swapped extends Struct<{ - b: UintN<256> + b: Uint<256> c: Bool d: Str - a: Tuple, DynamicArray, Str, UintN<256>, Bool, StaticArray, 3>]> + a: Tuple, DynamicArray, Str, Uint<256>, Bool, StaticArray, 3>]> }> {} const structDynamicArray = { abiTypeString: '(uint256,bool,string,(string[],string[],string,uint256,bool,uint256[3]))[]', @@ -429,7 +427,7 @@ const structDynamicArray = { stringDynamicArray.abiValues()[7], uint256DynamicArray.abiValues()[1], boolDynamicArray.abiValues()[2], - new StaticArray, 3>( + new StaticArray, 3>( uint256DynamicArray.abiValues()[2], uint256DynamicArray.abiValues()[3], uint256DynamicArray.abiValues()[4], @@ -458,7 +456,7 @@ const testDataArray = [ addressDynamicArray, boolDynamicArray, uint256DynamicArray, - ufixednxmDynamicArray, + ufixedDynamicArray, stringDynamicArray, addressDynamicArrayOfArray, boolDynamicArrayOfArray, diff --git a/tests/arc4/emit.spec.ts b/tests/arc4/emit.spec.ts index 0a4069c4..72a3711b 100644 --- a/tests/arc4/emit.spec.ts +++ b/tests/arc4/emit.spec.ts @@ -30,14 +30,14 @@ class Swapped { } } class SwappedArc4 extends arc4.Struct<{ - m: arc4.UintN<64> - n: arc4.UintN<256> - o: arc4.UFixedNxM<32, 8> - p: arc4.UFixedNxM<256, 16> + m: arc4.Uint<64> + n: arc4.Uint<256> + o: arc4.UFixed<32, 8> + p: arc4.UFixed<256, 16> q: arc4.Bool - r: arc4.StaticArray - s: arc4.DynamicArray - t: arc4.Tuple + r: arc4.StaticArray + s: arc4.DynamicArray + t: arc4.Tuple }> {} describe('arc4.emit', async () => { @@ -58,14 +58,14 @@ describe('arc4.emit', async () => { const test_data = new Swapped('hello', BigUint(MAX_UINT512), Uint64(MAX_UINT64), Bytes('world'), 16, false, Bytes('test'), 'greetings') const test_data_arc4 = new SwappedArc4({ - m: new arc4.UintN64(42), - n: new arc4.UintN256(512), - o: new arc4.UFixedNxM<32, 8>('42.94967295'), - p: new arc4.UFixedNxM<256, 16>('25.5'), + m: new arc4.Uint64(42), + n: new arc4.Uint256(512), + o: new arc4.UFixed<32, 8>('42.94967295'), + p: new arc4.UFixed<256, 16>('25.5'), q: new arc4.Bool(true), - r: new arc4.StaticArray(new arc4.UintN8(1), new arc4.UintN8(2), new arc4.UintN8(3)), - s: new arc4.DynamicArray(new arc4.UintN16(1), new arc4.UintN16(2), new arc4.UintN16(3)), - t: new arc4.Tuple(new arc4.UintN32(1), new arc4.UintN64(2), new arc4.Str('hello')), + r: new arc4.StaticArray(new arc4.Uint8(1), new arc4.Uint8(2), new arc4.Uint8(3)), + s: new arc4.DynamicArray(new arc4.Uint16(1), new arc4.Uint16(2), new arc4.Uint16(3)), + t: new arc4.Tuple(new arc4.Uint32(1), new arc4.Uint64(2), new arc4.Str('hello')), }) const avm_result = await getAvmResultLog( { appClient }, diff --git a/tests/arc4/encode-decode-arc4.spec.ts b/tests/arc4/encode-decode-arc4.spec.ts index 3d361337..c30d361d 100644 --- a/tests/arc4/encode-decode-arc4.spec.ts +++ b/tests/arc4/encode-decode-arc4.spec.ts @@ -1,6 +1,6 @@ import type { biguint, bytes, uint64 } from '@algorandfoundation/algorand-typescript' -import { assertMatch, Bytes } from '@algorandfoundation/algorand-typescript' -import type { StaticBytes, UFixedNxM } from '@algorandfoundation/algorand-typescript/arc4' +import { arc4, assertMatch, Bytes } from '@algorandfoundation/algorand-typescript' +import type { StaticBytes, UFixed, Uint64 } from '@algorandfoundation/algorand-typescript/arc4' import { Address, arc4EncodedLength, @@ -13,8 +13,7 @@ import { Str, Struct, Tuple, - UintN, - UintN64, + Uint, } from '@algorandfoundation/algorand-typescript/arc4' import { itob } from '@algorandfoundation/algorand-typescript/op' import { encodingUtil } from '@algorandfoundation/puya-ts' @@ -31,17 +30,17 @@ const nativeBool = true const nativeBytes = Bytes('hello') const abiString = new Str('hello') -const abiUint64 = new UintN<64>(42) -const abiUint512 = new UintN<512>(MAX_UINT128) +const abiUint64 = new Uint<64>(42) +const abiUint512 = new Uint<512>(MAX_UINT128) const abiBool = new Bool(true) const abiBytes = new DynamicBytes(Bytes('hello')) -type TestObj = { a: UintN64; b: DynamicBytes } +type TestObj = { a: Uint64; b: DynamicBytes } class Swapped1 extends Struct<{ - b: UintN<64> + b: Uint<64> c: Bool d: Str - a: Tuple<[UintN<64>, Bool, Bool]> + a: Tuple<[Uint<64>, Bool, Bool]> }> {} const testData = [ @@ -50,10 +49,10 @@ const testData = [ return [nativeNumber, nativeNumber, nativeBigInt, nativeBytes] as readonly [uint64, uint64, biguint, bytes] }, abiValues() { - return [abiUint64, abiUint64, abiUint512, abiBytes] as readonly [UintN<64>, UintN<64>, UintN<512>, DynamicBytes] + return [abiUint64, abiUint64, abiUint512, abiBytes] as readonly [Uint<64>, Uint<64>, Uint<512>, DynamicBytes] }, arc4Value() { - return new Tuple<[UintN<64>, UintN<64>, UintN<512>, DynamicBytes]>(abiUint64, abiUint64, abiUint512, abiBytes) + return new Tuple<[Uint<64>, Uint<64>, Uint<512>, DynamicBytes]>(abiUint64, abiUint64, abiUint512, abiBytes) }, encode() { return encodeArc4(this.nativeValues()) @@ -86,16 +85,16 @@ const testData = [ abiValues() { return [ new Tuple<[Bool, Tuple<[Str, Bool]>]>(abiBool, new Tuple<[Str, Bool]>(abiString, abiBool)), - new Tuple<[UintN<64>, UintN<64>]>(abiUint64, abiUint64), - new Tuple<[UintN<512>, DynamicBytes, Swapped1]>( + new Tuple<[Uint<64>, Uint<64>]>(abiUint64, abiUint64), + new Tuple<[Uint<512>, DynamicBytes, Swapped1]>( abiUint512, abiBytes, - new Swapped1({ b: abiUint64, c: abiBool, d: abiString, a: new Tuple<[UintN<64>, Bool, Bool]>(abiUint64, abiBool, abiBool) }), + new Swapped1({ b: abiUint64, c: abiBool, d: abiString, a: new Tuple<[Uint<64>, Bool, Bool]>(abiUint64, abiBool, abiBool) }), ), - ] as readonly [Tuple<[Bool, Tuple<[Str, Bool]>]>, Tuple<[UintN<64>, UintN<64>]>, Tuple<[UintN<512>, DynamicBytes, Swapped1]>] + ] as readonly [Tuple<[Bool, Tuple<[Str, Bool]>]>, Tuple<[Uint<64>, Uint<64>]>, Tuple<[Uint<512>, DynamicBytes, Swapped1]>] }, arc4Value() { - return new Tuple<[Tuple<[Bool, Tuple<[Str, Bool]>]>, Tuple<[UintN<64>, UintN<64>]>, Tuple<[UintN<512>, DynamicBytes, Swapped1]>]>( + return new Tuple<[Tuple<[Bool, Tuple<[Str, Bool]>]>, Tuple<[Uint<64>, Uint<64>]>, Tuple<[Uint<512>, DynamicBytes, Swapped1]>]>( ...this.abiValues(), ) }, @@ -131,7 +130,7 @@ const testData = [ } }, abiValues() { - return { b: abiUint64, c: abiBool, d: abiString, a: new Tuple<[UintN<64>, Bool, Bool]>(abiUint64, abiBool, abiBool) } + return { b: abiUint64, c: abiBool, d: abiString, a: new Tuple<[Uint<64>, Bool, Bool]>(abiUint64, abiBool, abiBool) } }, arc4Value() { return new Swapped1(this.abiValues()) @@ -168,7 +167,7 @@ describe('decodeArc4', () => { ...encodingUtil.utf8ToUint8Array('hello world'), ]), ) - const e = { a: new UintN64(50n), b: new DynamicBytes(asBytes(new Uint8Array([1, 2, 3, 4, 5]))) } + const e = { a: new arc4.Uint64(50n), b: new DynamicBytes(asBytes(new Uint8Array([1, 2, 3, 4, 5]))) } const eBytes = asBytes(new Uint8Array([...encodingUtil.bigIntToUint8Array(50n, 8), 0, 10, 0, 5, 1, 2, 3, 4, 5])) const f = new Address(Bytes.fromHex(`${'00'.repeat(31)}ff`)) const fBytes = Bytes.fromHex(`${'00'.repeat(31)}ff`) @@ -201,16 +200,16 @@ describe('encodeArc4', () => { const address = new Address(Bytes.fromHex(`${'00'.repeat(31)}ff`)) expect(encodeArc4(address)).toEqual(address.bytes) - expect(encodeArc4([nativeNumber])).toEqual(new StaticArray(new UintN64(nativeNumber)).bytes) + expect(encodeArc4([nativeNumber])).toEqual(new StaticArray(new arc4.Uint64(nativeNumber)).bytes) expect(encodeArc4([nativeBool])).toEqual(new StaticArray(new Bool(nativeBool)).bytes) - expect(encodeArc4([nativeBigInt])).toEqual(new StaticArray(new UintN<512>(nativeBigInt)).bytes) + expect(encodeArc4([nativeBigInt])).toEqual(new StaticArray(new Uint<512>(nativeBigInt)).bytes) expect(encodeArc4([nativeBytes])).toEqual(new StaticArray(new DynamicBytes(nativeBytes)).bytes) expect(encodeArc4([nativeString])).toEqual(new StaticArray(new Str(nativeString)).bytes) expect(encodeArc4([address])).toEqual(new StaticArray(address).bytes) - expect(encodeArc4([nativeNumber])).toEqual(new DynamicArray(new UintN64(nativeNumber)).bytes) + expect(encodeArc4([nativeNumber])).toEqual(new DynamicArray(new arc4.Uint64(nativeNumber)).bytes) expect(encodeArc4([nativeBool])).toEqual(new DynamicArray(new Bool(nativeBool)).bytes) - expect(encodeArc4([nativeBigInt])).toEqual(new DynamicArray(new UintN<512>(nativeBigInt)).bytes) + expect(encodeArc4([nativeBigInt])).toEqual(new DynamicArray(new Uint<512>(nativeBigInt)).bytes) expect(encodeArc4([nativeBytes])).toEqual(new DynamicArray(new DynamicBytes(nativeBytes)).bytes) expect(encodeArc4([nativeString])).toEqual(new DynamicArray(new Str(nativeString)).bytes) expect(encodeArc4([address])).toEqual(new DynamicArray(address).bytes) @@ -218,12 +217,12 @@ describe('encodeArc4', () => { }) class StaticStruct extends Struct<{ - a: UintN64 + a: Uint64 b: StaticArray c: Bool d: StaticBytes<32> e: Address - f: StaticArray, 10> + f: StaticArray, 10> }> {} describe('arc4EncodedLength', () => { test('should return the correct length', () => { @@ -231,7 +230,7 @@ describe('arc4EncodedLength', () => { expect(arc4EncodedLength()).toEqual(64) expect(arc4EncodedLength()).toEqual(1) expect(arc4EncodedLength()).toEqual(1) - expect(arc4EncodedLength>()).toEqual(64) + expect(arc4EncodedLength>()).toEqual(64) expect(arc4EncodedLength<[uint64, uint64, boolean]>()).toEqual(17) expect(arc4EncodedLength<[uint64, uint64, boolean, boolean]>()).toEqual(17) expect(arc4EncodedLength, Bool]>>()).toEqual(3) diff --git a/tests/arc4/method-selector.spec.ts b/tests/arc4/method-selector.spec.ts index a10a854e..af0dcd7e 100644 --- a/tests/arc4/method-selector.spec.ts +++ b/tests/arc4/method-selector.spec.ts @@ -33,7 +33,7 @@ describe('methodSelector', async () => { const contract = ctx.contract.create(SignaturesContract) contract.create() const arg1 = new arc4.Str('hello') - const arg2 = new arc4.DynamicArray(new arc4.UintN8(1), new arc4.UintN8(2)) + const arg2 = new arc4.DynamicArray(new arc4.Uint8(1), new arc4.Uint8(2)) // act // ensure same execution in AVM runs without errors @@ -55,7 +55,7 @@ describe('methodSelector', async () => { const contract = ctx.contract.create(SignaturesContract) contract.create() const arg1 = new arc4.Str('hello') - const arg2 = new arc4.DynamicArray(new arc4.UintN8(1), new arc4.UintN8(2)) + const arg2 = new arc4.DynamicArray(new arc4.Uint8(1), new arc4.Uint8(2)) // act // ensure same execution in AVM runs without errors @@ -78,7 +78,7 @@ describe('methodSelector', async () => { contract.create() const arg1 = new arc4.Str('hello') - const arg3 = new arc4.DynamicArray(new arc4.UintN8(1), new arc4.UintN8(2)) + const arg3 = new arc4.DynamicArray(new arc4.Uint8(1), new arc4.Uint8(2)) const localnetCreator = await algorand.account.localNetDispenser() const paymentTxn = await algorand.createTransaction.payment({ sender: localnetCreator, @@ -106,7 +106,7 @@ describe('methodSelector', async () => { contract.create() const arg1 = new arc4.Str('hello') - const arg3 = new arc4.DynamicArray(new arc4.UintN8(1), new arc4.UintN8(2)) + const arg3 = new arc4.DynamicArray(new arc4.Uint8(1), new arc4.Uint8(2)) const localnetCreator = await algorand.account.localNetDispenser() const asaId = ( await algorand.send.assetCreate({ @@ -139,7 +139,7 @@ describe('methodSelector', async () => { const contract = ctx.contract.create(SignaturesContract) contract.create() const arg1 = new arc4.Str('hello') - const arg3 = new arc4.DynamicArray(new arc4.UintN8(1), new arc4.UintN8(2)) + const arg3 = new arc4.DynamicArray(new arc4.Uint8(1), new arc4.Uint8(2)) const account = algorand.account.random() await algorand.account.ensureFundedFromEnvironment(account, new AlgoAmount({ microAlgo: _FUNDED_ACCOUNT_SPENDING })) @@ -172,7 +172,7 @@ describe('methodSelector', async () => { const contract = ctx.contract.create(SignaturesContract) contract.create() const arg1 = new arc4.Str('hello') - const arg4 = new arc4.DynamicArray(new arc4.UintN8(1), new arc4.UintN8(2)) + const arg4 = new arc4.DynamicArray(new arc4.Uint8(1), new arc4.Uint8(2)) const selfApp = ctx.ledger.getApplicationForContract(contract) const otherApp = await appFactorySignaturesContract.send.create({ method: 'create' }) @@ -181,7 +181,7 @@ describe('methodSelector', async () => { // act await getAvmResult({ appClient }, 'withApp', 'hello', otherAppId, otherAppId, [1, 2]) - contract.withApp(arg1, ctx.ledger.getApplication(otherAppId), new arc4.UintN64(otherAppId), arg4) + contract.withApp(arg1, ctx.ledger.getApplication(otherAppId), new arc4.Uint64(otherAppId), arg4) // assert const txn = ctx.txn.lastActive as gtxn.ApplicationCallTxn @@ -218,12 +218,12 @@ describe('methodSelector', async () => { const payment = ctx.any.txn.payment() const struct = new MyStruct({ - three: new arc4.UintN128(3), - four: new arc4.UintN128(4), - anotherStruct: new AnotherStruct({ one: new arc4.UintN64(1), two: new arc4.Str('2') }), - anotherStructAlias: new AnotherStruct({ one: new arc4.UintN64(1), two: new arc4.Str('2') }), + three: new arc4.Uint128(3), + four: new arc4.Uint128(4), + anotherStruct: new AnotherStruct({ one: new arc4.Uint64(1), two: new arc4.Str('2') }), + anotherStructAlias: new AnotherStruct({ one: new arc4.Uint64(1), two: new arc4.Str('2') }), }) - const five = new DynamicArray(new arc4.UintN8(5)) + const five = new DynamicArray(new arc4.Uint8(5)) // act const result = contract.complexSig(struct, payment, account, five) @@ -263,12 +263,12 @@ describe('methodSelector', async () => { }) const struct = new MyStruct({ - three: new arc4.UintN128(3), - four: new arc4.UintN128(4), - anotherStruct: new AnotherStruct({ one: new arc4.UintN64(1), two: new arc4.Str('2') }), - anotherStructAlias: new AnotherStruct({ one: new arc4.UintN64(1), two: new arc4.Str('2') }), + three: new arc4.Uint128(3), + four: new arc4.Uint128(4), + anotherStruct: new AnotherStruct({ one: new arc4.Uint64(1), two: new arc4.Str('2') }), + anotherStructAlias: new AnotherStruct({ one: new arc4.Uint64(1), two: new arc4.Str('2') }), }) - const five = new DynamicArray(new arc4.UintN8(5)) + const five = new DynamicArray(new arc4.Uint8(5)) const deferredAppCall = ctx.txn.deferAppCall(contract, contract.complexSig, 'complexSig', struct, ctx.any.txn.payment(), account, five) const localnetCreator = await algorand.account.localNetDispenser() diff --git a/tests/arc4/static-array.spec.ts b/tests/arc4/static-array.spec.ts index 5c342ea4..3cfddf28 100644 --- a/tests/arc4/static-array.spec.ts +++ b/tests/arc4/static-array.spec.ts @@ -10,8 +10,8 @@ import { Str, Struct, Tuple, - UFixedNxM, - UintN, + UFixed, + Uint, } from '@algorandfoundation/algorand-typescript/arc4' import { encodingUtil } from '@algorandfoundation/puya-ts' import { afterEach, describe, expect, it, test } from 'vitest' @@ -87,13 +87,13 @@ const uint256StaticArray = { return [0n, 1n, 2n, 3n, 2n ** 8n, 2n ** 16n, 2n ** 32n, 2n ** 64n, 2n ** 128n, 2n ** 256n - 1n] }, abiValues() { - return this.nativeValues().map((v) => new UintN<256>(v)) + return this.nativeValues().map((v) => new Uint<256>(v)) }, array() { - return new StaticArray, 10>(...(this.abiValues() as [])) + return new StaticArray, 10>(...(this.abiValues() as [])) }, create(value: StubBytesCompat) { - return interpretAsArc4, 10>>(asBytes(value)) + return interpretAsArc4, 10>>(asBytes(value)) }, concat() { return this.array().concat(this.array()) @@ -101,35 +101,35 @@ const uint256StaticArray = { concatABIValue() { return getABIEncodedValue([...this.nativeValues(), ...this.nativeValues()], 'uint256[]', {}) }, - clone(original: StaticArray, 10>) { + clone(original: StaticArray, 10>) { return clone(original) }, } -const ufixednxmStaticArray = { +const ufixedStaticArray = { abiTypeString: 'ufixed256x16[10]', nativeValues() { return this.abiValues().map((v) => v.native.valueOf()) }, abiValues() { return [ - new UFixedNxM<256, 16>('0.0'), - new UFixedNxM<256, 16>('1.0'), - new UFixedNxM<256, 16>('2.0'), - new UFixedNxM<256, 16>('3.0'), - new UFixedNxM<256, 16>('255.0'), - new UFixedNxM<256, 16>('65536.0'), - new UFixedNxM<256, 16>('4294967295.0'), - new UFixedNxM<256, 16>('1844.6744073709551616'), - new UFixedNxM<256, 16>('340282366920938463463374.607431768211456'), - new UFixedNxM<256, 16>('11579208923731619542357098500868790785326998466564056403945758.4007913129639935'), + new UFixed<256, 16>('0.0'), + new UFixed<256, 16>('1.0'), + new UFixed<256, 16>('2.0'), + new UFixed<256, 16>('3.0'), + new UFixed<256, 16>('255.0'), + new UFixed<256, 16>('65536.0'), + new UFixed<256, 16>('4294967295.0'), + new UFixed<256, 16>('1844.6744073709551616'), + new UFixed<256, 16>('340282366920938463463374.607431768211456'), + new UFixed<256, 16>('11579208923731619542357098500868790785326998466564056403945758.4007913129639935'), ] }, array() { - return new StaticArray, 10>(...(this.abiValues() as [])) + return new StaticArray, 10>(...(this.abiValues() as [])) }, create(value: StubBytesCompat) { - return interpretAsArc4, 10>>(asBytes(value)) + return interpretAsArc4, 10>>(asBytes(value)) }, concat() { return this.array().concat(this.array()) @@ -137,7 +137,7 @@ const ufixednxmStaticArray = { concatABIValue() { return getABIEncodedValue([...this.nativeValues(), ...this.nativeValues()], 'ufixed256x16[]', {}) }, - clone(original: StaticArray, 10>) { + clone(original: StaticArray, 10>) { return clone(original) }, } @@ -238,15 +238,15 @@ const uint256StaticArrayOfArray = { return [uint256StaticArray.nativeValues(), uint256StaticArray.nativeValues().reverse()] }, abiValues() { - return this.nativeValues().map((a) => new StaticArray, 10>(...(a.map((v) => new UintN<256>(v)) as []))) + return this.nativeValues().map((a) => new StaticArray, 10>(...(a.map((v) => new Uint<256>(v)) as []))) }, array() { - return new StaticArray, 10>, 2>( - ...(this.abiValues().map((a) => new StaticArray, 10>(...(a as DeliberateAny))) as []), + return new StaticArray, 10>, 2>( + ...(this.abiValues().map((a) => new StaticArray, 10>(...(a as DeliberateAny))) as []), ) }, create(value: StubBytesCompat) { - return interpretAsArc4, 10>, 2>>(asBytes(value)) + return interpretAsArc4, 10>, 2>>(asBytes(value)) }, concat() { return this.array().concat(this.array()) @@ -254,7 +254,7 @@ const uint256StaticArrayOfArray = { concatABIValue() { return getABIEncodedValue([...this.nativeValues(), ...this.nativeValues()], 'uint256[10][]', {}) }, - clone(original: StaticArray, 10>, 2>) { + clone(original: StaticArray, 10>, 2>) { return clone(original) }, } @@ -265,13 +265,13 @@ const uint256StaticArrayOfDynamicArray = { return [uint256StaticArray.nativeValues(), uint256StaticArray.nativeValues().reverse()] }, abiValues() { - return this.nativeValues().map((a) => new DynamicArray>(...a.map((v) => new UintN<256>(v)))) + return this.nativeValues().map((a) => new DynamicArray>(...a.map((v) => new Uint<256>(v)))) }, array() { - return new StaticArray>, 2>(...(this.abiValues().map((a) => new DynamicArray>(...a)) as [])) + return new StaticArray>, 2>(...(this.abiValues().map((a) => new DynamicArray>(...a)) as [])) }, create(value: StubBytesCompat) { - return interpretAsArc4>, 2>>(asBytes(value)) + return interpretAsArc4>, 2>>(asBytes(value)) }, concat() { return this.array().concat(this.array()) @@ -279,7 +279,7 @@ const uint256StaticArrayOfDynamicArray = { concatABIValue() { return getABIEncodedValue([...this.nativeValues(), ...this.nativeValues()], 'uint256[][]', {}) }, - clone(original: StaticArray>, 2>) { + clone(original: StaticArray>, 2>) { return clone(original) }, } @@ -364,27 +364,27 @@ const tupleStaticArray = { new Tuple( ...[ new DynamicArray(...stringStaticArray.abiValues().slice(0, 2)), - new Tuple<[DynamicArray, Str, UintN<256>, Address]>( + new Tuple<[DynamicArray, Str, Uint<256>, Address]>( new DynamicArray(...stringStaticArray.abiValues().slice(6, 8)), stringStaticArray.abiValues()[9], uint256StaticArray.abiValues()[4], addressStaticArray.abiValues()[5], ), boolStaticArray.abiValues()[3], - new StaticArray, 3>(...(uint256StaticArray.abiValues().slice(4, 7) as [])), + new StaticArray, 3>(...(uint256StaticArray.abiValues().slice(4, 7) as [])), ], ), ) }, array() { return new StaticArray< - Tuple<[DynamicArray, Tuple<[DynamicArray, Str, UintN<256>, Address]>, Bool, StaticArray, 3>]>, + Tuple<[DynamicArray, Tuple<[DynamicArray, Str, Uint<256>, Address]>, Bool, StaticArray, 3>]>, 2 >(...(this.abiValues() as [])) }, create(value: StubBytesCompat) { return interpretAsArc4< - StaticArray, Tuple<[DynamicArray, Str, UintN<256>, Address]>, Bool, StaticArray, 3>]>, 2> + StaticArray, Tuple<[DynamicArray, Str, Uint<256>, Address]>, Bool, StaticArray, 3>]>, 2> >(asBytes(value)) }, concat() { @@ -399,7 +399,7 @@ const tupleStaticArray = { }, clone( original: StaticArray< - Tuple<[DynamicArray, Tuple<[DynamicArray, Str, UintN<256>, Address]>, Bool, StaticArray, 3>]>, + Tuple<[DynamicArray, Tuple<[DynamicArray, Str, Uint<256>, Address]>, Bool, StaticArray, 3>]>, 2 >, ) { @@ -408,10 +408,10 @@ const tupleStaticArray = { } class Swapped extends Struct<{ - b: UintN<256> + b: Uint<256> c: Bool d: Str - a: Tuple, DynamicArray, Str, UintN<256>, Bool, StaticArray, 3>]> + a: Tuple, DynamicArray, Str, Uint<256>, Bool, StaticArray, 3>]> }> {} const structStaticArray = { abiTypeString: '(uint256,bool,string,(string[],string[],string,uint256,bool,uint256[3]))[2]', @@ -442,7 +442,7 @@ const structStaticArray = { stringStaticArray.abiValues()[7], uint256StaticArray.abiValues()[1], boolStaticArray.abiValues()[2], - new StaticArray, 3>( + new StaticArray, 3>( uint256StaticArray.abiValues()[2], uint256StaticArray.abiValues()[3], uint256StaticArray.abiValues()[4], @@ -482,7 +482,7 @@ describe('arc4.StaticArray', () => { addressStaticArray, boolStaticArray, uint256StaticArray, - ufixednxmStaticArray, + ufixedStaticArray, stringStaticArray, addressStaticArrayOfArray, boolStaticArrayOfArray, @@ -502,7 +502,7 @@ describe('arc4.StaticArray', () => { addressStaticArray, boolStaticArray, uint256StaticArray, - ufixednxmStaticArray, + ufixedStaticArray, stringStaticArray, addressStaticArrayOfArray, boolStaticArrayOfArray, @@ -525,7 +525,7 @@ describe('arc4.StaticArray', () => { addressStaticArray, boolStaticArray, uint256StaticArray, - ufixednxmStaticArray, + ufixedStaticArray, stringStaticArray, addressStaticArrayOfArray, boolStaticArrayOfArray, @@ -549,7 +549,7 @@ describe('arc4.StaticArray', () => { addressStaticArray, boolStaticArray, uint256StaticArray, - ufixednxmStaticArray, + ufixedStaticArray, stringStaticArray, addressStaticArrayOfArray, boolStaticArrayOfArray, @@ -572,7 +572,7 @@ describe('arc4.StaticArray', () => { addressStaticArray, boolStaticArray, uint256StaticArray, - ufixednxmStaticArray, + ufixedStaticArray, stringStaticArray, addressStaticArrayOfArray, boolStaticArrayOfArray, @@ -604,7 +604,7 @@ describe('arc4.StaticArray', () => { addressStaticArray, boolStaticArray, uint256StaticArray, - ufixednxmStaticArray, + ufixedStaticArray, stringStaticArray, addressStaticArrayOfArray, boolStaticArrayOfArray, @@ -627,7 +627,7 @@ describe('arc4.StaticArray', () => { addressStaticArray, boolStaticArray, uint256StaticArray, - ufixednxmStaticArray, + ufixedStaticArray, stringStaticArray, addressStaticArrayOfArray, boolStaticArrayOfArray, @@ -651,7 +651,7 @@ describe('arc4.StaticArray', () => { addressStaticArray, boolStaticArray, uint256StaticArray, - ufixednxmStaticArray, + ufixedStaticArray, stringStaticArray, addressStaticArrayOfArray, boolStaticArrayOfArray, diff --git a/tests/arc4/struct.spec.ts b/tests/arc4/struct.spec.ts index 04470a7d..f673115f 100644 --- a/tests/arc4/struct.spec.ts +++ b/tests/arc4/struct.spec.ts @@ -1,6 +1,6 @@ import { getABIEncodedValue } from '@algorandfoundation/algokit-utils/types/app-arc56' import { Bytes } from '@algorandfoundation/algorand-typescript' -import { Bool, DynamicArray, interpretAsArc4, StaticArray, Str, Struct, Tuple, UintN } from '@algorandfoundation/algorand-typescript/arc4' +import { Bool, DynamicArray, interpretAsArc4, StaticArray, Str, Struct, Tuple, Uint } from '@algorandfoundation/algorand-typescript/arc4' import { encodingUtil } from '@algorandfoundation/puya-ts' import { describe, expect, it, test } from 'vitest' import type { StubBytesCompat } from '../../src/impl/primitives' @@ -13,49 +13,49 @@ const nativeNumber = 42 const nativeBool = true const abiString = new Str('hello') -const abiUint64 = new UintN<64>(42) +const abiUint64 = new Uint<64>(42) const abiBool = new Bool(true) class Swapped1 extends Struct<{ - b: UintN<64> + b: Uint<64> c: Bool d: Str - a: Tuple<[UintN<64>, Bool, Bool]> + a: Tuple<[Uint<64>, Bool, Bool]> }> {} class Swapped2 extends Struct<{ - b: UintN<64> + b: Uint<64> c: Bool d: Str - a: Tuple<[Tuple<[UintN<64>, Bool, Bool]>, Tuple<[UintN<64>, Bool, Bool]>]> + a: Tuple<[Tuple<[Uint<64>, Bool, Bool]>, Tuple<[Uint<64>, Bool, Bool]>]> }> {} class Swapped3 extends Struct<{ - b: UintN<64> + b: Uint<64> c: Bool d: Str - a: Tuple<[DynamicArray, DynamicArray, Str, UintN<64>, Bool, StaticArray, 3>]> + a: Tuple<[DynamicArray, DynamicArray, Str, Uint<64>, Bool, StaticArray, 3>]> }> {} class Swapped4 extends Struct<{ - b: UintN<64> + b: Uint<64> c: Bool d: Str - a: Tuple<[Tuple<[Bool, DynamicArray, Str]>, UintN<64>, StaticArray, 3>]> + a: Tuple<[Tuple<[Bool, DynamicArray, Str]>, Uint<64>, StaticArray, 3>]> }> {} class Swapped5 extends Struct<{ - b: UintN<64> + b: Uint<64> c: Bool d: Str - a: Tuple<[Tuple<[Bool, DynamicArray, Str]>, Tuple<[UintN<64>, StaticArray, 3>]>]> + a: Tuple<[Tuple<[Bool, DynamicArray, Str]>, Tuple<[Uint<64>, StaticArray, 3>]>]> }> {} class Swapped6 extends Struct<{ - b: UintN<64> + b: Uint<64> c: Bool d: Str - a: Tuple<[Tuple<[Bool, Tuple<[DynamicArray, Str]>]>, Tuple<[UintN<64>, StaticArray, 3>]>]> + a: Tuple<[Tuple<[Bool, Tuple<[DynamicArray, Str]>]>, Tuple<[Uint<64>, StaticArray, 3>]>]> }> {} const testData = [ @@ -130,7 +130,7 @@ const testData = [ abiString, abiUint64, abiBool, - new StaticArray, 3>(abiUint64, abiUint64, abiUint64), + new StaticArray, 3>(abiUint64, abiUint64, abiUint64), ), } as Swapped3 }, @@ -159,7 +159,7 @@ const testData = [ a: new Tuple( new Tuple(abiBool, new DynamicArray(abiString, abiString), abiString), abiUint64, - new StaticArray, 3>(abiUint64, abiUint64, abiUint64), + new StaticArray, 3>(abiUint64, abiUint64, abiUint64), ), } as Swapped4 }, @@ -190,7 +190,7 @@ const testData = [ d: abiString, a: new Tuple( new Tuple(abiBool, new DynamicArray(abiString, abiString), abiString), - new Tuple(abiUint64, new StaticArray, 3>(abiUint64, abiUint64, abiUint64)), + new Tuple(abiUint64, new StaticArray, 3>(abiUint64, abiUint64, abiUint64)), ), } as Swapped5 }, @@ -221,7 +221,7 @@ const testData = [ d: abiString, a: new Tuple( new Tuple(abiBool, new Tuple(new DynamicArray(abiString, abiString), abiString)), - new Tuple(abiUint64, new StaticArray, 3>(abiUint64, abiUint64, abiUint64)), + new Tuple(abiUint64, new StaticArray, 3>(abiUint64, abiUint64, abiUint64)), ), } as Swapped6 }, @@ -266,11 +266,11 @@ describe('arc4.Struct', async () => { const sdkResult = getABIEncodedValue(nativeValues, data.abiTypeString, {}) const abiValues = data.struct() as Swapped6 - abiValues.b = new UintN<64>(43) + abiValues.b = new Uint<64>(43) abiValues.d = new Str('world') abiValues.a.at(0).at(1).at(0)[1] = new Str('hello, world') abiValues.a.at(0).at(1).at(0).push(new Str('test')) - abiValues.a.at(1).at(1)[0] = new UintN<64>(24) + abiValues.a.at(1).at(1)[0] = new Uint<64>(24) const result = abiValues.bytes expect(result).toEqual(Bytes(sdkResult)) @@ -289,11 +289,11 @@ describe('arc4.Struct', async () => { const bytes = Bytes(getABIEncodedValue(data.nativeValues(), data.abiTypeString, {})) const abiValues = data.create(bytes) as Swapped6 - abiValues.b = new UintN<64>(43) + abiValues.b = new Uint<64>(43) abiValues.d = new Str('world') abiValues.a.at(0).at(1).at(0)[1] = new Str('hello, world') abiValues.a.at(0).at(1).at(0).push(new Str('test')) - abiValues.a.at(1).at(1)[0] = new UintN<64>(24) + abiValues.a.at(1).at(1)[0] = new Uint<64>(24) const result = abiValues.bytes expect(result).toEqual(Bytes(sdkResult)) diff --git a/tests/arc4/tuple.spec.ts b/tests/arc4/tuple.spec.ts index 0ac31911..e386eb6d 100644 --- a/tests/arc4/tuple.spec.ts +++ b/tests/arc4/tuple.spec.ts @@ -1,7 +1,7 @@ import { getABIEncodedValue } from '@algorandfoundation/algokit-utils/types/app-arc56' import { Bytes } from '@algorandfoundation/algorand-typescript' import { TestExecutionContext } from '@algorandfoundation/algorand-typescript-testing' -import { Address, Bool, DynamicArray, interpretAsArc4, StaticArray, Str, Tuple, UintN } from '@algorandfoundation/algorand-typescript/arc4' +import { Address, Bool, DynamicArray, interpretAsArc4, StaticArray, Str, Tuple, Uint } from '@algorandfoundation/algorand-typescript/arc4' import { encodingUtil } from '@algorandfoundation/puya-ts' import { afterEach, describe, expect, test } from 'vitest' import type { StubBytesCompat } from '../../src/impl/primitives' @@ -18,12 +18,12 @@ const otherNativeString = 'hello' const otherNativeNumber = 42 const abiString = new Str('hello') -const abiUint8 = new UintN<8>(42) +const abiUint8 = new Uint<8>(42) const abiBool = new Bool(true) const abiAddress = new Address(Bytes.fromHex(`${'00'.repeat(31)}ff`)) const otherAbiString = new Str('hello') -const otherAbiUint8 = new UintN<8>(42) +const otherAbiUint8 = new Uint<8>(42) const testData = [ { @@ -55,13 +55,13 @@ const testData = [ return [nativeNumber, nativeBool, nativeBool, nativeAddress] }, abiValues() { - return [abiUint8, abiBool, abiBool, abiAddress] as readonly [UintN<8>, Bool, Bool, Address] + return [abiUint8, abiBool, abiBool, abiAddress] as readonly [Uint<8>, Bool, Bool, Address] }, tuple() { - return new Tuple<[UintN<8>, Bool, Bool, Address]>(...this.abiValues()) + return new Tuple<[Uint<8>, Bool, Bool, Address]>(...this.abiValues()) }, create(value: StubBytesCompat) { - return interpretAsArc4, Bool, Bool, Address]>>(asBytes(value)) + return interpretAsArc4, Bool, Bool, Address]>>(asBytes(value)) }, }, { @@ -70,13 +70,13 @@ const testData = [ return [nativeString, nativeNumber, nativeBool] }, abiValues() { - return [abiString, abiUint8, abiBool] as readonly [Str, UintN<8>, Bool] + return [abiString, abiUint8, abiBool] as readonly [Str, Uint<8>, Bool] }, tuple() { - return new Tuple<[Str, UintN<8>, Bool]>(...this.abiValues()) + return new Tuple<[Str, Uint<8>, Bool]>(...this.abiValues()) }, create(value: StubBytesCompat) { - return interpretAsArc4, Bool]>>(asBytes(value)) + return interpretAsArc4, Bool]>>(asBytes(value)) }, }, { @@ -89,15 +89,15 @@ const testData = [ }, abiValues() { return [ - new Tuple<[UintN<8>, Bool, Bool]>(abiUint8, abiBool, abiBool), - new Tuple<[UintN<8>, Bool, Bool]>(abiUint8, abiBool, abiBool), - ] as readonly [Tuple<[UintN<8>, Bool, Bool]>, Tuple<[UintN<8>, Bool, Bool]>] + new Tuple<[Uint<8>, Bool, Bool]>(abiUint8, abiBool, abiBool), + new Tuple<[Uint<8>, Bool, Bool]>(abiUint8, abiBool, abiBool), + ] as readonly [Tuple<[Uint<8>, Bool, Bool]>, Tuple<[Uint<8>, Bool, Bool]>] }, tuple() { - return new Tuple<[Tuple<[UintN<8>, Bool, Bool]>, Tuple<[UintN<8>, Bool, Bool]>]>(...this.abiValues()) + return new Tuple<[Tuple<[Uint<8>, Bool, Bool]>, Tuple<[Uint<8>, Bool, Bool]>]>(...this.abiValues()) }, create(value: StubBytesCompat) { - return interpretAsArc4, Bool, Bool]>, Tuple<[UintN<8>, Bool, Bool]>]>>(asBytes(value)) + return interpretAsArc4, Bool, Bool]>, Tuple<[Uint<8>, Bool, Bool]>]>>(asBytes(value)) }, }, { @@ -120,13 +120,13 @@ const testData = [ abiUint8, abiBool, new StaticArray(abiUint8, abiUint8, abiUint8), - ] as readonly [DynamicArray, DynamicArray, Str, UintN<8>, Bool, StaticArray, 3>] + ] as readonly [DynamicArray, DynamicArray, Str, Uint<8>, Bool, StaticArray, 3>] }, tuple() { - return new Tuple<[DynamicArray, DynamicArray, Str, UintN<8>, Bool, StaticArray, 3>]>(...this.abiValues()) + return new Tuple<[DynamicArray, DynamicArray, Str, Uint<8>, Bool, StaticArray, 3>]>(...this.abiValues()) }, create(value: StubBytesCompat) { - return interpretAsArc4, DynamicArray, Str, UintN<8>, Bool, StaticArray, 3>]>>(asBytes(value)) + return interpretAsArc4, DynamicArray, Str, Uint<8>, Bool, StaticArray, 3>]>>(asBytes(value)) }, }, { @@ -139,13 +139,13 @@ const testData = [ new Tuple<[Bool, DynamicArray, Str]>(abiBool, new DynamicArray(abiString, abiString), abiString), abiUint8, new StaticArray(abiUint8, abiUint8, abiUint8), - ] as readonly [Tuple<[Bool, DynamicArray, Str]>, UintN<8>, StaticArray, 3>] + ] as readonly [Tuple<[Bool, DynamicArray, Str]>, Uint<8>, StaticArray, 3>] }, tuple() { - return new Tuple<[Tuple<[Bool, DynamicArray, Str]>, UintN<8>, StaticArray, 3>]>(...this.abiValues()) + return new Tuple<[Tuple<[Bool, DynamicArray, Str]>, Uint<8>, StaticArray, 3>]>(...this.abiValues()) }, create(value: StubBytesCompat) { - return interpretAsArc4, Str]>, UintN<8>, StaticArray, 3>]>>(asBytes(value)) + return interpretAsArc4, Str]>, Uint<8>, StaticArray, 3>]>>(asBytes(value)) }, }, { @@ -159,14 +159,14 @@ const testData = [ abiValues() { return [ new Tuple<[Bool, DynamicArray, Str]>(abiBool, new DynamicArray(abiString, abiString), abiString), - new Tuple<[UintN<8>, StaticArray, 3>]>(abiUint8, new StaticArray(abiUint8, abiUint8, abiUint8)), - ] as readonly [Tuple<[Bool, DynamicArray, Str]>, Tuple<[UintN<8>, StaticArray, 3>]>] + new Tuple<[Uint<8>, StaticArray, 3>]>(abiUint8, new StaticArray(abiUint8, abiUint8, abiUint8)), + ] as readonly [Tuple<[Bool, DynamicArray, Str]>, Tuple<[Uint<8>, StaticArray, 3>]>] }, tuple() { - return new Tuple<[Tuple<[Bool, DynamicArray, Str]>, Tuple<[UintN<8>, StaticArray, 3>]>]>(...this.abiValues()) + return new Tuple<[Tuple<[Bool, DynamicArray, Str]>, Tuple<[Uint<8>, StaticArray, 3>]>]>(...this.abiValues()) }, create(value: StubBytesCompat) { - return interpretAsArc4, Str]>, Tuple<[UintN<8>, StaticArray, 3>]>]>>(asBytes(value)) + return interpretAsArc4, Str]>, Tuple<[Uint<8>, StaticArray, 3>]>]>>(asBytes(value)) }, }, { @@ -183,16 +183,16 @@ const testData = [ abiBool, new Tuple<[DynamicArray, Str, Address]>(new DynamicArray(abiString, abiString), abiString, abiAddress), ), - new Tuple<[UintN<8>, StaticArray, 3>]>(abiUint8, new StaticArray(abiUint8, abiUint8, abiUint8)), - ] as readonly [Tuple<[Bool, Tuple<[DynamicArray, Str, Address]>]>, Tuple<[UintN<8>, StaticArray, 3>]>] + new Tuple<[Uint<8>, StaticArray, 3>]>(abiUint8, new StaticArray(abiUint8, abiUint8, abiUint8)), + ] as readonly [Tuple<[Bool, Tuple<[DynamicArray, Str, Address]>]>, Tuple<[Uint<8>, StaticArray, 3>]>] }, tuple() { - return new Tuple<[Tuple<[Bool, Tuple<[DynamicArray, Str, Address]>]>, Tuple<[UintN<8>, StaticArray, 3>]>]>( + return new Tuple<[Tuple<[Bool, Tuple<[DynamicArray, Str, Address]>]>, Tuple<[Uint<8>, StaticArray, 3>]>]>( ...this.abiValues(), ) }, create(value: StubBytesCompat) { - return interpretAsArc4, Str, Address]>]>, Tuple<[UintN<8>, StaticArray, 3>]>]>>( + return interpretAsArc4, Str, Address]>]>, Tuple<[Uint<8>, StaticArray, 3>]>]>>( asBytes(value), ) }, @@ -220,12 +220,12 @@ const testDataWithArray = [ abiUint8, abiBool, new StaticArray(abiUint8, abiUint8, abiUint8), - ] as readonly [DynamicArray, DynamicArray, Str, UintN<8>, Bool, StaticArray, 3>] + ] as readonly [DynamicArray, DynamicArray, Str, Uint<8>, Bool, StaticArray, 3>] }, tuple() { - return new Tuple<[DynamicArray, DynamicArray, Str, UintN<8>, Bool, StaticArray, 3>]>(...this.abiValues()) + return new Tuple<[DynamicArray, DynamicArray, Str, Uint<8>, Bool, StaticArray, 3>]>(...this.abiValues()) }, - update(value: Tuple<[DynamicArray, DynamicArray, Str, UintN<8>, Bool, StaticArray, 3>]>) { + update(value: Tuple<[DynamicArray, DynamicArray, Str, Uint<8>, Bool, StaticArray, 3>]>) { value.native[0][0] = otherAbiString value.native[0].push(otherAbiString) value.native[1][0] = otherAbiString @@ -248,12 +248,12 @@ const testDataWithArray = [ new Tuple<[Bool, DynamicArray, Str]>(abiBool, new DynamicArray(abiString, abiString), abiString), abiUint8, new StaticArray(abiUint8, abiUint8, abiUint8), - ] as readonly [Tuple<[Bool, DynamicArray, Str]>, UintN<8>, StaticArray, 3>] + ] as readonly [Tuple<[Bool, DynamicArray, Str]>, Uint<8>, StaticArray, 3>] }, tuple() { - return new Tuple<[Tuple<[Bool, DynamicArray, Str]>, UintN<8>, StaticArray, 3>]>(...this.abiValues()) + return new Tuple<[Tuple<[Bool, DynamicArray, Str]>, Uint<8>, StaticArray, 3>]>(...this.abiValues()) }, - update(value: Tuple<[Tuple<[Bool, DynamicArray, Str]>, UintN<8>, StaticArray, 3>]>) { + update(value: Tuple<[Tuple<[Bool, DynamicArray, Str]>, Uint<8>, StaticArray, 3>]>) { value.native[0].native[1][0] = otherAbiString value.native[0].native[1].push(otherAbiString) value.native[2][0] = otherAbiUint8 @@ -271,13 +271,13 @@ const testDataWithArray = [ abiValues() { return [ new Tuple<[Bool, DynamicArray, Str]>(abiBool, new DynamicArray(abiString, abiString), abiString), - new Tuple<[UintN<8>, StaticArray, 3>]>(abiUint8, new StaticArray(abiUint8, abiUint8, abiUint8)), - ] as readonly [Tuple<[Bool, DynamicArray, Str]>, Tuple<[UintN<8>, StaticArray, 3>]>] + new Tuple<[Uint<8>, StaticArray, 3>]>(abiUint8, new StaticArray(abiUint8, abiUint8, abiUint8)), + ] as readonly [Tuple<[Bool, DynamicArray, Str]>, Tuple<[Uint<8>, StaticArray, 3>]>] }, tuple() { - return new Tuple<[Tuple<[Bool, DynamicArray, Str]>, Tuple<[UintN<8>, StaticArray, 3>]>]>(...this.abiValues()) + return new Tuple<[Tuple<[Bool, DynamicArray, Str]>, Tuple<[Uint<8>, StaticArray, 3>]>]>(...this.abiValues()) }, - update(value: Tuple<[Tuple<[Bool, DynamicArray, Str]>, Tuple<[UintN<8>, StaticArray, 3>]>]>) { + update(value: Tuple<[Tuple<[Bool, DynamicArray, Str]>, Tuple<[Uint<8>, StaticArray, 3>]>]>) { value.native[0].native[1][0] = otherAbiString value.native[0].native[1].push(otherAbiString) value.native[1].native[1][0] = otherAbiUint8 @@ -298,15 +298,15 @@ const testDataWithArray = [ abiBool, new Tuple<[DynamicArray, Str, Address]>(new DynamicArray(abiString, abiString), abiString, abiAddress), ), - new Tuple<[UintN<8>, StaticArray, 3>]>(abiUint8, new StaticArray(abiUint8, abiUint8, abiUint8)), - ] as readonly [Tuple<[Bool, Tuple<[DynamicArray, Str, Address]>]>, Tuple<[UintN<8>, StaticArray, 3>]>] + new Tuple<[Uint<8>, StaticArray, 3>]>(abiUint8, new StaticArray(abiUint8, abiUint8, abiUint8)), + ] as readonly [Tuple<[Bool, Tuple<[DynamicArray, Str, Address]>]>, Tuple<[Uint<8>, StaticArray, 3>]>] }, tuple() { - return new Tuple<[Tuple<[Bool, Tuple<[DynamicArray, Str, Address]>]>, Tuple<[UintN<8>, StaticArray, 3>]>]>( + return new Tuple<[Tuple<[Bool, Tuple<[DynamicArray, Str, Address]>]>, Tuple<[Uint<8>, StaticArray, 3>]>]>( ...this.abiValues(), ) }, - update(value: Tuple<[Tuple<[Bool, Tuple<[DynamicArray, Str, Address]>]>, Tuple<[UintN<8>, StaticArray, 3>]>]>) { + update(value: Tuple<[Tuple<[Bool, Tuple<[DynamicArray, Str, Address]>]>, Tuple<[Uint<8>, StaticArray, 3>]>]>) { value.native[0].native[1].native[0][0] = otherAbiString value.native[0].native[1].native[0].push(otherAbiString) value.native[1].native[1][0] = otherAbiUint8 diff --git a/tests/arc4/ufixednxm.spec.ts b/tests/arc4/ufixednxm.spec.ts index d8b6259d..c37abe5f 100644 --- a/tests/arc4/ufixednxm.spec.ts +++ b/tests/arc4/ufixednxm.spec.ts @@ -1,7 +1,7 @@ import type { bytes } from '@algorandfoundation/algorand-typescript' import { Bytes } from '@algorandfoundation/algorand-typescript' import { TestExecutionContext } from '@algorandfoundation/algorand-typescript-testing' -import { interpretAsArc4, UFixedNxM } from '@algorandfoundation/algorand-typescript/arc4' +import { interpretAsArc4, UFixed } from '@algorandfoundation/algorand-typescript/arc4' import { encodingUtil } from '@algorandfoundation/puya-ts' import { afterEach, beforeAll, describe, expect } from 'vitest' import { ABI_RETURN_VALUE_LOG_PREFIX } from '../../src/constants' @@ -10,7 +10,7 @@ import { getAvmResult } from '../avm-invoker' import { createArc4TestFixture } from '../test-fixture' const invalidBytesLengthError = (length: number) => `byte string must correspond to a ufixed${length}` -describe('arc4.UFixedNxM', async () => { +describe('arc4.UFixed', async () => { const [test, localnetFixture] = createArc4TestFixture('tests/artifacts/arc4-primitive-ops/contract.algo.ts', { Arc4PrimitiveOpsContract: { deployParams: { createParams: { extraProgramPages: undefined } } }, }) @@ -24,37 +24,37 @@ describe('arc4.UFixedNxM', async () => { }) test.for(['-1', '42.9496729501', '255.01two'])( - 'should throw error when instantiating UFixedNxM<32,8> with invalid value %s', + 'should throw error when instantiating UFixed<32,8> with invalid value %s', async (value) => { - expect(() => new UFixedNxM<32, 8>(value as `${number}.${number}`)).toThrow( + expect(() => new UFixed<32, 8>(value as `${number}.${number}`)).toThrow( 'expected positive decimal literal with max of 8 decimal places', ) }, ) test.for(['42.94967296', (2n ** 32n).toString()])( - 'should throw error when instantiating UFixedNxM<32,8> with overflowing value %s', + 'should throw error when instantiating UFixed<32,8> with overflowing value %s', async (value) => { - expect(() => new UFixedNxM<32, 8>(value as `${number}.${number}`)).toThrow('expected value <=') + expect(() => new UFixed<32, 8>(value as `${number}.${number}`)).toThrow('expected value <=') }, ) test.for(['0', '1', '25.5', '42.94967295', '42.9496729500'])( - 'should be able to get UFixedNxM<32,8> bytes representation of %s', + 'should be able to get UFixed<32,8> bytes representation of %s', async (value, { appClientArc4PrimitiveOpsContract: appClient }) => { - const a = new UFixedNxM<32, 8>(value as `${number}.${number}`) + const a = new UFixed<32, 8>(value as `${number}.${number}`) const bigIntValue = asBigUint(a.bytes).valueOf() - const avmResult = await getAvmResult({ appClient }, 'verify_ufixednxm_bytes', bigIntValue) + const avmResult = await getAvmResult({ appClient }, 'verify_ufixed_bytes', bigIntValue) expect(a.bytes).toEqual(avmResult) }, ) test.for(['0', '1', '25.5', '42.94967295', '11579208923731619542357098500868790785326998466564056403945758.4007913129639935'])( - 'should be able to get UFixedNxM<256,16> bytes representation of %s', + 'should be able to get UFixed<256,16> bytes representation of %s', async (value, { appClientArc4PrimitiveOpsContract: appClient }) => { - const a = new UFixedNxM<256, 16>(value as `${number}.${number}`) + const a = new UFixed<256, 16>(value as `${number}.${number}`) const bigIntValue = asBigUint(a.bytes).valueOf() - const avmResult = await getAvmResult({ appClient }, 'verify_bigufixednxm_bytes', bigIntValue) + const avmResult = await getAvmResult({ appClient }, 'verify_bigufixed_bytes', bigIntValue) expect(a.bytes).toEqual(avmResult) }, ) @@ -65,9 +65,9 @@ describe('arc4.UFixedNxM', async () => { encodingUtil.bigIntToUint8Array(2n ** 16n, 4), encodingUtil.bigIntToUint8Array(2n ** 32n - 1n, 4), encodingUtil.bigIntToUint8Array(2n ** 32n - 1n), - ])('create UFixedNxM<32,8> from bytes', async (value, { appClientArc4PrimitiveOpsContract: appClient }) => { - const avmResult = await getAvmResult({ appClient }, 'verify_ufixednxm_from_bytes', value) - const result = interpretAsArc4>(Bytes(value)) + ])('create UFixed<32,8> from bytes', async (value, { appClientArc4PrimitiveOpsContract: appClient }) => { + const avmResult = await getAvmResult({ appClient }, 'verify_ufixed_from_bytes', value) + const result = interpretAsArc4>(Bytes(value)) expect(result.native).toEqual(avmResult) }) @@ -78,11 +78,11 @@ describe('arc4.UFixedNxM', async () => { encodingUtil.bigIntToUint8Array(255n, 2), encodingUtil.bigIntToUint8Array(2n ** 32n - 1n, 8), ])( - 'sdk throws error when creating UFixedNxM<32,8> from bytes with invalid length', + 'sdk throws error when creating UFixed<32,8> from bytes with invalid length', async (value, { appClientArc4PrimitiveOpsContract: appClient }) => { - await expect(getAvmResult({ appClient }, 'verify_ufixednxm_from_bytes', value)).rejects.toThrowError(invalidBytesLengthError(32)) + await expect(getAvmResult({ appClient }, 'verify_ufixed_from_bytes', value)).rejects.toThrowError(invalidBytesLengthError(32)) - const result = interpretAsArc4>(Bytes(value)) + const result = interpretAsArc4>(Bytes(value)) expect(result.bytes).toEqual(value) }, ) @@ -92,11 +92,11 @@ describe('arc4.UFixedNxM', async () => { [encodingUtil.bigIntToUint8Array(255n, 4), 255n], [encodingUtil.bigIntToUint8Array(2n ** 16n, 4), 2n ** 16n], [encodingUtil.bigIntToUint8Array(2n ** 32n - 1n, 4), 2n ** 32n - 1n], - ])('create UFixedNxM<32,8> from abi log', async ([value, expected], { appClientArc4PrimitiveOpsContract: appClient }) => { + ])('create UFixed<32,8> from abi log', async ([value, expected], { appClientArc4PrimitiveOpsContract: appClient }) => { const logValue = asUint8Array(ABI_RETURN_VALUE_LOG_PREFIX.concat(Bytes(value as Uint8Array))) - const avmResult = await getAvmResult({ appClient }, 'verify_ufixednxm_from_log', logValue) + const avmResult = await getAvmResult({ appClient }, 'verify_ufixed_from_log', logValue) - const result = interpretAsArc4>(Bytes(logValue), 'log') + const result = interpretAsArc4>(Bytes(logValue), 'log') expect(avmResult).toEqual(expected) expect(result.native).toEqual(expected) }) @@ -105,11 +105,11 @@ describe('arc4.UFixedNxM', async () => { [encodingUtil.bigIntToUint8Array(255n, 4), Bytes()], [encodingUtil.bigIntToUint8Array(255n, 4), Bytes.fromHex('FF000102')], ])( - 'should throw error when log prefix is invalid for UFixedNxM<32,8>', + 'should throw error when log prefix is invalid for UFixed<32,8>', async ([value, prefix], { appClientArc4PrimitiveOpsContract: appClient }) => { const logValue = asUint8Array(Bytes(prefix as Uint8Array).concat(Bytes(value as bytes))) - await expect(() => getAvmResult({ appClient }, 'verify_ufixednxm_from_log', logValue)).rejects.toThrowError('has valid prefix') - expect(() => interpretAsArc4>(Bytes(logValue), 'log')).toThrowError('ABI return prefix not found') + await expect(() => getAvmResult({ appClient }, 'verify_ufixed_from_log', logValue)).rejects.toThrowError('has valid prefix') + expect(() => interpretAsArc4>(Bytes(logValue), 'log')).toThrowError('ABI return prefix not found') }, ) @@ -119,14 +119,12 @@ describe('arc4.UFixedNxM', async () => { encodingUtil.bigIntToUint8Array(255n, 2), encodingUtil.bigIntToUint8Array(2n ** 32n - 1n, 8), ])( - 'sdk throws error when creating UFixedNxM<32,8> from log with invalid length', + 'sdk throws error when creating UFixed<32,8> from log with invalid length', async (value, { appClientArc4PrimitiveOpsContract: appClient }) => { const logValue = asUint8Array(ABI_RETURN_VALUE_LOG_PREFIX.concat(Bytes(value))) - await expect(() => getAvmResult({ appClient }, 'verify_ufixednxm_from_log', logValue)).rejects.toThrowError( - invalidBytesLengthError(32), - ) + await expect(() => getAvmResult({ appClient }, 'verify_ufixed_from_log', logValue)).rejects.toThrowError(invalidBytesLengthError(32)) - const result = interpretAsArc4>(Bytes(logValue), 'log') + const result = interpretAsArc4>(Bytes(logValue), 'log') expect(result.native).toEqual(encodingUtil.uint8ArrayToBigInt(value)) }, ) @@ -136,9 +134,9 @@ describe('arc4.UFixedNxM', async () => { encodingUtil.bigIntToUint8Array(255n, 32), encodingUtil.bigIntToUint8Array(2n ** 256n - 1n, 32), encodingUtil.bigIntToUint8Array(2n ** 256n - 1n), - ])('create UFixedNxM<256,16> from bytes', async (value, { appClientArc4PrimitiveOpsContract: appClient }) => { - const avmResult = await getAvmResult({ appClient }, 'verify_bigufixednxm_from_bytes', value) - const result = interpretAsArc4>(Bytes(value)) + ])('create UFixed<256,16> from bytes', async (value, { appClientArc4PrimitiveOpsContract: appClient }) => { + const avmResult = await getAvmResult({ appClient }, 'verify_bigufixed_from_bytes', value) + const result = interpretAsArc4>(Bytes(value)) expect(result.native).toEqual(avmResult) }) @@ -149,11 +147,11 @@ describe('arc4.UFixedNxM', async () => { encodingUtil.bigIntToUint8Array(2n ** 128n - 1n, 16), encodingUtil.bigIntToUint8Array(2n ** 256n - 1n, 40), ])( - 'sdk throws error when creating UFixedNxM<256,16> from bytes with invalid length', + 'sdk throws error when creating UFixed<256,16> from bytes with invalid length', async (value, { appClientArc4PrimitiveOpsContract: appClient }) => { - await expect(getAvmResult({ appClient }, 'verify_bigufixednxm_from_bytes', value)).rejects.toThrowError(invalidBytesLengthError(256)) + await expect(getAvmResult({ appClient }, 'verify_bigufixed_from_bytes', value)).rejects.toThrowError(invalidBytesLengthError(256)) - const result = interpretAsArc4>(Bytes(value)) + const result = interpretAsArc4>(Bytes(value)) expect(result.bytes).toEqual(value) }, ) @@ -163,11 +161,11 @@ describe('arc4.UFixedNxM', async () => { [encodingUtil.bigIntToUint8Array(255n, 32), 255n], [encodingUtil.bigIntToUint8Array(2n ** 16n, 32), 2n ** 16n], [encodingUtil.bigIntToUint8Array(2n ** 256n - 1n, 32), 2n ** 256n - 1n], - ])('create UFixedNxM<256,16> from abi log', async ([value, expected], { appClientArc4PrimitiveOpsContract: appClient }) => { + ])('create UFixed<256,16> from abi log', async ([value, expected], { appClientArc4PrimitiveOpsContract: appClient }) => { const logValue = asUint8Array(ABI_RETURN_VALUE_LOG_PREFIX.concat(Bytes(value as Uint8Array))) - const avmResult = await getAvmResult({ appClient }, 'verify_bigufixednxm_from_log', logValue) + const avmResult = await getAvmResult({ appClient }, 'verify_bigufixed_from_log', logValue) - const result = interpretAsArc4>(Bytes(logValue), 'log') + const result = interpretAsArc4>(Bytes(logValue), 'log') expect(avmResult).toEqual(expected) expect(result.native).toEqual(expected) }) @@ -176,11 +174,11 @@ describe('arc4.UFixedNxM', async () => { [encodingUtil.bigIntToUint8Array(255n, 32), Bytes()], [encodingUtil.bigIntToUint8Array(255n, 32), Bytes.fromHex('FF000102')], ])( - 'should throw error when log prefix is invalid for UFixedNxM<256,16>', + 'should throw error when log prefix is invalid for UFixed<256,16>', async ([value, prefix], { appClientArc4PrimitiveOpsContract: appClient }) => { const logValue = asUint8Array(Bytes(prefix as Uint8Array).concat(Bytes(value as bytes))) - await expect(() => getAvmResult({ appClient }, 'verify_bigufixednxm_from_log', logValue)).rejects.toThrowError('has valid prefix') - expect(() => interpretAsArc4>(Bytes(logValue), 'log')).toThrowError('ABI return prefix not found') + await expect(() => getAvmResult({ appClient }, 'verify_bigufixed_from_log', logValue)).rejects.toThrowError('has valid prefix') + expect(() => interpretAsArc4>(Bytes(logValue), 'log')).toThrowError('ABI return prefix not found') }, ) @@ -190,14 +188,14 @@ describe('arc4.UFixedNxM', async () => { encodingUtil.bigIntToUint8Array(2n ** 128n - 1n, 16), encodingUtil.bigIntToUint8Array(2n ** 256n - 1n, 40), ])( - 'sdk throws error when creating UFixedNxM<256,16> from log with invalid length', + 'sdk throws error when creating UFixed<256,16> from log with invalid length', async (value, { appClientArc4PrimitiveOpsContract: appClient }) => { const logValue = asUint8Array(ABI_RETURN_VALUE_LOG_PREFIX.concat(Bytes(value))) - await expect(() => getAvmResult({ appClient }, 'verify_bigufixednxm_from_log', logValue)).rejects.toThrowError( + await expect(() => getAvmResult({ appClient }, 'verify_bigufixed_from_log', logValue)).rejects.toThrowError( invalidBytesLengthError(256), ) - const result = interpretAsArc4>(Bytes(logValue), 'log') + const result = interpretAsArc4>(Bytes(logValue), 'log') expect(result.native).toEqual(encodingUtil.uint8ArrayToBigInt(value)) }, ) diff --git a/tests/arc4/uintn.spec.ts b/tests/arc4/uintn.spec.ts index 3653f1c8..a6f132b8 100644 --- a/tests/arc4/uintn.spec.ts +++ b/tests/arc4/uintn.spec.ts @@ -1,8 +1,8 @@ import type { bytes } from '@algorandfoundation/algorand-typescript' -import { Bytes } from '@algorandfoundation/algorand-typescript' +import { arc4, Bytes } from '@algorandfoundation/algorand-typescript' import { TestExecutionContext } from '@algorandfoundation/algorand-typescript-testing' import type { BitSize } from '@algorandfoundation/algorand-typescript/arc4' -import { interpretAsArc4, UintN, UintN16, UintN256, UintN32, UintN64, UintN8 } from '@algorandfoundation/algorand-typescript/arc4' +import { interpretAsArc4, Uint, Uint16, Uint256, Uint32, Uint8 } from '@algorandfoundation/algorand-typescript/arc4' import { encodingUtil } from '@algorandfoundation/puya-ts' import { afterEach, beforeAll, describe, expect } from 'vitest' import { ABI_RETURN_VALUE_LOG_PREFIX, MAX_UINT512, MAX_UINT64 } from '../../src/constants' @@ -11,7 +11,7 @@ import { getAvmResult } from '../avm-invoker' import { createArc4TestFixture } from '../test-fixture' const invalidBytesLengthError = (length: number) => `byte string must correspond to a uint${length}` -describe('arc4.UintN', async () => { +describe('arc4.Uint', async () => { const [test, localnetFixture] = createArc4TestFixture('tests/artifacts/arc4-primitive-ops/contract.algo.ts', { Arc4PrimitiveOpsContract: { deployParams: { createParams: { extraProgramPages: undefined } } }, }) @@ -30,7 +30,7 @@ describe('arc4.UintN', async () => { const bytesValue = asBigUintCls(value).toBytes() await expect(getAvmResult({ appClient }, 'verify_uintn_init', asUint8Array(bytesValue))).rejects.toThrowError() - expect(() => new UintN32(value)).toThrowError(`expected value <= ${2 ** 32 - 1}`) + expect(() => new Uint32(value)).toThrowError(`expected value <= ${2 ** 32 - 1}`) }, ) @@ -40,7 +40,7 @@ describe('arc4.UintN', async () => { const bytesValue = asBigUintCls(value).toBytes() await expect(getAvmResult({ appClient }, 'verify_biguintn_init', asUint8Array(bytesValue))).rejects.toThrowError() - expect(() => new UintN256(value)).toThrowError(`expected value <= ${2 ** 256 - 1}`) + expect(() => new Uint256(value)).toThrowError(`expected value <= ${2 ** 256 - 1}`) }, ) @@ -48,11 +48,11 @@ describe('arc4.UintN', async () => { [0, 0], [255, 255], [2 ** 32 - 1, 2 ** 32 - 1], - ])('instantiating UintN<32> should work for %s', async ([value, expected], { appClientArc4PrimitiveOpsContract: appClient }) => { + ])('instantiating Uint<32> should work for %s', async ([value, expected], { appClientArc4PrimitiveOpsContract: appClient }) => { const bytesValue = asBigUintCls(value).toBytes() const avmResult = await getAvmResult({ appClient }, 'verify_uintn_init', asUint8Array(bytesValue)) - const result = new UintN32(value) + const result = new Uint32(value) expect(result.native).toEqual(expected) expect(avmResult).toEqual(expected) @@ -64,21 +64,21 @@ describe('arc4.UintN', async () => { [MAX_UINT64, MAX_UINT64], [2 ** 128, 2 ** 128], [2 ** 256 - 1, 2 ** 256 - 1], - ])('instantiating UintN<256> should work for %s', async ([value, expected], { appClientArc4PrimitiveOpsContract: appClient }) => { + ])('instantiating Uint<256> should work for %s', async ([value, expected], { appClientArc4PrimitiveOpsContract: appClient }) => { const bytesValue = asBigUintCls(value).toBytes() const avmResult = await getAvmResult({ appClient }, 'verify_biguintn_init', asUint8Array(bytesValue)) - const result = new UintN256(value) + const result = new Uint256(value) expect(result.native).toEqual(BigInt(expected)) expect(avmResult).toEqual(BigInt(expected)) }) test.for([0, 1, 255])('should be able to get bytes representation of %s', async (value) => { - expect(new UintN8(value).bytes).toEqual(encodingUtil.bigIntToUint8Array(BigInt(value), 1)) - expect(new UintN16(value).bytes).toEqual(encodingUtil.bigIntToUint8Array(BigInt(value), 2)) - expect(new UintN64(value).bytes).toEqual(encodingUtil.bigIntToUint8Array(BigInt(value), 8)) - expect(new UintN<512>(value).bytes).toEqual(encodingUtil.bigIntToUint8Array(BigInt(value), 64)) + expect(new Uint8(value).bytes).toEqual(encodingUtil.bigIntToUint8Array(BigInt(value), 1)) + expect(new Uint16(value).bytes).toEqual(encodingUtil.bigIntToUint8Array(BigInt(value), 2)) + expect(new arc4.Uint64(value).bytes).toEqual(encodingUtil.bigIntToUint8Array(BigInt(value), 8)) + expect(new Uint<512>(value).bytes).toEqual(encodingUtil.bigIntToUint8Array(BigInt(value), 64)) }) describe.each(['eq', 'ne', 'lt', 'le', 'gt', 'ge'])('logical operators', async (op) => { @@ -112,7 +112,7 @@ describe('arc4.UintN', async () => { [4294967295, MAX_UINT64], [MAX_UINT512, MAX_UINT512], ])(`${operator}`, async (a, b) => { - const getStubResult = (a: UintN, b: UintN) => { + const getStubResult = (a: Uint, b: Uint) => { switch (operator) { case '===': return a === b @@ -142,8 +142,8 @@ describe('arc4.UintN', async () => { asUint8Array(bytesValueB), ) - const encodedA = a <= MAX_UINT64 ? new UintN<64>(a) : new UintN<512>(a) - const encodedB = b <= MAX_UINT64 ? new UintN<64>(b) : new UintN<512>(b) + const encodedA = a <= MAX_UINT64 ? new Uint<64>(a) : new Uint<512>(a) + const encodedB = b <= MAX_UINT64 ? new Uint<64>(b) : new Uint<512>(b) const result = getStubResult(encodedA, encodedB) expect(result).toEqual(avmResult) @@ -156,10 +156,10 @@ describe('arc4.UintN', async () => { [0, MAX_UINT512], [1, MAX_UINT512], ])('should throw error when comparing UinN with different bit sizes', async ([a, b]) => { - const encodedA = a <= MAX_UINT64 ? new UintN<64>(a) : new UintN<512>(a) - const encodedB = b <= MAX_UINT64 ? new UintN<64>(b) : new UintN<512>(b) - expect(() => encodedA === encodedB).toThrowError(/Expected expression of type UintN<\d+>, got UintN<\d+>/) - expect(() => encodedA !== encodedB).toThrowError(/Expected expression of type UintN<\d+>, got UintN<\d+>/) + const encodedA = a <= MAX_UINT64 ? new Uint<64>(a) : new Uint<512>(a) + const encodedB = b <= MAX_UINT64 ? new Uint<64>(b) : new Uint<512>(b) + expect(() => encodedA === encodedB).toThrowError(/Expected expression of type Uint<\d+>, got Uint<\d+>/) + expect(() => encodedA !== encodedB).toThrowError(/Expected expression of type Uint<\d+>, got Uint<\d+>/) }) test.for([ @@ -168,10 +168,10 @@ describe('arc4.UintN', async () => { encodingUtil.bigIntToUint8Array(2n ** 16n, 4), encodingUtil.bigIntToUint8Array(2n ** 32n - 1n, 4), encodingUtil.bigIntToUint8Array(2n ** 32n - 1n), - ])('create UintN<32> from bytes', async (value, { appClientArc4PrimitiveOpsContract: appClient }) => { + ])('create Uint<32> from bytes', async (value, { appClientArc4PrimitiveOpsContract: appClient }) => { const avmResult = await getAvmResult({ appClient }, 'verify_uintn_from_bytes', value) - const result = interpretAsArc4(Bytes(value)) + const result = interpretAsArc4(Bytes(value)) expect(result.native).toEqual(avmResult) }) @@ -182,11 +182,11 @@ describe('arc4.UintN', async () => { encodingUtil.bigIntToUint8Array(255n, 2), encodingUtil.bigIntToUint8Array(2n ** 32n - 1n, 8), ])( - 'sdk throws error when creating UintN<32> from bytes with invalid length', + 'sdk throws error when creating Uint<32> from bytes with invalid length', async (value, { appClientArc4PrimitiveOpsContract: appClient }) => { await expect(getAvmResult({ appClient }, 'verify_uintn_from_bytes', value)).rejects.toThrowError(invalidBytesLengthError(32)) - const result = interpretAsArc4(Bytes(value)) + const result = interpretAsArc4(Bytes(value)) expect(result.native).toEqual(encodingUtil.uint8ArrayToBigInt(value)) }, ) @@ -196,9 +196,9 @@ describe('arc4.UintN', async () => { encodingUtil.bigIntToUint8Array(255n, 32), encodingUtil.bigIntToUint8Array(2n ** 256n - 1n, 32), encodingUtil.bigIntToUint8Array(2n ** 256n - 1n), - ])('create UintN<256> from bytes', async (value, { appClientArc4PrimitiveOpsContract: appClient }) => { + ])('create Uint<256> from bytes', async (value, { appClientArc4PrimitiveOpsContract: appClient }) => { const avmResult = await getAvmResult({ appClient }, 'verify_biguintn_from_bytes', value) - const result = interpretAsArc4(Bytes(value)) + const result = interpretAsArc4(Bytes(value)) expect(result.native).toEqual(avmResult) }) @@ -209,11 +209,11 @@ describe('arc4.UintN', async () => { encodingUtil.bigIntToUint8Array(2n ** 128n - 1n, 16), encodingUtil.bigIntToUint8Array(2n ** 256n - 1n, 40), ])( - 'sdk throws error when creating UintN<256> from bytes with invalid length', + 'sdk throws error when creating Uint<256> from bytes with invalid length', async (value, { appClientArc4PrimitiveOpsContract: appClient }) => { await expect(getAvmResult({ appClient }, 'verify_biguintn_from_bytes', value)).rejects.toThrowError(invalidBytesLengthError(256)) - const result = interpretAsArc4(Bytes(value)) + const result = interpretAsArc4(Bytes(value)) expect(result.native).toEqual(encodingUtil.uint8ArrayToBigInt(value)) }, ) @@ -223,11 +223,11 @@ describe('arc4.UintN', async () => { [encodingUtil.bigIntToUint8Array(255n, 4), 255n], [encodingUtil.bigIntToUint8Array(2n ** 16n, 4), 2n ** 16n], [encodingUtil.bigIntToUint8Array(2n ** 32n - 1n, 4), 2n ** 32n - 1n], - ])('create UintN<32> from abi log', async ([value, expected], { appClientArc4PrimitiveOpsContract: appClient }) => { + ])('create Uint<32> from abi log', async ([value, expected], { appClientArc4PrimitiveOpsContract: appClient }) => { const logValue = asUint8Array(ABI_RETURN_VALUE_LOG_PREFIX.concat(Bytes(value as Uint8Array))) const avmResult = await getAvmResult({ appClient }, 'verify_uintn_from_log', logValue) - const result = interpretAsArc4>(Bytes(logValue), 'log') + const result = interpretAsArc4>(Bytes(logValue), 'log') expect(BigInt(avmResult as number)).toEqual(expected) expect(result.native).toEqual(expected) }) @@ -236,11 +236,11 @@ describe('arc4.UintN', async () => { [encodingUtil.bigIntToUint8Array(255n, 4), Bytes()], [encodingUtil.bigIntToUint8Array(255n, 4), Bytes.fromHex('FF000102')], ])( - 'should throw error when log prefix is invalid for UintN<32>', + 'should throw error when log prefix is invalid for Uint<32>', async ([value, prefix], { appClientArc4PrimitiveOpsContract: appClient }) => { const logValue = asUint8Array(Bytes(prefix as Uint8Array).concat(Bytes(value as bytes))) await expect(() => getAvmResult({ appClient }, 'verify_uintn_from_log', logValue)).rejects.toThrowError('has valid prefix') - expect(() => interpretAsArc4>(Bytes(logValue), 'log')).toThrowError('ABI return prefix not found') + expect(() => interpretAsArc4>(Bytes(logValue), 'log')).toThrowError('ABI return prefix not found') }, ) @@ -250,12 +250,12 @@ describe('arc4.UintN', async () => { encodingUtil.bigIntToUint8Array(255n, 2), encodingUtil.bigIntToUint8Array(2n ** 32n - 1n, 8), ])( - 'sdk throws error when creating UintN<32> from log with invalid length', + 'sdk throws error when creating Uint<32> from log with invalid length', async (value, { appClientArc4PrimitiveOpsContract: appClient }) => { const logValue = asUint8Array(ABI_RETURN_VALUE_LOG_PREFIX.concat(Bytes(value))) await expect(() => getAvmResult({ appClient }, 'verify_uintn_from_log', logValue)).rejects.toThrowError(invalidBytesLengthError(32)) - const result = interpretAsArc4>(Bytes(logValue), 'log') + const result = interpretAsArc4>(Bytes(logValue), 'log') expect(result.native).toEqual(encodingUtil.uint8ArrayToBigInt(value)) }, ) @@ -265,11 +265,11 @@ describe('arc4.UintN', async () => { [encodingUtil.bigIntToUint8Array(255n, 32), 255n], [encodingUtil.bigIntToUint8Array(2n ** 16n, 32), 2n ** 16n], [encodingUtil.bigIntToUint8Array(2n ** 256n - 1n, 32), 2n ** 256n - 1n], - ])('create UintN<256> from abi log', async ([value, expected], { appClientArc4PrimitiveOpsContract: appClient }) => { + ])('create Uint<256> from abi log', async ([value, expected], { appClientArc4PrimitiveOpsContract: appClient }) => { const logValue = asUint8Array(ABI_RETURN_VALUE_LOG_PREFIX.concat(Bytes(value as Uint8Array))) const avmResult = await getAvmResult({ appClient }, 'verify_biguintn_from_log', logValue) - const result = interpretAsArc4>(Bytes(logValue), 'log') + const result = interpretAsArc4>(Bytes(logValue), 'log') expect(avmResult).toEqual(expected) expect(result.native).toEqual(expected) }) @@ -278,11 +278,11 @@ describe('arc4.UintN', async () => { [encodingUtil.bigIntToUint8Array(255n, 32), Bytes()], [encodingUtil.bigIntToUint8Array(255n, 32), Bytes.fromHex('FF000102')], ])( - 'should throw error when log prefix is invalid for UintN<256>', + 'should throw error when log prefix is invalid for Uint<256>', async ([value, prefix], { appClientArc4PrimitiveOpsContract: appClient }) => { const logValue = asUint8Array(Bytes(prefix as Uint8Array).concat(Bytes(value as bytes))) await expect(() => getAvmResult({ appClient }, 'verify_biguintn_from_log', logValue)).rejects.toThrowError('has valid prefix') - expect(() => interpretAsArc4>(Bytes(logValue), 'log')).toThrowError('ABI return prefix not found') + expect(() => interpretAsArc4>(Bytes(logValue), 'log')).toThrowError('ABI return prefix not found') }, ) @@ -292,14 +292,14 @@ describe('arc4.UintN', async () => { encodingUtil.bigIntToUint8Array(2n ** 128n - 1n, 16), encodingUtil.bigIntToUint8Array(2n ** 256n - 1n, 40), ])( - 'sdk throws error when creating UintN<256> from log with invalid length', + 'sdk throws error when creating Uint<256> from log with invalid length', async (value, { appClientArc4PrimitiveOpsContract: appClient }) => { const logValue = asUint8Array(ABI_RETURN_VALUE_LOG_PREFIX.concat(Bytes(value))) await expect(() => getAvmResult({ appClient }, 'verify_biguintn_from_log', logValue)).rejects.toThrowError( invalidBytesLengthError(256), ) - const result = interpretAsArc4>(Bytes(logValue), 'log') + const result = interpretAsArc4>(Bytes(logValue), 'log') expect(result.native).toEqual(encodingUtil.uint8ArrayToBigInt(value)) }, ) diff --git a/tests/arc4/zero-constructor.spec.ts b/tests/arc4/zero-constructor.spec.ts index 68e026c1..0eac787a 100644 --- a/tests/arc4/zero-constructor.spec.ts +++ b/tests/arc4/zero-constructor.spec.ts @@ -9,15 +9,15 @@ import { StaticBytes, Str, Tuple, - UFixedNxM, - UintN32, - UintN8, + UFixed, + Uint32, + Uint8, } from '@algorandfoundation/algorand-typescript/arc4' import { describe, expect, it } from 'vitest' describe('initialising ABI values with constructor args', () => { it('should set correct zero values', () => { - expect(new StaticArray().bytes).toEqual(new StaticArray(new UintN8(0), new UintN8(0), new UintN8(0), new UintN8(0)).bytes) + expect(new StaticArray().bytes).toEqual(new StaticArray(new Uint8(0), new Uint8(0), new Uint8(0), new Uint8(0)).bytes) expect(new StaticArray().bytes).toEqual( new StaticArray(new Bool(false), new Bool(false), new Bool(false), new Bool(false)).bytes, ) @@ -34,7 +34,7 @@ describe('initialising ABI values with constructor args', () => { new Bool(false), ).bytes, ) - expect(new DynamicArray().bytes).toEqual(op.bzero(2)) + expect(new DynamicArray().bytes).toEqual(op.bzero(2)) expect(new Tuple<[Bool, Bool, Bool, Bool, Bool, Bool, Bool, Bool, Bool]>().bytes).toEqual( encodeArc4([false, false, false, false, false, false, false, false, false]), ) @@ -43,8 +43,8 @@ describe('initialising ABI values with constructor args', () => { expect(new DynamicBytes().bytes).toEqual(op.bzero(2)) expect(new StaticBytes<5>().bytes).toEqual(op.bzero(5)) expect(new Address().bytes).toEqual(op.bzero(32)) - expect(new UFixedNxM<32, 4>().bytes).toEqual(op.bzero(32 / 8)) + expect(new UFixed<32, 4>().bytes).toEqual(op.bzero(32 / 8)) expect(new Bool().bytes).toEqual(op.bzero(1)) - expect(new UintN32().bytes).toEqual(op.bzero(32 / 8)) + expect(new Uint32().bytes).toEqual(op.bzero(32 / 8)) }) }) diff --git a/tests/artifacts/arc4-abi-method/contract.algo.ts b/tests/artifacts/arc4-abi-method/contract.algo.ts index ef4ea35d..064b1fc3 100644 --- a/tests/artifacts/arc4-abi-method/contract.algo.ts +++ b/tests/artifacts/arc4-abi-method/contract.algo.ts @@ -1,11 +1,11 @@ -type UInt8Array = arc4.DynamicArray -type MyAlias = arc4.UintN<128> +type UInt8Array = arc4.DynamicArray +type MyAlias = arc4.Uint<128> import type { Account, Application, Asset } from '@algorandfoundation/algorand-typescript' import { arc4, assert, clone, gtxn, op, Txn } from '@algorandfoundation/algorand-typescript' export class AnotherStruct extends arc4.Struct<{ - one: arc4.UintN64 + one: arc4.Uint64 two: arc4.Str }> {} @@ -14,7 +14,7 @@ type MyStructAlias = AnotherStruct export class MyStruct extends arc4.Struct<{ anotherStruct: AnotherStruct anotherStructAlias: MyStructAlias - three: arc4.UintN128 + three: arc4.Uint128 four: MyAlias }> {} @@ -58,7 +58,7 @@ export class SignaturesContract extends arc4.Contract { } @arc4.abimethod() - withApp(value: arc4.Str, app: Application, appId: arc4.UintN64, arr: UInt8Array) { + withApp(value: arc4.Str, app: Application, appId: arc4.Uint64, arr: UInt8Array) { assert(value.native) assert(arr.length) assert(app.id === appId.native, 'expected app id to match provided app id') @@ -95,7 +95,7 @@ export class SignaturesContract extends arc4.Contract { assert(txn.groupIndex === Txn.groupIndex - 1) // acc - assert(Txn.applicationArgs(2) === new arc4.UintN8(1).bytes) // acc array ref + assert(Txn.applicationArgs(2) === new arc4.Uint8(1).bytes) // acc array ref assert(acc.balance === acc.minBalance + 1234) assert(five[0].native === 5) diff --git a/tests/artifacts/arc4-primitive-ops/contract.algo.ts b/tests/artifacts/arc4-primitive-ops/contract.algo.ts index 22c70b03..3a711acf 100644 --- a/tests/artifacts/arc4-primitive-ops/contract.algo.ts +++ b/tests/artifacts/arc4-primitive-ops/contract.algo.ts @@ -1,39 +1,39 @@ import type { bytes } from '@algorandfoundation/algorand-typescript' import { arc4, BigUint, clone, emit } from '@algorandfoundation/algorand-typescript' -import type { Bool, UFixedNxM } from '@algorandfoundation/algorand-typescript/arc4' -import { Byte, Contract, interpretAsArc4, Str, UintN } from '@algorandfoundation/algorand-typescript/arc4' +import type { Bool, UFixed } from '@algorandfoundation/algorand-typescript/arc4' +import { Byte, Contract, interpretAsArc4, Str, Uint } from '@algorandfoundation/algorand-typescript/arc4' export class Arc4PrimitiveOpsContract extends Contract { @arc4.abimethod() public verify_uintn_uintn_eq(a: bytes, b: bytes): boolean { const aBiguint = BigUint(a) const bBiguint = BigUint(b) - const aUintN = new UintN<64>(aBiguint) - const bUintN = new UintN<64>(bBiguint) + const aUintN = new Uint<64>(aBiguint) + const bUintN = new Uint<64>(bBiguint) return aUintN === bUintN } @arc4.abimethod() public verify_biguintn_uintn_eq(a: bytes, b: bytes): boolean { const aBiguint = BigUint(a) const bBiguint = BigUint(b) - const aUintN = new UintN<512>(aBiguint) - const bUintN = new UintN<64>(bBiguint) + const aUintN = new Uint<512>(aBiguint) + const bUintN = new Uint<64>(bBiguint) return aUintN.bytes.equals(bUintN.bytes) } @arc4.abimethod() public verify_uintn_biguintn_eq(a: bytes, b: bytes): boolean { const aBiguint = BigUint(a) const bBiguint = BigUint(b) - const aUintN = new UintN<64>(aBiguint) - const bUintN = new UintN<512>(bBiguint) + const aUintN = new Uint<64>(aBiguint) + const bUintN = new Uint<512>(bBiguint) return aUintN.bytes.equals(bUintN.bytes) } @arc4.abimethod() public verify_biguintn_biguintn_eq(a: bytes, b: bytes): boolean { const aBiguint = BigUint(a) const bBiguint = BigUint(b) - const aUintN = new UintN<512>(aBiguint) - const bUintN = new UintN<512>(bBiguint) + const aUintN = new Uint<512>(aBiguint) + const bUintN = new Uint<512>(bBiguint) return aUintN === bUintN } @arc4.abimethod() @@ -48,32 +48,32 @@ export class Arc4PrimitiveOpsContract extends Contract { public verify_uintn_uintn_ne(a: bytes, b: bytes): boolean { const aBiguint = BigUint(a) const bBiguint = BigUint(b) - const aUintN = new UintN<64>(aBiguint) - const bUintN = new UintN<64>(bBiguint) + const aUintN = new Uint<64>(aBiguint) + const bUintN = new Uint<64>(bBiguint) return aUintN !== bUintN } @arc4.abimethod() public verify_biguintn_uintn_ne(a: bytes, b: bytes): boolean { const aBiguint = BigUint(a) const bBiguint = BigUint(b) - const aUintN = new UintN<512>(aBiguint) - const bUintN = new UintN<64>(bBiguint) + const aUintN = new Uint<512>(aBiguint) + const bUintN = new Uint<64>(bBiguint) return !aUintN.bytes.equals(bUintN.bytes) } @arc4.abimethod() public verify_uintn_biguintn_ne(a: bytes, b: bytes): boolean { const aBiguint = BigUint(a) const bBiguint = BigUint(b) - const aUintN = new UintN<64>(aBiguint) - const bUintN = new UintN<512>(bBiguint) + const aUintN = new Uint<64>(aBiguint) + const bUintN = new Uint<512>(bBiguint) return !aUintN.bytes.equals(bUintN.bytes) } @arc4.abimethod() public verify_biguintn_biguintn_ne(a: bytes, b: bytes): boolean { const aBiguint = BigUint(a) const bBiguint = BigUint(b) - const aUintN = new UintN<512>(aBiguint) - const bUintN = new UintN<512>(bBiguint) + const aUintN = new Uint<512>(aBiguint) + const bUintN = new Uint<512>(bBiguint) return aUintN !== bUintN } @arc4.abimethod() @@ -88,32 +88,32 @@ export class Arc4PrimitiveOpsContract extends Contract { public verify_uintn_uintn_lt(a: bytes, b: bytes): boolean { const aBiguint = BigUint(a) const bBiguint = BigUint(b) - const aUintN = new UintN<64>(aBiguint) - const bUintN = new UintN<64>(bBiguint) + const aUintN = new Uint<64>(aBiguint) + const bUintN = new Uint<64>(bBiguint) return aUintN.native < bUintN.native } @arc4.abimethod() public verify_biguintn_uintn_lt(a: bytes, b: bytes): boolean { const aBiguint = BigUint(a) const bBiguint = BigUint(b) - const aUintN = new UintN<512>(aBiguint) - const bUintN = new UintN<64>(bBiguint) + const aUintN = new Uint<512>(aBiguint) + const bUintN = new Uint<64>(bBiguint) return aUintN.native < BigUint(bUintN.native) } @arc4.abimethod() public verify_uintn_biguintn_lt(a: bytes, b: bytes): boolean { const aBiguint = BigUint(a) const bBiguint = BigUint(b) - const aUintN = new UintN<64>(aBiguint) - const bUintN = new UintN<512>(bBiguint) + const aUintN = new Uint<64>(aBiguint) + const bUintN = new Uint<512>(bBiguint) return BigUint(aUintN.native) < bUintN.native } @arc4.abimethod() public verify_biguintn_biguintn_lt(a: bytes, b: bytes): boolean { const aBiguint = BigUint(a) const bBiguint = BigUint(b) - const aUintN = new UintN<512>(aBiguint) - const bUintN = new UintN<512>(bBiguint) + const aUintN = new Uint<512>(aBiguint) + const bUintN = new Uint<512>(bBiguint) return aUintN.native < bUintN.native } @arc4.abimethod() @@ -128,32 +128,32 @@ export class Arc4PrimitiveOpsContract extends Contract { public verify_uintn_uintn_le(a: bytes, b: bytes): boolean { const aBiguint = BigUint(a) const bBiguint = BigUint(b) - const aUintN = new UintN<64>(aBiguint) - const bUintN = new UintN<64>(bBiguint) + const aUintN = new Uint<64>(aBiguint) + const bUintN = new Uint<64>(bBiguint) return aUintN.native <= bUintN.native } @arc4.abimethod() public verify_biguintn_uintn_le(a: bytes, b: bytes): boolean { const aBiguint = BigUint(a) const bBiguint = BigUint(b) - const aUintN = new UintN<512>(aBiguint) - const bUintN = new UintN<64>(bBiguint) + const aUintN = new Uint<512>(aBiguint) + const bUintN = new Uint<64>(bBiguint) return aUintN.native <= BigUint(bUintN.native) } @arc4.abimethod() public verify_uintn_biguintn_le(a: bytes, b: bytes): boolean { const aBiguint = BigUint(a) const bBiguint = BigUint(b) - const aUintN = new UintN<64>(aBiguint) - const bUintN = new UintN<512>(bBiguint) + const aUintN = new Uint<64>(aBiguint) + const bUintN = new Uint<512>(bBiguint) return BigUint(aUintN.native) <= bUintN.native } @arc4.abimethod() public verify_biguintn_biguintn_le(a: bytes, b: bytes): boolean { const aBiguint = BigUint(a) const bBiguint = BigUint(b) - const aUintN = new UintN<512>(aBiguint) - const bUintN = new UintN<512>(bBiguint) + const aUintN = new Uint<512>(aBiguint) + const bUintN = new Uint<512>(bBiguint) return aUintN.native <= bUintN.native } @arc4.abimethod() @@ -168,32 +168,32 @@ export class Arc4PrimitiveOpsContract extends Contract { public verify_uintn_uintn_gt(a: bytes, b: bytes): boolean { const aBiguint = BigUint(a) const bBiguint = BigUint(b) - const aUintN = new UintN<64>(aBiguint) - const bUintN = new UintN<64>(bBiguint) + const aUintN = new Uint<64>(aBiguint) + const bUintN = new Uint<64>(bBiguint) return aUintN.native > bUintN.native } @arc4.abimethod() public verify_biguintn_uintn_gt(a: bytes, b: bytes): boolean { const aBiguint = BigUint(a) const bBiguint = BigUint(b) - const aUintN = new UintN<512>(aBiguint) - const bUintN = new UintN<64>(bBiguint) + const aUintN = new Uint<512>(aBiguint) + const bUintN = new Uint<64>(bBiguint) return aUintN.native > BigUint(bUintN.native) } @arc4.abimethod() public verify_uintn_biguintn_gt(a: bytes, b: bytes): boolean { const aBiguint = BigUint(a) const bBiguint = BigUint(b) - const aUintN = new UintN<64>(aBiguint) - const bUintN = new UintN<512>(bBiguint) + const aUintN = new Uint<64>(aBiguint) + const bUintN = new Uint<512>(bBiguint) return BigUint(aUintN.native) > bUintN.native } @arc4.abimethod() public verify_biguintn_biguintn_gt(a: bytes, b: bytes): boolean { const aBiguint = BigUint(a) const bBiguint = BigUint(b) - const aUintN = new UintN<512>(aBiguint) - const bUintN = new UintN<512>(bBiguint) + const aUintN = new Uint<512>(aBiguint) + const bUintN = new Uint<512>(bBiguint) return aUintN.native > bUintN.native } @arc4.abimethod() @@ -208,32 +208,32 @@ export class Arc4PrimitiveOpsContract extends Contract { public verify_uintn_uintn_ge(a: bytes, b: bytes): boolean { const aBiguint = BigUint(a) const bBiguint = BigUint(b) - const aUintN = new UintN<64>(aBiguint) - const bUintN = new UintN<64>(bBiguint) + const aUintN = new Uint<64>(aBiguint) + const bUintN = new Uint<64>(bBiguint) return aUintN.native >= bUintN.native } @arc4.abimethod() public verify_biguintn_uintn_ge(a: bytes, b: bytes): boolean { const aBiguint = BigUint(a) const bBiguint = BigUint(b) - const aUintN = new UintN<512>(aBiguint) - const bUintN = new UintN<64>(bBiguint) + const aUintN = new Uint<512>(aBiguint) + const bUintN = new Uint<64>(bBiguint) return aUintN.native >= BigUint(bUintN.native) } @arc4.abimethod() public verify_uintn_biguintn_ge(a: bytes, b: bytes): boolean { const aBiguint = BigUint(a) const bBiguint = BigUint(b) - const aUintN = new UintN<64>(aBiguint) - const bUintN = new UintN<512>(bBiguint) + const aUintN = new Uint<64>(aBiguint) + const bUintN = new Uint<512>(bBiguint) return BigUint(aUintN.native) >= bUintN.native } @arc4.abimethod() public verify_biguintn_biguintn_ge(a: bytes, b: bytes): boolean { const aBiguint = BigUint(a) const bBiguint = BigUint(b) - const aUintN = new UintN<512>(aBiguint) - const bUintN = new UintN<512>(bBiguint) + const aUintN = new Uint<512>(aBiguint) + const bUintN = new Uint<512>(bBiguint) return aUintN.native >= bUintN.native } @arc4.abimethod() @@ -245,14 +245,14 @@ export class Arc4PrimitiveOpsContract extends Contract { return aByte.native >= bByte.native } @arc4.abimethod() - public verify_uintn_init(a: bytes): UintN<32> { + public verify_uintn_init(a: bytes): Uint<32> { const aBiguint = BigUint(a) - return new UintN<32>(aBiguint) + return new Uint<32>(aBiguint) } @arc4.abimethod() - public verify_biguintn_init(a: bytes): UintN<256> { + public verify_biguintn_init(a: bytes): Uint<256> { const aBiguint = BigUint(a) - return new UintN<256>(aBiguint) + return new Uint<256>(aBiguint) } @arc4.abimethod() public verify_byte_init(a: bytes): Byte { @@ -260,52 +260,52 @@ export class Arc4PrimitiveOpsContract extends Contract { return new Byte(aBiguint) } @arc4.abimethod() - public verify_uintn_from_bytes(a: bytes): UintN<32> { - return interpretAsArc4>(a) + public verify_uintn_from_bytes(a: bytes): Uint<32> { + return interpretAsArc4>(a) } @arc4.abimethod() - public verify_biguintn_from_bytes(a: bytes): UintN<256> { - return interpretAsArc4>(a) + public verify_biguintn_from_bytes(a: bytes): Uint<256> { + return interpretAsArc4>(a) } @arc4.abimethod() public verify_byte_from_bytes(a: bytes): Byte { return interpretAsArc4(a) } @arc4.abimethod() - public verify_uintn_from_log(a: bytes): UintN<32> { - return interpretAsArc4>(a, 'log') + public verify_uintn_from_log(a: bytes): Uint<32> { + return interpretAsArc4>(a, 'log') } @arc4.abimethod() - public verify_biguintn_from_log(a: bytes): UintN<256> { - return interpretAsArc4>(a, 'log') + public verify_biguintn_from_log(a: bytes): Uint<256> { + return interpretAsArc4>(a, 'log') } @arc4.abimethod() public verify_byte_from_log(a: bytes): Byte { return interpretAsArc4(a, 'log') } @arc4.abimethod() - public verify_ufixednxm_bytes(a: UFixedNxM<32, 8>): bytes { + public verify_ufixed_bytes(a: UFixed<32, 8>): bytes { return a.bytes } @arc4.abimethod() - public verify_bigufixednxm_bytes(a: UFixedNxM<256, 16>): bytes { + public verify_bigufixed_bytes(a: UFixed<256, 16>): bytes { return a.bytes } @arc4.abimethod() - public verify_ufixednxm_from_bytes(a: bytes): UFixedNxM<32, 8> { - return interpretAsArc4>(a) + public verify_ufixed_from_bytes(a: bytes): UFixed<32, 8> { + return interpretAsArc4>(a) } @arc4.abimethod() - public verify_bigufixednxm_from_bytes(a: bytes): UFixedNxM<256, 16> { - return interpretAsArc4>(a) + public verify_bigufixed_from_bytes(a: bytes): UFixed<256, 16> { + return interpretAsArc4>(a) } @arc4.abimethod() - public verify_ufixednxm_from_log(a: bytes): UFixedNxM<32, 8> { - return interpretAsArc4>(a, 'log') + public verify_ufixed_from_log(a: bytes): UFixed<32, 8> { + return interpretAsArc4>(a, 'log') } @arc4.abimethod() - public verify_bigufixednxm_from_log(a: bytes): UFixedNxM<256, 16> { - return interpretAsArc4>(a, 'log') + public verify_bigufixed_from_log(a: bytes): UFixed<256, 16> { + return interpretAsArc4>(a, 'log') } @arc4.abimethod() public verify_string_init(a: string): Str { @@ -351,25 +351,25 @@ export class Arc4PrimitiveOpsContract extends Contract { @arc4.abimethod() public verify_emit( a: arc4.Str, - b: arc4.UintN<512>, - c: arc4.UintN64, + b: arc4.Uint<512>, + c: arc4.Uint64, d: arc4.DynamicBytes, - e: arc4.UintN64, + e: arc4.Uint64, f: arc4.Bool, g: arc4.DynamicBytes, h: arc4.Str, - m: arc4.UintN<64>, - n: arc4.UintN<256>, - o: arc4.UFixedNxM<32, 8>, - p: arc4.UFixedNxM<256, 16>, + m: arc4.Uint<64>, + n: arc4.Uint<256>, + o: arc4.UFixed<32, 8>, + p: arc4.UFixed<256, 16>, q: arc4.Bool, r: bytes, s: bytes, t: bytes, ): void { - const arc4_r = interpretAsArc4>(r) - const arc4_s = interpretAsArc4>(s) - const arc4_t = interpretAsArc4>(t) + const arc4_r = interpretAsArc4>(r) + const arc4_s = interpretAsArc4>(s) + const arc4_t = interpretAsArc4>(t) emit(new SwappedArc4({ m, n, o, p, q, r: clone(arc4_r), s: clone(arc4_s), t: clone(arc4_t) })) emit('Swapped', a, b, c, d, e, f, g, h, m, n, o, p, q, arc4_r, arc4_s, arc4_t) @@ -396,12 +396,12 @@ export class Arc4PrimitiveOpsContract extends Contract { } class SwappedArc4 extends arc4.Struct<{ - m: arc4.UintN<64> - n: arc4.UintN<256> - o: arc4.UFixedNxM<32, 8> - p: arc4.UFixedNxM<256, 16> + m: arc4.Uint<64> + n: arc4.Uint<256> + o: arc4.UFixed<32, 8> + p: arc4.UFixed<256, 16> q: arc4.Bool - r: arc4.StaticArray - s: arc4.DynamicArray - t: arc4.Tuple<[arc4.UintN32, arc4.UintN64, arc4.Str]> + r: arc4.StaticArray + s: arc4.DynamicArray + t: arc4.Tuple<[arc4.Uint32, arc4.Uint64, arc4.Str]> }> {} diff --git a/tests/artifacts/box-contract/contract.algo.ts b/tests/artifacts/box-contract/contract.algo.ts index 45253fce..89e2ae95 100644 --- a/tests/artifacts/box-contract/contract.algo.ts +++ b/tests/artifacts/box-contract/contract.algo.ts @@ -1,5 +1,6 @@ import { arc4, assert, Box, Bytes, OnCompleteAction, op, TransactionType } from '@algorandfoundation/algorand-typescript' -import { Tuple, UintN64 } from '@algorandfoundation/algorand-typescript/arc4' +import type { Uint64 } from '@algorandfoundation/algorand-typescript/arc4' +import { Tuple } from '@algorandfoundation/algorand-typescript/arc4' export class BoxContract extends arc4.Contract { oca = Box({ key: Bytes('oca') }) @@ -12,10 +13,10 @@ export class BoxContract extends arc4.Contract { } @arc4.abimethod() - public read_enums(): Tuple { + public read_enums(): Tuple { assert(op.Box.get(Bytes('oca'))[0] === op.itob(this.oca.value)) assert(op.Box.get(Bytes('txn'))[0] === op.itob(this.txn.value)) - return new Tuple(new UintN64(this.oca.value), new UintN64(this.txn.value)) + return new Tuple(new arc4.Uint64(this.oca.value), new arc4.Uint64(this.txn.value)) } } diff --git a/tests/artifacts/created-app-asset/contract.algo.ts b/tests/artifacts/created-app-asset/contract.algo.ts index 506745f7..8420b064 100644 --- a/tests/artifacts/created-app-asset/contract.algo.ts +++ b/tests/artifacts/created-app-asset/contract.algo.ts @@ -1,6 +1,6 @@ import type { gtxn, uint64 } from '@algorandfoundation/algorand-typescript' import { arc4, assert, Global, op } from '@algorandfoundation/algorand-typescript' -import type { UintN64 } from '@algorandfoundation/algorand-typescript/arc4' +import type { Uint64 } from '@algorandfoundation/algorand-typescript/arc4' import { interpretAsArc4, methodSelector } from '@algorandfoundation/algorand-typescript/arc4' export class AppExpectingEffects extends arc4.Contract { @@ -18,6 +18,6 @@ export class AppExpectingEffects extends arc4.Contract { public log_group(appCall: gtxn.ApplicationCallTxn): void { assert(appCall.appArgs(0) === methodSelector('some_value()uint64'), 'expected correct method called') assert(appCall.numLogs === 1, 'expected logs') - assert(interpretAsArc4(appCall.lastLog, 'log').native === (appCall.groupIndex + 1) * Global.groupSize) + assert(interpretAsArc4(appCall.lastLog, 'log').native === (appCall.groupIndex + 1) * Global.groupSize) } } diff --git a/tests/artifacts/primitive-ops/contract.algo.ts b/tests/artifacts/primitive-ops/contract.algo.ts index 2759f383..bc068e09 100644 --- a/tests/artifacts/primitive-ops/contract.algo.ts +++ b/tests/artifacts/primitive-ops/contract.algo.ts @@ -6,12 +6,12 @@ import type { StaticArray, Str, Tuple, - UFixedNxM, - UintN, - UintN16, - UintN32, - UintN64, - UintN8, + UFixed, + Uint, + Uint16, + Uint32, + Uint64, + Uint8, } from '@algorandfoundation/algorand-typescript/arc4' import { interpretAsArc4 } from '@algorandfoundation/algorand-typescript/arc4' @@ -413,18 +413,18 @@ export class PrimitiveOpsContract extends arc4.Contract { d: bytes, e: Bool, f: Str, - g: UintN<64>, - h: UintN<256>, - i: UFixedNxM<32, 8>, - j: UFixedNxM<256, 16>, + g: Uint<64>, + h: Uint<256>, + i: UFixed<32, 8>, + j: UFixed<256, 16>, k: bytes, m: bytes, n: bytes, ) { const d_biguint = BigUint(d) - const arc4_k = interpretAsArc4>(k) - const arc4_m = interpretAsArc4>(m) - const arc4_n = interpretAsArc4>(n) + const arc4_k = interpretAsArc4>(k) + const arc4_m = interpretAsArc4>(m) + const arc4_n = interpretAsArc4>(n) log(a, b, c, d_biguint, e, f, g, h, i, j, arc4_k, arc4_m, arc4_n) } } diff --git a/tests/artifacts/state-ops/contract.algo.ts b/tests/artifacts/state-ops/contract.algo.ts index d6dbe9a0..6f2fe99f 100644 --- a/tests/artifacts/state-ops/contract.algo.ts +++ b/tests/artifacts/state-ops/contract.algo.ts @@ -16,7 +16,7 @@ import { Txn, Uint64, } from '@algorandfoundation/algorand-typescript' -import { Address, Bool, Byte, DynamicBytes, Str, UintN128, UintN64 } from '@algorandfoundation/algorand-typescript/arc4' +import { Address, Bool, Byte, DynamicBytes, Str, Uint128 } from '@algorandfoundation/algorand-typescript/arc4' function get_1st_ref_index(): uint64 { return op.btoi(Txn.applicationArgs(1)) @@ -599,28 +599,28 @@ export class ItxnDemoContract extends BaseContract { export class GlobalStateContract extends arc4.Contract { // Implicit key state variables - implicitKeyArc4UintN64 = GlobalState({ initialValue: new UintN64(1337) }) + implicitKeyArc4UintN64 = GlobalState({ initialValue: new arc4.Uint64(1337) }) implicitKeyArc4Str = GlobalState({ initialValue: new Str('Hello') }) implicitKeyArc4Byte = GlobalState({ initialValue: new Byte(0) }) implicitKeyArc4Bool = GlobalState({ initialValue: new Bool(true) }) implicitKeyArc4Address = GlobalState({ initialValue: new Address(Global.creatorAddress) }) - implicitKeyArc4UintN128 = GlobalState({ initialValue: new UintN128(2n ** 100n) }) + implicitKeyArc4UintN128 = GlobalState({ initialValue: new Uint128(2n ** 100n) }) implicitKeyArc4DynamicBytes = GlobalState({ initialValue: new DynamicBytes('dynamic bytes') }) implicitKeyTuple = GlobalState<[uint64, bytes, boolean]>({ initialValue: [Uint64(42), Bytes('Hello'), false] }) implicitKeyObj = GlobalState<{ a: uint64; b: bytes; c: boolean }>({ initialValue: { a: 42, b: Bytes('World'), c: true } }) // Explicit key state variables - arc4UintN64 = GlobalState({ initialValue: new UintN64(1337), key: 'explicit_key_arc4_uintn64' }) + arc4UintN64 = GlobalState({ initialValue: new arc4.Uint64(1337), key: 'explicit_key_arc4_uintn64' }) arc4Str = GlobalState({ initialValue: new Str('Hello'), key: 'explicit_key_arc4_str' }) arc4Byte = GlobalState({ initialValue: new Byte(0), key: 'explicit_key_arc4_byte' }) arc4Bool = GlobalState({ initialValue: new Bool(true), key: 'explicit_key_arc4_bool' }) arc4Address = GlobalState({ initialValue: new Address(Global.creatorAddress), key: 'explicit_key_arc4_address' }) - arc4UintN128 = GlobalState({ initialValue: new UintN128(2n ** 100n), key: 'explicit_key_arc4_uintn128' }) + arc4UintN128 = GlobalState({ initialValue: new Uint128(2n ** 100n), key: 'explicit_key_arc4_uintn128' }) arc4DynamicBytes = GlobalState({ initialValue: new DynamicBytes('dynamic bytes'), key: 'explicit_key_arc4_dynamic_bytes' }) // Getter methods for implicit key state variables @arc4.abimethod() - get_implicit_key_arc4_uintn64(): UintN64 { + get_implicit_key_arc4_uintn64(): arc4.Uint64 { return this.implicitKeyArc4UintN64.value } @@ -645,7 +645,7 @@ export class GlobalStateContract extends arc4.Contract { } @arc4.abimethod() - get_implicit_key_arc4_uintn128(): UintN128 { + get_implicit_key_arc4_uintn128(): Uint128 { return this.implicitKeyArc4UintN128.value } @@ -666,7 +666,7 @@ export class GlobalStateContract extends arc4.Contract { // Getter methods for explicit key state variables @arc4.abimethod() - get_arc4_uintn64(): UintN64 { + get_arc4_uintn64(): arc4.Uint64 { return this.arc4UintN64.value } @@ -691,7 +691,7 @@ export class GlobalStateContract extends arc4.Contract { } @arc4.abimethod() - get_arc4_uintn128(): UintN128 { + get_arc4_uintn128(): Uint128 { return this.arc4UintN128.value } @@ -702,7 +702,7 @@ export class GlobalStateContract extends arc4.Contract { // Setter methods for implicit key state variables @arc4.abimethod() - set_implicit_key_arc4_uintn64(value: UintN64) { + set_implicit_key_arc4_uintn64(value: arc4.Uint64) { this.implicitKeyArc4UintN64.value = value } @@ -727,7 +727,7 @@ export class GlobalStateContract extends arc4.Contract { } @arc4.abimethod() - set_implicit_key_arc4_uintn128(value: UintN128) { + set_implicit_key_arc4_uintn128(value: Uint128) { this.implicitKeyArc4UintN128.value = value } @@ -748,7 +748,7 @@ export class GlobalStateContract extends arc4.Contract { // Setter methods for explicit key state variables @arc4.abimethod() - set_arc4_uintn64(value: UintN64) { + set_arc4_uintn64(value: arc4.Uint64) { this.arc4UintN64.value = value } @@ -773,7 +773,7 @@ export class GlobalStateContract extends arc4.Contract { } @arc4.abimethod() - set_arc4_uintn128(value: UintN128) { + set_arc4_uintn128(value: Uint128) { this.arc4UintN128.value = value } @@ -785,49 +785,49 @@ export class GlobalStateContract extends arc4.Contract { export class LocalStateContract extends arc4.Contract { // Implicit key state variables - implicitKeyArc4UintN64 = LocalState() + implicitKeyArc4UintN64 = LocalState() implicitKeyArc4Str = LocalState() implicitKeyArc4Byte = LocalState() implicitKeyArc4Bool = LocalState() implicitKeyArc4Address = LocalState
() - implicitKeyArc4UintN128 = LocalState() + implicitKeyArc4UintN128 = LocalState() implicitKeyArc4DynamicBytes = LocalState() implicitKeyTuple = LocalState<[uint64, bytes, boolean]>() implicitKeyObj = LocalState<{ a: uint64; b: bytes; c: boolean }>() // Explicit key state variables - arc4UintN64 = LocalState({ key: 'explicit_key_arc4_uintn64' }) + arc4UintN64 = LocalState({ key: 'explicit_key_arc4_uintn64' }) arc4Str = LocalState({ key: 'explicit_key_arc4_str' }) arc4Byte = LocalState({ key: 'explicit_key_arc4_byte' }) arc4Bool = LocalState({ key: 'explicit_key_arc4_bool' }) arc4Address = LocalState
({ key: 'explicit_key_arc4_address' }) - arc4UintN128 = LocalState({ key: 'explicit_key_arc4_uintn128' }) + arc4UintN128 = LocalState({ key: 'explicit_key_arc4_uintn128' }) arc4DynamicBytes = LocalState({ key: 'explicit_key_arc4_dynamic_bytes' }) @arc4.abimethod({ allowActions: ['OptIn'] }) opt_in(): void { - this.implicitKeyArc4UintN64(Global.creatorAddress).value = new UintN64(1337) + this.implicitKeyArc4UintN64(Global.creatorAddress).value = new arc4.Uint64(1337) this.implicitKeyArc4Str(Global.creatorAddress).value = new Str('Hello') this.implicitKeyArc4Byte(Global.creatorAddress).value = new Byte(0) this.implicitKeyArc4Bool(Global.creatorAddress).value = new Bool(true) this.implicitKeyArc4Address(Global.creatorAddress).value = new Address(Global.creatorAddress) - this.implicitKeyArc4UintN128(Global.creatorAddress).value = new UintN128(2n ** 100n) + this.implicitKeyArc4UintN128(Global.creatorAddress).value = new Uint128(2n ** 100n) this.implicitKeyArc4DynamicBytes(Global.creatorAddress).value = new DynamicBytes('dynamic bytes') this.implicitKeyTuple(Global.creatorAddress).value = [42, Bytes('dummy_bytes'), true] this.implicitKeyObj(Global.creatorAddress).value = { a: Uint64(42), b: Bytes('dummy_bytes'), c: true } - this.arc4UintN64(Global.creatorAddress).value = new UintN64(1337) + this.arc4UintN64(Global.creatorAddress).value = new arc4.Uint64(1337) this.arc4Str(Global.creatorAddress).value = new Str('Hello') this.arc4Byte(Global.creatorAddress).value = new Byte(0) this.arc4Bool(Global.creatorAddress).value = new Bool(true) this.arc4Address(Global.creatorAddress).value = new Address(Global.creatorAddress) - this.arc4UintN128(Global.creatorAddress).value = new UintN128(2n ** 100n) + this.arc4UintN128(Global.creatorAddress).value = new Uint128(2n ** 100n) this.arc4DynamicBytes(Global.creatorAddress).value = new DynamicBytes('dynamic bytes') } // Getter methods for implicit key state variables @arc4.abimethod() - get_implicit_key_arc4_uintn64(a: Account): UintN64 { + get_implicit_key_arc4_uintn64(a: Account): arc4.Uint64 { return this.implicitKeyArc4UintN64(a).value } @@ -852,7 +852,7 @@ export class LocalStateContract extends arc4.Contract { } @arc4.abimethod() - get_implicit_key_arc4_uintn128(a: Account): UintN128 { + get_implicit_key_arc4_uintn128(a: Account): Uint128 { return this.implicitKeyArc4UintN128(a).value } @@ -873,7 +873,7 @@ export class LocalStateContract extends arc4.Contract { // Getter methods for explicit key state variables @arc4.abimethod() - get_arc4_uintn64(a: Account): arc4.UintN64 { + get_arc4_uintn64(a: Account): arc4.Uint64 { return this.arc4UintN64(a).value } @@ -898,7 +898,7 @@ export class LocalStateContract extends arc4.Contract { } @arc4.abimethod() - get_arc4_uintn128(a: Account): UintN128 { + get_arc4_uintn128(a: Account): Uint128 { return this.arc4UintN128(a).value } diff --git a/tests/fixed-array.spec.ts b/tests/fixed-array.spec.ts index 5da3dd70..da452438 100644 --- a/tests/fixed-array.spec.ts +++ b/tests/fixed-array.spec.ts @@ -21,7 +21,7 @@ class TestContract extends Contract { fixedArrayNative(a: FixedArray, b: string): readonly [FixedArray, string] { return [new FixedArray(a[0], a[1]), b] } - fixedArrayArc4(a: FixedArray, b: arc4.Str): readonly [FixedArray, arc4.Str] { + fixedArrayArc4(a: FixedArray, b: arc4.Str): readonly [FixedArray, arc4.Str] { return [new FixedArray(a[0], a[1]), b] } } @@ -150,57 +150,57 @@ describe('FixedArray', () => { describe('store arc4 value in fixed array', () => { it('can store primitive arc4 values', () => { - const a1: FixedArray = new FixedArray(new arc4.UintN64(1), new arc4.UintN64(2), new arc4.UintN64(3)) - assertMatch(a1, [new arc4.UintN64(1), new arc4.UintN64(2), new arc4.UintN64(3)]) + const a1: FixedArray = new FixedArray(new arc4.Uint64(1), new arc4.Uint64(2), new arc4.Uint64(3)) + assertMatch(a1, [new arc4.Uint64(1), new arc4.Uint64(2), new arc4.Uint64(3)]) const a2 = clone(a1) - a2[0] = new arc4.UintN64(10) - assertMatch(a1, [new arc4.UintN64(1), new arc4.UintN64(2), new arc4.UintN64(3)]) - assertMatch(a2, [new arc4.UintN64(10), new arc4.UintN64(2), new arc4.UintN64(3)]) + a2[0] = new arc4.Uint64(10) + assertMatch(a1, [new arc4.Uint64(1), new arc4.Uint64(2), new arc4.Uint64(3)]) + assertMatch(a2, [new arc4.Uint64(10), new arc4.Uint64(2), new arc4.Uint64(3)]) - const a3 = new FixedArray() - assertMatch(a3, [new arc4.UintN64(0), new arc4.UintN64(0), new arc4.UintN64(0)]) + const a3 = new FixedArray() + assertMatch(a3, [new arc4.Uint64(0), new arc4.Uint64(0), new arc4.Uint64(0)]) }) it('can store arc4 dynamic array', () => { - const a1: FixedArray, 1> = new FixedArray( - new arc4.DynamicArray(new arc4.UintN64(1), new arc4.UintN64(2), new arc4.UintN64(3)), + const a1: FixedArray, 1> = new FixedArray( + new arc4.DynamicArray(new arc4.Uint64(1), new arc4.Uint64(2), new arc4.Uint64(3)), ) - assertMatch(a1[0], [new arc4.UintN64(1), new arc4.UintN64(2), new arc4.UintN64(3)]) + assertMatch(a1[0], [new arc4.Uint64(1), new arc4.Uint64(2), new arc4.Uint64(3)]) const a2 = clone(a1) - a2[0][0] = new arc4.UintN64(10) - assertMatch(a1[0], [new arc4.UintN64(1), new arc4.UintN64(2), new arc4.UintN64(3)]) - assertMatch(a2[0], [new arc4.UintN64(10), new arc4.UintN64(2), new arc4.UintN64(3)]) + a2[0][0] = new arc4.Uint64(10) + assertMatch(a1[0], [new arc4.Uint64(1), new arc4.Uint64(2), new arc4.Uint64(3)]) + assertMatch(a2[0], [new arc4.Uint64(10), new arc4.Uint64(2), new arc4.Uint64(3)]) }) it('can store arc4 static array', () => { - const a1: FixedArray, 1> = new FixedArray( - new arc4.StaticArray(new arc4.UintN64(1), new arc4.UintN64(2), new arc4.UintN64(3)), + const a1: FixedArray, 1> = new FixedArray( + new arc4.StaticArray(new arc4.Uint64(1), new arc4.Uint64(2), new arc4.Uint64(3)), ) - assertMatch(a1[0], [new arc4.UintN64(1), new arc4.UintN64(2), new arc4.UintN64(3)]) + assertMatch(a1[0], [new arc4.Uint64(1), new arc4.Uint64(2), new arc4.Uint64(3)]) const a2 = clone(a1) - a2[0][0] = new arc4.UintN64(10) - assertMatch(a1[0], [new arc4.UintN64(1), new arc4.UintN64(2), new arc4.UintN64(3)]) - assertMatch(a2[0], [new arc4.UintN64(10), new arc4.UintN64(2), new arc4.UintN64(3)]) + a2[0][0] = new arc4.Uint64(10) + assertMatch(a1[0], [new arc4.Uint64(1), new arc4.Uint64(2), new arc4.Uint64(3)]) + assertMatch(a2[0], [new arc4.Uint64(10), new arc4.Uint64(2), new arc4.Uint64(3)]) - const a3 = new FixedArray, 1>() - assertMatch(a3[0], [new arc4.UintN64(0), new arc4.UintN64(0), new arc4.UintN64(0)]) + const a3 = new FixedArray, 1>() + assertMatch(a3[0], [new arc4.Uint64(0), new arc4.Uint64(0), new arc4.Uint64(0)]) }) it('can store arc4 tuple', () => { - const a1: FixedArray, 1> = new FixedArray( - new arc4.Tuple(new arc4.UintN64(1), new arc4.Str('Hello')), + const a1: FixedArray, 1> = new FixedArray( + new arc4.Tuple(new arc4.Uint64(1), new arc4.Str('Hello')), ) - assertMatch(a1[0].native, [new arc4.UintN64(1), new arc4.Str('Hello')]) + assertMatch(a1[0].native, [new arc4.Uint64(1), new arc4.Str('Hello')]) const a2 = clone(a1) - a2[0] = new arc4.Tuple(new arc4.UintN64(10), new arc4.Str('hello')) - assertMatch(a1[0].native, [new arc4.UintN64(1), new arc4.Str('Hello')]) - assertMatch(a2[0].native, [new arc4.UintN64(10), new arc4.Str('hello')]) + a2[0] = new arc4.Tuple(new arc4.Uint64(10), new arc4.Str('hello')) + assertMatch(a1[0].native, [new arc4.Uint64(1), new arc4.Str('Hello')]) + assertMatch(a2[0].native, [new arc4.Uint64(10), new arc4.Str('hello')]) - const a3 = new FixedArray, 1>() - assertMatch(a3[0].native, [new arc4.UintN64(0), new arc4.UintN64(0)]) + const a3 = new FixedArray, 1>() + assertMatch(a3[0].native, [new arc4.Uint64(0), new arc4.Uint64(0)]) }) }) @@ -431,7 +431,7 @@ describe('FixedArray', () => { it('should decode and encode native uint64 fixed array', () => { const arr = new FixedArray(10, 20, 30) const encoded = encodeArc4(arr) - const interpreted = interpretAsArc4>(encoded) + const interpreted = interpretAsArc4>(encoded) const decoded = decodeArc4>(encoded) assertMatch(interpreted.length, arr.length) @@ -483,7 +483,7 @@ describe('FixedArray', () => { it('should decode and encode nested fixed array', () => { const arr = new FixedArray, 2>(new FixedArray(1, 2), new FixedArray(3, 4)) const encoded = encodeArc4(arr) - const interpreted = interpretAsArc4, 2>>(encoded) + const interpreted = interpretAsArc4, 2>>(encoded) const decoded = decodeArc4, 2>>(encoded) assertMatch(interpreted.length, arr.length) @@ -499,7 +499,7 @@ describe('FixedArray', () => { it('should decode and encode fixed array with native arrays', () => { const arr = new FixedArray([1, 2, 3], [4, 5]) const encoded = encodeArc4(arr) - const interpreted = interpretAsArc4, 2>>(encoded) + const interpreted = interpretAsArc4, 2>>(encoded) const decoded = decodeArc4>(encoded) assertMatch(interpreted.length, arr.length) @@ -515,7 +515,7 @@ describe('FixedArray', () => { it('should decode and encode fixed array with tuples', () => { const arr = new FixedArray<[uint64, string], 2>([10, 'first'], [20, 'second']) const encoded = encodeArc4(arr) - const interpreted = interpretAsArc4, 2>>(encoded) + const interpreted = interpretAsArc4, 2>>(encoded) const decoded = decodeArc4>(encoded) assertMatch(interpreted.length, arr.length) @@ -529,7 +529,7 @@ describe('FixedArray', () => { it('should decode and encode fixed array with objects', () => { type Point = { x: uint64; y: uint64 } const arr = new FixedArray({ x: 1, y: 2 }, { x: 3, y: 4 }) - class ObjStruct extends arc4.Struct<{ x: arc4.UintN64; y: arc4.UintN64 }> {} + class ObjStruct extends arc4.Struct<{ x: arc4.Uint64; y: arc4.Uint64 }> {} const encoded = encodeArc4(arr) const interpreted = interpretAsArc4>(encoded) const decoded = decodeArc4>(encoded) @@ -543,10 +543,10 @@ describe('FixedArray', () => { }) it('should decode and encode arc4 fixed array', () => { - const arr = new FixedArray(new arc4.UintN64(100), new arc4.UintN64(200), new arc4.UintN64(300)) + const arr = new FixedArray(new arc4.Uint64(100), new arc4.Uint64(200), new arc4.Uint64(300)) const encoded = encodeArc4(arr) - const interpreted = interpretAsArc4>(encoded) - const decoded = decodeArc4>(encoded) + const interpreted = interpretAsArc4>(encoded) + const decoded = decodeArc4>(encoded) assertMatch(interpreted.length, arr.length) for (let i = 0; i < arr.length; i++) { diff --git a/tests/global-state-arc4-values.spec.ts b/tests/global-state-arc4-values.spec.ts index eec24bc4..b40826e6 100644 --- a/tests/global-state-arc4-values.spec.ts +++ b/tests/global-state-arc4-values.spec.ts @@ -7,9 +7,9 @@ import type { DynamicBytesImpl, StrImpl, } from '@algorandfoundation/algorand-typescript-testing/runtime-helpers' -import { UintNImpl } from '@algorandfoundation/algorand-typescript-testing/runtime-helpers' +import { UintImpl } from '@algorandfoundation/algorand-typescript-testing/runtime-helpers' import type { ARC4Encoded, BitSize } from '@algorandfoundation/algorand-typescript/arc4' -import { Address, Bool, Byte, DynamicBytes, Str, UintN } from '@algorandfoundation/algorand-typescript/arc4' +import { Address, Bool, Byte, DynamicBytes, Str, Uint } from '@algorandfoundation/algorand-typescript/arc4' import { afterEach, beforeAll, describe, expect } from 'vitest' import type { DeliberateAny, FunctionKeys } from '../src/typescript-helpers' import { asUint8Array } from '../src/util' @@ -34,12 +34,12 @@ describe('ARC4 AppGlobal values', async () => { const testData: DeliberateAny[] = ['_implicit_key', ''].flatMap((implicit) => [ { nativeValue: 42, - abiValue: new UintN<64>(42), + abiValue: new Uint<64>(42), methodName: `get${implicit}_arc4_uintn64`, assert: (value: ARC4Encoded, expectedValue: DeliberateAny) => { - const arc4Value = value as UintNImpl - const bitSize = UintNImpl.getMaxBitsLength(arc4Value.typeInfo) - expect(arc4Value).toBeInstanceOf(UintN) + const arc4Value = value as UintImpl + const bitSize = UintImpl.getMaxBitsLength(arc4Value.typeInfo) + expect(arc4Value).toBeInstanceOf(Uint) expect(bitSize).toEqual(64) expect(arc4Value.native).toEqual(expectedValue) }, @@ -86,12 +86,12 @@ describe('ARC4 AppGlobal values', async () => { }, { nativeValue: 2n ** 102n, - abiValue: new UintN<128>(2n ** 102n), + abiValue: new Uint<128>(2n ** 102n), methodName: `get${implicit}_arc4_uintn128`, assert: (value: ARC4Encoded, expectedValue: DeliberateAny) => { - const arc4Value = value as UintNImpl - const bitSize = UintNImpl.getMaxBitsLength(arc4Value.typeInfo) - expect(arc4Value).toBeInstanceOf(UintN) + const arc4Value = value as UintImpl + const bitSize = UintImpl.getMaxBitsLength(arc4Value.typeInfo) + expect(arc4Value).toBeInstanceOf(Uint) expect(bitSize).toEqual(128) expect(arc4Value.native).toEqual(expectedValue) }, diff --git a/tests/local-state-arc4-values.spec.ts b/tests/local-state-arc4-values.spec.ts index 2f7c767e..c3b4f758 100644 --- a/tests/local-state-arc4-values.spec.ts +++ b/tests/local-state-arc4-values.spec.ts @@ -8,9 +8,9 @@ import type { DynamicBytesImpl, StrImpl, } from '@algorandfoundation/algorand-typescript-testing/runtime-helpers' -import { UintNImpl } from '@algorandfoundation/algorand-typescript-testing/runtime-helpers' +import { UintImpl } from '@algorandfoundation/algorand-typescript-testing/runtime-helpers' import type { ARC4Encoded, BitSize } from '@algorandfoundation/algorand-typescript/arc4' -import { Address, Bool, Byte, DynamicBytes, Str, UintN } from '@algorandfoundation/algorand-typescript/arc4' +import { Address, Bool, Byte, DynamicBytes, Str, Uint } from '@algorandfoundation/algorand-typescript/arc4' import { afterEach, beforeAll, describe, expect } from 'vitest' import { OnApplicationComplete } from '../src/constants' import type { DeliberateAny } from '../src/typescript-helpers' @@ -36,9 +36,9 @@ describe('ARC4 AppLocal values', async () => { { methodName: `get${implicit}_arc4_uintn64`, assert: (value: ARC4Encoded, expectedValue: DeliberateAny) => { - const arc4Value = value as UintNImpl - const bitSize = UintNImpl.getMaxBitsLength(arc4Value.typeInfo) - expect(arc4Value).toBeInstanceOf(UintN) + const arc4Value = value as UintImpl + const bitSize = UintImpl.getMaxBitsLength(arc4Value.typeInfo) + expect(arc4Value).toBeInstanceOf(Uint) expect(bitSize).toEqual(64) expect(arc4Value.native).toEqual(expectedValue) }, @@ -78,9 +78,9 @@ describe('ARC4 AppLocal values', async () => { { methodName: `get${implicit}_arc4_uintn128`, assert: (value: ARC4Encoded, expectedValue: DeliberateAny) => { - const arc4Value = value as UintNImpl - const bitSize = UintNImpl.getMaxBitsLength(arc4Value.typeInfo) - expect(arc4Value).toBeInstanceOf(UintN) + const arc4Value = value as UintImpl + const bitSize = UintImpl.getMaxBitsLength(arc4Value.typeInfo) + expect(arc4Value).toBeInstanceOf(Uint) expect(bitSize).toEqual(128) expect(arc4Value.native).toEqual(expectedValue) }, diff --git a/tests/log.spec.ts b/tests/log.spec.ts index d038f2e0..936e9515 100644 --- a/tests/log.spec.ts +++ b/tests/log.spec.ts @@ -1,4 +1,4 @@ -import { Bytes, log, Uint64 } from '@algorandfoundation/algorand-typescript' +import { arc4, Bytes, log, Uint64 } from '@algorandfoundation/algorand-typescript' import { TestExecutionContext } from '@algorandfoundation/algorand-typescript-testing' import { Bool, @@ -6,12 +6,11 @@ import { StaticArray, Str, Tuple, - UFixedNxM, - UintN, - UintN16, - UintN32, - UintN64, - UintN8, + UFixed, + Uint, + Uint16, + Uint32, + Uint8, } from '@algorandfoundation/algorand-typescript/arc4' import { afterEach, beforeAll, describe, expect } from 'vitest' import { MAX_UINT512, MAX_UINT64 } from '../src/constants' @@ -41,13 +40,13 @@ describe('log', async () => { const d = BigInt(MAX_UINT512) const e = new Bool(true) const f = new Str('greetings') - const g = new UintN<64>(42) - const h = new UintN<256>(512) - const i = new UFixedNxM<32, 8>('42.94967295') - const j = new UFixedNxM<256, 16>('25.5') - const k = new StaticArray(new UintN8(1), new UintN8(2), new UintN8(3)) - const m = new DynamicArray(new UintN16(1), new UintN16(2), new UintN16(3)) - const n = new Tuple((new UintN32(1), new UintN64(2), new Str('hello'))) + const g = new Uint<64>(42) + const h = new Uint<256>(512) + const i = new UFixed<32, 8>('42.94967295') + const j = new UFixed<256, 16>('25.5') + const k = new StaticArray(new Uint8(1), new Uint8(2), new Uint8(3)) + const m = new DynamicArray(new Uint16(1), new Uint16(2), new Uint16(3)) + const n = new Tuple((new Uint32(1), new arc4.Uint64(2), new Str('hello'))) const avmResult = await getAvmResultLog( { appClient }, diff --git a/tests/native-mutable-array.spec.ts b/tests/native-mutable-array.spec.ts index 76d5bb0b..eece6603 100644 --- a/tests/native-mutable-array.spec.ts +++ b/tests/native-mutable-array.spec.ts @@ -21,7 +21,7 @@ class TestContract extends Contract { nativeArrayMethod(a: uint64[], b: string): readonly [uint64[], string] { return [a.slice(0, 2), b] } - arc4ArrayMethod(a: arc4.UintN8[], b: arc4.Str): readonly [arc4.UintN8[], arc4.Str] { + arc4ArrayMethod(a: arc4.Uint8[], b: arc4.Str): readonly [arc4.Uint8[], arc4.Str] { return [a.slice(0, 2), b] } } @@ -64,46 +64,46 @@ describe('native mutable array', () => { describe('store arc4 value in native array', () => { it('can store primitive arc4 values', () => { - const a1: arc4.UintN64[] = [new arc4.UintN64(1), new arc4.UintN64(2), new arc4.UintN64(3)] - assertMatch(a1, [new arc4.UintN64(1), new arc4.UintN64(2), new arc4.UintN64(3)]) + const a1: arc4.Uint64[] = [new arc4.Uint64(1), new arc4.Uint64(2), new arc4.Uint64(3)] + assertMatch(a1, [new arc4.Uint64(1), new arc4.Uint64(2), new arc4.Uint64(3)]) const a2 = clone(a1) - a2[0] = new arc4.UintN64(10) - assertMatch(a1, [new arc4.UintN64(1), new arc4.UintN64(2), new arc4.UintN64(3)]) - assertMatch(a2, [new arc4.UintN64(10), new arc4.UintN64(2), new arc4.UintN64(3)]) + a2[0] = new arc4.Uint64(10) + assertMatch(a1, [new arc4.Uint64(1), new arc4.Uint64(2), new arc4.Uint64(3)]) + assertMatch(a2, [new arc4.Uint64(10), new arc4.Uint64(2), new arc4.Uint64(3)]) - const a3: arc4.UintN64[] = [] + const a3: arc4.Uint64[] = [] assertMatch(a3, []) }) it('can store arc4 dynamic array', () => { - const a1: arc4.DynamicArray[] = [new arc4.DynamicArray(new arc4.UintN64(1), new arc4.UintN64(2), new arc4.UintN64(3))] - assertMatch(a1[0], [new arc4.UintN64(1), new arc4.UintN64(2), new arc4.UintN64(3)]) + const a1: arc4.DynamicArray[] = [new arc4.DynamicArray(new arc4.Uint64(1), new arc4.Uint64(2), new arc4.Uint64(3))] + assertMatch(a1[0], [new arc4.Uint64(1), new arc4.Uint64(2), new arc4.Uint64(3)]) const a2 = clone(a1) - a2[0][0] = new arc4.UintN64(10) - assertMatch(a1[0], [new arc4.UintN64(1), new arc4.UintN64(2), new arc4.UintN64(3)]) - assertMatch(a2[0], [new arc4.UintN64(10), new arc4.UintN64(2), new arc4.UintN64(3)]) + a2[0][0] = new arc4.Uint64(10) + assertMatch(a1[0], [new arc4.Uint64(1), new arc4.Uint64(2), new arc4.Uint64(3)]) + assertMatch(a2[0], [new arc4.Uint64(10), new arc4.Uint64(2), new arc4.Uint64(3)]) }) it('can store arc4 static array', () => { - const a1: arc4.StaticArray[] = [new arc4.StaticArray(new arc4.UintN64(1), new arc4.UintN64(2), new arc4.UintN64(3))] - assertMatch(a1[0], [new arc4.UintN64(1), new arc4.UintN64(2), new arc4.UintN64(3)]) + const a1: arc4.StaticArray[] = [new arc4.StaticArray(new arc4.Uint64(1), new arc4.Uint64(2), new arc4.Uint64(3))] + assertMatch(a1[0], [new arc4.Uint64(1), new arc4.Uint64(2), new arc4.Uint64(3)]) const a2 = clone(a1) - a2[0][0] = new arc4.UintN64(10) - assertMatch(a1[0], [new arc4.UintN64(1), new arc4.UintN64(2), new arc4.UintN64(3)]) - assertMatch(a2[0], [new arc4.UintN64(10), new arc4.UintN64(2), new arc4.UintN64(3)]) + a2[0][0] = new arc4.Uint64(10) + assertMatch(a1[0], [new arc4.Uint64(1), new arc4.Uint64(2), new arc4.Uint64(3)]) + assertMatch(a2[0], [new arc4.Uint64(10), new arc4.Uint64(2), new arc4.Uint64(3)]) }) it('can store arc4 tuple', () => { - const a1: arc4.Tuple[] = [new arc4.Tuple(new arc4.UintN64(1), new arc4.Str('Hello'))] - assertMatch(a1[0].native, [new arc4.UintN64(1), new arc4.Str('Hello')]) + const a1: arc4.Tuple[] = [new arc4.Tuple(new arc4.Uint64(1), new arc4.Str('Hello'))] + assertMatch(a1[0].native, [new arc4.Uint64(1), new arc4.Str('Hello')]) const a2 = clone(a1) - a2[0] = new arc4.Tuple(new arc4.UintN64(10), new arc4.Str('hello')) - assertMatch(a1[0].native, [new arc4.UintN64(1), new arc4.Str('Hello')]) - assertMatch(a2[0].native, [new arc4.UintN64(10), new arc4.Str('hello')]) + a2[0] = new arc4.Tuple(new arc4.Uint64(10), new arc4.Str('hello')) + assertMatch(a1[0].native, [new arc4.Uint64(1), new arc4.Str('Hello')]) + assertMatch(a2[0].native, [new arc4.Uint64(10), new arc4.Str('hello')]) }) }) @@ -309,7 +309,7 @@ describe('native mutable array', () => { it('should decode and encode mutable uint64 array', () => { const arr: uint64[] = [10, 20, 30, 40] const encoded = encodeArc4(arr) - const interpreted = interpretAsArc4>(encoded) + const interpreted = interpretAsArc4>(encoded) const decoded = decodeArc4(encoded) assertMatch(interpreted.length, arr.length) @@ -361,7 +361,7 @@ describe('native mutable array', () => { it('should decode and encode mutable nested array', () => { const arr: uint64[][] = [[1, 2], [3, 4, 5], [6], [7, 8, 9, 10]] const encoded = encodeArc4(arr) - const interpreted = interpretAsArc4>>(encoded) + const interpreted = interpretAsArc4>>(encoded) const decoded = decodeArc4(encoded) assertMatch(interpreted.length, arr.length) @@ -381,7 +381,7 @@ describe('native mutable array', () => { new FixedArray(5, 6), ] const encoded = encodeArc4(arr) - const interpreted = interpretAsArc4>>(encoded) + const interpreted = interpretAsArc4>>(encoded) const decoded = decodeArc4[]>(encoded) assertMatch(interpreted.length, arr.length) @@ -401,7 +401,7 @@ describe('native mutable array', () => { [30, 'third'], ] const encoded = encodeArc4(arr) - const interpreted = interpretAsArc4>>(encoded) + const interpreted = interpretAsArc4>>(encoded) const decoded = decodeArc4<[uint64, string][]>(encoded) assertMatch(interpreted.length, arr.length) @@ -419,7 +419,7 @@ describe('native mutable array', () => { { x: 3, y: 4 }, { x: 5, y: 6 }, ] - class PointStruct extends arc4.Struct<{ x: arc4.UintN64; y: arc4.UintN64 }> {} + class PointStruct extends arc4.Struct<{ x: arc4.Uint64; y: arc4.Uint64 }> {} const encoded = encodeArc4(arr) const interpreted = interpretAsArc4>(encoded) const decoded = decodeArc4(encoded) @@ -433,10 +433,10 @@ describe('native mutable array', () => { }) it('should decode and encode mutable arc4 array', () => { - const arr: arc4.UintN64[] = [new arc4.UintN64(100), new arc4.UintN64(200), new arc4.UintN64(300), new arc4.UintN64(400)] + const arr: arc4.Uint64[] = [new arc4.Uint64(100), new arc4.Uint64(200), new arc4.Uint64(300), new arc4.Uint64(400)] const encoded = encodeArc4(arr) - const interpreted = interpretAsArc4>(encoded) - const decoded = decodeArc4(encoded) + const interpreted = interpretAsArc4>(encoded) + const decoded = decodeArc4(encoded) assertMatch(interpreted.length, arr.length) for (let i = 0; i < arr.length; i++) { @@ -448,7 +448,7 @@ describe('native mutable array', () => { it('should decode and encode empty mutable array', () => { const arr: uint64[] = [] const encoded = encodeArc4(arr) - const interpreted = interpretAsArc4>(encoded) + const interpreted = interpretAsArc4>(encoded) const decoded = decodeArc4(encoded) assertMatch(interpreted.length, 0) @@ -463,7 +463,7 @@ describe('native mutable array', () => { arr[0] = 10 const encoded = encodeArc4(arr) - const interpreted = interpretAsArc4>(encoded) + const interpreted = interpretAsArc4>(encoded) const decoded = decodeArc4(encoded) const expected = [10, 2, 3, 4, 5] diff --git a/tests/native-mutable-object.spec.ts b/tests/native-mutable-object.spec.ts index e86ef61a..83cc6990 100644 --- a/tests/native-mutable-object.spec.ts +++ b/tests/native-mutable-object.spec.ts @@ -94,7 +94,7 @@ type DeepNestedObj = { } type Arc4PrimitiveObj = { - a: arc4.UintN64 + a: arc4.Uint64 b: arc4.Bool c: arc4.Str d: arc4.Byte @@ -102,19 +102,19 @@ type Arc4PrimitiveObj = { type Arc4DynamicArrayObj = { a: uint64 - b: arc4.DynamicArray + b: arc4.DynamicArray c: string } type Arc4StaticArrayObj = { a: uint64 - b: arc4.StaticArray + b: arc4.StaticArray c: string } type Arc4TupleObj = { a: uint64 - b: arc4.Tuple + b: arc4.Tuple c: string } @@ -327,26 +327,26 @@ describe('native mutable object', () => { describe('store arc4 value', () => { it('can store primitive arc4 values', () => { const obj: Arc4PrimitiveObj = { - a: new arc4.UintN64(42), + a: new arc4.Uint64(42), b: new arc4.Bool(true), c: new arc4.Str('hello'), d: new arc4.Byte(125), } const obj2 = clone(obj) - obj2.a = new arc4.UintN64(100) + obj2.a = new arc4.Uint64(100) obj2.b = new arc4.Bool(false) obj2.c = new arc4.Str('world') obj2.d = new arc4.Byte(42) assertMatch(obj, { - a: new arc4.UintN64(42), + a: new arc4.Uint64(42), b: new arc4.Bool(true), c: new arc4.Str('hello'), d: new arc4.Byte(125), }) assertMatch(obj2, { - a: new arc4.UintN64(100), + a: new arc4.Uint64(100), b: new arc4.Bool(false), c: new arc4.Str('world'), d: new arc4.Byte(42), @@ -356,45 +356,45 @@ describe('native mutable object', () => { it('can store arc4 dynamic array', () => { const obj: Arc4DynamicArrayObj = { a: 42, - b: new arc4.DynamicArray(new arc4.UintN64(10), new arc4.UintN64(20), new arc4.UintN64(30)), + b: new arc4.DynamicArray(new arc4.Uint64(10), new arc4.Uint64(20), new arc4.Uint64(30)), c: 'test', } const obj2 = clone(obj) - obj2.b[0] = new arc4.UintN64(100) - obj2.b.push(new arc4.UintN64(40)) + obj2.b[0] = new arc4.Uint64(100) + obj2.b.push(new arc4.Uint64(40)) - assertMatch(obj.b, [new arc4.UintN64(10), new arc4.UintN64(20), new arc4.UintN64(30)]) - assertMatch(obj2.b, [new arc4.UintN64(100), new arc4.UintN64(20), new arc4.UintN64(30), new arc4.UintN64(40)]) + assertMatch(obj.b, [new arc4.Uint64(10), new arc4.Uint64(20), new arc4.Uint64(30)]) + assertMatch(obj2.b, [new arc4.Uint64(100), new arc4.Uint64(20), new arc4.Uint64(30), new arc4.Uint64(40)]) }) it('can store arc4 static array', () => { const obj: Arc4StaticArrayObj = { a: 42, - b: new arc4.StaticArray(new arc4.UintN64(10), new arc4.UintN64(20), new arc4.UintN64(30)), + b: new arc4.StaticArray(new arc4.Uint64(10), new arc4.Uint64(20), new arc4.Uint64(30)), c: 'test', } const obj2 = clone(obj) - obj2.b[0] = new arc4.UintN64(100) - obj2.b[2] = new arc4.UintN64(300) + obj2.b[0] = new arc4.Uint64(100) + obj2.b[2] = new arc4.Uint64(300) - assertMatch(obj.b, [new arc4.UintN64(10), new arc4.UintN64(20), new arc4.UintN64(30)]) - assertMatch(obj2.b, [new arc4.UintN64(100), new arc4.UintN64(20), new arc4.UintN64(300)]) + assertMatch(obj.b, [new arc4.Uint64(10), new arc4.Uint64(20), new arc4.Uint64(30)]) + assertMatch(obj2.b, [new arc4.Uint64(100), new arc4.Uint64(20), new arc4.Uint64(300)]) }) it('can store arc4 tuple', () => { const obj: Arc4TupleObj = { a: 42, - b: new arc4.Tuple(new arc4.UintN64(10), new arc4.Str('hello'), new arc4.Bool(true)), + b: new arc4.Tuple(new arc4.Uint64(10), new arc4.Str('hello'), new arc4.Bool(true)), c: 'test', } const obj2 = clone(obj) - obj2.b = new arc4.Tuple(new arc4.UintN64(100), new arc4.Str('world'), new arc4.Bool(false)) + obj2.b = new arc4.Tuple(new arc4.Uint64(100), new arc4.Str('world'), new arc4.Bool(false)) - assertMatch(obj.b.native, [new arc4.UintN64(10), new arc4.Str('hello'), new arc4.Bool(true)]) - assertMatch(obj2.b.native, [new arc4.UintN64(100), new arc4.Str('world'), new arc4.Bool(false)]) + assertMatch(obj.b.native, [new arc4.Uint64(10), new arc4.Str('hello'), new arc4.Bool(true)]) + assertMatch(obj2.b.native, [new arc4.Uint64(100), new arc4.Str('world'), new arc4.Bool(false)]) }) }) @@ -584,7 +584,7 @@ describe('native mutable object', () => { describe('decode and encode', () => { it('should decode and encode simple mutable object', () => { class SimpleObjStruct extends arc4.Struct<{ - a: arc4.UintN64 + a: arc4.Uint64 b: arc4.Bool c: arc4.Str d: arc4.DynamicBytes @@ -603,12 +603,12 @@ describe('native mutable object', () => { it('should decode and encode nested mutable object', () => { class ObjectStruct extends arc4.Struct<{ - x: arc4.UintN64 + x: arc4.Uint64 y: arc4.Str z: arc4.Bool }> {} class NestedObjStruct extends arc4.Struct<{ - a: arc4.UintN64 + a: arc4.Uint64 b: arc4.Bool c: arc4.Str d: ObjectStruct @@ -636,10 +636,10 @@ describe('native mutable object', () => { it('should decode and encode array object', () => { class ArrayObjStruct extends arc4.Struct<{ - a: arc4.UintN64 + a: arc4.Uint64 b: arc4.Bool c: arc4.Str - d: arc4.DynamicArray + d: arc4.DynamicArray }> {} const obj: ArrayObj = { @@ -665,10 +665,10 @@ describe('native mutable object', () => { it('should decode and encode fixed array object', () => { class FixedArrayObjStruct extends arc4.Struct<{ - a: arc4.UintN64 + a: arc4.Uint64 b: arc4.Bool c: arc4.Str - d: arc4.StaticArray + d: arc4.StaticArray }> {} const obj: FixedArrayObj = { @@ -694,10 +694,10 @@ describe('native mutable object', () => { it('should decode and encode tuple object', () => { class TupleObjStruct extends arc4.Struct<{ - a: arc4.UintN64 + a: arc4.Uint64 b: arc4.Bool c: arc4.Str - d: arc4.Tuple<[arc4.UintN64, arc4.Str, arc4.Bool]> + d: arc4.Tuple<[arc4.Uint64, arc4.Str, arc4.Bool]> }> {} const obj: TupleObj = { @@ -722,14 +722,14 @@ describe('native mutable object', () => { it('should decode and encode arc4 primitive object', () => { class Arc4PrimitiveObjStruct extends arc4.Struct<{ - a: arc4.UintN64 + a: arc4.Uint64 b: arc4.Bool c: arc4.Str d: arc4.Byte }> {} const obj: Arc4PrimitiveObj = { - a: new arc4.UintN64(999), + a: new arc4.Uint64(999), b: new arc4.Bool(false), c: new arc4.Str('arc4 test'), d: new arc4.Byte(255), @@ -748,16 +748,16 @@ describe('native mutable object', () => { it('should decode and encode deep nested object', () => { class ObjStruct extends arc4.Struct<{ - p: arc4.UintN64 + p: arc4.Uint64 q: arc4.Str }> {} class NestedObjStruct extends arc4.Struct<{ x: ObjStruct y: arc4.DynamicArray - z: arc4.Tuple<[arc4.UintN64, arc4.Bool]> + z: arc4.Tuple<[arc4.Uint64, arc4.Bool]> }> {} class DeepNestedObjStruct extends arc4.Struct<{ - a: arc4.UintN64 + a: arc4.Uint64 b: NestedObjStruct c: arc4.Str }> {} diff --git a/tests/native-readonly-array.spec.ts b/tests/native-readonly-array.spec.ts index 8f7836b0..5826f602 100644 --- a/tests/native-readonly-array.spec.ts +++ b/tests/native-readonly-array.spec.ts @@ -21,7 +21,7 @@ class TestContract extends Contract { nativeReadonlyArrayMethod(a: readonly uint64[], b: string): readonly [readonly uint64[], string] { return [a.slice(0, 2), b] } - arc4ReadonlyArrayMethod(a: readonly arc4.UintN8[], b: arc4.Str): readonly [readonly arc4.UintN8[], arc4.Str] { + arc4ReadonlyArrayMethod(a: readonly arc4.Uint8[], b: arc4.Str): readonly [readonly arc4.Uint8[], arc4.Str] { return [a.slice(0, 2), b] } } @@ -98,48 +98,48 @@ describe('native readonly array', () => { describe('store arc4 value in readonly array', () => { it('can store primitive arc4 values', () => { - const a1: readonly arc4.UintN64[] = [new arc4.UintN64(1), new arc4.UintN64(2), new arc4.UintN64(3)] - assertMatch(a1, [new arc4.UintN64(1), new arc4.UintN64(2), new arc4.UintN64(3)]) + const a1: readonly arc4.Uint64[] = [new arc4.Uint64(1), new arc4.Uint64(2), new arc4.Uint64(3)] + assertMatch(a1, [new arc4.Uint64(1), new arc4.Uint64(2), new arc4.Uint64(3)]) const a2 = clone(a1) - assertMatch(a1, [new arc4.UintN64(1), new arc4.UintN64(2), new arc4.UintN64(3)]) - assertMatch(a2, [new arc4.UintN64(1), new arc4.UintN64(2), new arc4.UintN64(3)]) + assertMatch(a1, [new arc4.Uint64(1), new arc4.Uint64(2), new arc4.Uint64(3)]) + assertMatch(a2, [new arc4.Uint64(1), new arc4.Uint64(2), new arc4.Uint64(3)]) - const a3: readonly arc4.UintN64[] = [] + const a3: readonly arc4.Uint64[] = [] assertMatch(a3, []) }) it('can store arc4 dynamic array', () => { - const a1: readonly arc4.DynamicArray[] = [ - new arc4.DynamicArray(new arc4.UintN64(1), new arc4.UintN64(2), new arc4.UintN64(3)), + const a1: readonly arc4.DynamicArray[] = [ + new arc4.DynamicArray(new arc4.Uint64(1), new arc4.Uint64(2), new arc4.Uint64(3)), ] - assertMatch(a1[0], [new arc4.UintN64(1), new arc4.UintN64(2), new arc4.UintN64(3)]) + assertMatch(a1[0], [new arc4.Uint64(1), new arc4.Uint64(2), new arc4.Uint64(3)]) const a2 = clone(a1) - a2[0].push(new arc4.UintN64(4)) - assertMatch(a1[0], [new arc4.UintN64(1), new arc4.UintN64(2), new arc4.UintN64(3)]) - assertMatch(a2[0], [new arc4.UintN64(1), new arc4.UintN64(2), new arc4.UintN64(3), new arc4.UintN64(4)]) + a2[0].push(new arc4.Uint64(4)) + assertMatch(a1[0], [new arc4.Uint64(1), new arc4.Uint64(2), new arc4.Uint64(3)]) + assertMatch(a2[0], [new arc4.Uint64(1), new arc4.Uint64(2), new arc4.Uint64(3), new arc4.Uint64(4)]) }) it('can store arc4 static array', () => { - const a1: readonly arc4.StaticArray[] = [ - new arc4.StaticArray(new arc4.UintN64(1), new arc4.UintN64(2), new arc4.UintN64(3)), + const a1: readonly arc4.StaticArray[] = [ + new arc4.StaticArray(new arc4.Uint64(1), new arc4.Uint64(2), new arc4.Uint64(3)), ] - assertMatch(a1[0], [new arc4.UintN64(1), new arc4.UintN64(2), new arc4.UintN64(3)]) + assertMatch(a1[0], [new arc4.Uint64(1), new arc4.Uint64(2), new arc4.Uint64(3)]) const a2 = clone(a1) - a2[0][0] = new arc4.UintN64(10) - assertMatch(a1[0], [new arc4.UintN64(1), new arc4.UintN64(2), new arc4.UintN64(3)]) - assertMatch(a2[0], [new arc4.UintN64(10), new arc4.UintN64(2), new arc4.UintN64(3)]) + a2[0][0] = new arc4.Uint64(10) + assertMatch(a1[0], [new arc4.Uint64(1), new arc4.Uint64(2), new arc4.Uint64(3)]) + assertMatch(a2[0], [new arc4.Uint64(10), new arc4.Uint64(2), new arc4.Uint64(3)]) }) it('can store arc4 tuple', () => { - const a1: readonly arc4.Tuple[] = [new arc4.Tuple(new arc4.UintN64(1), new arc4.Str('Hello'))] - assertMatch(a1[0].native, [new arc4.UintN64(1), new arc4.Str('Hello')]) + const a1: readonly arc4.Tuple[] = [new arc4.Tuple(new arc4.Uint64(1), new arc4.Str('Hello'))] + assertMatch(a1[0].native, [new arc4.Uint64(1), new arc4.Str('Hello')]) const a2 = clone(a1) - assertMatch(a1[0].native, [new arc4.UintN64(1), new arc4.Str('Hello')]) - assertMatch(a2[0].native, [new arc4.UintN64(1), new arc4.Str('Hello')]) + assertMatch(a1[0].native, [new arc4.Uint64(1), new arc4.Str('Hello')]) + assertMatch(a2[0].native, [new arc4.Uint64(1), new arc4.Str('Hello')]) }) }) @@ -340,7 +340,7 @@ describe('native readonly array', () => { it('should decode and encode readonly uint64 array', () => { const arr: readonly uint64[] = [10, 20, 30, 40] const encoded = encodeArc4(arr) - const interpreted = interpretAsArc4>(encoded) + const interpreted = interpretAsArc4>(encoded) const decoded = decodeArc4(encoded) assertMatch(interpreted.length, arr.length) @@ -392,7 +392,7 @@ describe('native readonly array', () => { it('should decode and encode readonly nested array', () => { const arr: readonly (readonly uint64[])[] = [[1, 2], [3, 4, 5], [6]] const encoded = encodeArc4(arr) - const interpreted = interpretAsArc4>>(encoded) + const interpreted = interpretAsArc4>>(encoded) const decoded = decodeArc4(encoded) assertMatch(interpreted.length, arr.length) @@ -412,7 +412,7 @@ describe('native readonly array', () => { new FixedArray(5, 6), ] const encoded = encodeArc4(arr) - const interpreted = interpretAsArc4>>(encoded) + const interpreted = interpretAsArc4>>(encoded) const decoded = decodeArc4[]>(encoded) assertMatch(interpreted.length, arr.length) @@ -432,7 +432,7 @@ describe('native readonly array', () => { [30, 'third'], ] const encoded = encodeArc4(arr) - const interpreted = interpretAsArc4>>(encoded) + const interpreted = interpretAsArc4>>(encoded) const decoded = decodeArc4(encoded) assertMatch(interpreted.length, arr.length) @@ -450,7 +450,7 @@ describe('native readonly array', () => { { x: 3, y: 4 }, { x: 5, y: 6 }, ] - class PointStruct extends arc4.Struct<{ x: arc4.UintN64; y: arc4.UintN64 }> {} + class PointStruct extends arc4.Struct<{ x: arc4.Uint64; y: arc4.Uint64 }> {} const encoded = encodeArc4(arr) const interpreted = interpretAsArc4>(encoded) const decoded = decodeArc4(encoded) @@ -464,10 +464,10 @@ describe('native readonly array', () => { }) it('should decode and encode readonly arc4 array', () => { - const arr: readonly arc4.UintN64[] = [new arc4.UintN64(100), new arc4.UintN64(200), new arc4.UintN64(300)] + const arr: readonly arc4.Uint64[] = [new arc4.Uint64(100), new arc4.Uint64(200), new arc4.Uint64(300)] const encoded = encodeArc4(arr) - const interpreted = interpretAsArc4>(encoded) - const decoded = decodeArc4(encoded) + const interpreted = interpretAsArc4>(encoded) + const decoded = decodeArc4(encoded) assertMatch(interpreted.length, arr.length) for (let i = 0; i < arr.length; i++) { @@ -479,7 +479,7 @@ describe('native readonly array', () => { it('should decode and encode empty readonly array', () => { const arr: readonly uint64[] = [] const encoded = encodeArc4(arr) - const interpreted = interpretAsArc4>(encoded) + const interpreted = interpretAsArc4>(encoded) const decoded = decodeArc4(encoded) assertMatch(interpreted.length, 0) diff --git a/tests/native-readonly-object.spec.ts b/tests/native-readonly-object.spec.ts index e87ea5a7..81af00ef 100644 --- a/tests/native-readonly-object.spec.ts +++ b/tests/native-readonly-object.spec.ts @@ -83,7 +83,7 @@ type DeepNestedReadonlyObj = Readonly<{ }> type Arc4PrimitiveReadonlyObj = Readonly<{ - a: arc4.UintN64 + a: arc4.Uint64 b: arc4.Bool c: arc4.Str d: arc4.Byte @@ -91,19 +91,19 @@ type Arc4PrimitiveReadonlyObj = Readonly<{ type Arc4DynamicArrayReadonlyObj = Readonly<{ a: uint64 - b: arc4.DynamicArray + b: arc4.DynamicArray c: string }> type Arc4StaticArrayReadonlyObj = Readonly<{ a: uint64 - b: arc4.StaticArray + b: arc4.StaticArray c: string }> type Arc4TupleReadonlyObj = Readonly<{ a: uint64 - b: arc4.Tuple + b: arc4.Tuple c: string }> @@ -320,7 +320,7 @@ describe('native readonly object', () => { describe('store arc4 value', () => { it('can store primitive arc4 values', () => { const obj: Arc4PrimitiveReadonlyObj = { - a: new arc4.UintN64(42), + a: new arc4.Uint64(42), b: new arc4.Bool(true), c: new arc4.Str('hello'), d: new arc4.Byte(125), @@ -329,13 +329,13 @@ describe('native readonly object', () => { const obj2 = clone(obj) assertMatch(obj, { - a: new arc4.UintN64(42), + a: new arc4.Uint64(42), b: new arc4.Bool(true), c: new arc4.Str('hello'), d: new arc4.Byte(125), }) assertMatch(obj2, { - a: new arc4.UintN64(42), + a: new arc4.Uint64(42), b: new arc4.Bool(true), c: new arc4.Str('hello'), d: new arc4.Byte(125), @@ -345,22 +345,22 @@ describe('native readonly object', () => { it('can store arc4 dynamic array', () => { const obj: Arc4DynamicArrayReadonlyObj = { a: 42, - b: new arc4.DynamicArray(new arc4.UintN64(10), new arc4.UintN64(20), new arc4.UintN64(30)), + b: new arc4.DynamicArray(new arc4.Uint64(10), new arc4.Uint64(20), new arc4.Uint64(30)), c: 'test', } const obj2 = clone(obj) - obj2.b.push(new arc4.UintN64(40)) + obj2.b.push(new arc4.Uint64(40)) assertMatch(obj, { a: 42, - b: new arc4.DynamicArray(new arc4.UintN64(10), new arc4.UintN64(20), new arc4.UintN64(30)), + b: new arc4.DynamicArray(new arc4.Uint64(10), new arc4.Uint64(20), new arc4.Uint64(30)), c: 'test', }) assertMatch(obj2, { a: 42, - b: new arc4.DynamicArray(new arc4.UintN64(10), new arc4.UintN64(20), new arc4.UintN64(30), new arc4.UintN64(40)), + b: new arc4.DynamicArray(new arc4.Uint64(10), new arc4.Uint64(20), new arc4.Uint64(30), new arc4.Uint64(40)), c: 'test', }) }) @@ -368,22 +368,22 @@ describe('native readonly object', () => { it('can store arc4 static array', () => { const obj: Arc4StaticArrayReadonlyObj = { a: 42, - b: new arc4.StaticArray(new arc4.UintN64(10), new arc4.UintN64(20), new arc4.UintN64(30)), + b: new arc4.StaticArray(new arc4.Uint64(10), new arc4.Uint64(20), new arc4.Uint64(30)), c: 'test', } const obj2 = clone(obj) - obj2.b[0] = new arc4.UintN64(100) - obj2.b[2] = new arc4.UintN64(300) + obj2.b[0] = new arc4.Uint64(100) + obj2.b[2] = new arc4.Uint64(300) assertMatch(obj, { a: 42, - b: new arc4.StaticArray(new arc4.UintN64(10), new arc4.UintN64(20), new arc4.UintN64(30)), + b: new arc4.StaticArray(new arc4.Uint64(10), new arc4.Uint64(20), new arc4.Uint64(30)), c: 'test', }) assertMatch(obj2, { a: 42, - b: new arc4.StaticArray(new arc4.UintN64(100), new arc4.UintN64(20), new arc4.UintN64(300)), + b: new arc4.StaticArray(new arc4.Uint64(100), new arc4.Uint64(20), new arc4.Uint64(300)), c: 'test', }) }) @@ -391,7 +391,7 @@ describe('native readonly object', () => { it('can store arc4 tuple', () => { const obj: Arc4TupleReadonlyObj = { a: 42, - b: new arc4.Tuple(new arc4.UintN64(10), new arc4.Str('hello'), new arc4.Bool(true)), + b: new arc4.Tuple(new arc4.Uint64(10), new arc4.Str('hello'), new arc4.Bool(true)), c: 'test', } @@ -595,7 +595,7 @@ describe('native readonly object', () => { describe('decode and encode', () => { it('should decode and encode simple mutable object', () => { class SimpleObjStruct extends arc4.Struct<{ - a: arc4.UintN64 + a: arc4.Uint64 b: arc4.Bool c: arc4.Str d: arc4.DynamicBytes @@ -614,12 +614,12 @@ describe('native readonly object', () => { it('should decode and encode nested mutable object', () => { class ObjectStruct extends arc4.Struct<{ - x: arc4.UintN64 + x: arc4.Uint64 y: arc4.Str z: arc4.Bool }> {} class NestedObjStruct extends arc4.Struct<{ - a: arc4.UintN64 + a: arc4.Uint64 b: arc4.Bool c: arc4.Str d: ObjectStruct @@ -647,10 +647,10 @@ describe('native readonly object', () => { it('should decode and encode array object', () => { class ArrayObjStruct extends arc4.Struct<{ - a: arc4.UintN64 + a: arc4.Uint64 b: arc4.Bool c: arc4.Str - d: arc4.DynamicArray + d: arc4.DynamicArray }> {} const obj: ArrayObj = { @@ -676,10 +676,10 @@ describe('native readonly object', () => { it('should decode and encode fixed array object', () => { class FixedArrayObjStruct extends arc4.Struct<{ - a: arc4.UintN64 + a: arc4.Uint64 b: arc4.Bool c: arc4.Str - d: arc4.StaticArray + d: arc4.StaticArray }> {} const obj: FixedArrayReadonlyObj = { @@ -705,10 +705,10 @@ describe('native readonly object', () => { it('should decode and encode tuple object', () => { class TupleObjStruct extends arc4.Struct<{ - a: arc4.UintN64 + a: arc4.Uint64 b: arc4.Bool c: arc4.Str - d: arc4.Tuple<[arc4.UintN64, arc4.Str, arc4.Bool]> + d: arc4.Tuple<[arc4.Uint64, arc4.Str, arc4.Bool]> }> {} const obj: TupleObj = { @@ -733,14 +733,14 @@ describe('native readonly object', () => { it('should decode and encode arc4 primitive object', () => { class Arc4PrimitiveObjStruct extends arc4.Struct<{ - a: arc4.UintN64 + a: arc4.Uint64 b: arc4.Bool c: arc4.Str d: arc4.Byte }> {} const obj: Arc4PrimitiveReadonlyObj = { - a: new arc4.UintN64(999), + a: new arc4.Uint64(999), b: new arc4.Bool(false), c: new arc4.Str('arc4 test'), d: new arc4.Byte(255), @@ -759,16 +759,16 @@ describe('native readonly object', () => { it('should decode and encode deep nested object', () => { class ObjStruct extends arc4.Struct<{ - p: arc4.UintN64 + p: arc4.Uint64 q: arc4.Str }> {} class NestedObjStruct extends arc4.Struct<{ x: ObjStruct y: arc4.DynamicArray - z: arc4.Tuple<[arc4.UintN64, arc4.Bool]> + z: arc4.Tuple<[arc4.Uint64, arc4.Bool]> }> {} class DeepNestedObjStruct extends arc4.Struct<{ - a: arc4.UintN64 + a: arc4.Uint64 b: NestedObjStruct c: arc4.Str }> {} diff --git a/tests/references/box-map.spec.ts b/tests/references/box-map.spec.ts index fb63895c..6a5e0c8b 100644 --- a/tests/references/box-map.spec.ts +++ b/tests/references/box-map.spec.ts @@ -1,7 +1,7 @@ import type { biguint, bytes, uint64 } from '@algorandfoundation/algorand-typescript' -import { BigUint, BoxMap, Bytes, clone, op, Uint64 } from '@algorandfoundation/algorand-typescript' +import { arc4, BigUint, BoxMap, Bytes, clone, op, Uint64 } from '@algorandfoundation/algorand-typescript' import { TestExecutionContext } from '@algorandfoundation/algorand-typescript-testing' -import type { StaticArray, UintN16 } from '@algorandfoundation/algorand-typescript/arc4' +import type { StaticArray, Uint16 } from '@algorandfoundation/algorand-typescript/arc4' import { ARC4Encoded, Bool, @@ -10,8 +10,7 @@ import { interpretAsArc4, Str, Struct, - UintN64, - UintN8, + Uint8, } from '@algorandfoundation/algorand-typescript/arc4' import { afterEach, describe, expect, it, test } from 'vitest' import { MAX_UINT64 } from '../../src/constants' @@ -102,12 +101,12 @@ describe('BoxMap', () => { }, { key: new Str('jkl'), - value: new DynamicArray(new UintN64(100), new UintN64(200)), - newValue: new DynamicArray(new UintN64(200), new UintN64(300)), - emptyValue: interpretAsArc4>(Bytes('')), - withBoxContext: (test: (boxMap: BoxMap>) => void) => { + value: new DynamicArray(new arc4.Uint64(100), new arc4.Uint64(200)), + newValue: new DynamicArray(new arc4.Uint64(200), new arc4.Uint64(300)), + emptyValue: interpretAsArc4>(Bytes('')), + withBoxContext: (test: (boxMap: BoxMap>) => void) => { ctx.txn.createScope([ctx.any.txn.applicationCall()]).execute(() => { - const boxMap = BoxMap>({ keyPrefix }) + const boxMap = BoxMap>({ keyPrefix }) test(boxMap) }) }, @@ -308,21 +307,21 @@ describe('BoxMap', () => { it('can maintain the mutations to the array box value', () => { ctx.txn.createScope([ctx.any.txn.applicationCall()]).execute(() => { - const boxMap = BoxMap>({ keyPrefix }) + const boxMap = BoxMap>({ keyPrefix }) const key = new Str('jkl') - const value = new DynamicArray(new UintN64(100), new UintN64(200)) + const value = new DynamicArray(new arc4.Uint64(100), new arc4.Uint64(200)) boxMap(key).value = value expect(boxMap(key).value.length).toEqual(2) expect(boxMap(key).value.at(-1).native).toEqual(200) // newly pushed value should be retained - boxMap(key).value.push(new UintN64(300)) + boxMap(key).value.push(new arc4.Uint64(300)) expect(boxMap(key).value.length).toEqual(3) expect(boxMap(key).value.at(-1).native).toEqual(300) // setting bytes value through op should be reflected in the box value. const copy = clone(boxMap(key).value) - copy[2] = new UintN64(400) + copy[2] = new arc4.Uint64(400) expect(boxMap(key).value.at(-1).native).toEqual(300) const fullKey = keyPrefix.concat(toBytes(key)) @@ -334,18 +333,18 @@ describe('BoxMap', () => { test('should be able to replace specific bytes values using ref', () => { ctx.txn.createScope([ctx.any.txn.applicationCall()]).execute(() => { - const boxMap = BoxMap>({ keyPrefix: 'a' }) + const boxMap = BoxMap>({ keyPrefix: 'a' }) const box1 = boxMap(1) box1.create() const boxRefA = box1.ref - boxRefA.replace(1, new UintN8(123).bytes) + boxRefA.replace(1, new Uint8(123).bytes) expect(box1.value[0].native).toEqual(123) expect(boxMap(1).value[0].native).toEqual(123) const boxRefB = box1.ref - boxRefB.replace(2, new UintN8(255).bytes) + boxRefB.replace(2, new Uint8(255).bytes) expect(box1.value[1].native).toEqual(65280) expect(boxMap(1).value[1].native).toEqual(65280) @@ -353,12 +352,12 @@ describe('BoxMap', () => { box2.create() const boxRefC = box2.ref - boxRefC.replace(1, new UintN8(223).bytes) + boxRefC.replace(1, new Uint8(223).bytes) expect(box2.value[0].native).toEqual(223) expect(boxMap(2).value[0].native).toEqual(223) const boxRefD = box2.ref - boxRefD.replace(3, new UintN8(255).bytes) + boxRefD.replace(3, new Uint8(255).bytes) expect(box2.value[1].native).toEqual(255) expect(boxMap(2).value[1].native).toEqual(255) }) diff --git a/tests/references/box.spec.ts b/tests/references/box.spec.ts index d11a06fc..e2327f66 100644 --- a/tests/references/box.spec.ts +++ b/tests/references/box.spec.ts @@ -1,7 +1,7 @@ import type { biguint, bytes, uint64 } from '@algorandfoundation/algorand-typescript' -import { BigUint, Box, Bytes, clone, op, Uint64 } from '@algorandfoundation/algorand-typescript' +import { arc4, BigUint, Box, Bytes, clone, op, Uint64 } from '@algorandfoundation/algorand-typescript' import { TestExecutionContext } from '@algorandfoundation/algorand-typescript-testing' -import type { UintN16 } from '@algorandfoundation/algorand-typescript/arc4' +import type { Uint16 } from '@algorandfoundation/algorand-typescript/arc4' import { ARC4Encoded, Bool, @@ -11,9 +11,8 @@ import { StaticArray, Str, Tuple, - UintN32, - UintN64, - UintN8, + Uint32, + Uint8, } from '@algorandfoundation/algorand-typescript/arc4' import { itob } from '@algorandfoundation/algorand-typescript/op' import { afterEach, describe, expect, it, test } from 'vitest' @@ -98,12 +97,12 @@ describe('Box', () => { }, }, { - value: new DynamicArray(new UintN64(100), new UintN64(200)), - newValue: new DynamicArray(new UintN64(200), new UintN64(300)), - emptyValue: interpretAsArc4>(Bytes('')), - withBoxContext: (test: (boxMap: Box>) => void) => { + value: new DynamicArray(new arc4.Uint64(100), new arc4.Uint64(200)), + newValue: new DynamicArray(new arc4.Uint64(200), new arc4.Uint64(300)), + emptyValue: interpretAsArc4>(Bytes('')), + withBoxContext: (test: (boxMap: Box>) => void) => { ctx.txn.createScope([ctx.any.txn.applicationCall()]).execute(() => { - const boxMap = Box>({ key }) + const boxMap = Box>({ key }) test(boxMap) }) }, @@ -292,20 +291,20 @@ describe('Box', () => { it('can maintain the mutations to the box value', () => { ctx.txn.createScope([ctx.any.txn.applicationCall()]).execute(() => { - const box = Box>({ key }) - const value = new DynamicArray(new UintN64(100), new UintN64(200)) + const box = Box>({ key }) + const value = new DynamicArray(new arc4.Uint64(100), new arc4.Uint64(200)) box.value = value expect(box.value.length).toEqual(2) expect(box.value.at(-1).native).toEqual(200) // newly pushed value should be retained - box.value.push(new UintN64(300)) + box.value.push(new arc4.Uint64(300)) expect(box.value.length).toEqual(3) expect(box.value.at(-1).native).toEqual(300) // setting bytes value through op should be reflected in the box value. const copy = clone(box.value) - copy[2] = new UintN64(400) + copy[2] = new arc4.Uint64(400) expect(box.value.at(-1).native).toEqual(300) op.Box.put(key, toBytes(copy)) @@ -316,15 +315,15 @@ describe('Box', () => { test('should be able to replace specific bytes values using ref', () => { ctx.txn.createScope([ctx.any.txn.applicationCall()]).execute(() => { - const box = Box>({ key: 'a' }) + const box = Box>({ key: 'a' }) box.create() const boxRef1 = box.ref - boxRef1.replace(1, new UintN8(123).bytes) + boxRef1.replace(1, new Uint8(123).bytes) expect(box.value[0].native).toEqual(123) const boxRef2 = box.ref - boxRef2.replace(2, new UintN8(255).bytes) + boxRef2.replace(2, new Uint8(255).bytes) expect(box.value[1].native).toEqual(65280) }) }) @@ -333,9 +332,9 @@ describe('Box', () => { it('throw errors if size is not provided for dynamic value type', () => { ctx.txn.createScope([ctx.any.txn.applicationCall()]).execute(() => { const boxStr = Box({ key: 'a' }) - const boxStaticArray = Box, 10>>({ key: 'c' }) - const boxDynamicArray = Box>({ key: 'd' }) - const boxTuple = Box>({ key: 'e' }) + const boxStaticArray = Box, 10>>({ key: 'c' }) + const boxDynamicArray = Box>({ key: 'd' }) + const boxTuple = Box>({ key: 'e' }) const errorMessage = 'does not have a fixed byte size. Please specify a size argument' expect(() => boxStr.create()).toThrow(errorMessage) @@ -350,8 +349,8 @@ describe('Box', () => { const boxBool = Box({ key: 'bool' }) const boxArc4Bool = Box({ key: 'arc4b' }) const boxUint = Box({ key: 'b' }) - const boxStaticArray = Box>({ key: 'c' }) - const boxTuple = Box>({ key: 'e' }) + const boxStaticArray = Box>({ key: 'c' }) + const boxTuple = Box>({ key: 'e' }) const errorMessage = 'Box size cannot be less than' expect(() => boxBool.create({ size: 7 })).toThrow(`${errorMessage} 8`) expect(() => boxArc4Bool.create({ size: 0 })).toThrow(`${errorMessage} 1`) @@ -366,8 +365,8 @@ describe('Box', () => { const boxBool = Box({ key: 'bool' }) const boxArc4Bool = Box({ key: 'arc4b' }) const boxUint = Box({ key: 'b' }) - const boxStaticArray = Box>({ key: 'c' }) - const boxTuple = Box>({ key: 'e' }) + const boxStaticArray = Box>({ key: 'c' }) + const boxTuple = Box>({ key: 'e' }) const errorMessage = 'attempt to box_put wrong size' boxBool.create({ size: 9 }) @@ -383,21 +382,21 @@ describe('Box', () => { expect( () => (boxStaticArray.value = new StaticArray( - new UintN32(100), - new UintN32(200), - new UintN32(300), - new UintN32(400), - new UintN32(500), - new UintN32(600), - new UintN32(700), - new UintN32(800), - new UintN32(900), - new UintN32(1000), + new Uint32(100), + new Uint32(200), + new Uint32(300), + new Uint32(400), + new Uint32(500), + new Uint32(600), + new Uint32(700), + new Uint32(800), + new Uint32(900), + new Uint32(1000), )), ).toThrow(errorMessage) boxTuple.create({ size: 4 }) - expect(() => (boxTuple.value = new Tuple(new UintN8(1), new UintN8(2), new Bool(true), new Bool(false)))).toThrow(errorMessage) + expect(() => (boxTuple.value = new Tuple(new Uint8(1), new Uint8(2), new Bool(true), new Bool(false)))).toThrow(errorMessage) }) }) @@ -406,8 +405,8 @@ describe('Box', () => { const boxBool = Box({ key: 'bool' }) const boxArc4Bool = Box({ key: 'arc4b' }) const boxUint = Box({ key: 'b' }) - const boxStaticArray = Box>({ key: 'c' }) - const boxTuple = Box>({ key: 'e' }) + const boxStaticArray = Box>({ key: 'c' }) + const boxTuple = Box>({ key: 'e' }) boxBool.create() expect(boxBool.length).toEqual(8) @@ -427,22 +426,22 @@ describe('Box', () => { boxStaticArray.create() expect(boxStaticArray.length).toEqual(40) boxStaticArray.value = new StaticArray( - new UintN32(100), - new UintN32(200), - new UintN32(300), - new UintN32(400), - new UintN32(500), - new UintN32(600), - new UintN32(700), - new UintN32(800), - new UintN32(900), - new UintN32(1000), + new Uint32(100), + new Uint32(200), + new Uint32(300), + new Uint32(400), + new Uint32(500), + new Uint32(600), + new Uint32(700), + new Uint32(800), + new Uint32(900), + new Uint32(1000), ) expect(boxStaticArray.length).toEqual(40) boxTuple.create() expect(boxTuple.length).toEqual(3) - boxTuple.value = new Tuple(new UintN8(1), new UintN8(2), new Bool(true), new Bool(false)) + boxTuple.value = new Tuple(new Uint8(1), new Uint8(2), new Bool(true), new Bool(false)) expect(boxTuple.length).toEqual(3) }) }) @@ -450,9 +449,9 @@ describe('Box', () => { it('can set value if size provided is less than required for dynamic value type', () => { ctx.txn.createScope([ctx.any.txn.applicationCall()]).execute(() => { const boxStr = Box({ key: 'a' }) - const boxStaticArray = Box, 10>>({ key: 'c' }) - const boxDynamicArray = Box>({ key: 'd' }) - const boxTuple = Box>({ key: 'e' }) + const boxStaticArray = Box, 10>>({ key: 'c' }) + const boxDynamicArray = Box>({ key: 'd' }) + const boxTuple = Box>({ key: 'e' }) boxStr.create({ size: 2 }) boxStr.value = 'hello' @@ -460,25 +459,25 @@ describe('Box', () => { boxStaticArray.create({ size: 2 }) boxStaticArray.value = new StaticArray( - new DynamicArray(new UintN32(100), new UintN32(200)), - new DynamicArray(new UintN32(300), new UintN32(400)), - new DynamicArray(new UintN32(500), new UintN32(600)), - new DynamicArray(new UintN32(700), new UintN32(800)), - new DynamicArray(new UintN32(900), new UintN32(1000)), - new DynamicArray(new UintN32(1100), new UintN32(1200)), - new DynamicArray(new UintN32(1300), new UintN32(1400)), - new DynamicArray(new UintN32(1500), new UintN32(1600)), - new DynamicArray(new UintN32(1700), new UintN32(1800)), - new DynamicArray(new UintN32(1900), new UintN32(2000)), + new DynamicArray(new Uint32(100), new Uint32(200)), + new DynamicArray(new Uint32(300), new Uint32(400)), + new DynamicArray(new Uint32(500), new Uint32(600)), + new DynamicArray(new Uint32(700), new Uint32(800)), + new DynamicArray(new Uint32(900), new Uint32(1000)), + new DynamicArray(new Uint32(1100), new Uint32(1200)), + new DynamicArray(new Uint32(1300), new Uint32(1400)), + new DynamicArray(new Uint32(1500), new Uint32(1600)), + new DynamicArray(new Uint32(1700), new Uint32(1800)), + new DynamicArray(new Uint32(1900), new Uint32(2000)), ) expect(boxStaticArray.length).toEqual(120) boxDynamicArray.create({ size: 2 }) - boxDynamicArray.value = new DynamicArray(new UintN8(100), new UintN8(200)) + boxDynamicArray.value = new DynamicArray(new Uint8(100), new Uint8(200)) expect(boxDynamicArray.length).toEqual(4) boxTuple.create({ size: 2 }) - boxTuple.value = new Tuple(new UintN8(1), new UintN8(2), new Bool(true), new Bool(false), new Str('hello')) + boxTuple.value = new Tuple(new Uint8(1), new Uint8(2), new Bool(true), new Bool(false), new Str('hello')) expect(boxTuple.length).toEqual(12) }) }) @@ -486,9 +485,9 @@ describe('Box', () => { it('can set value if size provided is larger than required for dynamic value type', () => { ctx.txn.createScope([ctx.any.txn.applicationCall()]).execute(() => { const boxStr = Box({ key: 'a' }) - const boxStaticArray = Box, 10>>({ key: 'c' }) - const boxDynamicArray = Box>({ key: 'd' }) - const boxTuple = Box>({ key: 'e' }) + const boxStaticArray = Box, 10>>({ key: 'c' }) + const boxDynamicArray = Box>({ key: 'd' }) + const boxTuple = Box>({ key: 'e' }) boxStr.create({ size: 200 }) boxStr.value = 'hello' @@ -496,25 +495,25 @@ describe('Box', () => { boxStaticArray.create({ size: 200 }) boxStaticArray.value = new StaticArray( - new DynamicArray(new UintN32(100), new UintN32(200)), - new DynamicArray(new UintN32(300), new UintN32(400)), - new DynamicArray(new UintN32(500), new UintN32(600)), - new DynamicArray(new UintN32(700), new UintN32(800)), - new DynamicArray(new UintN32(900), new UintN32(1000)), - new DynamicArray(new UintN32(1100), new UintN32(1200)), - new DynamicArray(new UintN32(1300), new UintN32(1400)), - new DynamicArray(new UintN32(1500), new UintN32(1600)), - new DynamicArray(new UintN32(1700), new UintN32(1800)), - new DynamicArray(new UintN32(1900), new UintN32(2000)), + new DynamicArray(new Uint32(100), new Uint32(200)), + new DynamicArray(new Uint32(300), new Uint32(400)), + new DynamicArray(new Uint32(500), new Uint32(600)), + new DynamicArray(new Uint32(700), new Uint32(800)), + new DynamicArray(new Uint32(900), new Uint32(1000)), + new DynamicArray(new Uint32(1100), new Uint32(1200)), + new DynamicArray(new Uint32(1300), new Uint32(1400)), + new DynamicArray(new Uint32(1500), new Uint32(1600)), + new DynamicArray(new Uint32(1700), new Uint32(1800)), + new DynamicArray(new Uint32(1900), new Uint32(2000)), ) expect(boxStaticArray.length).toEqual(120) boxDynamicArray.create({ size: 200 }) - boxDynamicArray.value = new DynamicArray(new UintN8(100), new UintN8(200)) + boxDynamicArray.value = new DynamicArray(new Uint8(100), new Uint8(200)) expect(boxDynamicArray.length).toEqual(4) boxTuple.create({ size: 200 }) - boxTuple.value = new Tuple(new UintN8(1), new UintN8(2), new Bool(true), new Bool(false), new Str('hello')) + boxTuple.value = new Tuple(new Uint8(1), new Uint8(2), new Bool(true), new Bool(false), new Str('hello')) expect(boxTuple.length).toEqual(12) }) }) diff --git a/tests/state-op-codes.spec.ts b/tests/state-op-codes.spec.ts index fbb42d66..461a2772 100644 --- a/tests/state-op-codes.spec.ts +++ b/tests/state-op-codes.spec.ts @@ -2,7 +2,7 @@ import { AlgoAmount } from '@algorandfoundation/algokit-utils/types/amount' import type { AppClient } from '@algorandfoundation/algokit-utils/types/app-client' import type { bytes, uint64 } from '@algorandfoundation/algorand-typescript' import { Account, arc4, Bytes, Global, OnCompleteAction, op, TransactionType, Uint64 } from '@algorandfoundation/algorand-typescript' -import { DynamicBytes, UintN64 } from '@algorandfoundation/algorand-typescript/arc4' +import { DynamicBytes } from '@algorandfoundation/algorand-typescript/arc4' import { afterEach, beforeAll, describe, expect } from 'vitest' import { TestExecutionContext } from '../src' import { ABI_RETURN_VALUE_LOG_PREFIX, MIN_TXN_FEE, OnApplicationComplete, ZERO_ADDRESS } from '../src/constants' @@ -393,7 +393,7 @@ describe('State op codes', async () => { test('should be able to pass app call txn as app arg', async () => { const appCallTxn = ctx.any.txn.applicationCall({ appArgs: [arc4.methodSelector('some_value()uint64')], - appLogs: [ABI_RETURN_VALUE_LOG_PREFIX.concat(new UintN64(2).bytes)], + appLogs: [ABI_RETURN_VALUE_LOG_PREFIX.concat(new arc4.Uint64(2).bytes)], }) const contract = ctx.contract.create(AppExpectingEffects) contract.log_group(appCallTxn) From 500265eb344b5482678ca08478c748b67353f358 Mon Sep 17 00:00:00 2001 From: Bobby Lat Date: Thu, 24 Jul 2025 17:18:00 +0700 Subject: [PATCH 21/68] treat `byets` type as statically sized --- docs/code/index/functions/toExternalValue.md | 8 +- .../classes/ContractContext.md | 6 +- .../ledger-context/classes/LedgerContext.md | 20 +-- .../classes/TransactionGroup.md | 8 +- .../arc4/classes/Arc4ValueGenerator.md | 48 +++--- examples/voting/contract.spec.ts | 2 +- package-lock.json | 18 +-- package.json | 4 +- src/constants.ts | 7 +- src/impl/crypto.ts | 22 +-- src/impl/encoded-types/encoded-types.ts | 2 +- src/impl/encoded-types/helpers.ts | 2 +- src/impl/encoded-types/utils.ts | 7 + src/impl/primitives.ts | 139 +++++++++++------- src/runtime-helpers.ts | 1 + src/test-transformer/node-factory.ts | 13 ++ src/test-transformer/visitors.ts | 27 ++-- tests/crypto-op-codes.spec.ts | 49 +++--- tests/fixed-array.spec.ts | 7 +- tests/primitives/bytes.spec.ts | 51 ++++++- tests/references/asset.spec.ts | 2 +- 21 files changed, 272 insertions(+), 171 deletions(-) diff --git a/docs/code/index/functions/toExternalValue.md b/docs/code/index/functions/toExternalValue.md index 8e794b5e..b0bb3dca 100644 --- a/docs/code/index/functions/toExternalValue.md +++ b/docs/code/index/functions/toExternalValue.md @@ -10,7 +10,7 @@ > **toExternalValue**(`val`): `bigint` -Defined in: [src/impl/primitives.ts:42](https://github.com/algorandfoundation/algorand-typescript-testing/blob/main/src/impl/primitives.ts#L42) +Defined in: [src/impl/primitives.ts:43](https://github.com/algorandfoundation/algorand-typescript-testing/blob/main/src/impl/primitives.ts#L43) Converts internal Algorand type representations to their external primitive values. @@ -42,7 +42,7 @@ toExternalValue(bytesVal) // returns Uint8Array([72, 101, 108, 108, 111]) > **toExternalValue**(`val`): `bigint` -Defined in: [src/impl/primitives.ts:43](https://github.com/algorandfoundation/algorand-typescript-testing/blob/main/src/impl/primitives.ts#L43) +Defined in: [src/impl/primitives.ts:44](https://github.com/algorandfoundation/algorand-typescript-testing/blob/main/src/impl/primitives.ts#L44) Converts internal Algorand type representations to their external primitive values. @@ -74,7 +74,7 @@ toExternalValue(bytesVal) // returns Uint8Array([72, 101, 108, 108, 111]) > **toExternalValue**(`val`): `Uint8Array` -Defined in: [src/impl/primitives.ts:44](https://github.com/algorandfoundation/algorand-typescript-testing/blob/main/src/impl/primitives.ts#L44) +Defined in: [src/impl/primitives.ts:45](https://github.com/algorandfoundation/algorand-typescript-testing/blob/main/src/impl/primitives.ts#L45) Converts internal Algorand type representations to their external primitive values. @@ -106,7 +106,7 @@ toExternalValue(bytesVal) // returns Uint8Array([72, 101, 108, 108, 111]) > **toExternalValue**(`val`): `string` -Defined in: [src/impl/primitives.ts:45](https://github.com/algorandfoundation/algorand-typescript-testing/blob/main/src/impl/primitives.ts#L45) +Defined in: [src/impl/primitives.ts:46](https://github.com/algorandfoundation/algorand-typescript-testing/blob/main/src/impl/primitives.ts#L46) Converts internal Algorand type representations to their external primitive values. diff --git a/docs/code/subcontexts/contract-context/classes/ContractContext.md b/docs/code/subcontexts/contract-context/classes/ContractContext.md index 61450d56..a9b70fe9 100644 --- a/docs/code/subcontexts/contract-context/classes/ContractContext.md +++ b/docs/code/subcontexts/contract-context/classes/ContractContext.md @@ -6,7 +6,7 @@ # Class: ContractContext -Defined in: [src/subcontexts/contract-context.ts:146](https://github.com/algorandfoundation/algorand-typescript-testing/blob/main/src/subcontexts/contract-context.ts#L146) +Defined in: [src/subcontexts/contract-context.ts:145](https://github.com/algorandfoundation/algorand-typescript-testing/blob/main/src/subcontexts/contract-context.ts#L145) Provides a context for creating contracts and registering created contract instances with test execution context. @@ -27,7 +27,7 @@ with test execution context. > **create**\<`T`\>(`type`, ...`args`): `T` -Defined in: [src/subcontexts/contract-context.ts:158](https://github.com/algorandfoundation/algorand-typescript-testing/blob/main/src/subcontexts/contract-context.ts#L158) +Defined in: [src/subcontexts/contract-context.ts:157](https://github.com/algorandfoundation/algorand-typescript-testing/blob/main/src/subcontexts/contract-context.ts#L157) Creates a new contract instance and register the created instance with test execution context. @@ -72,7 +72,7 @@ const contract = ctx.contract.create(MyContract); > `static` **createMethodCallTxns**\<`TParams`\>(`contract`, `abiMetadata`, ...`args`): `Transaction`[] -Defined in: [src/subcontexts/contract-context.ts:180](https://github.com/algorandfoundation/algorand-typescript-testing/blob/main/src/subcontexts/contract-context.ts#L180) +Defined in: [src/subcontexts/contract-context.ts:179](https://github.com/algorandfoundation/algorand-typescript-testing/blob/main/src/subcontexts/contract-context.ts#L179) **`Internal`** diff --git a/docs/code/subcontexts/ledger-context/classes/LedgerContext.md b/docs/code/subcontexts/ledger-context/classes/LedgerContext.md index 69910878..2085c7fb 100644 --- a/docs/code/subcontexts/ledger-context/classes/LedgerContext.md +++ b/docs/code/subcontexts/ledger-context/classes/LedgerContext.md @@ -144,7 +144,7 @@ Checks if a box exists for an application by key. The application. -`Application` | `BaseContract` +`BaseContract` | `Application` ##### key @@ -174,7 +174,7 @@ Deletes a box for an application by key. The application. -`Application` | `BaseContract` +`BaseContract` | `Application` ##### key @@ -364,7 +364,7 @@ Retrieves a box for an application by key. The application. -`Application` | `BaseContract` +`BaseContract` | `Application` ##### key @@ -394,7 +394,7 @@ Retrieves global state for an application by key. The application. -`Application` | `BaseContract` +`BaseContract` | `Application` ##### key @@ -424,7 +424,7 @@ Retrieves local state for an application and account by key. The application. -`uint64` | `Application` | `BaseContract` +`uint64` | `BaseContract` | `Application` ##### account @@ -468,7 +468,7 @@ Retrieves a materialised box for an application by key. The application. -`Application` | `BaseContract` +`BaseContract` | `Application` ##### key @@ -658,7 +658,7 @@ Sets a box for an application by key. The application. -`Application` | `BaseContract` +`BaseContract` | `Application` ##### key @@ -692,7 +692,7 @@ Sets global state for an application by key. The application. -`Application` | `BaseContract` +`BaseContract` | `Application` ##### key @@ -732,7 +732,7 @@ Sets local state for an application and account by key. The application. -`uint64` | `Application` | `BaseContract` +`uint64` | `BaseContract` | `Application` ##### account @@ -780,7 +780,7 @@ Cache the materialised box for an application by key. The application. -`Application` | `BaseContract` +`BaseContract` | `Application` ##### key diff --git a/docs/code/subcontexts/transaction-context/classes/TransactionGroup.md b/docs/code/subcontexts/transaction-context/classes/TransactionGroup.md index 1efa02d4..b16e01ff 100644 --- a/docs/code/subcontexts/transaction-context/classes/TransactionGroup.md +++ b/docs/code/subcontexts/transaction-context/classes/TransactionGroup.md @@ -366,7 +366,7 @@ The payment transaction. ### getScratchSlot() -> **getScratchSlot**(`index`): `bytes` \| `uint64` +> **getScratchSlot**(`index`): `uint64` \| `bytes` Defined in: [src/subcontexts/transaction-context.ts:293](https://github.com/algorandfoundation/algorand-typescript-testing/blob/main/src/subcontexts/transaction-context.ts#L293) @@ -382,7 +382,7 @@ The index of the scratch slot. #### Returns -`bytes` \| `uint64` +`uint64` \| `bytes` The scratch slot value. @@ -390,7 +390,7 @@ The scratch slot value. ### getScratchSpace() -> **getScratchSpace**(): (`bytes` \| `uint64`)[] +> **getScratchSpace**(): (`uint64` \| `bytes`)[] Defined in: [src/subcontexts/transaction-context.ts:284](https://github.com/algorandfoundation/algorand-typescript-testing/blob/main/src/subcontexts/transaction-context.ts#L284) @@ -398,7 +398,7 @@ Gets the scratch space of the active transaction. #### Returns -(`bytes` \| `uint64`)[] +(`uint64` \| `bytes`)[] The scratch space. diff --git a/docs/code/value-generators/arc4/classes/Arc4ValueGenerator.md b/docs/code/value-generators/arc4/classes/Arc4ValueGenerator.md index de12e9bb..8baa1b21 100644 --- a/docs/code/value-generators/arc4/classes/Arc4ValueGenerator.md +++ b/docs/code/value-generators/arc4/classes/Arc4ValueGenerator.md @@ -1,6 +1,6 @@ [**@algorandfoundation/algorand-typescript-testing**](../../../README.md) ---- +*** [@algorandfoundation/algorand-typescript-testing](../../../README.md) / [value-generators/arc4](../README.md) / Arc4ValueGenerator @@ -33,7 +33,7 @@ Generate a random Algorand address. `Address` ---- +*** ### dynamicBytes() @@ -53,7 +53,7 @@ Generate a random dynamic bytes of size `n` bits. `DynamicBytes` ---- +*** ### str() @@ -73,11 +73,11 @@ Generate a random dynamic string of size `n` bits. `Str` ---- +*** -### uintN128() +### uint128() -> **uintN128**(`minValue`, `maxValue`): `Uint128` +> **uint128**(`minValue`, `maxValue`): `Uint128` Defined in: [src/value-generators/arc4.ts:67](https://github.com/algorandfoundation/algorand-typescript-testing/blob/main/src/value-generators/arc4.ts#L67) @@ -97,11 +97,11 @@ Generate a random Uint128 within the specified range. `Uint128` ---- +*** -### uintN16() +### uint16() -> **uintN16**(`minValue`, `maxValue`): `Uint16` +> **uint16**(`minValue`, `maxValue`): `Uint16` Defined in: [src/value-generators/arc4.ts:37](https://github.com/algorandfoundation/algorand-typescript-testing/blob/main/src/value-generators/arc4.ts#L37) @@ -121,11 +121,11 @@ Generate a random Uint16 within the specified range. `Uint16` ---- +*** -### uintN256() +### uint256() -> **uintN256**(`minValue`, `maxValue`): `Uint256` +> **uint256**(`minValue`, `maxValue`): `Uint256` Defined in: [src/value-generators/arc4.ts:77](https://github.com/algorandfoundation/algorand-typescript-testing/blob/main/src/value-generators/arc4.ts#L77) @@ -145,11 +145,11 @@ Generate a random Uint256 within the specified range. `Uint256` ---- +*** -### uintN32() +### uint32() -> **uintN32**(`minValue`, `maxValue`): `Uint32` +> **uint32**(`minValue`, `maxValue`): `Uint32` Defined in: [src/value-generators/arc4.ts:47](https://github.com/algorandfoundation/algorand-typescript-testing/blob/main/src/value-generators/arc4.ts#L47) @@ -169,11 +169,11 @@ Generate a random Uint32 within the specified range. `Uint32` ---- +*** -### uintN512() +### uint512() -> **uintN512**(`minValue`, `maxValue`): `Uint`\<`512`\> +> **uint512**(`minValue`, `maxValue`): `Uint`\<`512`\> Defined in: [src/value-generators/arc4.ts:87](https://github.com/algorandfoundation/algorand-typescript-testing/blob/main/src/value-generators/arc4.ts#L87) @@ -193,11 +193,11 @@ Generate a random Uint512 within the specified range. `Uint`\<`512`\> ---- +*** -### uintN64() +### uint64() -> **uintN64**(`minValue`, `maxValue`): `Uint64` +> **uint64**(`minValue`, `maxValue`): `Uint64` Defined in: [src/value-generators/arc4.ts:57](https://github.com/algorandfoundation/algorand-typescript-testing/blob/main/src/value-generators/arc4.ts#L57) @@ -217,11 +217,11 @@ Generate a random Uint64 within the specified range. `Uint64` ---- +*** -### uintN8() +### uint8() -> **uintN8**(`minValue`, `maxValue`): `Uint8` +> **uint8**(`minValue`, `maxValue`): `Uint8` Defined in: [src/value-generators/arc4.ts:27](https://github.com/algorandfoundation/algorand-typescript-testing/blob/main/src/value-generators/arc4.ts#L27) diff --git a/examples/voting/contract.spec.ts b/examples/voting/contract.spec.ts index 41d9ef4c..8d52cb38 100644 --- a/examples/voting/contract.spec.ts +++ b/examples/voting/contract.spec.ts @@ -16,7 +16,7 @@ describe('VotingRoundApp', () => { const createContract = () => { const contract = ctx.contract.create(VotingRoundApp) - const snapshotPublicKey = Bytes(keyPair.publicKey).toFixed({ length: 32 }) + const snapshotPublicKey = Bytes<32>(keyPair.publicKey) const metadataIpfsCid = ctx.any.string(16) const startTime = ctx.any.uint64(Date.now() - 10_000, Date.now()) const endTime = ctx.any.uint64(Date.now() + 10_000, Date.now() + 100_000) diff --git a/package-lock.json b/package-lock.json index 4318234a..cbb95d73 100644 --- a/package-lock.json +++ b/package-lock.json @@ -8,8 +8,8 @@ "name": "@algorandfoundation/algorand-typescript-testing", "version": "1.0.0", "dependencies": { - "@algorandfoundation/algorand-typescript": "1.0.0-alpha.64", - "@algorandfoundation/puya-ts": "1.0.0-alpha.64", + "@algorandfoundation/algorand-typescript": "1.0.0-alpha.65", + "@algorandfoundation/puya-ts": "1.0.0-alpha.65", "elliptic": "^6.6.1", "js-sha256": "^0.11.0", "js-sha3": "^0.9.3", @@ -74,21 +74,21 @@ } }, "node_modules/@algorandfoundation/algorand-typescript": { - "version": "1.0.0-alpha.64", - "resolved": "https://registry.npmjs.org/@algorandfoundation/algorand-typescript/-/algorand-typescript-1.0.0-alpha.64.tgz", - "integrity": "sha512-DgB6qNMQH/sWr2E3t0mz+rKPskJTZvUm4iuExaQ6VX3y5tzpAQI8VbuIeKLS7yVN4oVXUa9WY7w86OyJ7GmNXQ==", + "version": "1.0.0-alpha.65", + "resolved": "https://registry.npmjs.org/@algorandfoundation/algorand-typescript/-/algorand-typescript-1.0.0-alpha.65.tgz", + "integrity": "sha512-5lYCtrArfLhKZGzp6DnfxpQHJ7xjzO8V1+Gu/6IjPNK9PjRLYiKe/bJXdo+7cbfT98LE5l5EpxOp5pU7l9rzyA==", "peerDependencies": { "tslib": "^2.6.2" } }, "node_modules/@algorandfoundation/puya-ts": { - "version": "1.0.0-alpha.64", - "resolved": "https://registry.npmjs.org/@algorandfoundation/puya-ts/-/puya-ts-1.0.0-alpha.64.tgz", - "integrity": "sha512-cW5pBwJh4nz7OxGXgwbNLK0zc2nqGlbfrZVyBdrOPH6RVaE8Ao6T2zOm1RmlOzngSZZhq86EBFOkQ36FP06LTw==", + "version": "1.0.0-alpha.65", + "resolved": "https://registry.npmjs.org/@algorandfoundation/puya-ts/-/puya-ts-1.0.0-alpha.65.tgz", + "integrity": "sha512-laJSuSNSumVdqN5jMTn8I9Ewk244qZ7w+tMBi/mawds8pq+MiljMY7dBISJtm0kH7fbWZaL9TP2aoXb7XMzbDw==", "hasInstallScript": true, "license": "MIT", "dependencies": { - "@algorandfoundation/algorand-typescript": "1.0.0-alpha.64", + "@algorandfoundation/algorand-typescript": "1.0.0-alpha.65", "arcsecond": "^5.0.0", "argparse": "^2.0.1", "chalk": "^5.4.1", diff --git a/package.json b/package.json index 6c263ed1..2c0e3010 100644 --- a/package.json +++ b/package.json @@ -67,8 +67,8 @@ "vitest": "3.2.4" }, "dependencies": { - "@algorandfoundation/algorand-typescript": "1.0.0-alpha.64", - "@algorandfoundation/puya-ts": "1.0.0-alpha.64", + "@algorandfoundation/algorand-typescript": "1.0.0-alpha.65", + "@algorandfoundation/puya-ts": "1.0.0-alpha.65", "elliptic": "^6.6.1", "js-sha256": "^0.11.0", "js-sha3": "^0.9.3", diff --git a/src/constants.ts b/src/constants.ts index d3efc41f..8ade049c 100644 --- a/src/constants.ts +++ b/src/constants.ts @@ -1,4 +1,4 @@ -import { Bytes } from './impl/primitives' +import { Bytes, FixedBytes } from './impl/primitives' export const UINT64_SIZE = 64 export const UINT512_SIZE = 512 @@ -20,12 +20,13 @@ export const DEFAULT_ASSET_CREATE_MIN_BALANCE = 1000_000 export const DEFAULT_ASSET_OPT_IN_MIN_BALANCE = 10_000 // from python code: list(b"\x85Y\xb5\x14x\xfd\x89\xc1vC\xd0]\x15\xa8\xaek\x10\xabG\xbbm\x8a1\x88\x11V\xe6\xbd;\xae\x95\xd1") -export const DEFAULT_GLOBAL_GENESIS_HASH = Bytes( +export const DEFAULT_GLOBAL_GENESIS_HASH = FixedBytes( + 32, new Uint8Array([ 133, 89, 181, 20, 120, 253, 137, 193, 118, 67, 208, 93, 21, 168, 174, 107, 16, 171, 71, 187, 109, 138, 49, 136, 17, 86, 230, 189, 59, 174, 149, 209, ]), -).toFixed({ length: 32 }) +) // algorand encoded address of 32 zero bytes export const ZERO_ADDRESS = Bytes.fromBase32('AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA') diff --git a/src/impl/crypto.ts b/src/impl/crypto.ts index b3ae4dee..fc7595d8 100644 --- a/src/impl/crypto.ts +++ b/src/impl/crypto.ts @@ -10,34 +10,34 @@ import { lazyContext } from '../context-helpers/internal-context' import { InternalError, NotImplementedError } from '../errors' import { asBytes, asBytesCls, asUint8Array, conactUint8Arrays } from '../util' import type { StubBytesCompat, StubUint64Compat } from './primitives' -import { Bytes, BytesCls, Uint64Cls } from './primitives' +import { Bytes, BytesCls, FixedBytes, Uint64Cls } from './primitives' export const sha256 = (a: StubBytesCompat): bytes<32> => { const bytesA = BytesCls.fromCompat(a) const hashArray = js_sha256.sha256.create().update(bytesA.asUint8Array()).digest() - const hashBytes = BytesCls.fromCompat(new Uint8Array(hashArray)) - return hashBytes.asAlgoTs().toFixed({ length: 32 }) + const hashBytes = FixedBytes(32, new Uint8Array(hashArray)) + return hashBytes } export const sha3_256 = (a: StubBytesCompat): bytes<32> => { const bytesA = BytesCls.fromCompat(a) const hashArray = js_sha3.sha3_256.create().update(bytesA.asUint8Array()).digest() - const hashBytes = BytesCls.fromCompat(new Uint8Array(hashArray)) - return hashBytes.asAlgoTs().toFixed({ length: 32 }) + const hashBytes = FixedBytes(32, new Uint8Array(hashArray)) + return hashBytes } export const keccak256 = (a: StubBytesCompat): bytes<32> => { const bytesA = BytesCls.fromCompat(a) const hashArray = js_sha3.keccak256.create().update(bytesA.asUint8Array()).digest() - const hashBytes = BytesCls.fromCompat(new Uint8Array(hashArray)) - return hashBytes.asAlgoTs().toFixed({ length: 32 }) + const hashBytes = FixedBytes(32, new Uint8Array(hashArray)) + return hashBytes } export const sha512_256 = (a: StubBytesCompat): bytes<32> => { const bytesA = BytesCls.fromCompat(a) const hashArray = js_sha512.sha512_256.create().update(bytesA.asUint8Array()).digest() - const hashBytes = BytesCls.fromCompat(new Uint8Array(hashArray)) - return hashBytes.asAlgoTs().toFixed({ length: 32 }) + const hashBytes = FixedBytes(32, new Uint8Array(hashArray)) + return hashBytes } export const ed25519verifyBare = (a: StubBytesCompat, b: StubBytesCompat, c: StubBytesCompat): boolean => { @@ -106,7 +106,7 @@ export const ecdsaPkRecover = ( const x = pubKey.getX().toArray('be') const y = pubKey.getY().toArray('be') - return [Bytes(x).toFixed({ length: 32 }), Bytes(y).toFixed({ length: 32 })] + return [FixedBytes(32, x), FixedBytes(32, y)] } export const ecdsaPkDecompress = (v: Ecdsa, a: StubBytesCompat): readonly [bytes<32>, bytes<32>] => { @@ -118,7 +118,7 @@ export const ecdsaPkDecompress = (v: Ecdsa, a: StubBytesCompat): readonly [bytes const x = pubKey.getX().toArray('be') const y = pubKey.getY().toArray('be') - return [Bytes(new Uint8Array(x)).toFixed({ length: 32 }), Bytes(new Uint8Array(y)).toFixed({ length: 32 })] + return [FixedBytes(32, new Uint8Array(x)), FixedBytes(32, new Uint8Array(y))] } export const vrfVerify = (_s: VrfVerify, _a: StubBytesCompat, _b: StubBytesCompat, _c: StubBytesCompat): readonly [bytes<64>, boolean] => { diff --git a/src/impl/encoded-types/encoded-types.ts b/src/impl/encoded-types/encoded-types.ts index b22ff87c..0416bf0d 100644 --- a/src/impl/encoded-types/encoded-types.ts +++ b/src/impl/encoded-types/encoded-types.ts @@ -1282,7 +1282,7 @@ export const getEncoder = (typeInfo: TypeInfo): fromBytes => { asset: AssetCls.fromBytes, boolean: booleanFromBytes, biguint: bigUintFromBytes, - bytes: bytesFromBytes, + 'bytes(<.*>)?': bytesFromBytes, string: stringFromBytes, uint64: uint64FromBytes, OnCompleteAction: onCompletionFromBytes, diff --git a/src/impl/encoded-types/helpers.ts b/src/impl/encoded-types/helpers.ts index fa7b720f..6d86f814 100644 --- a/src/impl/encoded-types/helpers.ts +++ b/src/impl/encoded-types/helpers.ts @@ -132,7 +132,7 @@ export const holdsDynamicLengthContent = (value: TypeInfo): boolean => { itemTypeName === 'DynamicArray' || itemTypeName === 'ReferenceArray' || itemTypeName === 'DynamicBytes' || - itemTypeName === 'bytes' || + (itemTypeName === 'bytes' && itemTypeName === value.name) || // `bytes` has dynamic length but `bytes` is statically sized itemTypeName === 'string' || ((itemTypeName === 'StaticArray' || itemTypeName === 'FixedArray') && holdsDynamicLengthContent((value.genericArgs as StaticArrayGenericArgs).elementType)) || diff --git a/src/impl/encoded-types/utils.ts b/src/impl/encoded-types/utils.ts index d07c9b79..a6115fa8 100644 --- a/src/impl/encoded-types/utils.ts +++ b/src/impl/encoded-types/utils.ts @@ -44,6 +44,13 @@ export const getMaxLengthOfStaticContentType = (type: TypeInfo, asArc4Encoded: b } return size } + if (trimGenericTypeName(type.name) === 'bytes') { + // Extract length from bytes type + const match = type.name.match(/bytes<(\d+)>/) + if (match) { + return parseInt(match[1], 10) + } + } switch (trimGenericTypeName(type.name)) { case 'uint64': return UINT64_SIZE / BITS_IN_BYTE diff --git a/src/impl/primitives.ts b/src/impl/primitives.ts index 9997f4ea..cdf6a8ba 100644 --- a/src/impl/primitives.ts +++ b/src/impl/primitives.ts @@ -1,6 +1,7 @@ import type { biguint, BigUintCompat, bytes, BytesCompat, uint64, Uint64Compat } from '@algorandfoundation/algorand-typescript' import { encodingUtil } from '@algorandfoundation/puya-ts' import { avmError, AvmError, avmInvariant, CodeError, InternalError } from '../errors' +import type { DeliberateAny } from '../typescript-helpers' import { nameOfType } from '../typescript-helpers' import { base32ToUint8Array } from './base-32' @@ -112,6 +113,87 @@ export function BigUint(v?: BigUintCompat | string): biguint { return BigUintCls.fromCompat(v).asAlgoTs() } +/** + * Create a byte array from a string interpolation template and compatible replacements + * @param value + * @param replacements + */ +export function FixedBytes( + length: TLength, + value: TemplateStringsArray, + ...replacements: BytesCompat[] +): bytes +/** + * Create a byte array from a utf8 string + */ +export function FixedBytes(length: TLength, value: string): bytes +/** + * No op, returns the provided byte array. + */ +export function FixedBytes(length: TLength, value: bytes): bytes +/** + * Create a byte array from a biguint value encoded as a variable length big-endian number + */ +export function FixedBytes(length: TLength, value: biguint): bytes +/** + * Create a byte array from a uint64 value encoded as a fixed length 64-bit number + */ +export function FixedBytes(length: TLength, value: uint64): bytes +/** + * Create a byte array from an Iterable where each item is interpreted as a single byte and must be between 0 and 255 inclusively + */ +export function FixedBytes(length: TLength, value: Iterable): bytes +/** + * Create an empty byte array + */ +export function FixedBytes(length: TLength): bytes +export function FixedBytes( + length: TLength, + value?: BytesCompat | TemplateStringsArray | biguint | uint64 | Iterable, + ...replacements: BytesCompat[] +): bytes { + const result = Bytes((value ?? new Uint8Array(length)) as DeliberateAny, ...replacements) + if (length && length !== getNumber(result.length)) { + throw new CodeError(`Invalid bytes constant length of ${result.length}, expected ${length}`) + } + return result as bytes +} + +/** + * Create a new bytes value from a hexadecimal encoded string + * @param hex + */ +FixedBytes.fromHex = (length: TLength, hex: string): bytes => { + const result = BytesCls.fromHex(hex).asAlgoTs() + if (length && length !== getNumber(result.length)) { + throw new CodeError(`Expected decoded bytes value of length ${length}, received ${result.length}`) + } + return result as bytes +} +/** + * Create a new bytes value from a base 64 encoded string + * @param b64 + */ +FixedBytes.fromBase64 = (length: TLength, b64: string): bytes => { + const result = BytesCls.fromBase64(b64).asAlgoTs() + if (length && length !== getNumber(result.length)) { + throw new CodeError(`Expected decoded bytes value of length ${length}, received ${result.length}`) + } + return result as bytes +} + +/** + * Create a new bytes value from a base 32 encoded string + * @param b32 + */ +FixedBytes.fromBase32 = (length: TLength, b32: string): bytes => { + const result = BytesCls.fromBase32(b32).asAlgoTs() + if (length && length !== getNumber(result.length)) { + throw new CodeError(`Expected decoded bytes value of length ${length}, received ${result.length}`) + } + return result as bytes +} + /** * Create a byte array from a string interpolation template and compatible replacements * @param value @@ -266,12 +348,6 @@ export abstract class AlgoTsPrimitiveCls { abstract toBytes(): BytesCls } -export function Uint64Impl(v?: Uint64Compat | string): uint64 { - if (typeof v === 'string') { - v = BigInt(v) - } - return Uint64Cls.fromCompat(v ?? 0).asAlgoTs() -} export class Uint64Cls extends AlgoTsPrimitiveCls { readonly #value: bigint constructor(value: bigint | number | string) { @@ -321,12 +397,6 @@ export class Uint64Cls extends AlgoTsPrimitiveCls { return this.#value.toString() } } - -export function BigUintImpl(v?: BigUintCompat | string): biguint { - if (typeof v === 'string') v = BigInt(v) - else if (v === undefined) v = 0n - return BigUintCls.fromCompat(v).asAlgoTs() -} export class BigUintCls extends AlgoTsPrimitiveCls { readonly #value: bigint constructor(value: bigint) { @@ -373,51 +443,6 @@ export class BigUintCls extends AlgoTsPrimitiveCls { } } -export function BytesImpl( - value?: BytesCompat | TemplateStringsArray | biguint | uint64 | Iterable, - ...replacements: BytesCompat[] -): bytes { - if (isTemplateStringsArray(value)) { - return BytesCls.fromInterpolation(value, replacements).asAlgoTs() - } else if (typeof value === 'bigint' || value instanceof BigUintCls) { - return BigUintCls.fromCompat(value).toBytes().asAlgoTs() - } else if (typeof value === 'number' || value instanceof Uint64Cls) { - return Uint64Cls.fromCompat(value).toBytes().asAlgoTs() - } else if (typeof value === 'object' && Symbol.iterator in value) { - const valueItems = Array.from(value).map((v) => getNumber(v)) - const invalidValue = valueItems.find((v) => v < 0 && v > 255) - if (invalidValue) { - throw new CodeError(`Cannot convert ${invalidValue} to a byte`) - } - return new BytesCls(new Uint8Array(value)).asAlgoTs() - } else { - return BytesCls.fromCompat(value).asAlgoTs() - } -} - -/** - * Create a new bytes value from a hexadecimal encoded string - * @param hex - */ -export const fromHexImpl = (hex: string): bytes => { - return BytesCls.fromHex(hex).asAlgoTs() -} -/** - * Create a new bytes value from a base 64 encoded string - * @param b64 - */ -export const fromBase64Impl = (b64: string): bytes => { - return BytesCls.fromBase64(b64).asAlgoTs() -} - -/** - * Create a new bytes value from a base 32 encoded string - * @param b32 - */ -export const fromBase32Impl = (b32: string): bytes => { - return BytesCls.fromBase32(b32).asAlgoTs() -} - function isTemplateStringsArray(v: unknown): v is TemplateStringsArray { return Boolean(v) && Array.isArray(v) && typeof v[0] === 'string' } diff --git a/src/runtime-helpers.ts b/src/runtime-helpers.ts index c7167848..9901d96c 100644 --- a/src/runtime-helpers.ts +++ b/src/runtime-helpers.ts @@ -13,6 +13,7 @@ export { attachAbiMetadata } from './abi-metadata' export { cloneImpl } from './impl/clone' export { emitImpl } from './impl/emit' export * from './impl/encoded-types' +export { FixedBytes } from './impl/primitives' export function switchableValue(x: unknown): bigint | string | boolean { if (typeof x === 'boolean') return x diff --git a/src/test-transformer/node-factory.ts b/src/test-transformer/node-factory.ts index 6e9dae10..2e977846 100644 --- a/src/test-transformer/node-factory.ts +++ b/src/test-transformer/node-factory.ts @@ -133,4 +133,17 @@ export const nodeFactory = { } return node }, + + callFixedBytesFunction(functionName: string, node: ts.CallExpression, length: number) { + const updatedPropertyAccessExpression = factory.createPropertyAccessExpression( + factory.createIdentifier('runtimeHelpers'), + `FixedBytes${functionName === 'Bytes' ? '' : `.${functionName}`}`, + ) + + return factory.createCallExpression( + updatedPropertyAccessExpression, + node.typeArguments, + [factory.createNumericLiteral(length), ...(node.arguments ?? [])].filter((arg) => !!arg), + ) + }, } satisfies Record ts.Node> diff --git a/src/test-transformer/visitors.ts b/src/test-transformer/visitors.ts index f22c20b1..4cba6654 100644 --- a/src/test-transformer/visitors.ts +++ b/src/test-transformer/visitors.ts @@ -187,13 +187,18 @@ class ExpressionVisitor { infoArg = [sourceTypeInfo, targetTypeInfo] } - updatedNode = stubbedFunctionName - ? isCallingMethodSelector(stubbedFunctionName) - ? nodeFactory.callMethodSelectorFunction(updatedNode) - : isCallingAbiCall(stubbedFunctionName) - ? nodeFactory.callAbiCallFunction(updatedNode) - : nodeFactory.callStubbedFunction(stubbedFunctionName, updatedNode, infoArg) - : updatedNode + if (stubbedFunctionName) { + if (isCallingMethodSelector(stubbedFunctionName)) { + updatedNode = nodeFactory.callMethodSelectorFunction(updatedNode) + } else if (isCallingAbiCall(stubbedFunctionName)) { + updatedNode = nodeFactory.callAbiCallFunction(updatedNode) + } else if (isCallingBytes(stubbedFunctionName)) { + if (type instanceof ptypes.BytesPType && type.fixedByteSize) + updatedNode = nodeFactory.callFixedBytesFunction(stubbedFunctionName, updatedNode, Number(type.fixedByteSize)) + } else { + updatedNode = nodeFactory.callStubbedFunction(stubbedFunctionName, updatedNode, infoArg) + } + } } return needsToCaptureTypeInfo ? nodeFactory.captureGenericTypeInfo(ts.visitEachChild(updatedNode, this.visit, this.context), JSON.stringify(info)) @@ -483,6 +488,7 @@ const tryGetStubbedFunctionName = (node: ts.CallExpression, helper: VisitorHelpe 'arc4EncodedLength', 'abiCall', 'clone', + 'Bytes', ] if (stubbedFunctionNames.includes(functionName)) { @@ -493,11 +499,12 @@ const tryGetStubbedFunctionName = (node: ts.CallExpression, helper: VisitorHelpe return functionName } - if (['begin', 'next'].includes(functionName) && ts.isPropertyAccessExpression(node.expression)) { + if (['begin', 'next', 'fromHex', 'fromBase64', 'fromBase32'].includes(functionName) && ts.isPropertyAccessExpression(node.expression)) { const objectExpression = node.expression.expression const objectName = tryGetAlgoTsSymbolName(objectExpression, helper) - if (objectName === 'itxnCompose') return functionName + if (['itxnCompose', 'Bytes'].includes(objectName || '')) return functionName } + return undefined } @@ -517,3 +524,5 @@ const isCallingEmit = (functionName: string | undefined): boolean => 'emit' === const isCallingMethodSelector = (functionName: string | undefined): boolean => 'methodSelector' === (functionName ?? '') const isCallingAbiCall = (functionName: string | undefined): boolean => ['abiCall', 'begin', 'next'].includes(functionName ?? '') const isCallingClone = (functionName: string | undefined): boolean => 'clone' === (functionName ?? '') +const isCallingBytes = (functionName: string | undefined): boolean => + ['Bytes', 'fromHex', 'fromBase64', 'fromBase32'].includes(functionName ?? '') diff --git a/tests/crypto-op-codes.spec.ts b/tests/crypto-op-codes.spec.ts index f34b44d0..04105faa 100644 --- a/tests/crypto-op-codes.spec.ts +++ b/tests/crypto-op-codes.spec.ts @@ -162,17 +162,17 @@ describe('crypto op codes', async () => { }) }) test('should throw error when no active txn group', async () => { - expect(() => op.ed25519verify(Bytes(''), Bytes(''), Bytes(''))).toThrow('no active txn group') + expect(() => op.ed25519verify(Bytes(''), Bytes<64>(), Bytes<32>())).toThrow('no active txn group') }) }) describe('ecdsaVerify', async () => { test('should be able to verify k1 signature', async ({ appClientCryptoOpsContract: appClient }) => { - const messageHash = Bytes.fromHex('f809fd0aa0bb0f20b354c6b2f86ea751957a4e262a546bd716f34f69b9516ae1').toFixed({ length: 32 }) - const sigR = Bytes.fromHex('f7f913754e5c933f3825d3aef22e8bf75cfe35a18bede13e15a6e4adcfe816d2').toFixed({ length: 32 }) - const sigS = Bytes.fromHex('0b5599159aa859d79677f33280848ae4c09c2061e8b5881af8507f8112966754').toFixed({ length: 32 }) - const pubkeyX = Bytes.fromHex('a710244d62747aa8db022ddd70617240adaf881b439e5f69993800e614214076').toFixed({ length: 32 }) - const pubkeyY = Bytes.fromHex('48d0d337704fe2c675909d2c93f7995e199156f302f63c74a8b96827b28d777b').toFixed({ length: 32 }) + const messageHash = Bytes.fromHex<32>('f809fd0aa0bb0f20b354c6b2f86ea751957a4e262a546bd716f34f69b9516ae1') + const sigR = Bytes.fromHex<32>('f7f913754e5c933f3825d3aef22e8bf75cfe35a18bede13e15a6e4adcfe816d2') + const sigS = Bytes.fromHex<32>('0b5599159aa859d79677f33280848ae4c09c2061e8b5881af8507f8112966754') + const pubkeyX = Bytes.fromHex<32>('a710244d62747aa8db022ddd70617240adaf881b439e5f69993800e614214076') + const pubkeyY = Bytes.fromHex<32>('48d0d337704fe2c675909d2c93f7995e199156f302f63c74a8b96827b28d777b') const avmResult = await getAvmResult( { @@ -193,11 +193,11 @@ describe('crypto op codes', async () => { expect(result).toEqual(avmResult) }) test('should be able to verify r1 signature', async ({ appClientCryptoOpsContract: appClient }) => { - const messageHash = Bytes.fromHex('f809fd0aa0bb0f20b354c6b2f86ea751957a4e262a546bd716f34f69b9516ae1').toFixed({ length: 32 }) - const sigR = Bytes.fromHex('18d96c7cda4bc14d06277534681ded8a94828eb731d8b842e0da8105408c83cf').toFixed({ length: 32 }) - const sigS = Bytes.fromHex('7d33c61acf39cbb7a1d51c7126f1718116179adebd31618c4604a1f03b5c274a').toFixed({ length: 32 }) - const pubkeyX = Bytes.fromHex('f8140e3b2b92f7cbdc8196bc6baa9ce86cf15c18e8ad0145d50824e6fa890264').toFixed({ length: 32 }) - const pubkeyY = Bytes.fromHex('bd437b75d6f1db67155a95a0da4b41f2b6b3dc5d42f7db56238449e404a6c0a3').toFixed({ length: 32 }) + const messageHash = Bytes.fromHex<32>('f809fd0aa0bb0f20b354c6b2f86ea751957a4e262a546bd716f34f69b9516ae1') + const sigR = Bytes.fromHex<32>('18d96c7cda4bc14d06277534681ded8a94828eb731d8b842e0da8105408c83cf') + const sigS = Bytes.fromHex<32>('7d33c61acf39cbb7a1d51c7126f1718116179adebd31618c4604a1f03b5c274a') + const pubkeyX = Bytes.fromHex<32>('f8140e3b2b92f7cbdc8196bc6baa9ce86cf15c18e8ad0145d50824e6fa890264') + const pubkeyY = Bytes.fromHex<32>('bd437b75d6f1db67155a95a0da4b41f2b6b3dc5d42f7db56238449e404a6c0a3') const avmResult = await getAvmResult( { @@ -273,7 +273,7 @@ describe('crypto op codes', async () => { const v = Ecdsa.Secp256k1 const testData = generateEcdsaTestData(v) const ecdsa = new elliptic.ec(curveMap[v]) - const keyPair = ecdsa.keyFromPublic(testData.pubkeyX.concat(testData.pubkeyY).asUint8Array()) + const keyPair = ecdsa.keyFromPublic(asUint8Array(testData.pubkeyX.concat(testData.pubkeyY))) const pubKeyArray = new Uint8Array(keyPair.getPublic(true, 'array')) const avmResult = await getAvmResult( { appClient, sendParams: { staticFee: AlgoAmount.Algos(3000) } }, @@ -289,13 +289,12 @@ describe('crypto op codes', async () => { }) describe('vrfVerify', async () => { - const a = BytesCls.fromHex('528b9e23d93d0e020a119d7ba213f6beb1c1f3495a217166ecd20f5a70e7c2d7') - const b = BytesCls.fromHex( + const a = Bytes.fromHex('528b9e23d93d0e020a119d7ba213f6beb1c1f3495a217166ecd20f5a70e7c2d7') + const b = Bytes.fromHex<80>( '372a3afb42f55449c94aaa5f274f26543e77e8d8af4babee1a6fbc1c0391aa9e6e0b8d8d7f4ed045d5b517fea8ad3566025ae90d2f29f632e38384b4c4f5b9eb741c6e446b0f540c1b3761d814438b04', ) - .asAlgoTs() - .toFixed({ length: 80 }) - const c = BytesCls.fromHex('3a2740da7a0788ebb12a52154acbcca1813c128ca0b249e93f8eb6563fee418d').asAlgoTs().toFixed({ length: 32 }) + + const c = Bytes.fromHex<32>('3a2740da7a0788ebb12a52154acbcca1813c128ca0b249e93f8eb6563fee418d') test('should throw not available error', async () => { const mockedVrfVerify = op.vrfVerify as Mock @@ -313,7 +312,7 @@ describe('crypto op codes', async () => { asUint8Array(c), ) const mockedVrfVerify = op.vrfVerify as Mock - mockedVrfVerify.mockReturnValue([BytesCls.fromCompat(new Uint8Array(avmResult[0])).asAlgoTs().toFixed({ length: 64 }), avmResult[1]]) + mockedVrfVerify.mockReturnValue([Bytes<64>(new Uint8Array(avmResult[0])), avmResult[1]]) const result = op.vrfVerify(VrfVerify.VrfAlgorand, asBytes(a), b, c) expect(asUint8Array(result[0])).toEqual(new Uint8Array(avmResult[0])) @@ -375,15 +374,11 @@ const generateEcdsaTestData = (v: Ecdsa) => { const recoveryId = 0 // Recovery ID is typically 0 or 1 return { - data: BytesCls.fromCompat(new Uint8Array(messageHash)).asAlgoTs().toFixed({ length: 32 }), - r: BytesCls.fromCompat(new Uint8Array(signature.r.toArray('be', 32))) - .asAlgoTs() - .toFixed({ length: 32 }), - s: BytesCls.fromCompat(new Uint8Array(signature.s.toArray('be', 32))) - .asAlgoTs() - .toFixed({ length: 32 }), + data: Bytes<32>(new Uint8Array(messageHash)), + r: Bytes<32>(new Uint8Array(signature.r.toArray('be', 32))), + s: Bytes<32>(new Uint8Array(signature.s.toArray('be', 32))), recoveryId: Uint64Cls.fromCompat(recoveryId), - pubkeyX: BytesCls.fromCompat(new Uint8Array(pk.slice(0, 32))), - pubkeyY: BytesCls.fromCompat(new Uint8Array(pk.slice(32))), + pubkeyX: Bytes(new Uint8Array(pk.slice(0, 32))), + pubkeyY: Bytes(new Uint8Array(pk.slice(32))), } } diff --git a/tests/fixed-array.spec.ts b/tests/fixed-array.spec.ts index da452438..fd5fd39f 100644 --- a/tests/fixed-array.spec.ts +++ b/tests/fixed-array.spec.ts @@ -29,8 +29,11 @@ class TestContract extends Contract { describe('FixedArray', () => { describe('constructor', () => { it('creates empty array when no arguments provided', () => { - const arr = new FixedArray() - expect(arr.length).toEqual(2) + const arr1 = new FixedArray() + expect(arr1.length).toEqual(2) + + const arr2 = new FixedArray, 2>() + expect(arr2.length).toEqual(2) }) it('creates array with initial values', () => { diff --git a/tests/primitives/bytes.spec.ts b/tests/primitives/bytes.spec.ts index 533044ad..01aa6c3e 100644 --- a/tests/primitives/bytes.spec.ts +++ b/tests/primitives/bytes.spec.ts @@ -1,9 +1,11 @@ import type { bytes } from '@algorandfoundation/algorand-typescript' -import { Bytes } from '@algorandfoundation/algorand-typescript' +import { Bytes, FixedArray } from '@algorandfoundation/algorand-typescript' import { encodingUtil } from '@algorandfoundation/puya-ts' -import { beforeAll, describe, expect } from 'vitest' +import { beforeAll, describe, expect, it } from 'vitest' import { MAX_BYTES_SIZE } from '../../src/constants' +import type { Byte, StaticArray } from '@algorandfoundation/algorand-typescript/arc4' +import { arc4EncodedLength, decodeArc4, interpretAsArc4 } from '@algorandfoundation/algorand-typescript/arc4' import { sha256 } from '../../src/impl' import { BytesCls } from '../../src/impl/primitives' import { asUint8Array } from '../../src/util' @@ -195,4 +197,49 @@ describe('Bytes', async () => { expect(result.asUint8Array()).toEqual(b) }) }) + + describe('fixed size', () => { + it('should be able to create fixed size bytes with no parameter', () => { + const x = Bytes<32>() + expect(x.length).toEqual(32) + expect(x).toEqual(Bytes.fromHex<32>('0000000000000000000000000000000000000000000000000000000000000000')) + expect(x).toEqual(Bytes.fromBase64<32>('AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=')) + expect(x).toEqual(Bytes.fromBase32<32>('AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA==')) + }) + + it('should be able to create fixed size bytes with parameter', () => { + const x1 = Bytes<32>(new Uint8Array(32)) + expect(x1.length).toEqual(32) + expect(x1).toEqual(Bytes<32>()) + + const x2 = Bytes<32>('abcdefghijklmnopqrstuvwxyz123456') + expect(x2.length).toEqual(32) + expect(x2).toEqual(Bytes('abcdefghijklmnopqrstuvwxyz123456')) + expect(x2).toEqual(Bytes.fromHex('6162636465666768696a6b6c6d6e6f707172737475767778797a313233343536')) + expect(x2).toEqual(Bytes.fromBase64('YWJjZGVmZ2hpamtsbW5vcHFyc3R1dnd4eXoxMjM0NTY=')) + expect(x2).toEqual(Bytes.fromBase32('MFRGGZDFMZTWQ2LKNNWG23TPOBYXE43UOV3HO6DZPIYTEMZUGU3A====')) + }) + + it('should be treated as statically sized', () => { + expect(arc4EncodedLength>()).toEqual(32) + expect(arc4EncodedLength, 2>>()).toEqual(64) + + const x1 = new FixedArray, 2>() + expect(x1.length).toEqual(2) + expect(x1[0].length).toEqual(32) + expect(x1[1].length).toEqual(32) + expect(x1[0]).toEqual(Bytes<32>(new Uint8Array(32))) + expect(x1[1]).toEqual(Bytes<32>(new Uint8Array(32))) + + const x2 = decodeArc4, 2>>(Bytes<64>()) + expect(x2.length).toEqual(2) + expect(x2[0].length).toEqual(32) + expect(x2[1].length).toEqual(32) + + const x3 = interpretAsArc4, 2>>(Bytes<64>()) + expect(x3.length).toEqual(2) + expect(x3[0].bytes).toEqual(Bytes.fromHex<32>('0000000000000000000000000000000000000000000000000000000000000000')) + expect(x3[1].bytes).toEqual(Bytes.fromHex<32>('0000000000000000000000000000000000000000000000000000000000000000')) + }) + }) }) diff --git a/tests/references/asset.spec.ts b/tests/references/asset.spec.ts index 466ddb72..5ee954c3 100644 --- a/tests/references/asset.spec.ts +++ b/tests/references/asset.spec.ts @@ -55,7 +55,7 @@ describe('Asset', () => { unitName: asBytes('TEST'), name: asBytes('Test Asset'), url: asBytes('https://test.com'), - metadataHash: Bytes(new Uint8Array(32)).toFixed({ length: 32 }), + metadataHash: Bytes<32>(new Uint8Array(32)), manager: Account(), freeze: Account(), clawback: Account(), From 9699d0713381fe3e553e039b2955cf60d11d3bfa Mon Sep 17 00:00:00 2001 From: Bobby Lat Date: Mon, 28 Jul 2025 16:47:39 +0700 Subject: [PATCH 22/68] pass `foreign_key` as `resourceEncoding` to keep old behvaiour for now --- tests/test-fixture.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/tests/test-fixture.ts b/tests/test-fixture.ts index 0cbfde86..e54169fe 100644 --- a/tests/test-fixture.ts +++ b/tests/test-fixture.ts @@ -263,6 +263,8 @@ async function compilePath( outputTeal: false, outputSourceMap: true, optimizationLevel: 0, + + resourceEncoding: 'foreign_index', ...options, }), ) From dbbe7a40d503c2489e2c192172bed9f794f8b113 Mon Sep 17 00:00:00 2001 From: Bobby Lat Date: Wed, 6 Aug 2025 13:12:56 +0700 Subject: [PATCH 23/68] implement `resourceEncoding: Value` as default --- examples/precompiled/precompiled-apps.algo.ts | 1 + src/abi-metadata.ts | 10 +- src/impl/c2c.ts | 2 + src/impl/encoded-types/encoded-types.ts | 15 ++- src/impl/encoded-types/utils.ts | 19 ++-- src/impl/inner-transactions.ts | 9 +- src/impl/primitives.ts | 15 ++- src/subcontexts/contract-context.ts | 35 ++++-- tests/arc4/method-selector.spec.ts | 102 +++++++++++++++++ tests/arc4/resource-encoding.spec.ts | 67 +++++++++++ .../arc4-abi-method/contract.algo.ts | 33 +++++- .../resource-encoding/contract.algo.ts | 105 ++++++++++++++++++ tests/artifacts/state-ops/contract.algo.ts | 26 ++--- 13 files changed, 388 insertions(+), 51 deletions(-) create mode 100644 tests/arc4/resource-encoding.spec.ts create mode 100644 tests/artifacts/resource-encoding/contract.algo.ts diff --git a/examples/precompiled/precompiled-apps.algo.ts b/examples/precompiled/precompiled-apps.algo.ts index f3efd10b..808cadab 100644 --- a/examples/precompiled/precompiled-apps.algo.ts +++ b/examples/precompiled/precompiled-apps.algo.ts @@ -102,6 +102,7 @@ export class ReceivesTxns extends Contract { } export class ReceivesReferenceTypes extends Contract { + @abimethod({ resourceEncoding: 'Index' }) receivesReferenceTypes(app: Application, acc: Account, asset: Asset) { log(app.address) log(acc.bytes) diff --git a/src/abi-metadata.ts b/src/abi-metadata.ts index 61f6820a..8790ad28 100644 --- a/src/abi-metadata.ts +++ b/src/abi-metadata.ts @@ -1,5 +1,4 @@ -import type { OnCompleteActionStr } from '@algorandfoundation/algorand-typescript' -import type { CreateOptions } from '@algorandfoundation/algorand-typescript/arc4' +import type { arc4, OnCompleteActionStr } from '@algorandfoundation/algorand-typescript' import js_sha512 from 'js-sha512' import { ConventionalRouting } from './constants' import { Arc4MethodConfigSymbol, Contract } from './impl/contract' @@ -13,8 +12,9 @@ export interface AbiMetadata { methodSignature: string | undefined argTypes: string[] returnType: string - onCreate?: CreateOptions + onCreate?: arc4.CreateOptions allowActions?: OnCompleteActionStr[] + resourceEncoding?: arc4.ResourceEncodingOptions } const metadataStore: WeakMap<{ new (): Contract }, Record> = new WeakMap() @@ -70,8 +70,8 @@ export const getContractMethodAbiMetadata = (contract: T | { export const getArc4Signature = (metadata: AbiMetadata): string => { if (metadata.methodSignature === undefined) { - const argTypes = metadata.argTypes.map((t) => JSON.parse(t) as TypeInfo).map(getArc4TypeName) - const returnType = getArc4TypeName(JSON.parse(metadata.returnType) as TypeInfo) + const argTypes = metadata.argTypes.map((t) => JSON.parse(t) as TypeInfo).map((t) => getArc4TypeName(t, metadata.resourceEncoding, 'in')) + const returnType = getArc4TypeName(JSON.parse(metadata.returnType) as TypeInfo, metadata.resourceEncoding, 'out') metadata.methodSignature = `${metadata.name ?? metadata.methodName}(${argTypes.join(',')})${returnType}` } return metadata.methodSignature diff --git a/src/impl/c2c.ts b/src/impl/c2c.ts index 8b9efcc8..6a62e5ec 100644 --- a/src/impl/c2c.ts +++ b/src/impl/c2c.ts @@ -42,6 +42,7 @@ export function compileArc4( ...methodArgs, }, selector, + abiMetadata?.resourceEncoding, ) invokeAbiCall(itxnContext) return { @@ -97,6 +98,7 @@ export function getApplicationCallInnerTxnContext OnCompleteAction[action])[0], }, selector, + abiMetadata?.resourceEncoding, ) } export function abiCall( diff --git a/src/impl/encoded-types/encoded-types.ts b/src/impl/encoded-types/encoded-types.ts index 0416bf0d..a5a34101 100644 --- a/src/impl/encoded-types/encoded-types.ts +++ b/src/impl/encoded-types/encoded-types.ts @@ -1127,15 +1127,15 @@ export const getArc4Encoded = (value: DeliberateAny, sourceTypeInfoString?: stri } if (value instanceof AccountCls) { const index = (lazyContext.activeGroup.activeTransaction as ApplicationCallTransaction).apat.indexOf(value) - return new UintImpl({ name: 'Uint<64>', genericArgs: [{ name: '64' }] }, asBigInt(index)) + return index >= 0 ? new UintImpl({ name: 'Uint<64>', genericArgs: [{ name: '64' }] }, asBigInt(index)) : getArc4Encoded(value.bytes) } if (value instanceof AssetCls) { const index = (lazyContext.activeGroup.activeTransaction as ApplicationCallTransaction).apas.indexOf(value) - return new UintImpl({ name: 'Uint<64>', genericArgs: [{ name: '64' }] }, asBigInt(index)) + return index >= 0 ? new UintImpl({ name: 'Uint<64>', genericArgs: [{ name: '64' }] }, asBigInt(index)) : getArc4Encoded(value.id) } if (value instanceof ApplicationCls) { const index = (lazyContext.activeGroup.activeTransaction as ApplicationCallTransaction).apfa.indexOf(value) - return new UintImpl({ name: 'Uint<64>', genericArgs: [{ name: '64' }] }, asBigInt(index)) + return index >= 0 ? new UintImpl({ name: 'Uint<64>', genericArgs: [{ name: '64' }] }, asBigInt(index)) : getArc4Encoded(value.id) } if (typeof value === 'boolean') { return new BoolImpl({ name: 'Bool' }, value) @@ -1150,6 +1150,15 @@ export const getArc4Encoded = (value: DeliberateAny, sourceTypeInfoString?: stri return new UintImpl({ name: 'Uint<512>', genericArgs: [{ name: '512' }] }, value) } if (value instanceof BytesCls) { + if (value.fixedLength !== undefined) { + return new StaticBytesImpl( + { + name: 'StaticBytes', + genericArgs: { elementType: { name: 'Byte', genericArgs: [{ name: '8' }] }, size: { name: value.fixedLength.toString() } }, + }, + value.asAlgoTs(), + ) + } return new DynamicBytesImpl( { name: 'DynamicBytes', genericArgs: { elementType: { name: 'Byte', genericArgs: [{ name: '8' }] } } }, value.asAlgoTs(), diff --git a/src/impl/encoded-types/utils.ts b/src/impl/encoded-types/utils.ts index a6115fa8..5e4cd8c7 100644 --- a/src/impl/encoded-types/utils.ts +++ b/src/impl/encoded-types/utils.ts @@ -1,4 +1,5 @@ import type { uint64 } from '@algorandfoundation/algorand-typescript' +import type { ResourceEncodingOptions } from '@algorandfoundation/algorand-typescript/arc4' import { BITS_IN_BYTE, UINT512_SIZE, UINT64_SIZE } from '../../constants' import { CodeError } from '../../errors' import { findBoolTypes, trimGenericTypeName } from './helpers' @@ -82,16 +83,20 @@ export const getMaxLengthOfStaticContentType = (type: TypeInfo, asArc4Encoded: b } } -export const getArc4TypeName = (typeInfo: TypeInfo): string | undefined => { +export const getArc4TypeName = ( + typeInfo: TypeInfo, + resourceEncoding: ResourceEncodingOptions | undefined = undefined, + direction: 'in' | 'out' = 'in', +): string | undefined => { const getArc4TypeNameForObjectType = (typeInfo: TypeInfo): string => { const genericArgs = Object.values(typeInfo.genericArgs as Record) - return `(${genericArgs.map(getArc4TypeName).join(',')})` + return `(${genericArgs.map((arg) => getArc4TypeName(arg, resourceEncoding, direction)).join(',')})` } const map: Record string)> = { void: 'void', - account: 'account', - application: 'application', - asset: 'asset', + account: (_) => (resourceEncoding === 'Index' && direction === 'in' ? 'account' : 'address'), + application: (_) => (resourceEncoding === 'Index' && direction === 'in' ? 'application' : 'uint64'), + asset: (_) => (resourceEncoding === 'Index' && direction === 'in' ? 'asset' : 'uint64'), boolean: 'bool', biguint: 'uint512', bytes: 'byte[]', @@ -119,11 +124,11 @@ export const getArc4TypeName = (typeInfo: TypeInfo): string | undefined => { }, '(StaticArray|FixedArray)(<.*>)?': (t: TypeInfo) => { const genericArgs = t.genericArgs as StaticArrayGenericArgs - return `${getArc4TypeName(genericArgs.elementType)}[${genericArgs.size.name}]` + return `${getArc4TypeName(genericArgs.elementType, resourceEncoding, direction)}[${genericArgs.size.name}]` }, '(Dynamic|Readonly)?Array<.*>': (t: TypeInfo) => { const genericArgs = t.genericArgs as DynamicArrayGenericArgs - return `${getArc4TypeName(genericArgs.elementType)}[]` + return `${getArc4TypeName(genericArgs.elementType, resourceEncoding, direction)}[]` }, 'Struct(<.*>)?': getArc4TypeNameForObjectType, '(Readonly)?Object(<.*>)?': getArc4TypeNameForObjectType, diff --git a/src/impl/inner-transactions.ts b/src/impl/inner-transactions.ts index 9c281a3b..38a318fc 100644 --- a/src/impl/inner-transactions.ts +++ b/src/impl/inner-transactions.ts @@ -6,7 +6,11 @@ import type { itxn, } from '@algorandfoundation/algorand-typescript' import { TransactionType } from '@algorandfoundation/algorand-typescript' -import type { BareCreateApplicationCallFields, TypedApplicationCallFields } from '@algorandfoundation/algorand-typescript/arc4' +import type { + BareCreateApplicationCallFields, + ResourceEncodingOptions, + TypedApplicationCallFields, +} from '@algorandfoundation/algorand-typescript/arc4' import { ABI_RETURN_VALUE_LOG_PREFIX } from '../constants' import { lazyContext } from '../context-helpers/internal-context' import { InternalError, invariant } from '../errors' @@ -292,12 +296,13 @@ export class ApplicationCallInnerTxnContext extends Applicati static createFromTypedApplicationCallFields( methodArgs: TypedApplicationCallFields, methodSelector: bytes, + resourceEncoding: ResourceEncodingOptions | undefined, ) { const app = (methodArgs.appId instanceof Uint64Cls ? getApp(methodArgs.appId) : (methodArgs.appId as ApplicationType | undefined)) ?? lazyContext.any.application() const args = (methodArgs.args ?? []).map((x: DeliberateAny) => (x instanceof ItxnParams ? x.submit() : x)) - const { transactions, ...appCallArgs } = extractArraysFromArgs(app, asUint8Array(methodSelector), args) + const { transactions, ...appCallArgs } = extractArraysFromArgs(app, asUint8Array(methodSelector), resourceEncoding, args) const { args: _, ...methodArgsFields } = methodArgs const fields = { ...methodArgsFields, diff --git a/src/impl/primitives.ts b/src/impl/primitives.ts index cdf6a8ba..a680d259 100644 --- a/src/impl/primitives.ts +++ b/src/impl/primitives.ts @@ -156,7 +156,7 @@ export function FixedBytes( if (length && length !== getNumber(result.length)) { throw new CodeError(`Invalid bytes constant length of ${result.length}, expected ${length}`) } - return result as bytes + return result.toFixed({ length }) } /** @@ -168,7 +168,7 @@ FixedBytes.fromHex = (length: TLength, hex: str if (length && length !== getNumber(result.length)) { throw new CodeError(`Expected decoded bytes value of length ${length}, received ${result.length}`) } - return result as bytes + return result.toFixed({ length }) } /** * Create a new bytes value from a base 64 encoded string @@ -179,7 +179,7 @@ FixedBytes.fromBase64 = (length: TLength, b64: if (length && length !== getNumber(result.length)) { throw new CodeError(`Expected decoded bytes value of length ${length}, received ${result.length}`) } - return result as bytes + return result.toFixed({ length }) } /** @@ -191,7 +191,7 @@ FixedBytes.fromBase32 = (length: TLength, b32: if (length && length !== getNumber(result.length)) { throw new CodeError(`Expected decoded bytes value of length ${length}, received ${result.length}`) } - return result as bytes + return result.toFixed({ length }) } /** @@ -449,7 +449,10 @@ function isTemplateStringsArray(v: unknown): v is TemplateStringsArray { export class BytesCls extends AlgoTsPrimitiveCls { readonly #v: Uint8Array - constructor(v: Uint8Array) { + constructor( + v: Uint8Array, + public readonly fixedLength?: number, + ) { super() this.#v = v checkBytes(this.#v) @@ -536,7 +539,7 @@ export class BytesCls extends AlgoTsPrimitiveCls { throw new CodeError(`Invalid bytes constant length of ${this.#v.length}, expected ${options.length}`) } } - return new BytesCls(this.#v) as unknown as bytes + return new BytesCls(this.#v, options.length) as unknown as bytes } static [Symbol.hasInstance](x: unknown): x is BytesCls { return isInstanceOfTypeByName(x, BytesCls) diff --git a/src/subcontexts/contract-context.ts b/src/subcontexts/contract-context.ts index c1280571..45774145 100644 --- a/src/subcontexts/contract-context.ts +++ b/src/subcontexts/contract-context.ts @@ -6,7 +6,7 @@ import { type contract, type LocalState, } from '@algorandfoundation/algorand-typescript' -import type { ARC4Encoded } from '@algorandfoundation/algorand-typescript/arc4' +import type { ARC4Encoded, ResourceEncodingOptions } from '@algorandfoundation/algorand-typescript/arc4' import type { AbiMetadata } from '../abi-metadata' import { getArc4Selector, getContractAbiMetadata, getContractMethodAbiMetadata } from '../abi-metadata' import { BytesMap } from '../collections/custom-key-map' @@ -90,7 +90,12 @@ const extractStates = (contract: BaseContract, contractOptions: ContractOptionsP const getUint8Impl = (value: number) => new UintImpl({ name: 'Uint<8>', genericArgs: [{ name: '8' }] }, value) /** @ignore */ -export const extractArraysFromArgs = (app: Application, methodSelector: Uint8Array, args: DeliberateAny[]) => { +export const extractArraysFromArgs = ( + app: Application, + methodSelector: Uint8Array, + resourceEncoding: ResourceEncodingOptions | undefined, + args: DeliberateAny[], +) => { const transactions: Transaction[] = [] const accounts: Account[] = [lazyContext.defaultSender] const apps: Application[] = [app] @@ -101,14 +106,26 @@ export const extractArraysFromArgs = (app: Application, methodSelector: Uint8Arr if (isTransaction(arg)) { transactions.push(arg) } else if (arg instanceof AccountCls) { - appArgs.push(getUint8Impl(accounts.length)) - accounts.push(arg as Account) + if (resourceEncoding === 'Index') { + appArgs.push(getUint8Impl(accounts.length)) + accounts.push(arg as Account) + } else { + appArgs.push(getArc4Encoded(arg.bytes)) + } } else if (arg instanceof ApplicationCls) { - appArgs.push(getUint8Impl(apps.length)) - apps.push(arg as Application) + if (resourceEncoding === 'Index') { + appArgs.push(getUint8Impl(apps.length)) + apps.push(arg as Application) + } else { + appArgs.push(getArc4Encoded(arg.id)) + } } else if (arg instanceof AssetCls) { - appArgs.push(getUint8Impl(assets.length)) - assets.push(arg as Asset) + if (resourceEncoding === 'Index') { + appArgs.push(getUint8Impl(assets.length)) + assets.push(arg as Asset) + } else { + appArgs.push(getArc4Encoded(arg.id)) + } } else if (arg !== undefined) { appArgs.push(getArc4Encoded(arg)) } @@ -183,7 +200,7 @@ export class ContractContext { ): Transaction[] { const app = lazyContext.ledger.getApplicationForContract(contract) const methodSelector = abiMetadata ? getArc4Selector(abiMetadata) : new Uint8Array() - const { transactions, ...appCallArgs } = extractArraysFromArgs(app, methodSelector, args) + const { transactions, ...appCallArgs } = extractArraysFromArgs(app, methodSelector, abiMetadata?.resourceEncoding, args) const appTxn = lazyContext.any.txn.applicationCall({ appId: app, ...appCallArgs, diff --git a/tests/arc4/method-selector.spec.ts b/tests/arc4/method-selector.spec.ts index af0dcd7e..9aa0314f 100644 --- a/tests/arc4/method-selector.spec.ts +++ b/tests/arc4/method-selector.spec.ts @@ -5,6 +5,8 @@ import { TestExecutionContext } from '@algorandfoundation/algorand-typescript-te import { DynamicArray, methodSelector } from '@algorandfoundation/algorand-typescript/arc4' import { encodingUtil } from '@algorandfoundation/puya-ts' import { afterEach, beforeAll, describe, expect } from 'vitest' +import { toBytes } from '../../src/impl/encoded-types/encoded-types' +import { encodeAddress } from '../../src/impl/reference' import { AnotherStruct, MyStruct, SignaturesContract } from '../artifacts/arc4-abi-method/contract.algo' import { getAvmResult } from '../avm-invoker' import { createArc4TestFixture } from '../test-fixture' @@ -249,6 +251,106 @@ describe('methodSelector', async () => { expect(result[1].bytes).toEqual(struct.bytes) }) + test('app args is correct with index resource encoding', async ({ + appClientSignaturesContract: appClient, + algorand, + appFactorySignaturesContract, + }) => { + const contract = ctx.contract.create(SignaturesContract) + contract.create() + + const localnetCreator = await algorand.account.localNetDispenser() + const asaId = ( + await algorand.send.assetCreate({ + sender: localnetCreator, + total: 123n, + }) + ).confirmation.assetIndex + + const asset = ctx.any.asset({ assetId: asaId, total: 123 }) + + const otherApp = await appFactorySignaturesContract.send.create({ method: 'create' }) + const otherAppId = otherApp.appClient.appId + const otherApplication = ctx.any.application({ applicationId: otherAppId }) + + const acc = algorand.account.random() + await algorand.account.ensureFundedFromEnvironment(acc, new AlgoAmount({ microAlgo: _FUNDED_ACCOUNT_SPENDING })) + // ensure context has the same account with matching balance + const account = ctx.any.account({ + address: Bytes(acc.publicKey), + balance: Global.minBalance + _FUNDED_ACCOUNT_SPENDING, + }) + // act + const result = await getAvmResult({ appClient }, 'echoResourceByIndex', asaId, otherAppId, acc.publicKey) + contract.echoResourceByIndex(asset, otherApplication, account) + + // assert + const txn = ctx.txn.lastActive as gtxn.ApplicationCallTxn + const appArgs = Array(Number(txn.numAppArgs)) + .fill(0) + .map((_, i) => txn.appArgs(i)) + + expect(appArgs).toEqual([ + appClient.getABIMethod('echoResourceByIndex(asset,application,account)(uint64,uint64,address)').getSelector(), + Bytes.fromHex('00'), // asset index + Bytes.fromHex('01'), + Bytes.fromHex('01'), + ]) + expect(appArgs[0]).toEqual(methodSelector(SignaturesContract.prototype.echoResourceByIndex)) + + expect(result).toEqual([asaId, otherAppId, encodeAddress(acc.publicKey)]) + }) + + test('app args is correct with value resource encoding', async ({ + appClientSignaturesContract: appClient, + algorand, + appFactorySignaturesContract, + }) => { + const contract = ctx.contract.create(SignaturesContract) + contract.create() + + const localnetCreator = await algorand.account.localNetDispenser() + const asaId = ( + await algorand.send.assetCreate({ + sender: localnetCreator, + total: 123n, + }) + ).confirmation.assetIndex + + const asset = ctx.any.asset({ assetId: asaId, total: 123 }) + + const otherApp = await appFactorySignaturesContract.send.create({ method: 'create' }) + const otherAppId = otherApp.appClient.appId + const otherApplication = ctx.any.application({ applicationId: otherAppId }) + + const acc = algorand.account.random() + await algorand.account.ensureFundedFromEnvironment(acc, new AlgoAmount({ microAlgo: _FUNDED_ACCOUNT_SPENDING })) + // ensure context has the same account with matching balance + const account = ctx.any.account({ + address: Bytes(acc.publicKey), + balance: Global.minBalance + _FUNDED_ACCOUNT_SPENDING, + }) + // act + const result = await getAvmResult({ appClient }, 'echoResourceByValue', asaId, otherAppId, acc.publicKey) + contract.echoResourceByValue(asset, otherApplication, account) + + // assert + const txn = ctx.txn.lastActive as gtxn.ApplicationCallTxn + const appArgs = Array(Number(txn.numAppArgs)) + .fill(0) + .map((_, i) => txn.appArgs(i)) + + expect(appArgs).toEqual([ + appClient.getABIMethod('echoResourceByValue(uint64,uint64,address)(uint64,uint64,address)').getSelector(), + toBytes(asaId), + toBytes(otherAppId), + toBytes(account), + ]) + expect(appArgs[0]).toEqual(methodSelector(SignaturesContract.prototype.echoResourceByValue)) + + expect(result).toEqual([asaId, otherAppId, encodeAddress(acc.publicKey)]) + }) + test('prepare txns with complex', async ({ appClientSignaturesContract: appClient, algorand }) => { // arrange const contract = ctx.contract.create(SignaturesContract) diff --git a/tests/arc4/resource-encoding.spec.ts b/tests/arc4/resource-encoding.spec.ts new file mode 100644 index 00000000..052c5c28 --- /dev/null +++ b/tests/arc4/resource-encoding.spec.ts @@ -0,0 +1,67 @@ +import { algo } from '@algorandfoundation/algokit-utils' +import { afterEach, beforeAll, describe, expect } from 'vitest' +import { TestExecutionContext } from '../../src/test-execution-context' +import { createArc4TestFixture } from '../test-fixture' + +describe('resource encoding', () => { + const ctx = new TestExecutionContext() + const [test, localnetFixture] = createArc4TestFixture('tests/artifacts/resource-encoding/contract.algo.ts', { + ByIndex: {}, + ByValue: {}, + C2C: { funding: algo(1) }, + }) + + beforeAll(async () => { + await localnetFixture.newScope() + }) + + afterEach(() => { + ctx.reset() + }) + + test('index', async ({ appClientByIndex, localnet }) => { + const newAccount = await localnet.context.generateAccount({ initialFunds: algo(5) }) + const balance = 5_000_000n + const res2 = await appClientByIndex.send.call({ method: 'testExplicitIndex', args: [newAccount.addr.toString()] }) + expect(res2.return).toStrictEqual(balance) + + await expect( + appClientByIndex.send.call({ + method: 'testImplicitValue', + args: [newAccount.addr.toString()], + accountReferences: [], + populateAppCallResources: false, + }), + ).rejects.toThrow('invalid Account reference') + + const res3 = await appClientByIndex.send.call({ + method: 'testImplicitValue', + args: [newAccount.addr.toString()], + accountReferences: [newAccount.addr.toString()], + populateAppCallResources: false, + }) + expect(res3.return).toStrictEqual(balance) + }) + + test('c2c', async ({ appClientByIndex, appClientByValue, appClientC2C, localnet }) => { + const newAccount = await localnet.context.generateAccount({ initialFunds: algo(5) }) + + await appClientC2C.send.call({ + method: 'testCallToIndex', + args: [newAccount.addr.toString(), appClientByIndex.appId], + extraFee: algo(1), + }) + await appClientC2C.send.call({ + method: 'testCallToValue', + args: [newAccount.addr.toString(), appClientByValue.appId], + extraFee: algo(1), + }) + }) + + test('EchoResource', async ({ appClientC2C }) => { + await appClientC2C.send.call({ + method: 'testCallToEchoResource', + extraFee: algo(1), + }) + }) +}) diff --git a/tests/artifacts/arc4-abi-method/contract.algo.ts b/tests/artifacts/arc4-abi-method/contract.algo.ts index 064b1fc3..773a3a63 100644 --- a/tests/artifacts/arc4-abi-method/contract.algo.ts +++ b/tests/artifacts/arc4-abi-method/contract.algo.ts @@ -1,8 +1,7 @@ type UInt8Array = arc4.DynamicArray type MyAlias = arc4.Uint<128> -import type { Account, Application, Asset } from '@algorandfoundation/algorand-typescript' -import { arc4, assert, clone, gtxn, op, Txn } from '@algorandfoundation/algorand-typescript' +import { Account, Application, arc4, assert, Asset, clone, gtxn, op, Txn } from '@algorandfoundation/algorand-typescript' export class AnotherStruct extends arc4.Struct<{ one: arc4.Uint64 @@ -49,7 +48,7 @@ export class SignaturesContract extends arc4.Contract { assert(pay.amount === 123) } - @arc4.abimethod() + @arc4.abimethod({ resourceEncoding: 'Index' }) withAsset(value: arc4.Str, asset: Asset, arr: UInt8Array) { assert(value.native) assert(arr.length) @@ -57,7 +56,7 @@ export class SignaturesContract extends arc4.Contract { assert(Txn.assets(0) === asset) } - @arc4.abimethod() + @arc4.abimethod({ resourceEncoding: 'Index' }) withApp(value: arc4.Str, app: Application, appId: arc4.Uint64, arr: UInt8Array) { assert(value.native) assert(arr.length) @@ -70,7 +69,7 @@ export class SignaturesContract extends arc4.Contract { assert(Txn.applications(1) === app) } - @arc4.abimethod() + @arc4.abimethod({ resourceEncoding: 'Index' }) withAcc(value: arc4.Str, acc: Account, arr: UInt8Array) { assert(value.native) assert(arr.length) @@ -79,7 +78,7 @@ export class SignaturesContract extends arc4.Contract { assert(Txn.accounts(1) === acc) } - @arc4.abimethod() + @arc4.abimethod({ resourceEncoding: 'Index' }) complexSig(struct1: MyStruct, txn: gtxn.PaymentTxn, acc: Account, five: UInt8Array): readonly [MyStructAlias, MyStruct] { assert(Txn.numAppArgs === 4) @@ -101,4 +100,26 @@ export class SignaturesContract extends arc4.Contract { return [clone(struct1.anotherStruct), clone(struct1)] } + + @arc4.abimethod({ resourceEncoding: 'Index' }) + echoResourceByIndex(asset: Asset, app: Application, acc: Account) { + const assetIdx = op.btoi(Txn.applicationArgs(1)) + assert(asset === Txn.assets(assetIdx), 'expected asset to be passed by Index') + const appIdx = op.btoi(Txn.applicationArgs(2)) + assert(app === Txn.applications(appIdx), 'expected application to be passed by Index') + const accIdx = op.btoi(Txn.applicationArgs(3)) + assert(acc === Txn.accounts(accIdx), 'expected account to be passed by Index') + return [asset, app, acc] as const + } + + @arc4.abimethod({ resourceEncoding: 'Value' }) + echoResourceByValue(asset: Asset, app: Application, acc: Account): [Asset, Application, Account] { + const assetId = op.btoi(Txn.applicationArgs(1)) + assert(asset === Asset(assetId), 'expected asset to be passed by Value') + const appId = op.btoi(Txn.applicationArgs(2)) + assert(app === Application(appId), 'expected application to be passed by Value') + const address = Txn.applicationArgs(3) + assert(acc === Account(address), 'expected account to be passed by Value') + return [asset, app, acc] + } } diff --git a/tests/artifacts/resource-encoding/contract.algo.ts b/tests/artifacts/resource-encoding/contract.algo.ts new file mode 100644 index 00000000..90892eaa --- /dev/null +++ b/tests/artifacts/resource-encoding/contract.algo.ts @@ -0,0 +1,105 @@ +import { + abimethod, + Account, + Application, + assert, + assertMatch, + Asset, + Contract, + Global, + itxn, + op, + Txn, +} from '@algorandfoundation/algorand-typescript' +import { abiCall, compileArc4 } from '@algorandfoundation/algorand-typescript/arc4' + +class ByIndex extends Contract { + @abimethod({ resourceEncoding: 'Index' }) + testExplicitIndex(account: Account) { + return account.balance + } + + /** + * Should implicitly use default 'value' + * @param account + */ + testImplicitValue(account: Account) { + return account.balance + } +} + +class ByValue extends Contract { + @abimethod({ resourceEncoding: 'Value' }) + testExplicitValue(account: Account) { + return account.balance + } +} + +class EchoResource extends Contract { + @abimethod({ resourceEncoding: 'Index' }) + echoResourceByIndex(asset: Asset, app: Application, acc: Account): [Asset, Application, Account] { + const assetIdx = op.btoi(Txn.applicationArgs(1)) + assert(asset === Txn.assets(assetIdx), 'expected asset to be passed by Index') + const appIdx = op.btoi(Txn.applicationArgs(2)) + assert(app === Txn.applications(appIdx), 'expected application to be passed by Index') + const accIdx = op.btoi(Txn.applicationArgs(3)) + assert(acc === Txn.accounts(accIdx), 'expected account to be passed by Index') + return [asset, app, acc] as const + } + + @abimethod({ resourceEncoding: 'Value' }) + echoResourceByValue(asset: Asset, app: Application, acc: Account): [Asset, Application, Account] { + const assetId = op.btoi(Txn.applicationArgs(1)) + assert(asset === Asset(assetId), 'expected asset to be passed by Value') + const appId = op.btoi(Txn.applicationArgs(2)) + assert(app === Application(appId), 'expected application to be passed by Value') + const address = Txn.applicationArgs(3) + assert(acc === Account(address), 'expected account to be passed by Value') + return [asset, app, acc] + } +} + +export class C2C extends Contract { + testCallToIndex(account: Account, appId: Application) { + const { returnValue: res1 } = abiCall(ByIndex.prototype.testExplicitIndex, { + appId, + args: [account], + }) + + assert(res1 === account.balance) + } + testCallToValue(account: Account, appId: Application) { + const { returnValue: res1 } = abiCall(ByValue.prototype.testExplicitValue, { + appId, + args: [account], + }) + + assert(res1 === account.balance) + } + + testCallToEchoResource() { + const compiled = compileArc4(EchoResource) + + const appId = compiled.bareCreate().createdApp + const asset = itxn + .assetConfig({ + total: 1, + unitName: 'T', + assetName: 'TEST', + }) + .submit().createdAsset + + const { returnValue: indexes } = compiled.call.echoResourceByIndex({ + args: [asset, Global.currentApplicationId, Txn.sender], + appId, + }) + assertMatch(indexes, [asset, Global.currentApplicationId, Txn.sender]) + + const { returnValue: resources } = compiled.call.echoResourceByValue({ + args: [asset, Global.currentApplicationId, Txn.sender], + appId, + }) + + assertMatch(resources, [asset, Global.currentApplicationId, Txn.sender]) + } +} diff --git a/tests/artifacts/state-ops/contract.algo.ts b/tests/artifacts/state-ops/contract.algo.ts index 6f2fe99f..3765ae51 100644 --- a/tests/artifacts/state-ops/contract.algo.ts +++ b/tests/artifacts/state-ops/contract.algo.ts @@ -24,7 +24,7 @@ function get_1st_ref_index(): uint64 { @contract({ name: 'StateAcctParamsGetContract', avmVersion: 11 }) export class StateAcctParamsGetContract extends arc4.Contract { - @arc4.abimethod() + @arc4.abimethod({ resourceEncoding: 'Index' }) public verify_acct_balance(a: Account): uint64 { const [value, funded] = op.AcctParams.acctBalance(a) const [value_index, funded_index] = op.AcctParams.acctBalance(get_1st_ref_index()) @@ -36,7 +36,7 @@ export class StateAcctParamsGetContract extends arc4.Contract { return value } - @arc4.abimethod() + @arc4.abimethod({ resourceEncoding: 'Index' }) public verify_acct_min_balance(a: Account): uint64 { const [value, funded] = op.AcctParams.acctMinBalance(a) const [value_index, funded_index] = op.AcctParams.acctMinBalance(get_1st_ref_index()) @@ -48,7 +48,7 @@ export class StateAcctParamsGetContract extends arc4.Contract { return value } - @arc4.abimethod() + @arc4.abimethod({ resourceEncoding: 'Index' }) public verify_acct_auth_addr(a: Account): Address { const [value, funded] = op.AcctParams.acctAuthAddr(a) const [value_index, funded_index] = op.AcctParams.acctAuthAddr(get_1st_ref_index()) @@ -57,7 +57,7 @@ export class StateAcctParamsGetContract extends arc4.Contract { return new Address(value) } - @arc4.abimethod() + @arc4.abimethod({ resourceEncoding: 'Index' }) public verify_acct_total_num_uint(a: Account): uint64 { const [value, funded] = op.AcctParams.acctTotalNumUint(a) const [value_index, funded_index] = op.AcctParams.acctTotalNumUint(get_1st_ref_index()) @@ -66,7 +66,7 @@ export class StateAcctParamsGetContract extends arc4.Contract { return value } - @arc4.abimethod() + @arc4.abimethod({ resourceEncoding: 'Index' }) public verify_acct_total_num_byte_slice(a: Account): uint64 { const [value, funded] = op.AcctParams.acctTotalNumByteSlice(a) const [value_index, funded_index] = op.AcctParams.acctTotalNumByteSlice(get_1st_ref_index()) @@ -75,7 +75,7 @@ export class StateAcctParamsGetContract extends arc4.Contract { return value } - @arc4.abimethod() + @arc4.abimethod({ resourceEncoding: 'Index' }) public verify_acct_total_extra_app_pages(a: Account): uint64 { const [value, funded] = op.AcctParams.acctTotalExtraAppPages(a) const [value_index, funded_index] = op.AcctParams.acctTotalExtraAppPages(get_1st_ref_index()) @@ -84,7 +84,7 @@ export class StateAcctParamsGetContract extends arc4.Contract { return value } - @arc4.abimethod() + @arc4.abimethod({ resourceEncoding: 'Index' }) public verify_acct_total_apps_created(a: Account): uint64 { const [value, funded] = op.AcctParams.acctTotalAppsCreated(a) const [value_index, funded_index] = op.AcctParams.acctTotalAppsCreated(get_1st_ref_index()) @@ -93,7 +93,7 @@ export class StateAcctParamsGetContract extends arc4.Contract { return value } - @arc4.abimethod() + @arc4.abimethod({ resourceEncoding: 'Index' }) public verify_acct_total_apps_opted_in(a: Account): uint64 { const [value, funded] = op.AcctParams.acctTotalAppsOptedIn(a) const [value_index, funded_index] = op.AcctParams.acctTotalAppsOptedIn(get_1st_ref_index()) @@ -102,7 +102,7 @@ export class StateAcctParamsGetContract extends arc4.Contract { return value } - @arc4.abimethod() + @arc4.abimethod({ resourceEncoding: 'Index' }) public verify_acct_total_assets_created(a: Account): uint64 { const [value, funded] = op.AcctParams.acctTotalAssetsCreated(a) const [value_index, funded_index] = op.AcctParams.acctTotalAssetsCreated(get_1st_ref_index()) @@ -111,7 +111,7 @@ export class StateAcctParamsGetContract extends arc4.Contract { return value } - @arc4.abimethod() + @arc4.abimethod({ resourceEncoding: 'Index' }) public verify_acct_total_assets(a: Account): uint64 { const [value, funded] = op.AcctParams.acctTotalAssets(a) const [value_index, funded_index] = op.AcctParams.acctTotalAssets(get_1st_ref_index()) @@ -120,7 +120,7 @@ export class StateAcctParamsGetContract extends arc4.Contract { return value } - @arc4.abimethod() + @arc4.abimethod({ resourceEncoding: 'Index' }) public verify_acct_total_boxes(a: Account): uint64 { const [value, funded] = op.AcctParams.acctTotalBoxes(a) const [value_index, funded_index] = op.AcctParams.acctTotalBoxes(get_1st_ref_index()) @@ -129,7 +129,7 @@ export class StateAcctParamsGetContract extends arc4.Contract { return value } - @arc4.abimethod() + @arc4.abimethod({ resourceEncoding: 'Index' }) public verify_acct_total_box_bytes(a: Account): uint64 { const [value, funded] = op.AcctParams.acctTotalBoxBytes(a) const [value_index, funded_index] = op.AcctParams.acctTotalBoxBytes(get_1st_ref_index()) @@ -138,7 +138,7 @@ export class StateAcctParamsGetContract extends arc4.Contract { return value } - @arc4.abimethod() + @arc4.abimethod({ resourceEncoding: 'Index' }) public verify_acct_incentive_eligible(a: Account): boolean { const [value, funded] = op.AcctParams.acctIncentiveEligible(a) const [value_index, funded_index] = op.AcctParams.acctIncentiveEligible(get_1st_ref_index()) From 6b736b50e68340ee4822514c09e6ec11b109657f Mon Sep 17 00:00:00 2001 From: Bobby Lat Date: Wed, 6 Aug 2025 15:26:11 +0700 Subject: [PATCH 24/68] chore: update docs --- .../subcontexts/contract-context/classes/ContractContext.md | 6 +++--- docs/coverage.md | 5 ++++- 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/docs/code/subcontexts/contract-context/classes/ContractContext.md b/docs/code/subcontexts/contract-context/classes/ContractContext.md index a9b70fe9..f95ae0a5 100644 --- a/docs/code/subcontexts/contract-context/classes/ContractContext.md +++ b/docs/code/subcontexts/contract-context/classes/ContractContext.md @@ -6,7 +6,7 @@ # Class: ContractContext -Defined in: [src/subcontexts/contract-context.ts:145](https://github.com/algorandfoundation/algorand-typescript-testing/blob/main/src/subcontexts/contract-context.ts#L145) +Defined in: [src/subcontexts/contract-context.ts:162](https://github.com/algorandfoundation/algorand-typescript-testing/blob/main/src/subcontexts/contract-context.ts#L162) Provides a context for creating contracts and registering created contract instances with test execution context. @@ -27,7 +27,7 @@ with test execution context. > **create**\<`T`\>(`type`, ...`args`): `T` -Defined in: [src/subcontexts/contract-context.ts:157](https://github.com/algorandfoundation/algorand-typescript-testing/blob/main/src/subcontexts/contract-context.ts#L157) +Defined in: [src/subcontexts/contract-context.ts:174](https://github.com/algorandfoundation/algorand-typescript-testing/blob/main/src/subcontexts/contract-context.ts#L174) Creates a new contract instance and register the created instance with test execution context. @@ -72,7 +72,7 @@ const contract = ctx.contract.create(MyContract); > `static` **createMethodCallTxns**\<`TParams`\>(`contract`, `abiMetadata`, ...`args`): `Transaction`[] -Defined in: [src/subcontexts/contract-context.ts:179](https://github.com/algorandfoundation/algorand-typescript-testing/blob/main/src/subcontexts/contract-context.ts#L179) +Defined in: [src/subcontexts/contract-context.ts:196](https://github.com/algorandfoundation/algorand-typescript-testing/blob/main/src/subcontexts/contract-context.ts#L196) **`Internal`** diff --git a/docs/coverage.md b/docs/coverage.md index d8807861..c8a29cba 100644 --- a/docs/coverage.md +++ b/docs/coverage.md @@ -16,11 +16,13 @@ See which `algorand-typescript` stubs are implemented by the `algorand-typescrip | CompiledContract | Mockable | | CompiledLogicSig | Mockable | | ContractProxy | Mockable | +| FixedArray | Native | | Global | Emulated | | GlobalState | Emulated | | LocalState | Emulated | | OnCompleteAction | Native | | OpUpFeeSource | Native | +| ReferenceArray | Native | | StateTotals | Emulated | | TemplateVar | Emulated | | TransactionType | Native | @@ -32,6 +34,7 @@ See which `algorand-typescript` stubs are implemented by the `algorand-typescrip | compile | Mockable | | compileArc4 | Mockable | | decodeArc4 | Native | +| clone | Native | | emit | Emulated | | encodeArc4 | Native | | ensureBudget | Emulated | @@ -55,7 +58,7 @@ See which `algorand-typescript` stubs are implemented by the `algorand-typescrip | arc4.Struct | Native | | arc4.Tuple | Native | | arc4.UFixed | Native | -| arc4.UintN | Native | +| arc4.Uint | Native | | arc4.Uint128 | Native | | arc4.Uint16 | Native | | arc4.Uint256 | Native | From d7a6a4ba596c1ffd93847ecab92b796d9068d59a Mon Sep 17 00:00:00 2001 From: Bobby Lat Date: Thu, 7 Aug 2025 09:40:40 +0700 Subject: [PATCH 25/68] use lowercase for resourceEncoding options --- examples/precompiled/precompiled-apps.algo.ts | 2 +- src/impl/encoded-types/utils.ts | 6 ++--- src/subcontexts/contract-context.ts | 6 ++--- .../arc4-abi-method/contract.algo.ts | 12 ++++----- .../resource-encoding/contract.algo.ts | 8 +++--- tests/artifacts/state-ops/contract.algo.ts | 26 +++++++++---------- 6 files changed, 30 insertions(+), 30 deletions(-) diff --git a/examples/precompiled/precompiled-apps.algo.ts b/examples/precompiled/precompiled-apps.algo.ts index 808cadab..2c90361c 100644 --- a/examples/precompiled/precompiled-apps.algo.ts +++ b/examples/precompiled/precompiled-apps.algo.ts @@ -102,7 +102,7 @@ export class ReceivesTxns extends Contract { } export class ReceivesReferenceTypes extends Contract { - @abimethod({ resourceEncoding: 'Index' }) + @abimethod({ resourceEncoding: 'index' }) receivesReferenceTypes(app: Application, acc: Account, asset: Asset) { log(app.address) log(acc.bytes) diff --git a/src/impl/encoded-types/utils.ts b/src/impl/encoded-types/utils.ts index 5e4cd8c7..6be29165 100644 --- a/src/impl/encoded-types/utils.ts +++ b/src/impl/encoded-types/utils.ts @@ -94,9 +94,9 @@ export const getArc4TypeName = ( } const map: Record string)> = { void: 'void', - account: (_) => (resourceEncoding === 'Index' && direction === 'in' ? 'account' : 'address'), - application: (_) => (resourceEncoding === 'Index' && direction === 'in' ? 'application' : 'uint64'), - asset: (_) => (resourceEncoding === 'Index' && direction === 'in' ? 'asset' : 'uint64'), + account: (_) => (resourceEncoding === 'index' && direction === 'in' ? 'account' : 'address'), + application: (_) => (resourceEncoding === 'index' && direction === 'in' ? 'application' : 'uint64'), + asset: (_) => (resourceEncoding === 'index' && direction === 'in' ? 'asset' : 'uint64'), boolean: 'bool', biguint: 'uint512', bytes: 'byte[]', diff --git a/src/subcontexts/contract-context.ts b/src/subcontexts/contract-context.ts index 45774145..bc17a9ec 100644 --- a/src/subcontexts/contract-context.ts +++ b/src/subcontexts/contract-context.ts @@ -106,21 +106,21 @@ export const extractArraysFromArgs = ( if (isTransaction(arg)) { transactions.push(arg) } else if (arg instanceof AccountCls) { - if (resourceEncoding === 'Index') { + if (resourceEncoding === 'index') { appArgs.push(getUint8Impl(accounts.length)) accounts.push(arg as Account) } else { appArgs.push(getArc4Encoded(arg.bytes)) } } else if (arg instanceof ApplicationCls) { - if (resourceEncoding === 'Index') { + if (resourceEncoding === 'index') { appArgs.push(getUint8Impl(apps.length)) apps.push(arg as Application) } else { appArgs.push(getArc4Encoded(arg.id)) } } else if (arg instanceof AssetCls) { - if (resourceEncoding === 'Index') { + if (resourceEncoding === 'index') { appArgs.push(getUint8Impl(assets.length)) assets.push(arg as Asset) } else { diff --git a/tests/artifacts/arc4-abi-method/contract.algo.ts b/tests/artifacts/arc4-abi-method/contract.algo.ts index 773a3a63..19419e1d 100644 --- a/tests/artifacts/arc4-abi-method/contract.algo.ts +++ b/tests/artifacts/arc4-abi-method/contract.algo.ts @@ -48,7 +48,7 @@ export class SignaturesContract extends arc4.Contract { assert(pay.amount === 123) } - @arc4.abimethod({ resourceEncoding: 'Index' }) + @arc4.abimethod({ resourceEncoding: 'index' }) withAsset(value: arc4.Str, asset: Asset, arr: UInt8Array) { assert(value.native) assert(arr.length) @@ -56,7 +56,7 @@ export class SignaturesContract extends arc4.Contract { assert(Txn.assets(0) === asset) } - @arc4.abimethod({ resourceEncoding: 'Index' }) + @arc4.abimethod({ resourceEncoding: 'index' }) withApp(value: arc4.Str, app: Application, appId: arc4.Uint64, arr: UInt8Array) { assert(value.native) assert(arr.length) @@ -69,7 +69,7 @@ export class SignaturesContract extends arc4.Contract { assert(Txn.applications(1) === app) } - @arc4.abimethod({ resourceEncoding: 'Index' }) + @arc4.abimethod({ resourceEncoding: 'index' }) withAcc(value: arc4.Str, acc: Account, arr: UInt8Array) { assert(value.native) assert(arr.length) @@ -78,7 +78,7 @@ export class SignaturesContract extends arc4.Contract { assert(Txn.accounts(1) === acc) } - @arc4.abimethod({ resourceEncoding: 'Index' }) + @arc4.abimethod({ resourceEncoding: 'index' }) complexSig(struct1: MyStruct, txn: gtxn.PaymentTxn, acc: Account, five: UInt8Array): readonly [MyStructAlias, MyStruct] { assert(Txn.numAppArgs === 4) @@ -101,7 +101,7 @@ export class SignaturesContract extends arc4.Contract { return [clone(struct1.anotherStruct), clone(struct1)] } - @arc4.abimethod({ resourceEncoding: 'Index' }) + @arc4.abimethod({ resourceEncoding: 'index' }) echoResourceByIndex(asset: Asset, app: Application, acc: Account) { const assetIdx = op.btoi(Txn.applicationArgs(1)) assert(asset === Txn.assets(assetIdx), 'expected asset to be passed by Index') @@ -112,7 +112,7 @@ export class SignaturesContract extends arc4.Contract { return [asset, app, acc] as const } - @arc4.abimethod({ resourceEncoding: 'Value' }) + @arc4.abimethod({ resourceEncoding: 'value' }) echoResourceByValue(asset: Asset, app: Application, acc: Account): [Asset, Application, Account] { const assetId = op.btoi(Txn.applicationArgs(1)) assert(asset === Asset(assetId), 'expected asset to be passed by Value') diff --git a/tests/artifacts/resource-encoding/contract.algo.ts b/tests/artifacts/resource-encoding/contract.algo.ts index 90892eaa..11613420 100644 --- a/tests/artifacts/resource-encoding/contract.algo.ts +++ b/tests/artifacts/resource-encoding/contract.algo.ts @@ -14,7 +14,7 @@ import { import { abiCall, compileArc4 } from '@algorandfoundation/algorand-typescript/arc4' class ByIndex extends Contract { - @abimethod({ resourceEncoding: 'Index' }) + @abimethod({ resourceEncoding: 'index' }) testExplicitIndex(account: Account) { return account.balance } @@ -29,14 +29,14 @@ class ByIndex extends Contract { } class ByValue extends Contract { - @abimethod({ resourceEncoding: 'Value' }) + @abimethod({ resourceEncoding: 'value' }) testExplicitValue(account: Account) { return account.balance } } class EchoResource extends Contract { - @abimethod({ resourceEncoding: 'Index' }) + @abimethod({ resourceEncoding: 'index' }) echoResourceByIndex(asset: Asset, app: Application, acc: Account): [Asset, Application, Account] { const assetIdx = op.btoi(Txn.applicationArgs(1)) assert(asset === Txn.assets(assetIdx), 'expected asset to be passed by Index') @@ -47,7 +47,7 @@ class EchoResource extends Contract { return [asset, app, acc] as const } - @abimethod({ resourceEncoding: 'Value' }) + @abimethod({ resourceEncoding: 'value' }) echoResourceByValue(asset: Asset, app: Application, acc: Account): [Asset, Application, Account] { const assetId = op.btoi(Txn.applicationArgs(1)) assert(asset === Asset(assetId), 'expected asset to be passed by Value') diff --git a/tests/artifacts/state-ops/contract.algo.ts b/tests/artifacts/state-ops/contract.algo.ts index 3765ae51..d539d309 100644 --- a/tests/artifacts/state-ops/contract.algo.ts +++ b/tests/artifacts/state-ops/contract.algo.ts @@ -24,7 +24,7 @@ function get_1st_ref_index(): uint64 { @contract({ name: 'StateAcctParamsGetContract', avmVersion: 11 }) export class StateAcctParamsGetContract extends arc4.Contract { - @arc4.abimethod({ resourceEncoding: 'Index' }) + @arc4.abimethod({ resourceEncoding: 'index' }) public verify_acct_balance(a: Account): uint64 { const [value, funded] = op.AcctParams.acctBalance(a) const [value_index, funded_index] = op.AcctParams.acctBalance(get_1st_ref_index()) @@ -36,7 +36,7 @@ export class StateAcctParamsGetContract extends arc4.Contract { return value } - @arc4.abimethod({ resourceEncoding: 'Index' }) + @arc4.abimethod({ resourceEncoding: 'index' }) public verify_acct_min_balance(a: Account): uint64 { const [value, funded] = op.AcctParams.acctMinBalance(a) const [value_index, funded_index] = op.AcctParams.acctMinBalance(get_1st_ref_index()) @@ -48,7 +48,7 @@ export class StateAcctParamsGetContract extends arc4.Contract { return value } - @arc4.abimethod({ resourceEncoding: 'Index' }) + @arc4.abimethod({ resourceEncoding: 'index' }) public verify_acct_auth_addr(a: Account): Address { const [value, funded] = op.AcctParams.acctAuthAddr(a) const [value_index, funded_index] = op.AcctParams.acctAuthAddr(get_1st_ref_index()) @@ -57,7 +57,7 @@ export class StateAcctParamsGetContract extends arc4.Contract { return new Address(value) } - @arc4.abimethod({ resourceEncoding: 'Index' }) + @arc4.abimethod({ resourceEncoding: 'index' }) public verify_acct_total_num_uint(a: Account): uint64 { const [value, funded] = op.AcctParams.acctTotalNumUint(a) const [value_index, funded_index] = op.AcctParams.acctTotalNumUint(get_1st_ref_index()) @@ -66,7 +66,7 @@ export class StateAcctParamsGetContract extends arc4.Contract { return value } - @arc4.abimethod({ resourceEncoding: 'Index' }) + @arc4.abimethod({ resourceEncoding: 'index' }) public verify_acct_total_num_byte_slice(a: Account): uint64 { const [value, funded] = op.AcctParams.acctTotalNumByteSlice(a) const [value_index, funded_index] = op.AcctParams.acctTotalNumByteSlice(get_1st_ref_index()) @@ -75,7 +75,7 @@ export class StateAcctParamsGetContract extends arc4.Contract { return value } - @arc4.abimethod({ resourceEncoding: 'Index' }) + @arc4.abimethod({ resourceEncoding: 'index' }) public verify_acct_total_extra_app_pages(a: Account): uint64 { const [value, funded] = op.AcctParams.acctTotalExtraAppPages(a) const [value_index, funded_index] = op.AcctParams.acctTotalExtraAppPages(get_1st_ref_index()) @@ -84,7 +84,7 @@ export class StateAcctParamsGetContract extends arc4.Contract { return value } - @arc4.abimethod({ resourceEncoding: 'Index' }) + @arc4.abimethod({ resourceEncoding: 'index' }) public verify_acct_total_apps_created(a: Account): uint64 { const [value, funded] = op.AcctParams.acctTotalAppsCreated(a) const [value_index, funded_index] = op.AcctParams.acctTotalAppsCreated(get_1st_ref_index()) @@ -93,7 +93,7 @@ export class StateAcctParamsGetContract extends arc4.Contract { return value } - @arc4.abimethod({ resourceEncoding: 'Index' }) + @arc4.abimethod({ resourceEncoding: 'index' }) public verify_acct_total_apps_opted_in(a: Account): uint64 { const [value, funded] = op.AcctParams.acctTotalAppsOptedIn(a) const [value_index, funded_index] = op.AcctParams.acctTotalAppsOptedIn(get_1st_ref_index()) @@ -102,7 +102,7 @@ export class StateAcctParamsGetContract extends arc4.Contract { return value } - @arc4.abimethod({ resourceEncoding: 'Index' }) + @arc4.abimethod({ resourceEncoding: 'index' }) public verify_acct_total_assets_created(a: Account): uint64 { const [value, funded] = op.AcctParams.acctTotalAssetsCreated(a) const [value_index, funded_index] = op.AcctParams.acctTotalAssetsCreated(get_1st_ref_index()) @@ -111,7 +111,7 @@ export class StateAcctParamsGetContract extends arc4.Contract { return value } - @arc4.abimethod({ resourceEncoding: 'Index' }) + @arc4.abimethod({ resourceEncoding: 'index' }) public verify_acct_total_assets(a: Account): uint64 { const [value, funded] = op.AcctParams.acctTotalAssets(a) const [value_index, funded_index] = op.AcctParams.acctTotalAssets(get_1st_ref_index()) @@ -120,7 +120,7 @@ export class StateAcctParamsGetContract extends arc4.Contract { return value } - @arc4.abimethod({ resourceEncoding: 'Index' }) + @arc4.abimethod({ resourceEncoding: 'index' }) public verify_acct_total_boxes(a: Account): uint64 { const [value, funded] = op.AcctParams.acctTotalBoxes(a) const [value_index, funded_index] = op.AcctParams.acctTotalBoxes(get_1st_ref_index()) @@ -129,7 +129,7 @@ export class StateAcctParamsGetContract extends arc4.Contract { return value } - @arc4.abimethod({ resourceEncoding: 'Index' }) + @arc4.abimethod({ resourceEncoding: 'index' }) public verify_acct_total_box_bytes(a: Account): uint64 { const [value, funded] = op.AcctParams.acctTotalBoxBytes(a) const [value_index, funded_index] = op.AcctParams.acctTotalBoxBytes(get_1st_ref_index()) @@ -138,7 +138,7 @@ export class StateAcctParamsGetContract extends arc4.Contract { return value } - @arc4.abimethod({ resourceEncoding: 'Index' }) + @arc4.abimethod({ resourceEncoding: 'index' }) public verify_acct_incentive_eligible(a: Account): boolean { const [value, funded] = op.AcctParams.acctIncentiveEligible(a) const [value_index, funded_index] = op.AcctParams.acctIncentiveEligible(get_1st_ref_index()) From ffff719478b8071a801e65a1c6c21adf3dbdc458 Mon Sep 17 00:00:00 2001 From: Bobby Lat Date: Thu, 7 Aug 2025 10:49:11 +0700 Subject: [PATCH 26/68] refactor: replace `Impl` pattern for stubbing in testing implementation with `Internal` namespace replacement pattern for consistency --- examples/rollup.config.ts | 1 + src/impl/clone.ts | 2 +- src/impl/emit.ts | 2 +- src/impl/encoded-types/encoded-types.ts | 393 ++++++++++++------------ src/impl/encoded-types/helpers.ts | 2 +- src/impl/encoded-types/index.ts | 62 ++-- src/impl/encoded-types/utils.ts | 2 +- src/impl/ensure-budget.ts | 2 +- src/impl/inner-transactions.ts | 4 +- src/impl/itxn-compose.ts | 26 +- src/impl/match.ts | 22 +- src/impl/template-var.ts | 2 +- src/impl/urange.ts | 2 +- src/internal/arc4.ts | 20 ++ src/internal/index.ts | 10 +- src/runtime-helpers.ts | 9 - src/subcontexts/contract-context.ts | 10 +- src/subcontexts/transaction-context.ts | 32 +- src/test-transformer/node-factory.ts | 42 +-- src/test-transformer/visitors.ts | 30 +- src/value-generators/arc4.ts | 22 +- tests/global-state-arc4-values.spec.ts | 30 +- tests/local-state-arc4-values.spec.ts | 33 +- tests/match.spec.ts | 4 +- 24 files changed, 380 insertions(+), 384 deletions(-) diff --git a/examples/rollup.config.ts b/examples/rollup.config.ts index 43a8b4db..96a03fed 100644 --- a/examples/rollup.config.ts +++ b/examples/rollup.config.ts @@ -12,6 +12,7 @@ const config: RollupOptions = { 'examples/hello-world-abi/contract.algo.ts', 'examples/hello-world/contract.algo.ts', 'examples/htlc-logicsig/signature.algo.ts', + 'examples/local-storage/contract.algo.ts', 'examples/marketplace/contract.algo.ts', 'examples/precompiled/precompiled-factory.algo.ts', 'examples/precompiled/precompiled-typed.algo.ts', diff --git a/src/impl/clone.ts b/src/impl/clone.ts index 95194875..7a8a5529 100644 --- a/src/impl/clone.ts +++ b/src/impl/clone.ts @@ -1,6 +1,6 @@ import { getEncoder, toBytes } from './encoded-types' -export function cloneImpl(typeInfoString: string, value: T): T { +export function clone(typeInfoString: string, value: T): T { if (value && typeof value === 'object' && 'copy' in value && typeof value.copy === 'function') { return value.copy() as T } diff --git a/src/impl/emit.ts b/src/impl/emit.ts index 57eb3e12..5c6930e8 100644 --- a/src/impl/emit.ts +++ b/src/impl/emit.ts @@ -4,7 +4,7 @@ import { sha512_256 } from './crypto' import { getArc4Encoded, getArc4TypeName } from './encoded-types' import { log } from './log' -export function emitImpl(typeInfoString: string, event: T | string, ...eventProps: unknown[]) { +export function emit(typeInfoString: string, event: T | string, ...eventProps: unknown[]) { let eventData let eventName if (typeof event === 'string') { diff --git a/src/impl/encoded-types/encoded-types.ts b/src/impl/encoded-types/encoded-types.ts index a5a34101..c862377f 100644 --- a/src/impl/encoded-types/encoded-types.ts +++ b/src/impl/encoded-types/encoded-types.ts @@ -1,20 +1,20 @@ import type { Account as AccountType, bytes, NTuple, StringCompat, uint64, Uint64Compat } from '@algorandfoundation/algorand-typescript' -import { FixedArray, ReferenceArray } from '@algorandfoundation/algorand-typescript' +import { FixedArray as _FixedArray, ReferenceArray as _ReferenceArray } from '@algorandfoundation/algorand-typescript' import type { BitSize } from '@algorandfoundation/algorand-typescript/arc4' import { - Address, - ARC4Encoded, - Bool, - Byte, - DynamicArray, - DynamicBytes, - StaticArray, - StaticBytes, - Str, - Struct, - Tuple, - UFixed, - Uint, + Address as _Address, + ARC4Encoded as _ARC4Encoded, + Bool as _Bool, + Byte as _Byte, + DynamicArray as _DynamicArray, + DynamicBytes as _DynamicBytes, + StaticArray as _StaticArray, + StaticBytes as _StaticBytes, + Str as _Str, + Struct as _Struct, + Tuple as _Tuple, + UFixed as _UFixed, + Uint as _Uint, } from '@algorandfoundation/algorand-typescript/arc4' import { encodingUtil } from '@algorandfoundation/puya-ts' import assert from 'assert' @@ -55,6 +55,7 @@ import { encodeLength, findBool, findBoolTypes, + getNativeValue, holdsDynamicLengthContent, maxBigIntValue, maxBytesLength, @@ -78,7 +79,7 @@ import type { } from './types' import { getMaxLengthOfStaticContentType } from './utils' -export class UintImpl extends Uint { +export class Uint extends _Uint { private value: Uint8Array private bitSize: N typeInfo: TypeInfo @@ -86,7 +87,7 @@ export class UintImpl extends Uint { constructor(typeInfo: TypeInfo | string, v?: CompatForArc4Int) { super() this.typeInfo = typeof typeInfo === 'string' ? JSON.parse(typeInfo) : typeInfo - this.bitSize = UintImpl.getMaxBitsLength(this.typeInfo) as N + this.bitSize = Uint.getMaxBitsLength(this.typeInfo) as N assert(validBitSizes.includes(this.bitSize), `Invalid bit size ${this.bitSize}`) @@ -99,7 +100,7 @@ export class UintImpl extends Uint { get native() { const bigIntValue = encodingUtil.uint8ArrayToBigInt(this.value) - return (this.bitSize <= UINT64_SIZE ? asUint64(bigIntValue) : asBigUint(bigIntValue)) as Uint['native'] + return (this.bitSize <= UINT64_SIZE ? asUint64(bigIntValue) : asBigUint(bigIntValue)) as _Uint['native'] } get bytes(): bytes { @@ -107,23 +108,19 @@ export class UintImpl extends Uint { } equals(other: this): boolean { - if (!(other instanceof UintImpl) || JSON.stringify(this.typeInfo) !== JSON.stringify(other.typeInfo)) { + if (!(other instanceof Uint) || JSON.stringify(this.typeInfo) !== JSON.stringify(other.typeInfo)) { throw new CodeError(`Expected expression of type ${this.typeInfo.name}, got ${other.typeInfo.name}`) } return this.bytes.equals(other.bytes) } - static fromBytesImpl( - value: StubBytesCompat | Uint8Array, - typeInfo: string | TypeInfo, - prefix: 'none' | 'log' = 'none', - ): UintImpl { + static fromBytes(value: StubBytesCompat | Uint8Array, typeInfo: string | TypeInfo, prefix: 'none' | 'log' = 'none'): Uint { let bytesValue = asBytesCls(value) if (prefix === 'log') { assert(bytesValue.slice(0, 4).equals(ABI_RETURN_VALUE_LOG_PREFIX), 'ABI return prefix not found') bytesValue = bytesValue.slice(4) } - const result = new UintImpl(typeInfo) + const result = new Uint(typeInfo) result.value = asUint8Array(bytesValue) return result } @@ -133,7 +130,7 @@ export class UintImpl extends Uint { } } -export class UFixedImpl extends UFixed { +export class UFixed extends _UFixed { private value: Uint8Array private bitSize: N private precision: M @@ -143,7 +140,7 @@ export class UFixedImpl extends UFixed extends UFixed['native'] + return (this.bitSize <= UINT64_SIZE ? asUint64(bigIntValue) : asBigUint(bigIntValue)) as _UFixed['native'] } get bytes(): bytes { @@ -166,23 +163,23 @@ export class UFixedImpl extends UFixed { + ): _UFixed { let bytesValue = asBytesCls(value) if (prefix === 'log') { assert(bytesValue.slice(0, 4).equals(ABI_RETURN_VALUE_LOG_PREFIX), 'ABI return prefix not found') bytesValue = bytesValue.slice(4) } - const result = new UFixedImpl(typeInfo, '0.0') + const result = new UFixed(typeInfo, '0.0') result.value = asUint8Array(bytesValue) return result } @@ -193,14 +190,14 @@ export class UFixedImpl extends UFixed + private value: Uint<8> constructor(typeInfo: TypeInfo | string, v?: CompatForArc4Int<8>) { super(v) this.typeInfo = typeof typeInfo === 'string' ? JSON.parse(typeInfo) : typeInfo - this.value = new UintImpl<8>(typeInfo, v) + this.value = new Uint<8>(typeInfo, v) } get native() { @@ -212,25 +209,25 @@ export class ByteImpl extends Byte { } equals(other: this): boolean { - if (!(other instanceof ByteImpl) || JSON.stringify(this.value.typeInfo) !== JSON.stringify(other.value.typeInfo)) { + if (!(other instanceof Byte) || JSON.stringify(this.value.typeInfo) !== JSON.stringify(other.value.typeInfo)) { throw new CodeError(`Expected expression of type ${this.value.typeInfo.name}, got ${other.value.typeInfo.name}`) } return this.bytes.equals(other.bytes) } - static fromBytesImpl(value: StubBytesCompat | Uint8Array, typeInfo: string | TypeInfo, prefix: 'none' | 'log' = 'none'): ByteImpl { - const uintNValue = UintImpl.fromBytesImpl(value, typeInfo, prefix) as UintImpl<8> - const result = new ByteImpl(typeInfo) + static fromBytes(value: StubBytesCompat | Uint8Array, typeInfo: string | TypeInfo, prefix: 'none' | 'log' = 'none'): Byte { + const uintNValue = Uint.fromBytes(value, typeInfo, prefix) as Uint<8> + const result = new Byte(typeInfo) result.value = uintNValue return result } static getMaxBitsLength(typeInfo: TypeInfo): BitSize { - return UintImpl.getMaxBitsLength(typeInfo) + return Uint.getMaxBitsLength(typeInfo) } } -export class StrImpl extends Str { +export class Str extends _Str { typeInfo: TypeInfo private value: Uint8Array @@ -250,25 +247,25 @@ export class StrImpl extends Str { } equals(other: this): boolean { - if (!(other instanceof StrImpl)) { + if (!(other instanceof Str)) { throw new CodeError(`Expected expression of type Str, got ${(other as object).constructor.name}`) } return this.bytes.equals(other.bytes) } - static fromBytesImpl(value: StubBytesCompat | Uint8Array, typeInfo: string | TypeInfo, prefix: 'none' | 'log' = 'none'): StrImpl { + static fromBytes(value: StubBytesCompat | Uint8Array, typeInfo: string | TypeInfo, prefix: 'none' | 'log' = 'none'): Str { let bytesValue = asBytesCls(value) if (prefix === 'log') { assert(bytesValue.slice(0, 4).equals(ABI_RETURN_VALUE_LOG_PREFIX), 'ABI return prefix not found') bytesValue = bytesValue.slice(4) } - const result = new StrImpl(typeInfo) + const result = new Str(typeInfo) result.value = asUint8Array(bytesValue) return result } } -export class BoolImpl extends Bool { +export class Bool extends _Bool { private value: Uint8Array typeInfo: TypeInfo @@ -283,7 +280,7 @@ export class BoolImpl extends Bool { } equals(other: this): boolean { - if (!(other instanceof BoolImpl)) { + if (!(other instanceof Bool)) { throw new CodeError(`Expected expression of type Bool, got ${(other as object).constructor.name}`) } return this.bytes.equals(other.bytes) @@ -293,19 +290,19 @@ export class BoolImpl extends Bool { return Bytes(this.value?.length ? this.value : new Uint8Array([0])) } - static fromBytesImpl(value: StubBytesCompat | Uint8Array, typeInfo: string | TypeInfo, prefix: 'none' | 'log' = 'none'): BoolImpl { + static fromBytes(value: StubBytesCompat | Uint8Array, typeInfo: string | TypeInfo, prefix: 'none' | 'log' = 'none'): Bool { let bytesValue = asBytesCls(value) if (prefix === 'log') { assert(bytesValue.slice(0, 4).equals(ABI_RETURN_VALUE_LOG_PREFIX), 'ABI return prefix not found') bytesValue = bytesValue.slice(4) } - const result = new BoolImpl(typeInfo) + const result = new Bool(typeInfo) result.value = asUint8Array(bytesValue) return result } } -export class StaticArrayImpl extends StaticArray { +export class StaticArray extends _StaticArray { private value?: NTuple private uint8ArrayValue?: Uint8Array private size: number @@ -342,7 +339,7 @@ export class StaticArrayImpl this.uint8ArrayValue = new Uint8Array(getMaxLengthOfStaticContentType(this.typeInfo)) } } - return new Proxy(this, arrayProxyHandler()) as StaticArrayImpl + return new Proxy(this, arrayProxyHandler()) as StaticArray } get bytes(): bytes { @@ -350,7 +347,7 @@ export class StaticArrayImpl } equals(other: this): boolean { - if (!(other instanceof StaticArrayImpl) || JSON.stringify(this.typeInfo) !== JSON.stringify(other.typeInfo)) { + if (!(other instanceof StaticArray) || JSON.stringify(this.typeInfo) !== JSON.stringify(other.typeInfo)) { throw new CodeError(`Expected expression of type ${this.typeInfo.name}, got ${other.typeInfo.name}`) } return this.bytes.equals(other.bytes) @@ -377,11 +374,11 @@ export class StaticArrayImpl this.items[index] = value } - copy(): StaticArrayImpl { - return StaticArrayImpl.fromBytesImpl(this.bytes, JSON.stringify(this.typeInfo)) as unknown as StaticArrayImpl + copy(): StaticArray { + return StaticArray.fromBytes(this.bytes, JSON.stringify(this.typeInfo)) as unknown as StaticArray } - concat(other: Parameters['concat']>[0]): DynamicArrayImpl { + concat(other: Parameters['concat']>[0]): DynamicArray { const items = this.items const otherEntries = other.entries() let next = otherEntries.next() @@ -389,7 +386,7 @@ export class StaticArrayImpl items.push(next.value[1] as TItem) next = otherEntries.next() } - return new DynamicArrayImpl( + return new DynamicArray( { name: `DynamicArray<${this.genericArgs.elementType.name}>`, genericArgs: { elementType: this.genericArgs.elementType } }, ...items, ) @@ -399,26 +396,26 @@ export class StaticArrayImpl return this.items } - static fromBytesImpl( + static fromBytes( value: StubBytesCompat | Uint8Array, typeInfo: string | TypeInfo, prefix: 'none' | 'log' = 'none', - ): StaticArrayImpl { + ): StaticArray<_ARC4Encoded, number> { let bytesValue = asBytesCls(value) if (prefix === 'log') { assert(bytesValue.slice(0, 4).equals(ABI_RETURN_VALUE_LOG_PREFIX), 'ABI return prefix not found') bytesValue = bytesValue.slice(4) } // pass the symbol to the constructor to let it know we are initialising from bytes - const result = new StaticArrayImpl(typeInfo, IS_INITIALISING_FROM_BYTES_SYMBOL as DeliberateAny) + const result = new StaticArray<_ARC4Encoded, number>(typeInfo, IS_INITIALISING_FROM_BYTES_SYMBOL as DeliberateAny) result.uint8ArrayValue = asUint8Array(bytesValue) return result } } -export class AddressImpl extends Address { +export class Address extends _Address { typeInfo: TypeInfo - private value: StaticArrayImpl + private value: StaticArray constructor(typeInfo: TypeInfo | string, value?: AccountType | string | bytes) { super(value) @@ -434,9 +431,9 @@ export class AddressImpl extends Address { } avmInvariant(uint8ArrayValue.length === 32, 'Addresses should be 32 bytes') - this.value = StaticArrayImpl.fromBytesImpl(uint8ArrayValue, typeInfo) as StaticArrayImpl + this.value = StaticArray.fromBytes(uint8ArrayValue, typeInfo) as StaticArray this.typeInfo = typeof typeInfo === 'string' ? JSON.parse(typeInfo) : typeInfo - return new Proxy(this, arrayProxyHandler()) as AddressImpl + return new Proxy(this, arrayProxyHandler()) as Address } get bytes(): bytes { @@ -444,7 +441,7 @@ export class AddressImpl extends Address { } equals(other: this): boolean { - if (!(other instanceof AddressImpl) || JSON.stringify(this.typeInfo) !== JSON.stringify(other.typeInfo)) { + if (!(other instanceof Address) || JSON.stringify(this.typeInfo) !== JSON.stringify(other.typeInfo)) { throw new CodeError(`Expected expression of type ${this.typeInfo.name}, got ${other.typeInfo.name}`) } return this.bytes.equals(other.bytes) @@ -458,23 +455,23 @@ export class AddressImpl extends Address { return Account(this.value.bytes) } - get items(): readonly ByteImpl[] { + get items(): readonly Byte[] { return this.value.items } - setItem(_index: number, _value: ByteImpl): void { + setItem(_index: number, _value: Byte): void { throw new CodeError('Address is immutable') } - static fromBytesImpl(value: StubBytesCompat | Uint8Array, typeInfo: string | TypeInfo, prefix: 'none' | 'log' = 'none'): AddressImpl { - const staticArrayValue = StaticArrayImpl.fromBytesImpl(value, typeInfo, prefix) as StaticArrayImpl - const result = new AddressImpl(typeInfo) + static fromBytes(value: StubBytesCompat | Uint8Array, typeInfo: string | TypeInfo, prefix: 'none' | 'log' = 'none'): Address { + const staticArrayValue = StaticArray.fromBytes(value, typeInfo, prefix) as StaticArray + const result = new Address(typeInfo) result.value = staticArrayValue return result } } -export class DynamicArrayImpl extends DynamicArray { +export class DynamicArray extends _DynamicArray { private value?: TItem[] private uint8ArrayValue?: Uint8Array typeInfo: TypeInfo @@ -492,7 +489,7 @@ export class DynamicArrayImpl extends DynamicArray()) as DynamicArrayImpl + return new Proxy(this, arrayProxyHandler()) as DynamicArray } get bytes(): bytes { @@ -500,7 +497,7 @@ export class DynamicArrayImpl extends DynamicArray extends DynamicArray { - return DynamicArrayImpl.fromBytesImpl(this.bytes, JSON.stringify(this.typeInfo)) as DynamicArrayImpl + copy(): DynamicArray { + return DynamicArray.fromBytes(this.bytes, JSON.stringify(this.typeInfo)) as DynamicArray } get native(): TItem[] { @@ -548,7 +545,7 @@ export class DynamicArrayImpl extends DynamicArray['concat']>[0]): DynamicArrayImpl { + concat(other: Parameters['concat']>[0]): DynamicArray { const items = this.items const otherEntries = other.entries() let next = otherEntries.next() @@ -556,20 +553,20 @@ export class DynamicArrayImpl extends DynamicArray(this.typeInfo, ...items) + return new DynamicArray(this.typeInfo, ...items) } - static fromBytesImpl( + static fromBytes( value: StubBytesCompat | Uint8Array, typeInfo: string | TypeInfo, prefix: 'none' | 'log' = 'none', - ): DynamicArrayImpl { + ): DynamicArray<_ARC4Encoded> { let bytesValue = asBytesCls(value) if (prefix === 'log') { assert(bytesValue.slice(0, 4).equals(ABI_RETURN_VALUE_LOG_PREFIX), 'ABI return prefix not found') bytesValue = bytesValue.slice(4) } - const result = new DynamicArrayImpl(typeInfo) + const result = new DynamicArray(typeInfo) result.uint8ArrayValue = asUint8Array(bytesValue) return result } @@ -579,7 +576,7 @@ export class DynamicArrayImpl extends DynamicArray extends Tuple { +export class Tuple extends _Tuple { private value?: TTuple private uint8ArrayValue?: Uint8Array typeInfo: TypeInfo @@ -613,7 +610,7 @@ export class TupleImpl extends T } equals(other: this): boolean { - if (!(other instanceof TupleImpl) || JSON.stringify(this.typeInfo) !== JSON.stringify(other.typeInfo)) { + if (!(other instanceof Tuple) || JSON.stringify(this.typeInfo) !== JSON.stringify(other.typeInfo)) { throw new CodeError(`Expected expression of type ${this.typeInfo.name}, got ${other.typeInfo.name}`) } return this.bytes.equals(other.bytes) @@ -643,24 +640,24 @@ export class TupleImpl extends T throw new CodeError('value is not set') } - static fromBytesImpl( + static fromBytes( value: StubBytesCompat | Uint8Array, typeInfo: string | TypeInfo, prefix: 'none' | 'log' = 'none', - ): TupleImpl<[ARC4Encoded, ...ARC4Encoded[]]> { + ): Tuple<[_ARC4Encoded, ..._ARC4Encoded[]]> { let bytesValue = asBytesCls(value) if (prefix === 'log') { assert(bytesValue.slice(0, 4).equals(ABI_RETURN_VALUE_LOG_PREFIX), 'ABI return prefix not found') bytesValue = bytesValue.slice(4) } // pass the symbol to the constructor to let it know we are initialising from bytes - const result = new TupleImpl(typeInfo, IS_INITIALISING_FROM_BYTES_SYMBOL as DeliberateAny) + const result = new Tuple(typeInfo, IS_INITIALISING_FROM_BYTES_SYMBOL as DeliberateAny) result.uint8ArrayValue = asUint8Array(bytesValue) return result } } -export class StructImpl extends (Struct as DeliberateAny) { +export class Struct extends (_Struct as DeliberateAny) { private uint8ArrayValue?: Uint8Array genericArgs: Record @@ -711,8 +708,8 @@ export class StructImpl extends (Struct { - return StructImpl.fromBytesImpl(this.bytes, JSON.stringify(this.typeInfo)) as StructImpl + copy(): Struct { + return Struct.fromBytes(this.bytes, JSON.stringify(this.typeInfo)) as Struct } private decodeAsProperties() { @@ -725,32 +722,32 @@ export class StructImpl extends (Struct { + ): Struct { let bytesValue = asBytesCls(value) if (prefix === 'log') { assert(bytesValue.slice(0, 4).equals(ABI_RETURN_VALUE_LOG_PREFIX), 'ABI return prefix not found') bytesValue = bytesValue.slice(4) } - const result = new StructImpl(typeInfo) + const result = new Struct(typeInfo) result.uint8ArrayValue = asUint8Array(bytesValue) return result } } -export class DynamicBytesImpl extends DynamicBytes { +export class DynamicBytes extends _DynamicBytes { typeInfo: TypeInfo - private value: DynamicArrayImpl + private value: DynamicArray constructor(typeInfo: TypeInfo | string, value?: bytes | string) { super(value) const uint8ArrayValue = conactUint8Arrays(encodeLength(value?.length ?? 0).asUint8Array(), asUint8Array(value ?? new Uint8Array())) - this.value = DynamicArrayImpl.fromBytesImpl(uint8ArrayValue, typeInfo) as DynamicArrayImpl + this.value = DynamicArray.fromBytes(uint8ArrayValue, typeInfo) as DynamicArray this.typeInfo = typeof typeInfo === 'string' ? JSON.parse(typeInfo) : typeInfo - return new Proxy(this, arrayProxyHandler()) as DynamicBytesImpl + return new Proxy(this, arrayProxyHandler()) as DynamicBytes } get bytes(): bytes { @@ -758,7 +755,7 @@ export class DynamicBytesImpl extends DynamicBytes { } equals(other: this): boolean { - if (!(other instanceof DynamicBytesImpl) || JSON.stringify(this.typeInfo) !== JSON.stringify(other.typeInfo)) { + if (!(other instanceof DynamicBytes) || JSON.stringify(this.typeInfo) !== JSON.stringify(other.typeInfo)) { throw new CodeError(`Expected expression of type ${this.typeInfo.name}, got ${other.typeInfo.name}`) } return this.bytes.equals(other.bytes) @@ -772,50 +769,46 @@ export class DynamicBytesImpl extends DynamicBytes { return this.value.bytes.slice(ABI_LENGTH_SIZE) } - get items(): ByteImpl[] { + get items(): Byte[] { return this.value.items } - setItem(_index: number, _value: ByteImpl): void { + setItem(_index: number, _value: Byte): void { throw new CodeError('DynamicBytes is immutable') } - concat(other: Parameters['concat']>[0]): DynamicBytesImpl { + concat(other: Parameters['concat']>[0]): DynamicBytes { const items = this.items const otherEntries = other.entries() let next = otherEntries.next() while (!next.done) { - items.push(next.value[1] as ByteImpl) + items.push(next.value[1] as Byte) next = otherEntries.next() } const concatenatedBytes = items .map((item) => item.bytes) .reduce((acc, curr) => conactUint8Arrays(acc, asUint8Array(curr)), new Uint8Array()) - return new DynamicBytesImpl(this.typeInfo, asBytes(concatenatedBytes)) + return new DynamicBytes(this.typeInfo, asBytes(concatenatedBytes)) } - static fromBytesImpl( - value: StubBytesCompat | Uint8Array, - typeInfo: string | TypeInfo, - prefix: 'none' | 'log' = 'none', - ): DynamicBytesImpl { - const dynamicArrayValue = DynamicArrayImpl.fromBytesImpl(value, typeInfo, prefix) as DynamicArrayImpl - const result = new DynamicBytesImpl(typeInfo) + static fromBytes(value: StubBytesCompat | Uint8Array, typeInfo: string | TypeInfo, prefix: 'none' | 'log' = 'none'): DynamicBytes { + const dynamicArrayValue = DynamicArray.fromBytes(value, typeInfo, prefix) as DynamicArray + const result = new DynamicBytes(typeInfo) result.value = dynamicArrayValue return result } } -export class StaticBytesImpl extends StaticBytes { - private value: StaticArrayImpl +export class StaticBytes extends _StaticBytes { + private value: StaticArray typeInfo: TypeInfo constructor(typeInfo: TypeInfo | string, value?: bytes) { super(value ?? (Bytes() as bytes)) this.typeInfo = typeof typeInfo === 'string' ? JSON.parse(typeInfo) : typeInfo const uint8ArrayValue = asUint8Array(value ?? new Uint8Array(getMaxLengthOfStaticContentType(this.typeInfo))) - this.value = StaticArrayImpl.fromBytesImpl(uint8ArrayValue, typeInfo) as unknown as StaticArrayImpl - return new Proxy(this, arrayProxyHandler()) as StaticBytesImpl + this.value = StaticArray.fromBytes(uint8ArrayValue, typeInfo) as unknown as StaticArray + return new Proxy(this, arrayProxyHandler()) as StaticBytes } get bytes(): bytes { @@ -823,7 +816,7 @@ export class StaticBytesImpl extends StaticBytes extends StaticBytes } - get items(): ByteImpl[] { + get items(): Byte[] { return this.value.items } - setItem(_index: number, _value: ByteImpl): void { + setItem(_index: number, _value: Byte): void { throw new CodeError('StaticBytes is immutable') } - concat(other: Parameters['concat']>[0]): DynamicBytesImpl { + concat(other: Parameters['concat']>[0]): DynamicBytes { const items = this.items const otherEntries = other.entries() let next = otherEntries.next() while (!next.done) { - items.push(next.value[1] as ByteImpl) + items.push(next.value[1] as Byte) next = otherEntries.next() } const concatenatedBytes = items .map((item) => item.bytes) .reduce((acc, curr) => conactUint8Arrays(acc, asUint8Array(curr)), new Uint8Array()) - return new DynamicBytesImpl(this.typeInfo, asBytes(concatenatedBytes)) + return new DynamicBytes(this.typeInfo, asBytes(concatenatedBytes)) } - static fromBytesImpl(value: StubBytesCompat | Uint8Array, typeInfo: string | TypeInfo, prefix: 'none' | 'log' = 'none'): StaticBytesImpl { - const staticArrayValue = StaticArrayImpl.fromBytesImpl(value, typeInfo, prefix) as StaticArrayImpl - const result = new StaticBytesImpl(typeInfo) - result.value = staticArrayValue as StaticArrayImpl + static fromBytes(value: StubBytesCompat | Uint8Array, typeInfo: string | TypeInfo, prefix: 'none' | 'log' = 'none'): StaticBytes { + const staticArrayValue = StaticArray.fromBytes(value, typeInfo, prefix) as StaticArray + const result = new StaticBytes(typeInfo) + result.value = staticArrayValue as StaticArray return result } } -export class ReferenceArrayImpl extends ReferenceArray { +export class ReferenceArray extends _ReferenceArray { private _values: TItem[] typeInfo: TypeInfo @@ -876,7 +869,7 @@ export class ReferenceArrayImpl extends ReferenceArray { this.typeInfo = typeof typeInfo === 'string' ? JSON.parse(typeInfo) : typeInfo this._values = items - return new Proxy(this, arrayProxyHandler()) as ReferenceArrayImpl + return new Proxy(this, arrayProxyHandler()) as ReferenceArray } /** @@ -894,10 +887,10 @@ export class ReferenceArrayImpl extends ReferenceArray { this.items[index] = value } - slice(start?: Uint64Compat, end?: Uint64Compat): ReferenceArray { + slice(start?: Uint64Compat, end?: Uint64Compat): _ReferenceArray { const startIndex = end === undefined ? 0 : asNumber(start ?? 0) const endIndex = end === undefined ? asNumber(start ?? this._values.length) : asNumber(end) - return new ReferenceArrayImpl(this.typeInfo, ...this._values.slice(startIndex, endIndex)) + return new ReferenceArray(this.typeInfo, ...this._values.slice(startIndex, endIndex)) } /** @@ -915,13 +908,13 @@ export class ReferenceArrayImpl extends ReferenceArray { return this._values.pop()! } - copy(): ReferenceArray { + copy(): _ReferenceArray { const bytesValue = toBytes(this) - return getEncoder>(this.typeInfo)(bytesValue, this.typeInfo) + return getEncoder<_ReferenceArray>(this.typeInfo)(bytesValue, this.typeInfo) } } -export class FixedArrayImpl extends FixedArray { +export class FixedArray extends _FixedArray { private _values: NTuple private size: number typeInfo: TypeInfo @@ -936,11 +929,9 @@ export class FixedArrayImpl extends FixedArray } else { const bytesValue = asBytes(new Uint8Array(getMaxLengthOfStaticContentType(this.typeInfo))) - this._values = ( - getEncoder>(this.typeInfo)(bytesValue, this.typeInfo) as FixedArrayImpl - ).items + this._values = (getEncoder<_FixedArray>(this.typeInfo)(bytesValue, this.typeInfo) as FixedArray).items } - return new Proxy(this, arrayProxyHandler()) as FixedArrayImpl + return new Proxy(this, arrayProxyHandler()) as FixedArray } concat(...others: (TItem | ConcatArray)[]): TItem[] { @@ -965,9 +956,9 @@ export class FixedArrayImpl extends FixedArray { + copy(): _FixedArray { const bytesValue = toBytes(this) - return getEncoder>(this.typeInfo)(bytesValue, this.typeInfo) + return getEncoder<_FixedArray>(this.typeInfo)(bytesValue, this.typeInfo) } } @@ -1039,10 +1030,10 @@ const decode = (value: Uint8Array, childTypes: TypeInfo[]) => { } }) - const values: ARC4Encoded[] = [] + const values: _ARC4Encoded[] = [] childTypes.forEach((childType, index) => { values.push( - getEncoder(childType)( + getEncoder<_ARC4Encoded>(childType)( ['bytes', 'string'].includes(childType.name) ? valuePartitions[index].slice(2) : valuePartitions[index], childType, ), @@ -1051,29 +1042,29 @@ const decode = (value: Uint8Array, childTypes: TypeInfo[]) => { return values } -const encode = (values: ARC4Encoded[]) => { +const encode = (values: _ARC4Encoded[]) => { const length = values.length const heads = [] const tails = [] const dynamicLengthTypeIndex = [] let i = 0 - const valuesLengthBytes = values instanceof DynamicArray ? encodeLength(length).asUint8Array() : new Uint8Array() + const valuesLengthBytes = values instanceof _DynamicArray ? encodeLength(length).asUint8Array() : new Uint8Array() while (i < length) { const value = values[i] - assert(value instanceof ARC4Encoded, `expected ARC4 type ${value.constructor.name}`) + assert(value instanceof _ARC4Encoded, `expected ARC4 type ${value.constructor.name}`) dynamicLengthTypeIndex.push(isDynamicLengthType(value)) if (dynamicLengthTypeIndex.at(-1)) { heads.push(asUint8Array(Bytes.fromHex('0000'))) tails.push(asUint8Array(value.bytes)) } else { - if (value instanceof Bool) { + if (value instanceof _Bool) { const before = findBool(values, i, -1) let after = findBool(values, i, 1) if (before % 8 != 0) { throw new CodeError('"expected before index should have number of bool mod 8 equal 0"') } after = Math.min(7, after) - const consecutiveBools = values.slice(i, i + after + 1) as Bool[] + const consecutiveBools = values.slice(i, i + after + 1) as _Bool[] const compressedNumber = compressMultipleBool(consecutiveBools) heads.push(new Uint8Array([compressedNumber])) i += after @@ -1105,53 +1096,75 @@ const encode = (values: ARC4Encoded[]) => { return conactUint8Arrays(valuesLengthBytes, ...heads, ...tails) } -const isDynamicLengthType = (value: ARC4Encoded) => { +const isDynamicLengthType = (value: _ARC4Encoded) => { return ( - value instanceof StrImpl || - (value instanceof StaticArrayImpl && holdsDynamicLengthContent(value.typeInfo)) || - (value instanceof TupleImpl && value.genericArgs.some(holdsDynamicLengthContent)) || - (value instanceof StructImpl && Object.values(value.genericArgs).some(holdsDynamicLengthContent)) || - value instanceof DynamicArrayImpl || - value instanceof DynamicBytesImpl + value instanceof Str || + (value instanceof StaticArray && holdsDynamicLengthContent(value.typeInfo)) || + (value instanceof Tuple && value.genericArgs.some(holdsDynamicLengthContent)) || + (value instanceof Struct && Object.values(value.genericArgs).some(holdsDynamicLengthContent)) || + value instanceof DynamicArray || + value instanceof DynamicBytes ) } -export function encodeArc4Impl(sourceTypeInfoString: string | undefined, source: T): bytes { +export function encodeArc4(sourceTypeInfoString: string | undefined, source: T): bytes { const arc4Encoded = getArc4Encoded(source, sourceTypeInfoString) return arc4Encoded.bytes } -export const getArc4Encoded = (value: DeliberateAny, sourceTypeInfoString?: string): ARC4Encoded => { - if (value instanceof ARC4Encoded) { +export function decodeArc4( + sourceTypeInfoString: string, + targetTypeInfoString: string, + bytes: StubBytesCompat, + prefix: 'none' | 'log' = 'none', +): T { + const sourceTypeInfo = JSON.parse(sourceTypeInfoString) + const targetTypeInfo = JSON.parse(targetTypeInfoString) + const encoder = getEncoder(sourceTypeInfo) + const source = encoder(bytes, sourceTypeInfo, prefix) as { typeInfo: TypeInfo } + return getNativeValue(source, targetTypeInfo) as T +} + +export function interpretAsArc4( + typeInfoString: string, + bytes: StubBytesCompat, + prefix: 'none' | 'log' = 'none', +): T { + const typeInfo = JSON.parse(typeInfoString) + return getEncoder(typeInfo)(bytes, typeInfo, prefix) +} + +export const getArc4Encoded = (value: DeliberateAny, sourceTypeInfoString?: string): _ARC4Encoded => { + if (value instanceof _ARC4Encoded) { return value } if (value instanceof AccountCls) { const index = (lazyContext.activeGroup.activeTransaction as ApplicationCallTransaction).apat.indexOf(value) - return index >= 0 ? new UintImpl({ name: 'Uint<64>', genericArgs: [{ name: '64' }] }, asBigInt(index)) : getArc4Encoded(value.bytes) + return index >= 0 ? new Uint({ name: 'Uint<64>', genericArgs: [{ name: '64' }] }, asBigInt(index)) : getArc4Encoded(value.bytes) } if (value instanceof AssetCls) { const index = (lazyContext.activeGroup.activeTransaction as ApplicationCallTransaction).apas.indexOf(value) - return index >= 0 ? new UintImpl({ name: 'Uint<64>', genericArgs: [{ name: '64' }] }, asBigInt(index)) : getArc4Encoded(value.id) + return index >= 0 ? new Uint({ name: 'Uint<64>', genericArgs: [{ name: '64' }] }, asBigInt(index)) : getArc4Encoded(value.id) } if (value instanceof ApplicationCls) { const index = (lazyContext.activeGroup.activeTransaction as ApplicationCallTransaction).apfa.indexOf(value) - return index >= 0 ? new UintImpl({ name: 'Uint<64>', genericArgs: [{ name: '64' }] }, asBigInt(index)) : getArc4Encoded(value.id) + return index >= 0 ? new Uint({ name: 'Uint<64>', genericArgs: [{ name: '64' }] }, asBigInt(index)) : getArc4Encoded(value.id) } if (typeof value === 'boolean') { - return new BoolImpl({ name: 'Bool' }, value) + return new Bool({ name: 'Bool' }, value) } if (value instanceof Uint64Cls || typeof value === 'number') { - return new UintImpl({ name: 'Uint<64>', genericArgs: [{ name: '64' }] }, asBigInt(value)) + return new Uint({ name: 'Uint<64>', genericArgs: [{ name: '64' }] }, asBigInt(value)) } if (value instanceof BigUintCls) { - return new UintImpl({ name: 'Uint<512>', genericArgs: [{ name: '512' }] }, value.asBigInt()) + return new Uint({ name: 'Uint<512>', genericArgs: [{ name: '512' }] }, value.asBigInt()) } if (typeof value === 'bigint') { - return new UintImpl({ name: 'Uint<512>', genericArgs: [{ name: '512' }] }, value) + return new Uint({ name: 'Uint<512>', genericArgs: [{ name: '512' }] }, value) } if (value instanceof BytesCls) { if (value.fixedLength !== undefined) { - return new StaticBytesImpl( + return new StaticBytes( { name: 'StaticBytes', genericArgs: { elementType: { name: 'Byte', genericArgs: [{ name: '8' }] }, size: { name: value.fixedLength.toString() } }, @@ -1159,19 +1172,19 @@ export const getArc4Encoded = (value: DeliberateAny, sourceTypeInfoString?: stri value.asAlgoTs(), ) } - return new DynamicBytesImpl( + return new DynamicBytes( { name: 'DynamicBytes', genericArgs: { elementType: { name: 'Byte', genericArgs: [{ name: '8' }] } } }, value.asAlgoTs(), ) } if (typeof value === 'string') { - return new StrImpl({ name: 'Str' }, value) + return new Str({ name: 'Str' }, value) } - if (Array.isArray(value) || value instanceof ReferenceArrayImpl || value instanceof FixedArrayImpl) { + if (Array.isArray(value) || value instanceof ReferenceArray || value instanceof FixedArray) { const sourceTypeInfo = sourceTypeInfoString ? JSON.parse(sourceTypeInfoString) : undefined const sourceGenericArgs = ((value as DeliberateAny).typeInfo || sourceTypeInfo || {})?.genericArgs - const result: ARC4Encoded[] = (value instanceof ReferenceArrayImpl || value instanceof FixedArrayImpl ? value.items : value).reduce( - (acc: ARC4Encoded[], cur: DeliberateAny, currentIndex: number) => { + const result: _ARC4Encoded[] = (value instanceof ReferenceArray || value instanceof FixedArray ? value.items : value).reduce( + (acc: _ARC4Encoded[], cur: DeliberateAny, currentIndex: number) => { const elementTypeInfo = sourceGenericArgs?.elementType || sourceGenericArgs?.[currentIndex] const elementTypeInfoString = elementTypeInfo ? JSON.stringify(elementTypeInfo) : undefined return acc.concat(getArc4Encoded(cur, elementTypeInfoString)) @@ -1180,29 +1193,29 @@ export const getArc4Encoded = (value: DeliberateAny, sourceTypeInfoString?: stri ) const genericArgs: TypeInfo[] = result.map((x) => (x as DeliberateAny).typeInfo) - if (value instanceof FixedArrayImpl) { + if (value instanceof FixedArray) { const typeInfo = { name: `StaticArray<${genericArgs[0].name},${genericArgs.length}>`, genericArgs: { elementType: genericArgs[0], size: { name: genericArgs.length.toString() } }, } - return new StaticArrayImpl(typeInfo, ...(result as [ARC4Encoded, ...ARC4Encoded[]])) + return new StaticArray(typeInfo, ...(result as [_ARC4Encoded, ..._ARC4Encoded[]])) } else if ( sourceTypeInfo?.name?.startsWith('Array') || sourceTypeInfo?.name?.startsWith('ReadonlyArray') || - value instanceof ReferenceArrayImpl + value instanceof ReferenceArray ) { const elementType = genericArgs[0] ?? sourceTypeInfo.genericArgs?.elementType const typeInfo = { name: `DynamicArray<${elementType.name}>`, genericArgs: { elementType } } - return new DynamicArrayImpl(typeInfo, ...(result as [ARC4Encoded, ...ARC4Encoded[]])) + return new DynamicArray(typeInfo, ...(result as [_ARC4Encoded, ..._ARC4Encoded[]])) } else { const typeInfo = { name: `Tuple<[${genericArgs.map((x) => x.name).join(',')}]>`, genericArgs } - return new TupleImpl(typeInfo, ...(result as [ARC4Encoded, ...ARC4Encoded[]])) + return new Tuple(typeInfo, ...(result as [_ARC4Encoded, ..._ARC4Encoded[]])) } } if (typeof value === 'object') { const sourceTypeInfo = sourceTypeInfoString ? JSON.parse(sourceTypeInfoString) : undefined const propTypeInfos = (value.typeInfo || sourceTypeInfo || {}).genericArgs - const result = Object.entries(value).reduce((acc: ARC4Encoded[], [key, cur]: DeliberateAny) => { + const result = Object.entries(value).reduce((acc: _ARC4Encoded[], [key, cur]: DeliberateAny) => { const propTypeInfoString = propTypeInfos?.[key] ? JSON.stringify(propTypeInfos[key]) : undefined return acc.concat(getArc4Encoded(cur, propTypeInfoString)) }, []) @@ -1211,7 +1224,7 @@ export const getArc4Encoded = (value: DeliberateAny, sourceTypeInfoString?: stri name: `Struct<${value.constructor.name}>`, genericArgs: Object.fromEntries(Object.keys(value).map((x, i) => [x, genericArgs[i]])), } - return new StructImpl(typeInfo, Object.fromEntries(Object.keys(value).map((x, i) => [x, result[i]]))) + return new Struct(typeInfo, Object.fromEntries(Object.keys(value).map((x, i) => [x, result[i]]))) } throw new CodeError(`Unsupported type for encoding: ${typeof value}`) @@ -1237,14 +1250,14 @@ export const toBytes = (val: unknown, sourceTypeInfoString?: string): bytes => { return asUint64Cls(val.uint64).toBytes().asAlgoTs() } if (Array.isArray(val) || typeof val === 'object') { - return encodeArc4Impl(sourceTypeInfoString, val) + return encodeArc4(sourceTypeInfoString, val) } throw new InternalError(`Invalid type for bytes: ${nameOfType(val)}`) } export const getEncoder = (typeInfo: TypeInfo): fromBytes => { const mutableTupleFromBytes = (value: StubBytesCompat | Uint8Array, typeInfo: string | TypeInfo, prefix: 'none' | 'log' = 'none') => { - const tuple = TupleImpl.fromBytesImpl(value, typeInfo, prefix) + const tuple = Tuple.fromBytes(value, typeInfo, prefix) return asNumber(tuple.bytes.length) ? tuple.native : ([] as unknown as typeof tuple.native) } const readonlyMutableTupleFromBytes = ( @@ -1256,7 +1269,7 @@ export const getEncoder = (typeInfo: TypeInfo): fromBytes => { return result as Readonly } const mutableObjectFromBytes = (value: StubBytesCompat | Uint8Array, typeInfo: string | TypeInfo, prefix: 'none' | 'log' = 'none') => { - const struct = StructImpl.fromBytesImpl(value, typeInfo, prefix) + const struct = Struct.fromBytes(value, typeInfo, prefix) return asNumber(struct.bytes.length) ? struct.native : ({} as unknown as typeof struct.native) } const readonlyObjectFromBytes = (value: StubBytesCompat | Uint8Array, typeInfo: string | TypeInfo, prefix: 'none' | 'log' = 'none') => { @@ -1264,7 +1277,7 @@ export const getEncoder = (typeInfo: TypeInfo): fromBytes => { return result as Readonly } const arrayFromBytes = (value: StubBytesCompat | Uint8Array, typeInfo: string | TypeInfo, prefix: 'none' | 'log' = 'none') => { - const dynamicArray = DynamicArrayImpl.fromBytesImpl(value, typeInfo, prefix) + const dynamicArray = DynamicArray.fromBytes(value, typeInfo, prefix) return asNumber(dynamicArray.bytes.length) ? dynamicArray.native : ([] as unknown as typeof dynamicArray.native) } const readonlyArrayFromBytes = (value: StubBytesCompat | Uint8Array, typeInfo: string | TypeInfo, prefix: 'none' | 'log' = 'none') => { @@ -1272,15 +1285,15 @@ export const getEncoder = (typeInfo: TypeInfo): fromBytes => { return result as Readonly } const referenceArrayFromBytes = (value: StubBytesCompat | Uint8Array, typeInfo: string | TypeInfo, prefix: 'none' | 'log' = 'none') => { - const dynamicArray = DynamicArrayImpl.fromBytesImpl(value, typeInfo, prefix) - return new ReferenceArrayImpl( + const dynamicArray = DynamicArray.fromBytes(value, typeInfo, prefix) + return new ReferenceArray( typeInfo, ...(asNumber(dynamicArray.bytes.length) ? dynamicArray.native : ([] as unknown as typeof dynamicArray.native)), ) } const fixedArrayFromBytes = (value: StubBytesCompat | Uint8Array, typeInfo: string | TypeInfo, prefix: 'none' | 'log' = 'none') => { - const staticArray = StaticArrayImpl.fromBytesImpl(value, typeInfo, prefix) - return new FixedArrayImpl( + const staticArray = StaticArray.fromBytes(value, typeInfo, prefix) + return new FixedArray( typeInfo, ...(asNumber(staticArray.bytes.length) ? staticArray.native : ([] as unknown as typeof staticArray.native)), ) @@ -1296,21 +1309,21 @@ export const getEncoder = (typeInfo: TypeInfo): fromBytes => { uint64: uint64FromBytes, OnCompleteAction: onCompletionFromBytes, TransactionType: transactionTypeFromBytes, - Address: AddressImpl.fromBytesImpl, - Bool: BoolImpl.fromBytesImpl, - Byte: ByteImpl.fromBytesImpl, - Str: StrImpl.fromBytesImpl, - 'Uint<.*>': UintImpl.fromBytesImpl, - 'UFixed<.*>': UFixedImpl.fromBytesImpl, - 'StaticArray<.*>': StaticArrayImpl.fromBytesImpl, - 'DynamicArray<.*>': DynamicArrayImpl.fromBytesImpl, - 'Tuple(<.*>)?': TupleImpl.fromBytesImpl, + Address: Address.fromBytes, + Bool: Bool.fromBytes, + Byte: Byte.fromBytes, + Str: Str.fromBytes, + 'Uint<.*>': Uint.fromBytes, + 'UFixed<.*>': UFixed.fromBytes, + 'StaticArray<.*>': StaticArray.fromBytes, + 'DynamicArray<.*>': DynamicArray.fromBytes, + 'Tuple(<.*>)?': Tuple.fromBytes, 'ReadonlyTuple(<.*>)?': readonlyMutableTupleFromBytes, 'MutableTuple(<.*>)?': mutableTupleFromBytes, - 'Struct(<.*>)?': StructImpl.fromBytesImpl, - DynamicBytes: DynamicBytesImpl.fromBytesImpl, - 'StaticBytes<.*>': StaticBytesImpl.fromBytesImpl, - object: StructImpl.fromBytesImpl, + 'Struct(<.*>)?': Struct.fromBytes, + DynamicBytes: DynamicBytes.fromBytes, + 'StaticBytes<.*>': StaticBytes.fromBytes, + object: Struct.fromBytes, 'Object<.*>': mutableObjectFromBytes, 'ReadonlyObject<.*>': readonlyObjectFromBytes, 'ReferenceArray<.*>': referenceArrayFromBytes, diff --git a/src/impl/encoded-types/helpers.ts b/src/impl/encoded-types/helpers.ts index 6d86f814..bb0bad2d 100644 --- a/src/impl/encoded-types/helpers.ts +++ b/src/impl/encoded-types/helpers.ts @@ -26,7 +26,7 @@ export const areAllARC4Encoded = (items: T[]): items is T export const checkItemTypeName = (type: TypeInfo, value: ARC4Encoded) => { const typeName = trimGenericTypeName(type.name) - const validTypeNames = [typeName, `${typeName}Impl`] + const validTypeNames = [typeName] assert(validTypeNames.includes(value.constructor.name), `item must be of type ${typeName}, not ${value.constructor.name}`) } diff --git a/src/impl/encoded-types/index.ts b/src/impl/encoded-types/index.ts index 9dce036a..bd7968d5 100644 --- a/src/impl/encoded-types/index.ts +++ b/src/impl/encoded-types/index.ts @@ -1,50 +1,24 @@ -import type { ARC4Encoded } from '@algorandfoundation/algorand-typescript/arc4' -import type { StubBytesCompat } from '../primitives' -import { getEncoder } from './encoded-types' -import { getNativeValue } from './helpers' -import type { TypeInfo } from './types' - export { - AddressImpl, - BoolImpl, - ByteImpl, - DynamicArrayImpl, - DynamicBytesImpl, - encodeArc4Impl, - FixedArrayImpl, + Address, + Bool, + Byte, + decodeArc4, + DynamicArray, + DynamicBytes, + encodeArc4, + FixedArray, getArc4Encoded, getEncoder, - ReferenceArrayImpl, - StaticArrayImpl, - StaticBytesImpl, - StrImpl, - StructImpl, + interpretAsArc4, + ReferenceArray, + StaticArray, + StaticBytes, + Str, + Struct, toBytes, - TupleImpl, - UFixedImpl, - UintImpl, + Tuple, + UFixed, + Uint, } from './encoded-types' export { TypeInfo } from './types' -export { arc4EncodedLengthImpl, getArc4TypeName, getMaxLengthOfStaticContentType, minLengthForType } from './utils' - -export function decodeArc4Impl( - sourceTypeInfoString: string, - targetTypeInfoString: string, - bytes: StubBytesCompat, - prefix: 'none' | 'log' = 'none', -): T { - const sourceTypeInfo = JSON.parse(sourceTypeInfoString) - const targetTypeInfo = JSON.parse(targetTypeInfoString) - const encoder = getEncoder(sourceTypeInfo) - const source = encoder(bytes, sourceTypeInfo, prefix) as { typeInfo: TypeInfo } - return getNativeValue(source, targetTypeInfo) as T -} - -export function interpretAsArc4Impl( - typeInfoString: string, - bytes: StubBytesCompat, - prefix: 'none' | 'log' = 'none', -): T { - const typeInfo = JSON.parse(typeInfoString) - return getEncoder(typeInfo)(bytes, typeInfo, prefix) -} +export { arc4EncodedLength, getArc4TypeName, getMaxLengthOfStaticContentType, minLengthForType } from './utils' diff --git a/src/impl/encoded-types/utils.ts b/src/impl/encoded-types/utils.ts index 6be29165..a696dabe 100644 --- a/src/impl/encoded-types/utils.ts +++ b/src/impl/encoded-types/utils.ts @@ -144,7 +144,7 @@ export const getArc4TypeName = ( return undefined } -export const arc4EncodedLengthImpl = (typeInfoString: string): uint64 => { +export const arc4EncodedLength = (typeInfoString: string): uint64 => { const typeInfo = JSON.parse(typeInfoString) return getMaxLengthOfStaticContentType(typeInfo, true) } diff --git a/src/impl/ensure-budget.ts b/src/impl/ensure-budget.ts index 834d023e..9e37971c 100644 --- a/src/impl/ensure-budget.ts +++ b/src/impl/ensure-budget.ts @@ -1,6 +1,6 @@ import type { uint64 } from '@algorandfoundation/algorand-typescript' import { OpUpFeeSource } from '@algorandfoundation/algorand-typescript' -export function ensureBudgetImpl(_budget: uint64, _feeSource: OpUpFeeSource = OpUpFeeSource.GroupCredit) { +export function ensureBudget(_budget: uint64, _feeSource: OpUpFeeSource = OpUpFeeSource.GroupCredit) { // ensureBudget function is emulated to be a no-op } diff --git a/src/impl/inner-transactions.ts b/src/impl/inner-transactions.ts index 38a318fc..6a0f705c 100644 --- a/src/impl/inner-transactions.ts +++ b/src/impl/inner-transactions.ts @@ -19,7 +19,7 @@ import type { DeliberateAny } from '../typescript-helpers' import { asBytes, asNumber, asUint64, asUint8Array } from '../util' import { getApp } from './app-params' import { getAsset } from './asset-params' -import { encodeArc4Impl } from './encoded-types' +import { encodeArc4 } from './encoded-types' import type { InnerTxn, InnerTxnFields } from './itxn' import type { StubBytesCompat } from './primitives' import { Uint64Cls } from './primitives' @@ -324,7 +324,7 @@ export class ApplicationCallInnerTxnContext extends Applicati setReturnValue(value: TReturn) { // Ignore undefined (void) values if (value === undefined) return - this.appendLog(ABI_RETURN_VALUE_LOG_PREFIX.concat(encodeArc4Impl(undefined, value))) + this.appendLog(ABI_RETURN_VALUE_LOG_PREFIX.concat(encodeArc4(undefined, value))) this.#returnValue = value } /* @internal */ diff --git a/src/impl/itxn-compose.ts b/src/impl/itxn-compose.ts index 142764cd..18f238a3 100644 --- a/src/impl/itxn-compose.ts +++ b/src/impl/itxn-compose.ts @@ -1,21 +1,21 @@ -import { - type AnyTransactionComposeFields, - type ApplicationCallComposeFields, - type AssetConfigComposeFields, - type AssetFreezeComposeFields, - type AssetTransferComposeFields, - type ComposeItxnParams, - type Contract, - type ItxnCompose, - type KeyRegistrationComposeFields, - type PaymentComposeFields, +import type { + ItxnCompose as _ItxnCompose, + AnyTransactionComposeFields, + ApplicationCallComposeFields, + AssetConfigComposeFields, + AssetFreezeComposeFields, + AssetTransferComposeFields, + ComposeItxnParams, + Contract, + KeyRegistrationComposeFields, + PaymentComposeFields, } from '@algorandfoundation/algorand-typescript' import type { TypedApplicationCallFields } from '@algorandfoundation/algorand-typescript/arc4' import { lazyContext } from '../context-helpers/internal-context' import type { DeliberateAny, InstanceMethod } from '../typescript-helpers' import { getApplicationCallInnerTxnContext } from './c2c' -class ItxnComposeImpl { +class ItxnCompose { begin(fields: PaymentComposeFields): void begin(fields: KeyRegistrationComposeFields): void begin(fields: AssetConfigComposeFields): void @@ -67,4 +67,4 @@ class ItxnComposeImpl { } } -export const itxnCompose: ItxnCompose = new ItxnComposeImpl() +export const itxnCompose: _ItxnCompose = new ItxnCompose() diff --git a/src/impl/match.ts b/src/impl/match.ts index c25a053c..dda748f4 100644 --- a/src/impl/match.ts +++ b/src/impl/match.ts @@ -1,15 +1,15 @@ -import type { assertMatch, match } from '@algorandfoundation/algorand-typescript' +import type { assertMatch as _assertMatch, match as _match } from '@algorandfoundation/algorand-typescript' import { ARC4Encoded } from '@algorandfoundation/algorand-typescript/arc4' import type { DeliberateAny } from '../typescript-helpers' import { asBytes, asMaybeBigUintCls, assert } from '../util' import { BytesBackedCls, Uint64BackedCls } from './base' -import { FixedArrayImpl } from './encoded-types/encoded-types' +import { FixedArray } from './encoded-types/encoded-types' import type { StubBytesCompat, Uint64Cls } from './primitives' import { BytesCls } from './primitives' -export const matchImpl: typeof match = (subject, test): boolean => { +export const match: typeof _match = (subject, test): boolean => { if (Object.hasOwn(test, 'not')) { - return !matchImpl(subject, (test as DeliberateAny).not) + return !match(subject, (test as DeliberateAny).not) } const bigIntSubjectValue = getBigIntValue(subject) if (bigIntSubjectValue !== undefined) { @@ -35,7 +35,7 @@ export const matchImpl: typeof match = (subject, test): boolean => { } else if (subject instanceof BytesBackedCls) { return subject.bytes.equals((test as unknown as BytesBackedCls).bytes) } else if (subject instanceof Uint64BackedCls) { - return matchImpl( + return match( getBigIntValue(subject.uint64 as unknown as Uint64Cls), getBigIntValue((test as unknown as Uint64BackedCls).uint64 as unknown as Uint64Cls), ) @@ -44,20 +44,20 @@ export const matchImpl: typeof match = (subject, test): boolean => { } else if (Array.isArray(test)) { return ( (subject as DeliberateAny).length === test.length && - test.map((x, i) => matchImpl((subject as DeliberateAny)[i], x as DeliberateAny)).every((x) => x) + test.map((x, i) => match((subject as DeliberateAny)[i], x as DeliberateAny)).every((x) => x) ) - } else if (test instanceof FixedArrayImpl) { - return test.items.map((x, i) => matchImpl((subject as DeliberateAny[])[i], x as DeliberateAny)).every((x) => x) + } else if (test instanceof FixedArray) { + return test.items.map((x, i) => match((subject as DeliberateAny[])[i], x as DeliberateAny)).every((x) => x) } else if (typeof test === 'object') { return Object.entries(test!) - .map(([k, v]) => matchImpl((subject as DeliberateAny)[k], v as DeliberateAny)) + .map(([k, v]) => match((subject as DeliberateAny)[k], v as DeliberateAny)) .every((x) => x) } return false } -export const assertMatchImpl: typeof assertMatch = (subject, test, message): void => { - const isMatching = matchImpl(subject, test) +export const assertMatch: typeof _assertMatch = (subject, test, message): void => { + const isMatching = match(subject, test) assert(isMatching, message) } diff --git a/src/impl/template-var.ts b/src/impl/template-var.ts index 578060b9..ee9df5b1 100644 --- a/src/impl/template-var.ts +++ b/src/impl/template-var.ts @@ -2,7 +2,7 @@ import { DEFAULT_TEMPLATE_VAR_PREFIX } from '../constants' import { lazyContext } from '../context-helpers/internal-context' import { CodeError } from '../errors' -export function TemplateVarImpl(variableName: string, prefix = DEFAULT_TEMPLATE_VAR_PREFIX): T { +export function TemplateVar(variableName: string, prefix = DEFAULT_TEMPLATE_VAR_PREFIX): T { const key = prefix + variableName if (!Object.hasOwn(lazyContext.value.templateVars, key)) { throw new CodeError(`Template variable ${key} not found in test context!`) diff --git a/src/impl/urange.ts b/src/impl/urange.ts index 3f00420b..6deee538 100644 --- a/src/impl/urange.ts +++ b/src/impl/urange.ts @@ -1,7 +1,7 @@ import { asBigInt, asUint64 } from '../util' import type { StubUint64Compat } from './primitives' -export function* urangeImpl(a: StubUint64Compat, b?: StubUint64Compat, c?: StubUint64Compat) { +export function* urange(a: StubUint64Compat, b?: StubUint64Compat, c?: StubUint64Compat) { const start = b ? asBigInt(a) : BigInt(0) const end = b ? asBigInt(b) : asBigInt(a) const step = c ? asBigInt(c) : BigInt(1) diff --git a/src/internal/arc4.ts b/src/internal/arc4.ts index 94097ff8..8c1fa9c7 100644 --- a/src/internal/arc4.ts +++ b/src/internal/arc4.ts @@ -1,4 +1,24 @@ export * from '@algorandfoundation/algorand-typescript/arc4' export { abiCall, compileArc4 } from '../impl/c2c' export { abimethod, baremethod, Contract } from '../impl/contract' +export { + Address, + arc4EncodedLength, + Bool, + Byte, + decodeArc4, + DynamicArray, + DynamicBytes, + encodeArc4, + FixedArray, + interpretAsArc4, + ReferenceArray, + StaticArray, + StaticBytes, + Str, + Struct, + Tuple, + UFixed, + Uint, +} from '../impl/encoded-types' export { methodSelector } from '../impl/method-selector' diff --git a/src/internal/index.ts b/src/internal/index.ts index 1f97e32c..4a8878a3 100644 --- a/src/internal/index.ts +++ b/src/internal/index.ts @@ -1,17 +1,19 @@ export * from '@algorandfoundation/algorand-typescript' export { BaseContract, contract } from '../impl/base-contract' +export { clone } from '../impl/clone' export { compile } from '../impl/compiled' export { abimethod, baremethod, Contract } from '../impl/contract' -export { ensureBudgetImpl as ensureBudget } from '../impl/ensure-budget' +export { emit } from '../impl/emit' +export { ensureBudget } from '../impl/ensure-budget' export { Global } from '../impl/global' export { log } from '../impl/log' -export { assertMatchImpl as assertMatch, matchImpl as match } from '../impl/match' +export { assertMatch, match } from '../impl/match' export { BigUint, Bytes, Uint64 } from '../impl/primitives' export { Account, Application, Asset } from '../impl/reference' export { Box, BoxMap, BoxRef, GlobalState, LocalState } from '../impl/state' -export { TemplateVarImpl as TemplateVar } from '../impl/template-var' +export { TemplateVar } from '../impl/template-var' export { Txn } from '../impl/txn' -export { urangeImpl as urange } from '../impl/urange' +export { urange } from '../impl/urange' export { assert, err } from '../util' export * as arc4 from './arc4' export * as op from './op' diff --git a/src/runtime-helpers.ts b/src/runtime-helpers.ts index 9901d96c..13064e88 100644 --- a/src/runtime-helpers.ts +++ b/src/runtime-helpers.ts @@ -10,9 +10,6 @@ import { nameOfType, type DeliberateAny } from './typescript-helpers' import { flattenAsBytes } from './util' export { attachAbiMetadata } from './abi-metadata' -export { cloneImpl } from './impl/clone' -export { emitImpl } from './impl/emit' -export * from './impl/encoded-types' export { FixedBytes } from './impl/primitives' export function switchableValue(x: unknown): bigint | string | boolean { @@ -22,12 +19,6 @@ export function switchableValue(x: unknown): bigint | string | boolean { if (x instanceof AlgoTsPrimitiveCls) return x.valueOf() throw new InternalError(`Cannot convert ${nameOfType(x)} to switchable value`) } -// export function wrapLiteral(x: unknown) { -// if (typeof x === 'boolean') return x -// if (isBytes(x)) return makeBytes(x) -// if (isUint64(x)) return makeUint64(x) -// internalError(`Cannot wrap ${nameOfType(x)}`) -// } type BinaryOps = '+' | '-' | '*' | '**' | '/' | '%' | '>' | '>=' | '<' | '<=' | '===' | '!==' | '<<' | '>>' | '&' | '|' | '^' type UnaryOps = '~' diff --git a/src/subcontexts/contract-context.ts b/src/subcontexts/contract-context.ts index bc17a9ec..608b945b 100644 --- a/src/subcontexts/contract-context.ts +++ b/src/subcontexts/contract-context.ts @@ -15,7 +15,7 @@ import { lazyContext } from '../context-helpers/internal-context' import { CodeError } from '../errors' import { BaseContract, ContractOptionsSymbol } from '../impl/base-contract' import { Contract } from '../impl/contract' -import { getArc4Encoded, UintImpl, type TypeInfo } from '../impl/encoded-types' +import { getArc4Encoded, Uint, type TypeInfo } from '../impl/encoded-types' import { Bytes } from '../impl/primitives' import { AccountCls, ApplicationCls, AssetCls } from '../impl/reference' import { BoxCls, BoxMapCls, BoxRefCls, GlobalStateCls } from '../impl/state' @@ -87,7 +87,7 @@ const extractStates = (contract: BaseContract, contractOptions: ContractOptionsP return states } -const getUint8Impl = (value: number) => new UintImpl({ name: 'Uint<8>', genericArgs: [{ name: '8' }] }, value) +const getUint8 = (value: number) => new Uint({ name: 'Uint<8>', genericArgs: [{ name: '8' }] }, value) /** @ignore */ export const extractArraysFromArgs = ( @@ -107,21 +107,21 @@ export const extractArraysFromArgs = ( transactions.push(arg) } else if (arg instanceof AccountCls) { if (resourceEncoding === 'index') { - appArgs.push(getUint8Impl(accounts.length)) + appArgs.push(getUint8(accounts.length)) accounts.push(arg as Account) } else { appArgs.push(getArc4Encoded(arg.bytes)) } } else if (arg instanceof ApplicationCls) { if (resourceEncoding === 'index') { - appArgs.push(getUint8Impl(apps.length)) + appArgs.push(getUint8(apps.length)) apps.push(arg as Application) } else { appArgs.push(getArc4Encoded(arg.id)) } } else if (arg instanceof AssetCls) { if (resourceEncoding === 'index') { - appArgs.push(getUint8Impl(assets.length)) + appArgs.push(getUint8(assets.length)) assets.push(arg as Asset) } else { appArgs.push(getArc4Encoded(arg.id)) diff --git a/src/subcontexts/transaction-context.ts b/src/subcontexts/transaction-context.ts index f328c55b..ca14585c 100644 --- a/src/subcontexts/transaction-context.ts +++ b/src/subcontexts/transaction-context.ts @@ -403,7 +403,7 @@ export class TransactionGroup { * @returns The application transaction. */ getApplicationCallTransaction(index?: StubUint64Compat): ApplicationCallTransaction { - return this.getTransactionImpl({ type: TransactionType.ApplicationCall, index }) as ApplicationCallTransaction + return this._getTransaction({ type: TransactionType.ApplicationCall, index }) as ApplicationCallTransaction } /** @@ -412,7 +412,7 @@ export class TransactionGroup { * @returns The asset configuration transaction. */ getAssetConfigTransaction(index?: StubUint64Compat): AssetConfigTransaction { - return this.getTransactionImpl({ type: TransactionType.AssetConfig, index }) as AssetConfigTransaction + return this._getTransaction({ type: TransactionType.AssetConfig, index }) as AssetConfigTransaction } /** @@ -421,7 +421,7 @@ export class TransactionGroup { * @returns The asset transfer transaction. */ getAssetTransferTransaction(index?: StubUint64Compat): AssetTransferTransaction { - return this.getTransactionImpl({ type: TransactionType.AssetTransfer, index }) as AssetTransferTransaction + return this._getTransaction({ type: TransactionType.AssetTransfer, index }) as AssetTransferTransaction } /** @@ -430,7 +430,7 @@ export class TransactionGroup { * @returns The asset freeze transaction. */ getAssetFreezeTransaction(index?: StubUint64Compat): AssetFreezeTransaction { - return this.getTransactionImpl({ type: TransactionType.AssetFreeze, index }) as AssetFreezeTransaction + return this._getTransaction({ type: TransactionType.AssetFreeze, index }) as AssetFreezeTransaction } /** @@ -439,7 +439,7 @@ export class TransactionGroup { * @returns The key registration transaction. */ getKeyRegistrationTransaction(index?: StubUint64Compat): KeyRegistrationTransaction { - return this.getTransactionImpl({ type: TransactionType.KeyRegistration, index }) as KeyRegistrationTransaction + return this._getTransaction({ type: TransactionType.KeyRegistration, index }) as KeyRegistrationTransaction } /** @@ -448,7 +448,7 @@ export class TransactionGroup { * @returns The payment transaction. */ getPaymentTransaction(index?: StubUint64Compat): PaymentTransaction { - return this.getTransactionImpl({ type: TransactionType.Payment, index }) as PaymentTransaction + return this._getTransaction({ type: TransactionType.Payment, index }) as PaymentTransaction } /** @@ -457,9 +457,9 @@ export class TransactionGroup { * @returns The transaction. */ getTransaction(index?: StubUint64Compat): Transaction { - return this.getTransactionImpl({ index }) + return this._getTransaction({ index }) } - private getTransactionImpl({ type, index }: { type?: TransactionType; index?: StubUint64Compat }) { + private _getTransaction({ type, index }: { type?: TransactionType; index?: StubUint64Compat }) { const i = index !== undefined ? asNumber(index) : undefined if (i !== undefined && i >= lazyContext.activeGroup.transactions.length) { throw new InternalError('Invalid group index') @@ -505,7 +505,7 @@ export class ItxnGroup { * @returns The application inner transaction. */ getApplicationCallInnerTxn(index?: StubUint64Compat): ApplicationCallInnerTxn { - return this.getInnerTxnImpl({ type: TransactionType.ApplicationCall, index }) as ApplicationCallInnerTxn + return this._getInnerTxn({ type: TransactionType.ApplicationCall, index }) as ApplicationCallInnerTxn } /** @@ -514,7 +514,7 @@ export class ItxnGroup { * @returns The asset configuration inner transaction. */ getAssetConfigInnerTxn(index?: StubUint64Compat): AssetConfigInnerTxn { - return this.getInnerTxnImpl({ type: TransactionType.AssetConfig, index }) as AssetConfigInnerTxn + return this._getInnerTxn({ type: TransactionType.AssetConfig, index }) as AssetConfigInnerTxn } /** @@ -523,7 +523,7 @@ export class ItxnGroup { * @returns The asset transfer inner transaction. */ getAssetTransferInnerTxn(index?: StubUint64Compat): AssetTransferInnerTxn { - return this.getInnerTxnImpl({ type: TransactionType.AssetTransfer, index }) as AssetTransferInnerTxn + return this._getInnerTxn({ type: TransactionType.AssetTransfer, index }) as AssetTransferInnerTxn } /** @@ -532,7 +532,7 @@ export class ItxnGroup { * @returns The asset freeze inner transaction. */ getAssetFreezeInnerTxn(index?: StubUint64Compat): AssetFreezeInnerTxn { - return this.getInnerTxnImpl({ type: TransactionType.AssetFreeze, index }) as AssetFreezeInnerTxn + return this._getInnerTxn({ type: TransactionType.AssetFreeze, index }) as AssetFreezeInnerTxn } /** @@ -541,7 +541,7 @@ export class ItxnGroup { * @returns The key registration inner transaction. */ getKeyRegistrationInnerTxn(index?: StubUint64Compat): KeyRegistrationInnerTxn { - return this.getInnerTxnImpl({ type: TransactionType.KeyRegistration, index }) as KeyRegistrationInnerTxn + return this._getInnerTxn({ type: TransactionType.KeyRegistration, index }) as KeyRegistrationInnerTxn } /** @@ -550,7 +550,7 @@ export class ItxnGroup { * @returns The payment inner transaction. */ getPaymentInnerTxn(index?: StubUint64Compat): PaymentInnerTxn { - return this.getInnerTxnImpl({ type: TransactionType.Payment, index }) as PaymentInnerTxn + return this._getInnerTxn({ type: TransactionType.Payment, index }) as PaymentInnerTxn } /** @@ -559,10 +559,10 @@ export class ItxnGroup { * @returns The inner transaction. */ getInnerTxn(index?: StubUint64Compat): InnerTxn { - return this.getInnerTxnImpl({ index }) + return this._getInnerTxn({ index }) } - private getInnerTxnImpl({ type, index }: { type?: TransactionType; index?: StubUint64Compat }) { + private _getInnerTxn({ type, index }: { type?: TransactionType; index?: StubUint64Compat }) { invariant(this.itxns.length > 0, 'no previous inner transactions') const i = index !== undefined ? asNumber(index) : undefined if (i !== undefined && i >= this.itxns.length) { diff --git a/src/test-transformer/node-factory.ts b/src/test-transformer/node-factory.ts index 2e977846..63e7fb63 100644 --- a/src/test-transformer/node-factory.ts +++ b/src/test-transformer/node-factory.ts @@ -7,12 +7,24 @@ import { getPropertyNameAsString, trimGenericTypeName } from './helpers' const factory = ts.factory export const nodeFactory = { importHelpers(testingPackageName: string) { - return factory.createImportDeclaration( - undefined, - factory.createImportClause(false, undefined, factory.createNamespaceImport(factory.createIdentifier('runtimeHelpers'))), - factory.createStringLiteral(`${testingPackageName}/runtime-helpers`), - undefined, - ) + return [ + factory.createImportDeclaration( + undefined, + factory.createImportClause(false, undefined, factory.createNamespaceImport(factory.createIdentifier('runtimeHelpers'))), + factory.createStringLiteral(`${testingPackageName}/runtime-helpers`), + undefined, + ), + factory.createImportDeclaration( + undefined, + factory.createImportClause( + false, + undefined, + factory.createNamedImports([factory.createImportSpecifier(false, undefined, factory.createIdentifier('arc4'))]), + ), + factory.createStringLiteral(`${testingPackageName}/internal`), + undefined, + ), + ] }, switchableValue(x: ts.Expression) { @@ -88,26 +100,18 @@ export const nodeFactory = { const infoString = JSON.stringify(typeInfo) const classIdentifier = node.expression.getText().replace('arc4.', '') return factory.createNewExpression( - factory.createIdentifier(`runtimeHelpers.${trimGenericTypeName(typeInfo?.name ?? classIdentifier)}Impl`), + factory.createIdentifier(`arc4.${trimGenericTypeName(typeInfo?.name ?? classIdentifier)}`), node.typeArguments, [infoString ? factory.createStringLiteral(infoString) : undefined, ...(node.arguments ?? [])].filter((arg) => !!arg), ) }, - callStubbedFunction(functionName: string, node: ts.CallExpression, typeInfo?: TypeInfo | TypeInfo[]) { + callStubbedFunction(node: ts.CallExpression, typeInfo?: TypeInfo | TypeInfo[]) { const typeInfoArgs = typeInfo ? (Array.isArray(typeInfo) ? typeInfo : [typeInfo]).map((t) => factory.createStringLiteral(JSON.stringify(t))) - : undefined - const updatedPropertyAccessExpression = factory.createPropertyAccessExpression( - factory.createIdentifier('runtimeHelpers'), - `${functionName}Impl`, - ) + : [] - return factory.createCallExpression( - updatedPropertyAccessExpression, - node.typeArguments, - [...(typeInfoArgs ?? []), ...(node.arguments ?? [])].filter((arg) => !!arg), - ) + return factory.updateCallExpression(node, node.expression, node.typeArguments, [...typeInfoArgs, ...(node.arguments ?? [])]) }, callMethodSelectorFunction(node: ts.CallExpression) { @@ -146,4 +150,4 @@ export const nodeFactory = { [factory.createNumericLiteral(length), ...(node.arguments ?? [])].filter((arg) => !!arg), ) }, -} satisfies Record ts.Node> +} satisfies Record ts.Node | ts.Node[]> diff --git a/src/test-transformer/visitors.ts b/src/test-transformer/visitors.ts index 4cba6654..c2e9fd3f 100644 --- a/src/test-transformer/visitors.ts +++ b/src/test-transformer/visitors.ts @@ -46,9 +46,8 @@ export class SourceFileVisitor { this.helper = { additionalStatements: [], resolveType(node: ts.Node): ptypes.PType { - let sourceLocation = undefined + const sourceLocation = this.sourceLocation(node) try { - sourceLocation = this.sourceLocation(node) return loggingContext.run(() => typeResolver.resolve(node, sourceLocation!)) } catch (e) { const err = e as Error @@ -69,7 +68,11 @@ export class SourceFileVisitor { return s && s.flags & ts.SymbolFlags.Alias ? typeChecker.getAliasedSymbol(s) : s }, sourceLocation(node: ts.Node): SourceLocation { - return SourceLocation.fromNode(node, program.getCurrentDirectory()) + try { + return SourceLocation.fromNode(node, program.getCurrentDirectory()) + } catch { + return SourceLocation.None + } }, } } @@ -77,7 +80,7 @@ export class SourceFileVisitor { public result(): ts.SourceFile { const updatedSourceFile = ts.visitNode(this.sourceFile, this.visit) as ts.SourceFile return factory.updateSourceFile(updatedSourceFile, [ - nodeFactory.importHelpers(this.config.testingPackageName), + ...nodeFactory.importHelpers(this.config.testingPackageName), ...updatedSourceFile.statements, ...this.helper.additionalStatements, ]) @@ -117,8 +120,11 @@ class ImportDeclarationVisitor { if (this.declarationNode.importClause?.isTypeOnly || !algotsModuleRegExp.test(moduleSpecifier)) return this.declarationNode const namedBindings = this.declarationNode.importClause?.namedBindings + // remove `arc4` from named bindings, as it is explicitly imported in the `importHelpers` method const nonTypeNamedBindings = - namedBindings && ts.isNamedImports(namedBindings) ? (namedBindings as ts.NamedImports).elements.filter((e) => !e.isTypeOnly) : [] + namedBindings && ts.isNamedImports(namedBindings) + ? (namedBindings as ts.NamedImports).elements.filter((e) => !e.isTypeOnly && e.name.getText() !== 'arc4') + : [] return factory.createImportDeclaration( this.declarationNode.modifiers, nonTypeNamedBindings.length @@ -148,7 +154,7 @@ class ExpressionVisitor { } private visit = (node: ts.Node): ts.Node => { - if (ts.isCallExpression(node) || ts.isNewExpression(node)) { + handleTypeInfoCaputre: if (ts.isCallExpression(node) || ts.isNewExpression(node)) { let type = this.helper.resolveType(node) // `voted = LocalState()` is resolved to FunctionPType with returnType LocalState @@ -171,16 +177,18 @@ class ExpressionVisitor { this.stubbedFunctionName = undefined let infoArg: TypeInfo | TypeInfo[] | undefined = info + /// if the function being called is not part of the program being transformed, do not process further + const sourceLocation = this.helper.sourceLocation(updatedNode) + if (sourceLocation === SourceLocation.None) break handleTypeInfoCaputre + if ( isCallingEmit(stubbedFunctionName) || isCallingEncodeArc4(stubbedFunctionName) || isCallingArc4EncodedLength(stubbedFunctionName) || isCallingClone(stubbedFunctionName) ) { - const sourceLocation = this.helper.sourceLocation(updatedNode) infoArg = this.helper.resolveTypeParameters(updatedNode).map((t) => getGenericTypeInfo(t, sourceLocation))[0] } else if (isCallingDecodeArc4(stubbedFunctionName)) { - const sourceLocation = this.helper.sourceLocation(updatedNode) const sourceType = ptypes.ptypeToArc4EncodedType(type, sourceLocation) const sourceTypeInfo = getGenericTypeInfo(sourceType, sourceLocation) const targetTypeInfo = getGenericTypeInfo(type, sourceLocation) @@ -196,7 +204,7 @@ class ExpressionVisitor { if (type instanceof ptypes.BytesPType && type.fixedByteSize) updatedNode = nodeFactory.callFixedBytesFunction(stubbedFunctionName, updatedNode, Number(type.fixedByteSize)) } else { - updatedNode = nodeFactory.callStubbedFunction(stubbedFunctionName, updatedNode, infoArg) + updatedNode = nodeFactory.callStubbedFunction(updatedNode, infoArg) } } } @@ -517,8 +525,8 @@ const tryGetAlgoTsSymbolName = (node: ts.Node, helper: VisitorHelper): string | return s?.getName() ?? (ts.isMemberName(node) ? node.text : node.getText()) } -const isCallingDecodeArc4 = (functionName: string | undefined): boolean => ['decodeArc4'].includes(functionName ?? '') -const isCallingEncodeArc4 = (functionName: string | undefined): boolean => ['encodeArc4'].includes(functionName ?? '') +const isCallingDecodeArc4 = (functionName: string | undefined): boolean => 'decodeArc4' === (functionName ?? '') +const isCallingEncodeArc4 = (functionName: string | undefined): boolean => 'encodeArc4' === (functionName ?? '') const isCallingArc4EncodedLength = (functionName: string | undefined): boolean => 'arc4EncodedLength' === (functionName ?? '') const isCallingEmit = (functionName: string | undefined): boolean => 'emit' === (functionName ?? '') const isCallingMethodSelector = (functionName: string | undefined): boolean => 'methodSelector' === (functionName ?? '') diff --git a/src/value-generators/arc4.ts b/src/value-generators/arc4.ts index 7ede41b0..119032c8 100644 --- a/src/value-generators/arc4.ts +++ b/src/value-generators/arc4.ts @@ -1,6 +1,6 @@ import type { arc4 } from '@algorandfoundation/algorand-typescript' import { BITS_IN_BYTE, MAX_UINT128, MAX_UINT16, MAX_UINT256, MAX_UINT32, MAX_UINT512, MAX_UINT64, MAX_UINT8 } from '../constants' -import { AddressImpl, DynamicBytesImpl, StrImpl, UintImpl } from '../impl/encoded-types' +import { Address, DynamicBytes, Str, Uint } from '../impl/encoded-types' import { getRandomBigInt, getRandomBytes } from '../util' import { AvmValueGenerator } from './avm' @@ -11,7 +11,7 @@ export class Arc4ValueGenerator { * */ address(): arc4.Address { const source = new AvmValueGenerator().account() - const result = new AddressImpl( + const result = new Address( { name: 'Address', genericArgs: { elementType: { name: 'Byte', genericArgs: [{ name: '8' }] }, size: { name: '32' } } }, source, ) @@ -25,7 +25,7 @@ export class Arc4ValueGenerator { * @returns: A random Uint8 value. * */ uint8(minValue: number | bigint = 0, maxValue: number | bigint = MAX_UINT8): arc4.Uint8 { - return new UintImpl({ name: 'Uint', genericArgs: [{ name: '8' }] }, getRandomBigInt(minValue, maxValue)) as arc4.Uint8 + return new Uint({ name: 'Uint', genericArgs: [{ name: '8' }] }, getRandomBigInt(minValue, maxValue)) as arc4.Uint8 } /** @@ -35,7 +35,7 @@ export class Arc4ValueGenerator { * @returns: A random Uint16 value. * */ uint16(minValue: number | bigint = 0, maxValue: number | bigint = MAX_UINT16): arc4.Uint16 { - return new UintImpl({ name: 'Uint', genericArgs: [{ name: '16' }] }, getRandomBigInt(minValue, maxValue)) as arc4.Uint16 + return new Uint({ name: 'Uint', genericArgs: [{ name: '16' }] }, getRandomBigInt(minValue, maxValue)) as arc4.Uint16 } /** @@ -45,7 +45,7 @@ export class Arc4ValueGenerator { * @returns: A random Uint32 value. * */ uint32(minValue: number | bigint = 0, maxValue: number | bigint = MAX_UINT32): arc4.Uint32 { - return new UintImpl({ name: 'Uint', genericArgs: [{ name: '32' }] }, getRandomBigInt(minValue, maxValue)) as arc4.Uint32 + return new Uint({ name: 'Uint', genericArgs: [{ name: '32' }] }, getRandomBigInt(minValue, maxValue)) as arc4.Uint32 } /** @@ -55,7 +55,7 @@ export class Arc4ValueGenerator { * @returns: A random Uint64 value. * */ uint64(minValue: number | bigint = 0, maxValue: number | bigint = MAX_UINT64): arc4.Uint64 { - return new UintImpl({ name: 'Uint', genericArgs: [{ name: '64' }] }, getRandomBigInt(minValue, maxValue)) as arc4.Uint64 + return new Uint({ name: 'Uint', genericArgs: [{ name: '64' }] }, getRandomBigInt(minValue, maxValue)) as arc4.Uint64 } /** @@ -65,7 +65,7 @@ export class Arc4ValueGenerator { * @returns: A random Uint128 value. * */ uint128(minValue: number | bigint = 0, maxValue: number | bigint = MAX_UINT128): arc4.Uint128 { - return new UintImpl({ name: 'Uint', genericArgs: [{ name: '128' }] }, getRandomBigInt(minValue, maxValue)) as arc4.Uint128 + return new Uint({ name: 'Uint', genericArgs: [{ name: '128' }] }, getRandomBigInt(minValue, maxValue)) as arc4.Uint128 } /** @@ -75,7 +75,7 @@ export class Arc4ValueGenerator { * @returns: A random Uint256 value. * */ uint256(minValue: number | bigint = 0, maxValue: number | bigint = MAX_UINT256): arc4.Uint256 { - return new UintImpl({ name: 'Uint', genericArgs: [{ name: '256' }] }, getRandomBigInt(minValue, maxValue)) as arc4.Uint256 + return new Uint({ name: 'Uint', genericArgs: [{ name: '256' }] }, getRandomBigInt(minValue, maxValue)) as arc4.Uint256 } /** @@ -85,7 +85,7 @@ export class Arc4ValueGenerator { * @returns: A random Uint512 value. * */ uint512(minValue: number | bigint = 0, maxValue: number | bigint = MAX_UINT512): arc4.Uint<512> { - return new UintImpl({ name: 'Uint', genericArgs: [{ name: '512' }] }, getRandomBigInt(minValue, maxValue)) as arc4.Uint<512> + return new Uint({ name: 'Uint', genericArgs: [{ name: '512' }] }, getRandomBigInt(minValue, maxValue)) as arc4.Uint<512> } /** @@ -95,7 +95,7 @@ export class Arc4ValueGenerator { * @returns: A new, random dynamic bytes of size `n` bits. * */ dynamicBytes(n: number): arc4.DynamicBytes { - return new DynamicBytesImpl( + return new DynamicBytes( { name: 'DynamicBytes', genericArgs: { elementType: { name: 'Byte', genericArgs: [{ name: '8' }] } } }, getRandomBytes(n / BITS_IN_BYTE).asAlgoTs(), ) @@ -112,6 +112,6 @@ export class Arc4ValueGenerator { // Generate random string const bytes = getRandomBytes(numChars) - return new StrImpl(JSON.stringify(undefined), bytes.toString()) as unknown as arc4.Str + return new Str(JSON.stringify(undefined), bytes.toString()) as unknown as arc4.Str } } diff --git a/tests/global-state-arc4-values.spec.ts b/tests/global-state-arc4-values.spec.ts index b40826e6..cd5175d7 100644 --- a/tests/global-state-arc4-values.spec.ts +++ b/tests/global-state-arc4-values.spec.ts @@ -1,14 +1,6 @@ import { Bytes, Uint64 } from '@algorandfoundation/algorand-typescript' import { TestExecutionContext } from '@algorandfoundation/algorand-typescript-testing' -import type { - AddressImpl, - BoolImpl, - ByteImpl, - DynamicBytesImpl, - StrImpl, -} from '@algorandfoundation/algorand-typescript-testing/runtime-helpers' -import { UintImpl } from '@algorandfoundation/algorand-typescript-testing/runtime-helpers' -import type { ARC4Encoded, BitSize } from '@algorandfoundation/algorand-typescript/arc4' +import type { ARC4Encoded } from '@algorandfoundation/algorand-typescript/arc4' import { Address, Bool, Byte, DynamicBytes, Str, Uint } from '@algorandfoundation/algorand-typescript/arc4' import { afterEach, beforeAll, describe, expect } from 'vitest' import type { DeliberateAny, FunctionKeys } from '../src/typescript-helpers' @@ -37,10 +29,9 @@ describe('ARC4 AppGlobal values', async () => { abiValue: new Uint<64>(42), methodName: `get${implicit}_arc4_uintn64`, assert: (value: ARC4Encoded, expectedValue: DeliberateAny) => { - const arc4Value = value as UintImpl - const bitSize = UintImpl.getMaxBitsLength(arc4Value.typeInfo) + const arc4Value = value as Uint<64> expect(arc4Value).toBeInstanceOf(Uint) - expect(bitSize).toEqual(64) + expect(arc4Value.bytes.length).toEqual(8) expect(arc4Value.native).toEqual(expectedValue) }, }, @@ -49,7 +40,7 @@ describe('ARC4 AppGlobal values', async () => { abiValue: new Str('World'), methodName: `get${implicit}_arc4_str`, assert: (value: ARC4Encoded, expectedValue: DeliberateAny) => { - const arc4Value = value as StrImpl + const arc4Value = value as Str expect(arc4Value).toBeInstanceOf(Str) expect(arc4Value.native).toEqual(expectedValue) }, @@ -59,7 +50,7 @@ describe('ARC4 AppGlobal values', async () => { abiValue: new Byte(12), methodName: `get${implicit}_arc4_byte`, assert: (value: ARC4Encoded, expectedValue: DeliberateAny) => { - const arc4Value = value as ByteImpl + const arc4Value = value as Byte expect(arc4Value).toBeInstanceOf(Byte) expect(arc4Value.native).toEqual(expectedValue) }, @@ -69,7 +60,7 @@ describe('ARC4 AppGlobal values', async () => { abiValue: new Bool(false), methodName: `get${implicit}_arc4_bool`, assert: (value: ARC4Encoded, expectedValue: DeliberateAny) => { - const arc4Value = value as BoolImpl + const arc4Value = value as Bool expect(arc4Value).toBeInstanceOf(Bool) expect(arc4Value.native).toEqual(expectedValue) }, @@ -79,7 +70,7 @@ describe('ARC4 AppGlobal values', async () => { abiValue: new Address(Bytes.fromHex(`${'00'.repeat(31)}ff`)), methodName: `get${implicit}_arc4_address`, assert: (value: ARC4Encoded, expectedValue: DeliberateAny) => { - const arc4Value = value as AddressImpl + const arc4Value = value as Address expect(arc4Value).toBeInstanceOf(Address) expect(arc4Value.native).toEqual(expectedValue) }, @@ -89,10 +80,9 @@ describe('ARC4 AppGlobal values', async () => { abiValue: new Uint<128>(2n ** 102n), methodName: `get${implicit}_arc4_uintn128`, assert: (value: ARC4Encoded, expectedValue: DeliberateAny) => { - const arc4Value = value as UintImpl - const bitSize = UintImpl.getMaxBitsLength(arc4Value.typeInfo) + const arc4Value = value as Uint<128> expect(arc4Value).toBeInstanceOf(Uint) - expect(bitSize).toEqual(128) + expect(arc4Value.bytes.length).toEqual(16) expect(arc4Value.native).toEqual(expectedValue) }, }, @@ -101,7 +91,7 @@ describe('ARC4 AppGlobal values', async () => { abiValue: new DynamicBytes(Bytes.fromHex(`${'00'.repeat(30)}${'ff'.repeat(2)}`)), methodName: `get${implicit}_arc4_dynamic_bytes`, assert: (value: ARC4Encoded, expectedValue: DeliberateAny) => { - const arc4Value = value as DynamicBytesImpl + const arc4Value = value as DynamicBytes expect(arc4Value).toBeInstanceOf(DynamicBytes) expect(arc4Value.native).toEqual(expectedValue) }, diff --git a/tests/local-state-arc4-values.spec.ts b/tests/local-state-arc4-values.spec.ts index c3b4f758..d0ed1f48 100644 --- a/tests/local-state-arc4-values.spec.ts +++ b/tests/local-state-arc4-values.spec.ts @@ -1,18 +1,12 @@ import type { AppClient } from '@algorandfoundation/algokit-utils/types/app-client' import { Account, Bytes } from '@algorandfoundation/algorand-typescript' import { TestExecutionContext } from '@algorandfoundation/algorand-typescript-testing' -import type { - AddressImpl, - BoolImpl, - ByteImpl, - DynamicBytesImpl, - StrImpl, -} from '@algorandfoundation/algorand-typescript-testing/runtime-helpers' -import { UintImpl } from '@algorandfoundation/algorand-typescript-testing/runtime-helpers' -import type { ARC4Encoded, BitSize } from '@algorandfoundation/algorand-typescript/arc4' +import type { ARC4Encoded } from '@algorandfoundation/algorand-typescript/arc4' + import { Address, Bool, Byte, DynamicBytes, Str, Uint } from '@algorandfoundation/algorand-typescript/arc4' import { afterEach, beforeAll, describe, expect } from 'vitest' import { OnApplicationComplete } from '../src/constants' + import type { DeliberateAny } from '../src/typescript-helpers' import { LocalStateContract } from './artifacts/state-ops/contract.algo' import { getAvmResult } from './avm-invoker' @@ -36,17 +30,17 @@ describe('ARC4 AppLocal values', async () => { { methodName: `get${implicit}_arc4_uintn64`, assert: (value: ARC4Encoded, expectedValue: DeliberateAny) => { - const arc4Value = value as UintImpl - const bitSize = UintImpl.getMaxBitsLength(arc4Value.typeInfo) + const arc4Value = value as Uint<64> + expect(arc4Value).toBeInstanceOf(Uint) - expect(bitSize).toEqual(64) + expect(arc4Value.bytes.length).toEqual(8) expect(arc4Value.native).toEqual(expectedValue) }, }, { methodName: `get${implicit}_arc4_str`, assert: (value: ARC4Encoded, expectedValue: DeliberateAny) => { - const arc4Value = value as StrImpl + const arc4Value = value as Str expect(arc4Value).toBeInstanceOf(Str) expect(arc4Value.native).toEqual(expectedValue) }, @@ -54,7 +48,7 @@ describe('ARC4 AppLocal values', async () => { { methodName: `get${implicit}_arc4_byte`, assert: (value: ARC4Encoded, expectedValue: DeliberateAny) => { - const arc4Value = value as ByteImpl + const arc4Value = value as Byte expect(arc4Value).toBeInstanceOf(Byte) expect(arc4Value.native).toEqual(expectedValue) }, @@ -62,7 +56,7 @@ describe('ARC4 AppLocal values', async () => { { methodName: `get${implicit}_arc4_bool`, assert: (value: ARC4Encoded, expectedValue: DeliberateAny) => { - const arc4Value = value as BoolImpl + const arc4Value = value as Bool expect(arc4Value).toBeInstanceOf(Bool) expect(arc4Value.native).toEqual(expectedValue) }, @@ -70,7 +64,7 @@ describe('ARC4 AppLocal values', async () => { { methodName: `get${implicit}_arc4_address`, assert: (value: ARC4Encoded, expectedValue: DeliberateAny) => { - const arc4Value = value as AddressImpl + const arc4Value = value as Address expect(arc4Value).toBeInstanceOf(Address) expect(arc4Value.native).toEqual(expectedValue) }, @@ -78,17 +72,16 @@ describe('ARC4 AppLocal values', async () => { { methodName: `get${implicit}_arc4_uintn128`, assert: (value: ARC4Encoded, expectedValue: DeliberateAny) => { - const arc4Value = value as UintImpl - const bitSize = UintImpl.getMaxBitsLength(arc4Value.typeInfo) + const arc4Value = value as Uint<128> expect(arc4Value).toBeInstanceOf(Uint) - expect(bitSize).toEqual(128) + expect(arc4Value.bytes.length).toEqual(16) expect(arc4Value.native).toEqual(expectedValue) }, }, { methodName: `get${implicit}_arc4_dynamic_bytes`, assert: (value: ARC4Encoded, expectedValue: DeliberateAny) => { - const arc4Value = value as DynamicBytesImpl + const arc4Value = value as DynamicBytes expect(arc4Value).toBeInstanceOf(DynamicBytes) expect(arc4Value.native).toEqual(expectedValue) }, diff --git a/tests/match.spec.ts b/tests/match.spec.ts index a5fb9afa..7e51ca32 100644 --- a/tests/match.spec.ts +++ b/tests/match.spec.ts @@ -3,7 +3,7 @@ import { assertMatch, BigUint, Bytes, match, Uint64 } from '@algorandfoundation/ import { TestExecutionContext } from '@algorandfoundation/algorand-typescript-testing' import { afterEach, describe, expect, test } from 'vitest' import { MAX_UINT512, MAX_UINT64 } from '../src/constants' -import { StrImpl } from '../src/impl/encoded-types' +import { Str } from '../src/impl/encoded-types' describe('match', () => { const ctx = new TestExecutionContext() @@ -68,7 +68,7 @@ describe('match', () => { const differentAsset = ctx.any.application() const arc4Str1 = ctx.any.arc4.str(10) - const sameArc4Str = new StrImpl((arc4Str1 as StrImpl).typeInfo, arc4Str1.native) + const sameArc4Str = new Str((arc4Str1 as Str).typeInfo, arc4Str1.native) const differentArc4Str = ctx.any.arc4.str(10) const testData = [ From b37b66c6cb90e372525776ef1e6ccea6739f6ca5 Mon Sep 17 00:00:00 2001 From: Bobby Lat Date: Fri, 8 Aug 2025 10:36:46 +0700 Subject: [PATCH 27/68] chore: update puya-ts dependency --- package-lock.json | 18 +++++++++--------- package.json | 4 ++-- src/test-transformer/visitors.ts | 7 ++++--- 3 files changed, 15 insertions(+), 14 deletions(-) diff --git a/package-lock.json b/package-lock.json index cbb95d73..0bd8f902 100644 --- a/package-lock.json +++ b/package-lock.json @@ -8,8 +8,8 @@ "name": "@algorandfoundation/algorand-typescript-testing", "version": "1.0.0", "dependencies": { - "@algorandfoundation/algorand-typescript": "1.0.0-alpha.65", - "@algorandfoundation/puya-ts": "1.0.0-alpha.65", + "@algorandfoundation/algorand-typescript": "1.0.0-alpha.74", + "@algorandfoundation/puya-ts": "1.0.0-alpha.74", "elliptic": "^6.6.1", "js-sha256": "^0.11.0", "js-sha3": "^0.9.3", @@ -74,21 +74,21 @@ } }, "node_modules/@algorandfoundation/algorand-typescript": { - "version": "1.0.0-alpha.65", - "resolved": "https://registry.npmjs.org/@algorandfoundation/algorand-typescript/-/algorand-typescript-1.0.0-alpha.65.tgz", - "integrity": "sha512-5lYCtrArfLhKZGzp6DnfxpQHJ7xjzO8V1+Gu/6IjPNK9PjRLYiKe/bJXdo+7cbfT98LE5l5EpxOp5pU7l9rzyA==", + "version": "1.0.0-alpha.74", + "resolved": "https://registry.npmjs.org/@algorandfoundation/algorand-typescript/-/algorand-typescript-1.0.0-alpha.74.tgz", + "integrity": "sha512-rUJtmbJt8cfju2U/4ZoaCUlgN4AW9Nor3rOLozVg6IFoML0N9QzgRlqebdFUgJvJGXQ7zrJdcy19movZMc9fnw==", "peerDependencies": { "tslib": "^2.6.2" } }, "node_modules/@algorandfoundation/puya-ts": { - "version": "1.0.0-alpha.65", - "resolved": "https://registry.npmjs.org/@algorandfoundation/puya-ts/-/puya-ts-1.0.0-alpha.65.tgz", - "integrity": "sha512-laJSuSNSumVdqN5jMTn8I9Ewk244qZ7w+tMBi/mawds8pq+MiljMY7dBISJtm0kH7fbWZaL9TP2aoXb7XMzbDw==", + "version": "1.0.0-alpha.74", + "resolved": "https://registry.npmjs.org/@algorandfoundation/puya-ts/-/puya-ts-1.0.0-alpha.74.tgz", + "integrity": "sha512-pDpHBNpR+iWIZVDwdGxxnTZOTSAaA1JURsegO0bbCHpVwgfrgks9MLAtJiHiN7/7eE7QKF/zo3DSLxNb6PY35A==", "hasInstallScript": true, "license": "MIT", "dependencies": { - "@algorandfoundation/algorand-typescript": "1.0.0-alpha.65", + "@algorandfoundation/algorand-typescript": "1.0.0-alpha.74", "arcsecond": "^5.0.0", "argparse": "^2.0.1", "chalk": "^5.4.1", diff --git a/package.json b/package.json index 2c0e3010..02a14cab 100644 --- a/package.json +++ b/package.json @@ -67,8 +67,8 @@ "vitest": "3.2.4" }, "dependencies": { - "@algorandfoundation/algorand-typescript": "1.0.0-alpha.65", - "@algorandfoundation/puya-ts": "1.0.0-alpha.65", + "@algorandfoundation/algorand-typescript": "1.0.0-alpha.74", + "@algorandfoundation/puya-ts": "1.0.0-alpha.74", "elliptic": "^6.6.1", "js-sha256": "^0.11.0", "js-sha3": "^0.9.3", diff --git a/src/test-transformer/visitors.ts b/src/test-transformer/visitors.ts index c2e9fd3f..482c4202 100644 --- a/src/test-transformer/visitors.ts +++ b/src/test-transformer/visitors.ts @@ -177,7 +177,8 @@ class ExpressionVisitor { this.stubbedFunctionName = undefined let infoArg: TypeInfo | TypeInfo[] | undefined = info - /// if the function being called is not part of the program being transformed, do not process further + // the nodes which have been created or updated by the node factory will not have source location, + // and we do not need to process them further const sourceLocation = this.helper.sourceLocation(updatedNode) if (sourceLocation === SourceLocation.None) break handleTypeInfoCaputre @@ -201,8 +202,8 @@ class ExpressionVisitor { } else if (isCallingAbiCall(stubbedFunctionName)) { updatedNode = nodeFactory.callAbiCallFunction(updatedNode) } else if (isCallingBytes(stubbedFunctionName)) { - if (type instanceof ptypes.BytesPType && type.fixedByteSize) - updatedNode = nodeFactory.callFixedBytesFunction(stubbedFunctionName, updatedNode, Number(type.fixedByteSize)) + if (type instanceof ptypes.BytesPType && type.length) + updatedNode = nodeFactory.callFixedBytesFunction(stubbedFunctionName, updatedNode, Number(type.length)) } else { updatedNode = nodeFactory.callStubbedFunction(updatedNode, infoArg) } From 7631c8a8af7b0143f5672d6434ea6d869b79d8d8 Mon Sep 17 00:00:00 2001 From: Bobby Lat Date: Fri, 8 Aug 2025 14:38:59 +0700 Subject: [PATCH 28/68] docs: add a section to describe puyaTsTransformer scope --- README.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/README.md b/README.md index c8f8d29c..d7294900 100644 --- a/README.md +++ b/README.md @@ -42,6 +42,12 @@ npm i @algorandfoundation/algorand-typescript-testing Let's write a simple contract and test it using the `algorand-typescript-testing` framework. +#### Simulating AVM + +`algorand-typescript-testing` includes a TypeScript transformer (`puyaTsTransformer`) that ensures contracts (with `.algo.ts` extension) and tests (with `.spec.ts` or `.test.ts` extensions) behave consistently between Node.js and AVM environments. + +The transformer replicates AVM behavior, such as integer-only arithmetic where `3 / 2` produces `1`. For code requiring standard Node.js behaviour (e.g., `3 / 2` produces `1.5`), place it in separate `.ts` files and reference them from test files. + #### Configuring vitest If you are using [vitest](https://vitest.dev/) with [@rollup/plugin-typescript](https://www.npmjs.com/package/@rollup/plugin-typescript) plugin, configure `puyaTsTransformer` as a `before` stage transformer of the `typescript` plugin in `vitest.config.mts` file. From 168e634cbdf9c9ed35d50689380b11afd92cf789 Mon Sep 17 00:00:00 2001 From: Bobby Lat Date: Mon, 11 Aug 2025 09:13:10 +0700 Subject: [PATCH 29/68] docs: add notes on transformer providing testing implementation of algo-ts constructs --- README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.md b/README.md index d7294900..d93dfaec 100644 --- a/README.md +++ b/README.md @@ -48,6 +48,8 @@ Let's write a simple contract and test it using the `algorand-typescript-testing The transformer replicates AVM behavior, such as integer-only arithmetic where `3 / 2` produces `1`. For code requiring standard Node.js behaviour (e.g., `3 / 2` produces `1.5`), place it in separate `.ts` files and reference them from test files. +The transformer also redirects `@algorandfoundation/algorand-typescript` imports to `@algorandfoundation/algorand-typescript-testing/internal` to provide executable implementations of Algorand TypeScript constructs like `Global`, `Box`, `Uint64`, and `clone`. + #### Configuring vitest If you are using [vitest](https://vitest.dev/) with [@rollup/plugin-typescript](https://www.npmjs.com/package/@rollup/plugin-typescript) plugin, configure `puyaTsTransformer` as a `before` stage transformer of the `typescript` plugin in `vitest.config.mts` file. From df1a442b70a5255c01bc4b4b96016fa5a740dfb4 Mon Sep 17 00:00:00 2001 From: Bobby Lat Date: Mon, 11 Aug 2025 10:18:46 +0700 Subject: [PATCH 30/68] refactor: change test file extensions to opt-in to puyaTsTransformer --- README.md | 6 ++++-- .../{contract.spec.ts => contract.algo.spec.ts} | 0 .../auction/{contract.spec.ts => contract.algo.spec.ts} | 0 .../calculator/{contract.spec.ts => contract.algo.spec.ts} | 0 .../{contract.spec.ts => contract.algo.spec.ts} | 0 .../hello-world/{contract.spec.ts => contract.algo.spec.ts} | 0 .../{signature.spec.ts => signature.algo.spec.ts} | 0 .../{contract.spec.ts => contract.algo.spec.ts} | 0 .../marketplace/{contract.spec.ts => contract.algo.spec.ts} | 0 ...led-factory.spec.ts => precompiled-factory.algo.spec.ts} | 0 ...ompiled-typed.spec.ts => precompiled-typed.algo.spec.ts} | 0 .../{contract.spec.ts => contract.algo.spec.ts} | 0 .../{contract.spec.ts => contract.algo.spec.ts} | 0 .../{contract.spec.ts => contract.algo.spec.ts} | 0 examples/voting/{contract.spec.ts => contract.algo.spec.ts} | 0 .../{contract.spec.ts => contract.algo.spec.ts} | 0 package-lock.json | 2 +- package.json | 2 +- src/test-transformer/program-factory.ts | 2 +- src/test-transformer/vitest-transformer.ts | 2 +- tests/arc4/{address.spec.ts => address.algo.spec.ts} | 0 tests/arc4/{bool.spec.ts => bool.algo.spec.ts} | 0 tests/arc4/{byte.spec.ts => byte.algo.spec.ts} | 0 .../{dynamic-array.spec.ts => dynamic-array.algo.spec.ts} | 0 .../{dynamic-bytes.spec.ts => dynamic-bytes.algo.spec.ts} | 0 tests/arc4/{emit.spec.ts => emit.algo.spec.ts} | 0 ...-decode-arc4.spec.ts => encode-decode-arc4.algo.spec.ts} | 0 ...method-selector.spec.ts => method-selector.algo.spec.ts} | 0 ...urce-encoding.spec.ts => resource-encoding.algo.spec.ts} | 0 .../{static-array.spec.ts => static-array.algo.spec.ts} | 0 .../{static-bytes.spec.ts => static-bytes.algo.spec.ts} | 0 tests/arc4/{str.spec.ts => str.algo.spec.ts} | 0 tests/arc4/{struct.spec.ts => struct.algo.spec.ts} | 0 tests/arc4/{tuple.spec.ts => tuple.algo.spec.ts} | 0 tests/arc4/{ufixednxm.spec.ts => ufixednxm.algo.spec.ts} | 0 tests/arc4/{uintn.spec.ts => uintn.algo.spec.ts} | 0 ...ro-constructor.spec.ts => zero-constructor.algo.spec.ts} | 0 ...crypto-op-codes.spec.ts => crypto-op-codes.algo.spec.ts} | 0 tests/{fixed-array.spec.ts => fixed-array.algo.spec.ts} | 0 ...values.spec.ts => global-state-arc4-values.algo.spec.ts} | 0 tests/{itxn-compose.spec.ts => itxn-compose.algo.spec.ts} | 0 ...-values.spec.ts => local-state-arc4-values.algo.spec.ts} | 0 tests/{log.spec.ts => log.algo.spec.ts} | 0 tests/{match.spec.ts => match.algo.spec.ts} | 0 ...i-inheritance.spec.ts => multi-inheritance.algo.spec.ts} | 0 ...able-array.spec.ts => native-mutable-array.algo.spec.ts} | 0 ...le-object.spec.ts => native-mutable-object.algo.spec.ts} | 0 ...nly-array.spec.ts => native-readonly-array.algo.spec.ts} | 0 ...y-object.spec.ts => native-readonly-object.algo.spec.ts} | 0 tests/primitives/{biguint.spec.ts => biguint.algo.spec.ts} | 0 tests/primitives/{bytes.spec.ts => bytes.algo.spec.ts} | 0 tests/primitives/{uint64.spec.ts => uint64.algo.spec.ts} | 0 tests/{pure-op-codes.spec.ts => pure-op-codes.algo.spec.ts} | 0 ...reference-array.spec.ts => reference-array.algo.spec.ts} | 0 .../{arc4-contract.spec.ts => arc4-contract.algo.spec.ts} | 0 tests/references/{asset.spec.ts => asset.algo.spec.ts} | 0 tests/references/{box-map.spec.ts => box-map.algo.spec.ts} | 0 tests/references/{box-ref.spec.ts => box-ref.algo.spec.ts} | 0 tests/references/{box.spec.ts => box.algo.spec.ts} | 0 .../{state-op-codes.spec.ts => state-op-codes.algo.spec.ts} | 0 ...ch-statements.spec.ts => switch-statements.algo.spec.ts} | 0 tests/{urange.spec.ts => urange.algo.spec.ts} | 0 62 files changed, 8 insertions(+), 6 deletions(-) rename examples/arc4-simple-voting/{contract.spec.ts => contract.algo.spec.ts} (100%) rename examples/auction/{contract.spec.ts => contract.algo.spec.ts} (100%) rename examples/calculator/{contract.spec.ts => contract.algo.spec.ts} (100%) rename examples/hello-world-abi/{contract.spec.ts => contract.algo.spec.ts} (100%) rename examples/hello-world/{contract.spec.ts => contract.algo.spec.ts} (100%) rename examples/htlc-logicsig/{signature.spec.ts => signature.algo.spec.ts} (100%) rename examples/local-storage/{contract.spec.ts => contract.algo.spec.ts} (100%) rename examples/marketplace/{contract.spec.ts => contract.algo.spec.ts} (100%) rename examples/precompiled/{precompiled-factory.spec.ts => precompiled-factory.algo.spec.ts} (100%) rename examples/precompiled/{precompiled-typed.spec.ts => precompiled-typed.algo.spec.ts} (100%) rename examples/proof-of-attendance/{contract.spec.ts => contract.algo.spec.ts} (100%) rename examples/scratch-storage/{contract.spec.ts => contract.algo.spec.ts} (100%) rename examples/simple-voting/{contract.spec.ts => contract.algo.spec.ts} (100%) rename examples/voting/{contract.spec.ts => contract.algo.spec.ts} (100%) rename examples/zk-whitelist/{contract.spec.ts => contract.algo.spec.ts} (100%) rename tests/arc4/{address.spec.ts => address.algo.spec.ts} (100%) rename tests/arc4/{bool.spec.ts => bool.algo.spec.ts} (100%) rename tests/arc4/{byte.spec.ts => byte.algo.spec.ts} (100%) rename tests/arc4/{dynamic-array.spec.ts => dynamic-array.algo.spec.ts} (100%) rename tests/arc4/{dynamic-bytes.spec.ts => dynamic-bytes.algo.spec.ts} (100%) rename tests/arc4/{emit.spec.ts => emit.algo.spec.ts} (100%) rename tests/arc4/{encode-decode-arc4.spec.ts => encode-decode-arc4.algo.spec.ts} (100%) rename tests/arc4/{method-selector.spec.ts => method-selector.algo.spec.ts} (100%) rename tests/arc4/{resource-encoding.spec.ts => resource-encoding.algo.spec.ts} (100%) rename tests/arc4/{static-array.spec.ts => static-array.algo.spec.ts} (100%) rename tests/arc4/{static-bytes.spec.ts => static-bytes.algo.spec.ts} (100%) rename tests/arc4/{str.spec.ts => str.algo.spec.ts} (100%) rename tests/arc4/{struct.spec.ts => struct.algo.spec.ts} (100%) rename tests/arc4/{tuple.spec.ts => tuple.algo.spec.ts} (100%) rename tests/arc4/{ufixednxm.spec.ts => ufixednxm.algo.spec.ts} (100%) rename tests/arc4/{uintn.spec.ts => uintn.algo.spec.ts} (100%) rename tests/arc4/{zero-constructor.spec.ts => zero-constructor.algo.spec.ts} (100%) rename tests/{crypto-op-codes.spec.ts => crypto-op-codes.algo.spec.ts} (100%) rename tests/{fixed-array.spec.ts => fixed-array.algo.spec.ts} (100%) rename tests/{global-state-arc4-values.spec.ts => global-state-arc4-values.algo.spec.ts} (100%) rename tests/{itxn-compose.spec.ts => itxn-compose.algo.spec.ts} (100%) rename tests/{local-state-arc4-values.spec.ts => local-state-arc4-values.algo.spec.ts} (100%) rename tests/{log.spec.ts => log.algo.spec.ts} (100%) rename tests/{match.spec.ts => match.algo.spec.ts} (100%) rename tests/{multi-inheritance.spec.ts => multi-inheritance.algo.spec.ts} (100%) rename tests/{native-mutable-array.spec.ts => native-mutable-array.algo.spec.ts} (100%) rename tests/{native-mutable-object.spec.ts => native-mutable-object.algo.spec.ts} (100%) rename tests/{native-readonly-array.spec.ts => native-readonly-array.algo.spec.ts} (100%) rename tests/{native-readonly-object.spec.ts => native-readonly-object.algo.spec.ts} (100%) rename tests/primitives/{biguint.spec.ts => biguint.algo.spec.ts} (100%) rename tests/primitives/{bytes.spec.ts => bytes.algo.spec.ts} (100%) rename tests/primitives/{uint64.spec.ts => uint64.algo.spec.ts} (100%) rename tests/{pure-op-codes.spec.ts => pure-op-codes.algo.spec.ts} (100%) rename tests/{reference-array.spec.ts => reference-array.algo.spec.ts} (100%) rename tests/references/{arc4-contract.spec.ts => arc4-contract.algo.spec.ts} (100%) rename tests/references/{asset.spec.ts => asset.algo.spec.ts} (100%) rename tests/references/{box-map.spec.ts => box-map.algo.spec.ts} (100%) rename tests/references/{box-ref.spec.ts => box-ref.algo.spec.ts} (100%) rename tests/references/{box.spec.ts => box.algo.spec.ts} (100%) rename tests/{state-op-codes.spec.ts => state-op-codes.algo.spec.ts} (100%) rename tests/{switch-statements.spec.ts => switch-statements.algo.spec.ts} (100%) rename tests/{urange.spec.ts => urange.algo.spec.ts} (100%) diff --git a/README.md b/README.md index d93dfaec..47f169c7 100644 --- a/README.md +++ b/README.md @@ -44,12 +44,14 @@ Let's write a simple contract and test it using the `algorand-typescript-testing #### Simulating AVM -`algorand-typescript-testing` includes a TypeScript transformer (`puyaTsTransformer`) that ensures contracts (with `.algo.ts` extension) and tests (with `.spec.ts` or `.test.ts` extensions) behave consistently between Node.js and AVM environments. +`algorand-typescript-testing` includes a TypeScript transformer (`puyaTsTransformer`) that ensures contracts (with `.algo.ts` extension) and tests (with `.algo.spec.ts` or `.algo.test.ts` extensions) behave consistently between Node.js and AVM environments. The transformer replicates AVM behavior, such as integer-only arithmetic where `3 / 2` produces `1`. For code requiring standard Node.js behaviour (e.g., `3 / 2` produces `1.5`), place it in separate `.ts` files and reference them from test files. The transformer also redirects `@algorandfoundation/algorand-typescript` imports to `@algorandfoundation/algorand-typescript-testing/internal` to provide executable implementations of Algorand TypeScript constructs like `Global`, `Box`, `Uint64`, and `clone`. +If there are tests which do not need to be executed in the AVM context such as end to end tests, simply use `.test.ts` or `.spec.ts` file extensions without `.algo` part and the transformer would skip them. + #### Configuring vitest If you are using [vitest](https://vitest.dev/) with [@rollup/plugin-typescript](https://www.npmjs.com/package/@rollup/plugin-typescript) plugin, configure `puyaTsTransformer` as a `before` stage transformer of the `typescript` plugin in `vitest.config.mts` file. @@ -95,7 +97,7 @@ import { createDefaultEsmPreset, type JestConfigWithTsJest } from 'ts-jest' const presetConfig = createDefaultEsmPreset({}) const jestConfig: JestConfigWithTsJest = { ...presetConfig, - testMatch: ['**/*.test.ts'], + testMatch: ['**/*.algo.test.ts'], setupFilesAfterEnv: ['/jest.setup.ts'], transform: { '^.+\\.tsx?$': [ diff --git a/examples/arc4-simple-voting/contract.spec.ts b/examples/arc4-simple-voting/contract.algo.spec.ts similarity index 100% rename from examples/arc4-simple-voting/contract.spec.ts rename to examples/arc4-simple-voting/contract.algo.spec.ts diff --git a/examples/auction/contract.spec.ts b/examples/auction/contract.algo.spec.ts similarity index 100% rename from examples/auction/contract.spec.ts rename to examples/auction/contract.algo.spec.ts diff --git a/examples/calculator/contract.spec.ts b/examples/calculator/contract.algo.spec.ts similarity index 100% rename from examples/calculator/contract.spec.ts rename to examples/calculator/contract.algo.spec.ts diff --git a/examples/hello-world-abi/contract.spec.ts b/examples/hello-world-abi/contract.algo.spec.ts similarity index 100% rename from examples/hello-world-abi/contract.spec.ts rename to examples/hello-world-abi/contract.algo.spec.ts diff --git a/examples/hello-world/contract.spec.ts b/examples/hello-world/contract.algo.spec.ts similarity index 100% rename from examples/hello-world/contract.spec.ts rename to examples/hello-world/contract.algo.spec.ts diff --git a/examples/htlc-logicsig/signature.spec.ts b/examples/htlc-logicsig/signature.algo.spec.ts similarity index 100% rename from examples/htlc-logicsig/signature.spec.ts rename to examples/htlc-logicsig/signature.algo.spec.ts diff --git a/examples/local-storage/contract.spec.ts b/examples/local-storage/contract.algo.spec.ts similarity index 100% rename from examples/local-storage/contract.spec.ts rename to examples/local-storage/contract.algo.spec.ts diff --git a/examples/marketplace/contract.spec.ts b/examples/marketplace/contract.algo.spec.ts similarity index 100% rename from examples/marketplace/contract.spec.ts rename to examples/marketplace/contract.algo.spec.ts diff --git a/examples/precompiled/precompiled-factory.spec.ts b/examples/precompiled/precompiled-factory.algo.spec.ts similarity index 100% rename from examples/precompiled/precompiled-factory.spec.ts rename to examples/precompiled/precompiled-factory.algo.spec.ts diff --git a/examples/precompiled/precompiled-typed.spec.ts b/examples/precompiled/precompiled-typed.algo.spec.ts similarity index 100% rename from examples/precompiled/precompiled-typed.spec.ts rename to examples/precompiled/precompiled-typed.algo.spec.ts diff --git a/examples/proof-of-attendance/contract.spec.ts b/examples/proof-of-attendance/contract.algo.spec.ts similarity index 100% rename from examples/proof-of-attendance/contract.spec.ts rename to examples/proof-of-attendance/contract.algo.spec.ts diff --git a/examples/scratch-storage/contract.spec.ts b/examples/scratch-storage/contract.algo.spec.ts similarity index 100% rename from examples/scratch-storage/contract.spec.ts rename to examples/scratch-storage/contract.algo.spec.ts diff --git a/examples/simple-voting/contract.spec.ts b/examples/simple-voting/contract.algo.spec.ts similarity index 100% rename from examples/simple-voting/contract.spec.ts rename to examples/simple-voting/contract.algo.spec.ts diff --git a/examples/voting/contract.spec.ts b/examples/voting/contract.algo.spec.ts similarity index 100% rename from examples/voting/contract.spec.ts rename to examples/voting/contract.algo.spec.ts diff --git a/examples/zk-whitelist/contract.spec.ts b/examples/zk-whitelist/contract.algo.spec.ts similarity index 100% rename from examples/zk-whitelist/contract.spec.ts rename to examples/zk-whitelist/contract.algo.spec.ts diff --git a/package-lock.json b/package-lock.json index 0bd8f902..5b78a10c 100644 --- a/package-lock.json +++ b/package-lock.json @@ -52,7 +52,7 @@ "tsx": "4.19.3", "typedoc": "^0.28.1", "typedoc-plugin-markdown": "^4.6.0", - "typescript": "^5.8.2", + "typescript": "^5.8.3", "upath": "^2.0.1", "vitest": "3.2.4" } diff --git a/package.json b/package.json index 02a14cab..83328e5f 100644 --- a/package.json +++ b/package.json @@ -62,7 +62,7 @@ "tsx": "4.19.3", "typedoc": "^0.28.1", "typedoc-plugin-markdown": "^4.6.0", - "typescript": "^5.8.2", + "typescript": "^5.8.3", "upath": "^2.0.1", "vitest": "3.2.4" }, diff --git a/src/test-transformer/program-factory.ts b/src/test-transformer/program-factory.ts index a4360395..c9ceda7f 100644 --- a/src/test-transformer/program-factory.ts +++ b/src/test-transformer/program-factory.ts @@ -7,7 +7,7 @@ export interface TransformerConfig { testingPackageName: string } export const defaultTransformerConfig: TransformerConfig = { - includeExt: ['.algo.ts', '.spec.ts', '.test.ts'], + includeExt: ['.algo.ts', '.algo.spec.ts', '.algo.test.ts'], testingPackageName: '@algorandfoundation/algorand-typescript-testing', } diff --git a/src/test-transformer/vitest-transformer.ts b/src/test-transformer/vitest-transformer.ts index c088c720..b57fae8e 100644 --- a/src/test-transformer/vitest-transformer.ts +++ b/src/test-transformer/vitest-transformer.ts @@ -28,7 +28,7 @@ programTransformer.factory = createProgramFactory(defaultTransformerConfig) ** @type {ts.TransformerFactory & ((config: Partial) => ts.TransformerFactory)} * * @param {Partial} [config] Configuration options - * @param {string[]} [config.includeExt=['.algo.ts', '.spec.ts']] File extensions to process + * @param {string[]} [config.includeExt=['.algo.ts', '.algo.spec.ts']] File extensions to process * @param {string} [config.testingPackageName='@algorandfoundation/algorand-typescript-testing'] Package name for testing imports * * @example diff --git a/tests/arc4/address.spec.ts b/tests/arc4/address.algo.spec.ts similarity index 100% rename from tests/arc4/address.spec.ts rename to tests/arc4/address.algo.spec.ts diff --git a/tests/arc4/bool.spec.ts b/tests/arc4/bool.algo.spec.ts similarity index 100% rename from tests/arc4/bool.spec.ts rename to tests/arc4/bool.algo.spec.ts diff --git a/tests/arc4/byte.spec.ts b/tests/arc4/byte.algo.spec.ts similarity index 100% rename from tests/arc4/byte.spec.ts rename to tests/arc4/byte.algo.spec.ts diff --git a/tests/arc4/dynamic-array.spec.ts b/tests/arc4/dynamic-array.algo.spec.ts similarity index 100% rename from tests/arc4/dynamic-array.spec.ts rename to tests/arc4/dynamic-array.algo.spec.ts diff --git a/tests/arc4/dynamic-bytes.spec.ts b/tests/arc4/dynamic-bytes.algo.spec.ts similarity index 100% rename from tests/arc4/dynamic-bytes.spec.ts rename to tests/arc4/dynamic-bytes.algo.spec.ts diff --git a/tests/arc4/emit.spec.ts b/tests/arc4/emit.algo.spec.ts similarity index 100% rename from tests/arc4/emit.spec.ts rename to tests/arc4/emit.algo.spec.ts diff --git a/tests/arc4/encode-decode-arc4.spec.ts b/tests/arc4/encode-decode-arc4.algo.spec.ts similarity index 100% rename from tests/arc4/encode-decode-arc4.spec.ts rename to tests/arc4/encode-decode-arc4.algo.spec.ts diff --git a/tests/arc4/method-selector.spec.ts b/tests/arc4/method-selector.algo.spec.ts similarity index 100% rename from tests/arc4/method-selector.spec.ts rename to tests/arc4/method-selector.algo.spec.ts diff --git a/tests/arc4/resource-encoding.spec.ts b/tests/arc4/resource-encoding.algo.spec.ts similarity index 100% rename from tests/arc4/resource-encoding.spec.ts rename to tests/arc4/resource-encoding.algo.spec.ts diff --git a/tests/arc4/static-array.spec.ts b/tests/arc4/static-array.algo.spec.ts similarity index 100% rename from tests/arc4/static-array.spec.ts rename to tests/arc4/static-array.algo.spec.ts diff --git a/tests/arc4/static-bytes.spec.ts b/tests/arc4/static-bytes.algo.spec.ts similarity index 100% rename from tests/arc4/static-bytes.spec.ts rename to tests/arc4/static-bytes.algo.spec.ts diff --git a/tests/arc4/str.spec.ts b/tests/arc4/str.algo.spec.ts similarity index 100% rename from tests/arc4/str.spec.ts rename to tests/arc4/str.algo.spec.ts diff --git a/tests/arc4/struct.spec.ts b/tests/arc4/struct.algo.spec.ts similarity index 100% rename from tests/arc4/struct.spec.ts rename to tests/arc4/struct.algo.spec.ts diff --git a/tests/arc4/tuple.spec.ts b/tests/arc4/tuple.algo.spec.ts similarity index 100% rename from tests/arc4/tuple.spec.ts rename to tests/arc4/tuple.algo.spec.ts diff --git a/tests/arc4/ufixednxm.spec.ts b/tests/arc4/ufixednxm.algo.spec.ts similarity index 100% rename from tests/arc4/ufixednxm.spec.ts rename to tests/arc4/ufixednxm.algo.spec.ts diff --git a/tests/arc4/uintn.spec.ts b/tests/arc4/uintn.algo.spec.ts similarity index 100% rename from tests/arc4/uintn.spec.ts rename to tests/arc4/uintn.algo.spec.ts diff --git a/tests/arc4/zero-constructor.spec.ts b/tests/arc4/zero-constructor.algo.spec.ts similarity index 100% rename from tests/arc4/zero-constructor.spec.ts rename to tests/arc4/zero-constructor.algo.spec.ts diff --git a/tests/crypto-op-codes.spec.ts b/tests/crypto-op-codes.algo.spec.ts similarity index 100% rename from tests/crypto-op-codes.spec.ts rename to tests/crypto-op-codes.algo.spec.ts diff --git a/tests/fixed-array.spec.ts b/tests/fixed-array.algo.spec.ts similarity index 100% rename from tests/fixed-array.spec.ts rename to tests/fixed-array.algo.spec.ts diff --git a/tests/global-state-arc4-values.spec.ts b/tests/global-state-arc4-values.algo.spec.ts similarity index 100% rename from tests/global-state-arc4-values.spec.ts rename to tests/global-state-arc4-values.algo.spec.ts diff --git a/tests/itxn-compose.spec.ts b/tests/itxn-compose.algo.spec.ts similarity index 100% rename from tests/itxn-compose.spec.ts rename to tests/itxn-compose.algo.spec.ts diff --git a/tests/local-state-arc4-values.spec.ts b/tests/local-state-arc4-values.algo.spec.ts similarity index 100% rename from tests/local-state-arc4-values.spec.ts rename to tests/local-state-arc4-values.algo.spec.ts diff --git a/tests/log.spec.ts b/tests/log.algo.spec.ts similarity index 100% rename from tests/log.spec.ts rename to tests/log.algo.spec.ts diff --git a/tests/match.spec.ts b/tests/match.algo.spec.ts similarity index 100% rename from tests/match.spec.ts rename to tests/match.algo.spec.ts diff --git a/tests/multi-inheritance.spec.ts b/tests/multi-inheritance.algo.spec.ts similarity index 100% rename from tests/multi-inheritance.spec.ts rename to tests/multi-inheritance.algo.spec.ts diff --git a/tests/native-mutable-array.spec.ts b/tests/native-mutable-array.algo.spec.ts similarity index 100% rename from tests/native-mutable-array.spec.ts rename to tests/native-mutable-array.algo.spec.ts diff --git a/tests/native-mutable-object.spec.ts b/tests/native-mutable-object.algo.spec.ts similarity index 100% rename from tests/native-mutable-object.spec.ts rename to tests/native-mutable-object.algo.spec.ts diff --git a/tests/native-readonly-array.spec.ts b/tests/native-readonly-array.algo.spec.ts similarity index 100% rename from tests/native-readonly-array.spec.ts rename to tests/native-readonly-array.algo.spec.ts diff --git a/tests/native-readonly-object.spec.ts b/tests/native-readonly-object.algo.spec.ts similarity index 100% rename from tests/native-readonly-object.spec.ts rename to tests/native-readonly-object.algo.spec.ts diff --git a/tests/primitives/biguint.spec.ts b/tests/primitives/biguint.algo.spec.ts similarity index 100% rename from tests/primitives/biguint.spec.ts rename to tests/primitives/biguint.algo.spec.ts diff --git a/tests/primitives/bytes.spec.ts b/tests/primitives/bytes.algo.spec.ts similarity index 100% rename from tests/primitives/bytes.spec.ts rename to tests/primitives/bytes.algo.spec.ts diff --git a/tests/primitives/uint64.spec.ts b/tests/primitives/uint64.algo.spec.ts similarity index 100% rename from tests/primitives/uint64.spec.ts rename to tests/primitives/uint64.algo.spec.ts diff --git a/tests/pure-op-codes.spec.ts b/tests/pure-op-codes.algo.spec.ts similarity index 100% rename from tests/pure-op-codes.spec.ts rename to tests/pure-op-codes.algo.spec.ts diff --git a/tests/reference-array.spec.ts b/tests/reference-array.algo.spec.ts similarity index 100% rename from tests/reference-array.spec.ts rename to tests/reference-array.algo.spec.ts diff --git a/tests/references/arc4-contract.spec.ts b/tests/references/arc4-contract.algo.spec.ts similarity index 100% rename from tests/references/arc4-contract.spec.ts rename to tests/references/arc4-contract.algo.spec.ts diff --git a/tests/references/asset.spec.ts b/tests/references/asset.algo.spec.ts similarity index 100% rename from tests/references/asset.spec.ts rename to tests/references/asset.algo.spec.ts diff --git a/tests/references/box-map.spec.ts b/tests/references/box-map.algo.spec.ts similarity index 100% rename from tests/references/box-map.spec.ts rename to tests/references/box-map.algo.spec.ts diff --git a/tests/references/box-ref.spec.ts b/tests/references/box-ref.algo.spec.ts similarity index 100% rename from tests/references/box-ref.spec.ts rename to tests/references/box-ref.algo.spec.ts diff --git a/tests/references/box.spec.ts b/tests/references/box.algo.spec.ts similarity index 100% rename from tests/references/box.spec.ts rename to tests/references/box.algo.spec.ts diff --git a/tests/state-op-codes.spec.ts b/tests/state-op-codes.algo.spec.ts similarity index 100% rename from tests/state-op-codes.spec.ts rename to tests/state-op-codes.algo.spec.ts diff --git a/tests/switch-statements.spec.ts b/tests/switch-statements.algo.spec.ts similarity index 100% rename from tests/switch-statements.spec.ts rename to tests/switch-statements.algo.spec.ts diff --git a/tests/urange.spec.ts b/tests/urange.algo.spec.ts similarity index 100% rename from tests/urange.spec.ts rename to tests/urange.algo.spec.ts From 146f997eb23fe7f19524b507418a5a01fda4ec34 Mon Sep 17 00:00:00 2001 From: Bobby Lat Date: Tue, 12 Aug 2025 12:54:47 +0700 Subject: [PATCH 31/68] build: Create patch release for refactor commits --- .releaserc.json | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/.releaserc.json b/.releaserc.json index 63b2fc5a..b4397a62 100644 --- a/.releaserc.json +++ b/.releaserc.json @@ -25,6 +25,10 @@ { "type": "chore", "release": "patch" + }, + { + "type": "refactor", + "release": "patch" } ] } From 15809dc285e9c6067f4610cf46f03fc926132365 Mon Sep 17 00:00:00 2001 From: Bobby Lat Date: Mon, 11 Aug 2025 17:05:38 +0700 Subject: [PATCH 32/68] chore: delete internal type definitions from package --- package.json | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/package.json b/package.json index 83328e5f..fa82a2a4 100644 --- a/package.json +++ b/package.json @@ -15,8 +15,9 @@ "build:1-lint": "eslint \"src/**/*.ts\" \"tests/**/*.ts\" --max-warnings 0", "build:2-check-types": "tsc -p tsconfig.json", "build:3-build": "rollup -c --configPlugin typescript", - "build:4-copy-pkg-json": "tstk copy-package-json -c", - "build:5-copy-readme": "copyfiles ./README.md ./dist", + "build:4-remove-internal-types": "rimraf dist/impl/**/*.d.ts dist/internal/*.d.ts -g", + "build:5-copy-pkg-json": "tstk copy-package-json -c", + "build:6-copy-readme": "copyfiles ./README.md ./dist", "watch": "rollup -c -w --configPlugin typescript", "test": "vitest run", "test:coverage": "vitest run --coverage", From 3cfccbf7a4950356e6481b799532e387bf035819 Mon Sep 17 00:00:00 2001 From: Bobby Lat Date: Tue, 12 Aug 2025 14:11:32 +0700 Subject: [PATCH 33/68] move toExternalValue function to util since impl types are not exported anymore --- package.json | 2 +- src/impl/primitives.ts | 40 ---------------------------------------- src/index.ts | 2 +- src/util.ts | 42 +++++++++++++++++++++++++++++++++++++++++- 4 files changed, 43 insertions(+), 43 deletions(-) diff --git a/package.json b/package.json index fa82a2a4..d1391953 100644 --- a/package.json +++ b/package.json @@ -15,7 +15,7 @@ "build:1-lint": "eslint \"src/**/*.ts\" \"tests/**/*.ts\" --max-warnings 0", "build:2-check-types": "tsc -p tsconfig.json", "build:3-build": "rollup -c --configPlugin typescript", - "build:4-remove-internal-types": "rimraf dist/impl/**/*.d.ts dist/internal/*.d.ts -g", + "build:4-remove-internal-types": "rimraf -g dist/impl/*.d.ts dist/impl/**/*.d.ts dist/internal/*.d.ts", "build:5-copy-pkg-json": "tstk copy-package-json -c", "build:6-copy-readme": "copyfiles ./README.md ./dist", "watch": "rollup -c -w --configPlugin typescript", diff --git a/src/impl/primitives.ts b/src/impl/primitives.ts index a680d259..37113b52 100644 --- a/src/impl/primitives.ts +++ b/src/impl/primitives.ts @@ -12,46 +12,6 @@ export type StubBigUintCompat = BigUintCompat | BigUintCls | Uint64Cls export type StubBytesCompat = BytesCompat | BytesCls export type StubUint64Compat = Uint64Compat | Uint64Cls -/** - * Converts internal Algorand type representations to their external primitive values. - * - * @overload - * @param {uint64} val - A uint64 value to convert - * @returns {bigint} The uint64 value as a bigint - * - * @overload - * @param {biguint} val - A biguint value to convert - * @returns {bigint} The biguint value as a bigint - * - * @overload - * @param {bytes} val - A bytes value to convert - * @returns {Uint8Array} The bytes value as a Uint8Array - * - * @overload - * @param {string} val - A string value to pass through - * @returns {string} The original string value unchanged - * - * @example - * ```ts - * const uint64Val = Uint64(123n) - * toExternalValue(uint64Val) // returns 123n - * - * const bytesVal = Bytes.fromBase64("SGVsbG8="); - * toExternalValue(bytesVal) // returns Uint8Array([72, 101, 108, 108, 111]) - * ``` - */ -export function toExternalValue(val: uint64): bigint -export function toExternalValue(val: biguint): bigint -export function toExternalValue(val: bytes): Uint8Array -export function toExternalValue(val: string): string -export function toExternalValue(val: uint64 | biguint | bytes | string) { - const instance = val as unknown - if (instance instanceof BytesCls) return instance.asUint8Array() - if (instance instanceof Uint64Cls) return instance.asBigInt() - if (instance instanceof BigUintCls) return instance.asBigInt() - if (typeof val === 'string') return val -} - /** * Create a uint64 with the default value of 0 */ diff --git a/src/index.ts b/src/index.ts index aa9d4225..f04a179e 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,5 +1,5 @@ export { ApplicationSpy } from './application-spy' export { AssertError, AvmError, CodeError, InternalError, NotImplementedError } from './errors' -export { toExternalValue } from './impl/primitives' export { addEqualityTesters } from './set-up' export { TestExecutionContext } from './test-execution-context' +export { toExternalValue } from './util' diff --git a/src/util.ts b/src/util.ts index 400335cb..24aa9509 100644 --- a/src/util.ts +++ b/src/util.ts @@ -1,4 +1,4 @@ -import type { bytes } from '@algorandfoundation/algorand-typescript' +import type { biguint, bytes, uint64 } from '@algorandfoundation/algorand-typescript' import { randomBytes } from 'crypto' import { BITS_IN_BYTE, MAX_BYTES_SIZE, MAX_UINT512, MAX_UINT8, UINT512_SIZE } from './constants' import { AssertError, AvmError, InternalError } from './errors' @@ -6,6 +6,46 @@ import type { StubBigUintCompat, StubBytesCompat, StubUint64Compat } from './imp import { BigUintCls, Bytes, BytesCls, Uint64Cls } from './impl/primitives' import type { DeliberateAny } from './typescript-helpers' +/** + * Converts internal Algorand type representations to their external primitive values. + * + * @overload + * @param {uint64} val - A uint64 value to convert + * @returns {bigint} The uint64 value as a bigint + * + * @overload + * @param {biguint} val - A biguint value to convert + * @returns {bigint} The biguint value as a bigint + * + * @overload + * @param {bytes} val - A bytes value to convert + * @returns {Uint8Array} The bytes value as a Uint8Array + * + * @overload + * @param {string} val - A string value to pass through + * @returns {string} The original string value unchanged + * + * @example + * ```ts + * const uint64Val = Uint64(123n) + * toExternalValue(uint64Val) // returns 123n + * + * const bytesVal = Bytes.fromBase64("SGVsbG8="); + * toExternalValue(bytesVal) // returns Uint8Array([72, 101, 108, 108, 111]) + * ``` + */ +export function toExternalValue(val: uint64): bigint +export function toExternalValue(val: biguint): bigint +export function toExternalValue(val: bytes): Uint8Array +export function toExternalValue(val: string): string +export function toExternalValue(val: uint64 | biguint | bytes | string) { + const instance = val as unknown + if (instance instanceof BytesCls) return instance.asUint8Array() + if (instance instanceof Uint64Cls) return instance.asBigInt() + if (instance instanceof BigUintCls) return instance.asBigInt() + if (typeof val === 'string') return val +} + export function* iterBigInt(start: bigint, end: bigint): Generator { for (let i = start; i < end; i++) { yield BigInt(i) From 49550e0b5681283d35aa6b0308c2118d2dc581ac Mon Sep 17 00:00:00 2001 From: Bobby Lat Date: Wed, 13 Aug 2025 10:40:15 +0700 Subject: [PATCH 34/68] prevent unnecessary processing of symbols loaded from testing package in puyaTsTransformer --- src/test-transformer/visitors.ts | 49 ++++++++++++++++++++++---------- 1 file changed, 34 insertions(+), 15 deletions(-) diff --git a/src/test-transformer/visitors.ts b/src/test-transformer/visitors.ts index 482c4202..cbeb9526 100644 --- a/src/test-transformer/visitors.ts +++ b/src/test-transformer/visitors.ts @@ -22,6 +22,13 @@ const algotsModulePaths = [ '/puya-ts/packages/algo-ts/', `${path.sep}puya-ts${path.sep}packages${path.sep}algo-ts${path.sep}`, ] +const algotsTestingModulePaths = (testingPackageName: string) => [ + testingPackageName, + `${path.sep}algorand-typescript-testing${path.sep}src${path.sep}`, + `${path.sep}algorand-typescript-testing${path.sep}dist${path.sep}`, +] + +const testingExamplePath = `${path.sep}algorand-typescript-testing${path.sep}examples${path.sep}` type VisitorHelper = { additionalStatements: ts.Statement[] @@ -29,6 +36,7 @@ type VisitorHelper = { resolveTypeParameters(node: ts.CallExpression): ptypes.PType[] sourceLocation(node: ts.Node): SourceLocation tryGetSymbol(node: ts.Node): ts.Symbol | undefined + getConfig(): TransformerConfig } export class SourceFileVisitor { @@ -38,7 +46,7 @@ export class SourceFileVisitor { private context: ts.TransformationContext, private sourceFile: ts.SourceFile, program: ts.Program, - private config: TransformerConfig, + config: TransformerConfig, ) { const typeChecker = program.getTypeChecker() const loggingContext = LoggingContext.create() @@ -74,13 +82,16 @@ export class SourceFileVisitor { return SourceLocation.None } }, + getConfig(): TransformerConfig { + return config + }, } } public result(): ts.SourceFile { const updatedSourceFile = ts.visitNode(this.sourceFile, this.visit) as ts.SourceFile return factory.updateSourceFile(updatedSourceFile, [ - ...nodeFactory.importHelpers(this.config.testingPackageName), + ...nodeFactory.importHelpers(this.helper.getConfig().testingPackageName), ...updatedSourceFile.statements, ...this.helper.additionalStatements, ]) @@ -88,7 +99,7 @@ export class SourceFileVisitor { private visit = (node: ts.Node): ts.Node => { if (ts.isImportDeclaration(node)) { - return new ImportDeclarationVisitor(this.context, this.helper, this.config, node).result() + return new ImportDeclarationVisitor(node, this.helper).result() } if (ts.isFunctionLike(node)) { return new FunctionLikeDecVisitor(this.context, this.helper, node).result() @@ -109,10 +120,8 @@ export class SourceFileVisitor { class ImportDeclarationVisitor { constructor( - private context: ts.TransformationContext, - private helper: VisitorHelper, - private config: TransformerConfig, private declarationNode: ts.ImportDeclaration, + private helper: VisitorHelper, ) {} public result(): ts.ImportDeclaration { @@ -132,7 +141,7 @@ class ImportDeclarationVisitor { : this.declarationNode.importClause, factory.createStringLiteral( moduleSpecifier - .replace(algotsModuleSpecifier, testingInternalModuleSpecifier(this.config.testingPackageName)) + .replace(algotsModuleSpecifier, testingInternalModuleSpecifier(this.helper.getConfig().testingPackageName)) .replace(/^("|')/, '') .replace(/("|')$/, ''), ), @@ -154,7 +163,9 @@ class ExpressionVisitor { } private visit = (node: ts.Node): ts.Node => { - handleTypeInfoCaputre: if (ts.isCallExpression(node) || ts.isNewExpression(node)) { + handleTypeInfoCapture: if (ts.isCallExpression(node) || ts.isNewExpression(node)) { + if (!tryGetAlgoTsSymbolName(node.expression, this.helper)) break handleTypeInfoCapture + let type = this.helper.resolveType(node) // `voted = LocalState()` is resolved to FunctionPType with returnType LocalState @@ -180,7 +191,7 @@ class ExpressionVisitor { // the nodes which have been created or updated by the node factory will not have source location, // and we do not need to process them further const sourceLocation = this.helper.sourceLocation(updatedNode) - if (sourceLocation === SourceLocation.None) break handleTypeInfoCaputre + if (sourceLocation === SourceLocation.None) break handleTypeInfoCapture if ( isCallingEmit(stubbedFunctionName) || @@ -518,12 +529,20 @@ const tryGetStubbedFunctionName = (node: ts.CallExpression, helper: VisitorHelpe } const tryGetAlgoTsSymbolName = (node: ts.Node, helper: VisitorHelper): string | undefined => { - const s = helper.tryGetSymbol(node) - if (s) { - const sourceFileName = s.valueDeclaration?.getSourceFile().fileName - if (sourceFileName && !algotsModulePaths.some((s) => sourceFileName.includes(s))) return undefined - } - return s?.getName() ?? (ts.isMemberName(node) ? node.text : node.getText()) + const symbol = helper.tryGetSymbol(node) + if (!symbol) return undefined + + const sourceFileName = symbol.valueDeclaration?.getSourceFile().fileName + if (!sourceFileName) return undefined + + // If the symbol is from algorand-typescript package or testing example path, return its name + if (algotsModulePaths.some((path) => sourceFileName.includes(path)) || sourceFileName.includes(testingExamplePath)) + return symbol.getName() + + // If the symbol is from algorand-typescript-testing package, return undefined as they do not need to be processed + if (algotsTestingModulePaths(helper.getConfig().testingPackageName).some((path) => sourceFileName.includes(path))) return undefined + + return symbol.getName() } const isCallingDecodeArc4 = (functionName: string | undefined): boolean => 'decodeArc4' === (functionName ?? '') From 535393b7eddc3d6f964169d1827798e5c4d0e80d Mon Sep 17 00:00:00 2001 From: Bobby Lat Date: Tue, 12 Aug 2025 16:31:16 +0700 Subject: [PATCH 35/68] refactor: explicitly mark the items which should not be visible externally as internal --- package.json | 5 +- src/abi-metadata.ts | 6 + src/collections/custom-key-map.ts | 7 +- src/constants.ts | 49 ++++- src/context-helpers/context-manager.ts | 1 + src/context-helpers/context-util.ts | 1 + src/context-helpers/internal-context.ts | 3 +- src/decode-logs.ts | 2 +- src/impl/acct-params.ts | 5 + src/impl/app-global.ts | 11 +- src/impl/app-local.ts | 9 +- src/impl/app-params.ts | 2 + src/impl/asset-holding.ts | 1 + src/impl/asset-params.ts | 2 + src/impl/base-32.ts | 2 + src/impl/base-contract.ts | 3 + src/impl/base.ts | 4 +- src/impl/block.ts | 8 +- src/impl/box.ts | 1 + src/impl/c2c.ts | 4 + src/impl/clone.ts | 1 + src/impl/compiled.ts | 1 + src/impl/contract.ts | 4 + src/impl/crypto.ts | 12 ++ src/impl/emit.ts | 1 + src/impl/encoded-types/array-proxy.ts | 1 + src/impl/encoded-types/constants.ts | 4 + src/impl/encoded-types/encoded-types.ts | 20 +++ src/impl/encoded-types/helpers.ts | 22 +++ src/impl/encoded-types/index.ts | 3 + src/impl/encoded-types/types.ts | 7 + src/impl/encoded-types/utils.ts | 4 + src/impl/ensure-budget.ts | 1 + src/impl/global.ts | 2 + src/impl/gtxn.ts | 150 ++++++++-------- src/impl/index.ts | 18 ++ src/impl/inner-transactions.ts | 15 +- src/impl/itxn-compose.ts | 1 + src/impl/itxn.ts | 168 +++++++++++------- src/impl/log.ts | 1 + src/impl/logicSigArg.ts | 1 + src/impl/match.ts | 2 + src/impl/method-selector.ts | 1 + src/impl/online-stake.ts | 1 + src/impl/primitives.ts | 53 +++++- src/impl/pure.ts | 29 +++ src/impl/reference.ts | 19 ++ src/impl/scratch.ts | 20 ++- src/impl/state.ts | 21 ++- src/impl/template-var.ts | 1 + src/impl/transactions.ts | 22 +-- src/impl/txn.ts | 4 +- src/impl/urange.ts | 1 + src/impl/voter-params.ts | 2 + src/internal/arc4.ts | 5 + src/internal/index.ts | 22 +++ src/internal/op.ts | 19 ++ src/runtime-helpers.ts | 7 + src/subcontexts/contract-context.ts | 9 +- src/subcontexts/ledger-context.ts | 56 +++--- src/subcontexts/transaction-context.ts | 49 ++--- src/test-transformer/errors.ts | 1 + src/test-transformer/helpers.ts | 2 + src/test-transformer/node-factory.ts | 1 + src/test-transformer/program-factory.ts | 2 + .../supported-binary-op-string.ts | 3 + src/test-transformer/visitors.ts | 1 + src/util.ts | 25 +++ src/value-generators/avm.ts | 32 ++-- src/value-generators/txn.ts | 4 +- 70 files changed, 723 insertions(+), 254 deletions(-) diff --git a/package.json b/package.json index d1391953..83328e5f 100644 --- a/package.json +++ b/package.json @@ -15,9 +15,8 @@ "build:1-lint": "eslint \"src/**/*.ts\" \"tests/**/*.ts\" --max-warnings 0", "build:2-check-types": "tsc -p tsconfig.json", "build:3-build": "rollup -c --configPlugin typescript", - "build:4-remove-internal-types": "rimraf -g dist/impl/*.d.ts dist/impl/**/*.d.ts dist/internal/*.d.ts", - "build:5-copy-pkg-json": "tstk copy-package-json -c", - "build:6-copy-readme": "copyfiles ./README.md ./dist", + "build:4-copy-pkg-json": "tstk copy-package-json -c", + "build:5-copy-readme": "copyfiles ./README.md ./dist", "watch": "rollup -c -w --configPlugin typescript", "test": "vitest run", "test:coverage": "vitest run --coverage", diff --git a/src/abi-metadata.ts b/src/abi-metadata.ts index 8790ad28..1747668b 100644 --- a/src/abi-metadata.ts +++ b/src/abi-metadata.ts @@ -6,6 +6,7 @@ import type { TypeInfo } from './impl/encoded-types' import { getArc4TypeName } from './impl/encoded-types' import type { DeliberateAny } from './typescript-helpers' +/** @internal */ export interface AbiMetadata { methodName: string name?: string @@ -18,6 +19,7 @@ export interface AbiMetadata { } const metadataStore: WeakMap<{ new (): Contract }, Record> = new WeakMap() +/** @internal */ export const attachAbiMetadata = (contract: { new (): Contract }, methodName: string, metadata: AbiMetadata): void => { if (!metadataStore.has(contract)) { metadataStore.set(contract, {}) @@ -31,6 +33,7 @@ export const attachAbiMetadata = (contract: { new (): Contract }, methodName: st } } +/** @internal */ export const getContractAbiMetadata = (contract: T | { new (): T }): Record => { // Initialize result object to store merged metadata const result: Record = {} @@ -63,11 +66,13 @@ export const getContractAbiMetadata = (contract: T | { new ( return result } +/** @internal */ export const getContractMethodAbiMetadata = (contract: T | { new (): T }, methodName: string): AbiMetadata => { const metadatas = getContractAbiMetadata(contract) return metadatas[methodName] } +/** @internal */ export const getArc4Signature = (metadata: AbiMetadata): string => { if (metadata.methodSignature === undefined) { const argTypes = metadata.argTypes.map((t) => JSON.parse(t) as TypeInfo).map((t) => getArc4TypeName(t, metadata.resourceEncoding, 'in')) @@ -77,6 +82,7 @@ export const getArc4Signature = (metadata: AbiMetadata): string => { return metadata.methodSignature } +/** @internal */ export const getArc4Selector = (metadata: AbiMetadata): Uint8Array => { const hash = js_sha512.sha512_256.array(getArc4Signature(metadata)) return new Uint8Array(hash.slice(0, 4)) diff --git a/src/collections/custom-key-map.ts b/src/collections/custom-key-map.ts index cd09fe9c..7aeb590e 100644 --- a/src/collections/custom-key-map.ts +++ b/src/collections/custom-key-map.ts @@ -1,6 +1,5 @@ -import type { Account } from '@algorandfoundation/algorand-typescript' +import type { Account, BytesCompat, Uint64Compat } from '@algorandfoundation/algorand-typescript' import { InternalError } from '../errors' -import type { StubBytesCompat, StubUint64Compat } from '../impl/primitives' import type { DeliberateAny } from '../typescript-helpers' import { asBytesCls, asUint64Cls } from '../util' @@ -75,13 +74,13 @@ export class AccountMap extends CustomKeyMap { } } -export class BytesMap extends CustomKeyMap { +export class BytesMap extends CustomKeyMap { constructor() { super((bytes) => asBytesCls(bytes).valueOf()) } } -export class Uint64Map extends CustomKeyMap { +export class Uint64Map extends CustomKeyMap { constructor() { super((uint64) => asUint64Cls(uint64).valueOf()) } diff --git a/src/constants.ts b/src/constants.ts index 8ade049c..1d8d9ec9 100644 --- a/src/constants.ts +++ b/src/constants.ts @@ -1,25 +1,45 @@ import { Bytes, FixedBytes } from './impl/primitives' +/** @internal */ export const UINT64_SIZE = 64 +/** @internal */ export const UINT512_SIZE = 512 +/** @internal */ export const MAX_UINT8 = 2 ** 8 - 1 +/** @internal */ export const MAX_UINT16 = 2 ** 16 - 1 +/** @internal */ export const MAX_UINT32 = 2 ** 32 - 1 +/** @internal */ export const MAX_UINT64 = 2n ** 64n - 1n +/** @internal */ export const MAX_UINT128 = 2n ** 128n - 1n +/** @internal */ export const MAX_UINT256 = 2n ** 256n - 1n +/** @internal */ export const MAX_UINT512 = 2n ** 512n - 1n +/** @internal */ export const MAX_BYTES_SIZE = 4096 +/** @internal */ export const MAX_LOG_SIZE = 1024 +/** @internal */ export const MAX_ITEMS_IN_LOG = 32 +/** @internal */ export const MAX_BOX_SIZE = 32768 +/** @internal */ export const BITS_IN_BYTE = 8 +/** @internal */ export const DEFAULT_ACCOUNT_MIN_BALANCE = 100_000 +/** @internal */ export const DEFAULT_MAX_TXN_LIFE = 1_000 +/** @internal */ export const DEFAULT_ASSET_CREATE_MIN_BALANCE = 1000_000 +/** @internal */ export const DEFAULT_ASSET_OPT_IN_MIN_BALANCE = 10_000 -// from python code: list(b"\x85Y\xb5\x14x\xfd\x89\xc1vC\xd0]\x15\xa8\xaek\x10\xabG\xbbm\x8a1\x88\x11V\xe6\xbd;\xae\x95\xd1") +/** @internal + * from python code: list(b"\x85Y\xb5\x14x\xfd\x89\xc1vC\xd0]\x15\xa8\xaek\x10\xabG\xbbm\x8a1\x88\x11V\xe6\xbd;\xae\x95\xd1") + */ export const DEFAULT_GLOBAL_GENESIS_HASH = FixedBytes( 32, new Uint8Array([ @@ -28,37 +48,55 @@ export const DEFAULT_GLOBAL_GENESIS_HASH = FixedBytes( ]), ) -// algorand encoded address of 32 zero bytes +/** @internal + * algorand encoded address of 32 zero bytes + */ export const ZERO_ADDRESS = Bytes.fromBase32('AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA') -/** +/** @internal "\x09" # pragma version 9 "\x81\x01" # pushint 1 */ export const ALWAYS_APPROVE_TEAL_PROGRAM = Bytes(new Uint8Array([0x09, 0x81, 0x01])) -// bytes: program (logic) data prefix when signing +/** @internal + * bytes: program (logic) data prefix when signing + */ export const LOGIC_DATA_PREFIX = Bytes('ProgData') -//number: minimum transaction fee +/** @internal + * number: minimum transaction fee + */ export const MIN_TXN_FEE = 1000 +/** @internal */ export const ABI_RETURN_VALUE_LOG_PREFIX = Bytes.fromHex('151F7C75') +/** @internal */ export const UINT64_OVERFLOW_UNDERFLOW_MESSAGE = 'Uint64 overflow or underflow' +/** @internal */ export const BIGUINT_OVERFLOW_UNDERFLOW_MESSAGE = 'BigUint overflow or underflow' +/** @internal */ export const DEFAULT_TEMPLATE_VAR_PREFIX = 'TMPL_' +/** @internal */ export const APP_ID_PREFIX = 'appID' +/** @internal */ export const HASH_BYTES_LENGTH = 32 +/** @internal */ export const ALGORAND_ADDRESS_BYTE_LENGTH = 36 +/** @internal */ export const ALGORAND_CHECKSUM_BYTE_LENGTH = 4 +/** @internal */ export const ALGORAND_ADDRESS_LENGTH = 58 +/** @internal */ export const PROGRAM_TAG = 'Program' +/** @internal */ export const TRANSACTION_GROUP_MAX_SIZE = 16 +/** @internal */ export enum OnApplicationComplete { NoOpOC = 0, OptInOC = 1, @@ -68,6 +106,7 @@ export enum OnApplicationComplete { DeleteApplicationOC = 5, } +/** @internal */ export const ConventionalRouting = { methodNames: { closeOutOfApplication: 'closeOutOfApplication', diff --git a/src/context-helpers/context-manager.ts b/src/context-helpers/context-manager.ts index daa02de7..9b81057a 100644 --- a/src/context-helpers/context-manager.ts +++ b/src/context-helpers/context-manager.ts @@ -1,5 +1,6 @@ import type { TestExecutionContext } from '../test-execution-context' +/** @internal */ export class ContextManager { private static _instance: TestExecutionContext | undefined diff --git a/src/context-helpers/context-util.ts b/src/context-helpers/context-util.ts index b78836b9..57e36b74 100644 --- a/src/context-helpers/context-util.ts +++ b/src/context-helpers/context-util.ts @@ -5,6 +5,7 @@ import { AssertError } from '../errors' import { ApplicationCallTransaction } from '../impl/transactions' import { lazyContext } from './internal-context' +/** @internal */ export const checkRoutingConditions = (appId: uint64, metadata: AbiMetadata) => { const appData = lazyContext.getApplicationData(appId) const isCreating = appData.isCreating diff --git a/src/context-helpers/internal-context.ts b/src/context-helpers/internal-context.ts index b3df401b..27773046 100644 --- a/src/context-helpers/internal-context.ts +++ b/src/context-helpers/internal-context.ts @@ -65,7 +65,7 @@ class InternalContext { } getApplicationData(id: StubUint64Compat | BaseContract): ApplicationData { - const uint64Id = id instanceof BaseContract ? this.ledger.getApplicationForContract(id).id : Uint64Cls.fromCompat(id) + const uint64Id = id instanceof BaseContract ? this.ledger.getApplicationForContract(id).id : Uint64Cls.fromCompat(id).asAlgoTs() const data = this.ledger.applicationDataMap.get(uint64Id) if (!data) { throw new InternalError('Unknown application, check correct testing context is active') @@ -82,4 +82,5 @@ class InternalContext { } } +/** @internal */ export const lazyContext = new InternalContext() diff --git a/src/decode-logs.ts b/src/decode-logs.ts index 6bd0f9ba..8ab82ec5 100644 --- a/src/decode-logs.ts +++ b/src/decode-logs.ts @@ -4,13 +4,13 @@ import { btoi } from './impl/pure' import { asNumber } from './util' export type LogDecoding = 'i' | 's' | 'b' - export type DecodedLog = T extends 'i' ? bigint : T extends 's' ? string : Uint8Array export type DecodedLogs = { [Index in keyof T]: DecodedLog } & { length: T['length'] } const ABI_RETURN_VALUE_LOG_PREFIX_LENGTH = asNumber(ABI_RETURN_VALUE_LOG_PREFIX.length) +/** @internal */ export function decodeLogs(logs: bytes[], decoding: T): DecodedLogs { return logs.map((log, i) => { const value = log.slice(0, ABI_RETURN_VALUE_LOG_PREFIX_LENGTH).equals(ABI_RETURN_VALUE_LOG_PREFIX) diff --git a/src/impl/acct-params.ts b/src/impl/acct-params.ts index fae3b15f..3cbfb541 100644 --- a/src/impl/acct-params.ts +++ b/src/impl/acct-params.ts @@ -5,6 +5,7 @@ import { getApp } from './app-params' import { Global } from './global' import type { StubUint64Compat } from './primitives' +/** @internal */ export const getAccount = (acct: Account | StubUint64Compat): Account => { const acctId = asMaybeUint64Cls(acct) if (acctId !== undefined) { @@ -14,16 +15,19 @@ export const getAccount = (acct: Account | StubUint64Compat): Account => { return acct as Account } +/** @internal */ export const balance = (a: Account | StubUint64Compat): uint64 => { const acct = getAccount(a) return acct.balance } +/** @internal */ export const minBalance = (a: Account | StubUint64Compat): uint64 => { const acct = getAccount(a) return acct.minBalance } +/** @internal */ export const appOptedIn = (a: Account | StubUint64Compat, b: Application | StubUint64Compat): boolean => { const account = getAccount(a) const app = getApp(b) @@ -34,6 +38,7 @@ export const appOptedIn = (a: Account | StubUint64Compat, b: Application | StubU return account.isOptedIn(app) } +/** @internal */ export const AcctParams: typeof op.AcctParams = { acctBalance(a: Account | StubUint64Compat): readonly [uint64, boolean] { const acct = getAccount(a) diff --git a/src/impl/app-global.ts b/src/impl/app-global.ts index fb75523b..67ec19f3 100644 --- a/src/impl/app-global.ts +++ b/src/impl/app-global.ts @@ -1,13 +1,14 @@ -import type { Application, bytes, op, uint64 } from '@algorandfoundation/algorand-typescript' +import type { Application, bytes, BytesCompat, op, uint64, Uint64Compat } from '@algorandfoundation/algorand-typescript' import { lazyContext } from '../context-helpers/internal-context' import { asBytes } from '../util' import { getApp } from './app-params' import { toBytes } from './encoded-types' import { Bytes, Uint64, type StubBytesCompat, type StubUint64Compat } from './primitives' +/** @internal */ export const AppGlobal: typeof op.AppGlobal = { delete(a: StubBytesCompat): void { - lazyContext.ledger.setGlobalState(lazyContext.activeApplication, a, undefined) + lazyContext.ledger.setGlobalState(lazyContext.activeApplication, asBytes(a), undefined) }, getBytes(a: StubBytesCompat): bytes { return this.getExBytes(0, asBytes(a))[0] @@ -20,7 +21,7 @@ export const AppGlobal: typeof op.AppGlobal = { if (app === undefined) { return [Bytes(), false] } - const [state, exists] = lazyContext.ledger.getGlobalState(app, b) + const [state, exists] = lazyContext.ledger.getGlobalState(app, asBytes(b)) if (!exists) { return [Bytes(), false] } @@ -31,13 +32,13 @@ export const AppGlobal: typeof op.AppGlobal = { if (app === undefined) { return [Uint64(0), false] } - const [state, exists] = lazyContext.ledger.getGlobalState(app, b) + const [state, exists] = lazyContext.ledger.getGlobalState(app, asBytes(b)) if (!exists) { return [Uint64(0), false] } return [state!.value as uint64, exists] }, put(a: StubBytesCompat, b: StubUint64Compat | StubBytesCompat): void { - lazyContext.ledger.setGlobalState(lazyContext.activeApplication, a, b) + lazyContext.ledger.setGlobalState(lazyContext.activeApplication, asBytes(a), b as unknown as Uint64Compat | BytesCompat) }, } diff --git a/src/impl/app-local.ts b/src/impl/app-local.ts index 610170e9..0f56b053 100644 --- a/src/impl/app-local.ts +++ b/src/impl/app-local.ts @@ -6,11 +6,12 @@ import { getApp } from './app-params' import { toBytes } from './encoded-types' import { Bytes, Uint64, type StubBytesCompat, type StubUint64Compat } from './primitives' +/** @internal */ export const AppLocal: typeof op.AppLocal = { delete: function (a: Account | StubUint64Compat, b: StubBytesCompat): void { const app = lazyContext.activeApplication const account = getAccount(a) - lazyContext.ledger.setLocalState(app, account, b, undefined) + lazyContext.ledger.setLocalState(app, account, asBytes(b), undefined) }, getBytes: function (a: Account | StubUint64Compat, b: StubBytesCompat): bytes { const account = getAccount(a) @@ -26,7 +27,7 @@ export const AppLocal: typeof op.AppLocal = { if (app === undefined || account === undefined) { return [Bytes(), false] } - const [state, exists] = lazyContext.ledger.getLocalState(app, account, c) + const [state, exists] = lazyContext.ledger.getLocalState(app, account, asBytes(c)) if (!exists) { return [Bytes(), false] } @@ -38,7 +39,7 @@ export const AppLocal: typeof op.AppLocal = { if (app === undefined || account === undefined) { return [Uint64(0), false] } - const [state, exists] = lazyContext.ledger.getLocalState(app, account, c) + const [state, exists] = lazyContext.ledger.getLocalState(app, account, asBytes(c)) if (!exists) { return [Uint64(0), false] } @@ -47,6 +48,6 @@ export const AppLocal: typeof op.AppLocal = { put: function (a: Account | StubUint64Compat, b: StubBytesCompat, c: uint64 | bytes): void { const app = lazyContext.activeApplication const account = getAccount(a) - lazyContext.ledger.setLocalState(app, account, b, c) + lazyContext.ledger.setLocalState(app, account, asBytes(b), c) }, } diff --git a/src/impl/app-params.ts b/src/impl/app-params.ts index 8989ee59..03d23508 100644 --- a/src/impl/app-params.ts +++ b/src/impl/app-params.ts @@ -20,6 +20,7 @@ const resolveAppIndex = (appIdOrIndex: StubUint64Compat): uint64 => { return txn.apps(input).id } +/** @internal */ export const getApp = (app: ApplicationType | StubUint64Compat): ApplicationType | undefined => { try { const appId = asMaybeUint64Cls(app) @@ -32,6 +33,7 @@ export const getApp = (app: ApplicationType | StubUint64Compat): ApplicationType } } +/** @internal */ export const AppParams: typeof op.AppParams = { appApprovalProgram(a: ApplicationType | StubUint64Compat): readonly [bytes, boolean] { const app = getApp(a) diff --git a/src/impl/asset-holding.ts b/src/impl/asset-holding.ts index 06256bfb..634cdd6e 100644 --- a/src/impl/asset-holding.ts +++ b/src/impl/asset-holding.ts @@ -20,6 +20,7 @@ const getAssetHolding = (acctOrIndex: Account | StubUint64Compat, assetOrIndex: return holding } +/** @internal */ export const AssetHolding: typeof op.AssetHolding = { assetBalance(a: Account | StubUint64Compat, b: Asset | StubUint64Compat): readonly [uint64, boolean] { const holding = getAssetHolding(a, b) diff --git a/src/impl/asset-params.ts b/src/impl/asset-params.ts index 27f0375c..0e2cffcf 100644 --- a/src/impl/asset-params.ts +++ b/src/impl/asset-params.ts @@ -13,6 +13,7 @@ const resolveAssetIndex = (assetIdOrIndex: StubUint64Compat): uint64 => { return txn.assets(input).id } +/** @internal */ export const getAsset = (asset: AssetType | StubUint64Compat): AssetType | undefined => { try { const assetId = asMaybeUint64Cls(asset) @@ -25,6 +26,7 @@ export const getAsset = (asset: AssetType | StubUint64Compat): AssetType | undef } } +/** @internal */ export const AssetParams: typeof op.AssetParams = { assetTotal(a: AssetType | StubUint64Compat): readonly [uint64, boolean] { const asset = getAsset(a) diff --git a/src/impl/base-32.ts b/src/impl/base-32.ts index 1d345e75..b98972d8 100644 --- a/src/impl/base-32.ts +++ b/src/impl/base-32.ts @@ -2,6 +2,7 @@ const BASE32_ALPHABET = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ234567'.split('') const CHAR_TO_NUM = BASE32_ALPHABET.reduce((acc, cur, index) => ((acc[cur] = index), acc), {} as Record) +/** @internal */ export const base32ToUint8Array = (value: string): Uint8Array => { let allChars = value .split('') @@ -29,6 +30,7 @@ export const base32ToUint8Array = (value: string): Uint8Array => { return new Uint8Array(bytes) } +/** @internal */ export const uint8ArrayToBase32 = (value: Uint8Array): string => { let allBytes = Array.from(value) let base32str = '' diff --git a/src/impl/base-contract.ts b/src/impl/base-contract.ts index b8429a3d..8ff2df71 100644 --- a/src/impl/base-contract.ts +++ b/src/impl/base-contract.ts @@ -1,6 +1,7 @@ import type { contract as contractType, uint64 } from '@algorandfoundation/algorand-typescript' import type { ConstructorFor } from '../typescript-helpers' +/** @internal */ export abstract class BaseContract { static isArc4 = false @@ -10,7 +11,9 @@ export abstract class BaseContract { } } +/** @internal */ export const ContractOptionsSymbol = Symbol('ContractOptions') +/** @internal */ export function contract(options: Parameters[0]) { return >(contract: T, ctx: ClassDecoratorContext) => { ctx.addInitializer(function () { diff --git a/src/impl/base.ts b/src/impl/base.ts index fcbf481e..6b620fce 100644 --- a/src/impl/base.ts +++ b/src/impl/base.ts @@ -4,16 +4,15 @@ import type { TypeInfo } from '../impl/encoded-types' import type { StubBytesCompat } from './primitives' import { BytesCls, Uint64 } from './primitives' +/** @internal */ export abstract class BytesBackedCls { #value: bytes - // #typeInfo: GenericTypeInfo | undefined get bytes() { return this.#value } constructor(value: bytes, _typeInfo?: TypeInfo) { this.#value = value - // this.#typeInfo = typeInfo } static fromBytes( @@ -25,6 +24,7 @@ export abstract class BytesBackedCls { } } +/** @internal */ export abstract class Uint64BackedCls { #value: uint64 diff --git a/src/impl/block.ts b/src/impl/block.ts index 284be871..06f2e549 100644 --- a/src/impl/block.ts +++ b/src/impl/block.ts @@ -1,7 +1,7 @@ import type { Account as AccountType, bytes, op, uint64 } from '@algorandfoundation/algorand-typescript' import { lazyContext } from '../context-helpers/internal-context' import { asUint64, getRandomBytes } from '../util' -import { Uint64, type StubUint64Compat } from './primitives' +import { Uint64 } from './primitives' import { Account } from './reference' export class BlockData { @@ -16,6 +16,7 @@ export class BlockData { txnCounter: uint64 proposerPayout: uint64 + /** @internal */ constructor() { this.seed = getRandomBytes(32).asAlgoTs().toFixed({ length: 32 }) this.timestamp = asUint64(Date.now()) @@ -30,11 +31,12 @@ export class BlockData { } } +/** @internal */ export const Block: typeof op.Block = { - blkSeed: function (a: StubUint64Compat): bytes<32> { + blkSeed: function (a: uint64): bytes<32> { return lazyContext.ledger.getBlockData(a).seed }, - blkTimestamp: function (a: StubUint64Compat): uint64 { + blkTimestamp: function (a: uint64): uint64 { return lazyContext.ledger.getBlockData(a).timestamp }, blkProposer: function (a: uint64): AccountType { diff --git a/src/impl/box.ts b/src/impl/box.ts index 440193e0..0120feea 100644 --- a/src/impl/box.ts +++ b/src/impl/box.ts @@ -6,6 +6,7 @@ import { asBytes, asBytesCls, asNumber, asUint8Array, conactUint8Arrays } from ' import { toBytes } from './encoded-types' import type { StubBytesCompat, StubUint64Compat } from './primitives' +/** @internal */ export const Box: typeof op.Box = { create(a: StubBytesCompat, b: StubUint64Compat): boolean { const name = asBytes(a) diff --git a/src/impl/c2c.ts b/src/impl/c2c.ts index 6a62e5ec..293b48a7 100644 --- a/src/impl/c2c.ts +++ b/src/impl/c2c.ts @@ -12,6 +12,7 @@ import { ApplicationCallInnerTxnContext } from './inner-transactions' import { methodSelector } from './method-selector' import type { ApplicationData } from './reference' +/** @internal */ export function compileArc4( contract: ConstructorFor, options?: CompileContractOptions, @@ -71,6 +72,7 @@ export function compileArc4( } as unknown as ContractProxy } +/** @internal */ export const invokeAbiCall = (itxnContext: ApplicationCallInnerTxnContext) => { lazyContext.value.notifyApplicationSpies(itxnContext) lazyContext.txn.activeGroup.addInnerTransactionGroup(...(itxnContext.itxns ?? []), itxnContext) @@ -85,6 +87,7 @@ const getCommonApplicationCallFields = (app: ApplicationData | undefined, option localNumBytes: options?.localBytes ?? app?.application.localNumBytes ?? lazyContext.any.uint64(), }) +/** @internal */ export function getApplicationCallInnerTxnContext( method: InstanceMethod, methodArgs: TypedApplicationCallFields, @@ -101,6 +104,7 @@ export function getApplicationCallInnerTxnContext( method: InstanceMethod, methodArgs: TypedApplicationCallFields, diff --git a/src/impl/clone.ts b/src/impl/clone.ts index 7a8a5529..c361ac2c 100644 --- a/src/impl/clone.ts +++ b/src/impl/clone.ts @@ -1,5 +1,6 @@ import { getEncoder, toBytes } from './encoded-types' +/** @internal */ export function clone(typeInfoString: string, value: T): T { if (value && typeof value === 'object' && 'copy' in value && typeof value.copy === 'function') { return value.copy() as T diff --git a/src/impl/compiled.ts b/src/impl/compiled.ts index 1d4ae2ec..b1e175a4 100644 --- a/src/impl/compiled.ts +++ b/src/impl/compiled.ts @@ -11,6 +11,7 @@ import { lazyContext } from '../context-helpers/internal-context' import type { ConstructorFor } from '../typescript-helpers' import type { ApplicationData } from './reference' +/** @internal */ export function compile( artefact: ConstructorFor | ConstructorFor, options?: CompileContractOptions | CompileLogicSigOptions, diff --git a/src/impl/contract.ts b/src/impl/contract.ts index e09ff717..9f317838 100644 --- a/src/impl/contract.ts +++ b/src/impl/contract.ts @@ -2,6 +2,7 @@ import type { arc4, OnCompleteActionStr } from '@algorandfoundation/algorand-typ import type { DeliberateAny } from '../typescript-helpers' import { BaseContract } from './base-contract' +/** @internal */ export class Contract extends BaseContract { static isArc4 = true @@ -10,7 +11,9 @@ export class Contract extends BaseContract { } } +/** @internal */ export const Arc4MethodConfigSymbol = Symbol('Arc4MethodConfig') +/** @internal */ export function abimethod(config?: arc4.AbiMethodConfig) { return function ( target: { [Arc4MethodConfigSymbol]: arc4.AbiMethodConfig } & ((this: TContract, ...args: TArgs) => TReturn), @@ -24,6 +27,7 @@ export function abimethod(config?: arc4.AbiMethodCon } } +/** @internal */ export function baremethod(config?: arc4.BareMethodConfig) { return function ( target: { [Arc4MethodConfigSymbol]: arc4.AbiMethodConfig } & ((this: TContract, ...args: TArgs) => TReturn), diff --git a/src/impl/crypto.ts b/src/impl/crypto.ts index fc7595d8..eb037ced 100644 --- a/src/impl/crypto.ts +++ b/src/impl/crypto.ts @@ -12,6 +12,7 @@ import { asBytes, asBytesCls, asUint8Array, conactUint8Arrays } from '../util' import type { StubBytesCompat, StubUint64Compat } from './primitives' import { Bytes, BytesCls, FixedBytes, Uint64Cls } from './primitives' +/** @internal */ export const sha256 = (a: StubBytesCompat): bytes<32> => { const bytesA = BytesCls.fromCompat(a) const hashArray = js_sha256.sha256.create().update(bytesA.asUint8Array()).digest() @@ -19,6 +20,7 @@ export const sha256 = (a: StubBytesCompat): bytes<32> => { return hashBytes } +/** @internal */ export const sha3_256 = (a: StubBytesCompat): bytes<32> => { const bytesA = BytesCls.fromCompat(a) const hashArray = js_sha3.sha3_256.create().update(bytesA.asUint8Array()).digest() @@ -26,6 +28,7 @@ export const sha3_256 = (a: StubBytesCompat): bytes<32> => { return hashBytes } +/** @internal */ export const keccak256 = (a: StubBytesCompat): bytes<32> => { const bytesA = BytesCls.fromCompat(a) const hashArray = js_sha3.keccak256.create().update(bytesA.asUint8Array()).digest() @@ -33,6 +36,7 @@ export const keccak256 = (a: StubBytesCompat): bytes<32> => { return hashBytes } +/** @internal */ export const sha512_256 = (a: StubBytesCompat): bytes<32> => { const bytesA = BytesCls.fromCompat(a) const hashArray = js_sha512.sha512_256.create().update(bytesA.asUint8Array()).digest() @@ -40,6 +44,7 @@ export const sha512_256 = (a: StubBytesCompat): bytes<32> => { return hashBytes } +/** @internal */ export const ed25519verifyBare = (a: StubBytesCompat, b: StubBytesCompat, c: StubBytesCompat): boolean => { const bytesA = BytesCls.fromCompat(a) const bytesB = BytesCls.fromCompat(b) @@ -47,6 +52,7 @@ export const ed25519verifyBare = (a: StubBytesCompat, b: StubBytesCompat, c: Stu return nacl.sign.detached.verify(bytesA.asUint8Array(), bytesB.asUint8Array(), bytesC.asUint8Array()) } +/** @internal */ export const ed25519verify = (a: StubBytesCompat, b: StubBytesCompat, c: StubBytesCompat): boolean => { const txn = lazyContext.activeGroup.activeTransaction as gtxn.ApplicationCallTxn const programBytes = asBytesCls(txn.onCompletion == OnCompleteAction.ClearState ? txn.clearStateProgram : txn.approvalProgram) @@ -59,6 +65,7 @@ export const ed25519verify = (a: StubBytesCompat, b: StubBytesCompat, c: StubByt return ed25519verifyBare(data, b, c) } +/** @internal */ export const ecdsaVerify = ( v: Ecdsa, a: StubBytesCompat, @@ -82,6 +89,7 @@ export const ecdsaVerify = ( return keyPair.verify(dataBytes.asUint8Array(), { r: sigRBytes.asUint8Array(), s: sigSBytes.asUint8Array() }) } +/** @internal */ export const ecdsaPkRecover = ( v: Ecdsa, a: StubBytesCompat, @@ -109,6 +117,7 @@ export const ecdsaPkRecover = ( return [FixedBytes(32, x), FixedBytes(32, y)] } +/** @internal */ export const ecdsaPkDecompress = (v: Ecdsa, a: StubBytesCompat): readonly [bytes<32>, bytes<32>] => { const bytesA = BytesCls.fromCompat(a) @@ -121,16 +130,19 @@ export const ecdsaPkDecompress = (v: Ecdsa, a: StubBytesCompat): readonly [bytes return [FixedBytes(32, new Uint8Array(x)), FixedBytes(32, new Uint8Array(y))] } +/** @internal */ export const vrfVerify = (_s: VrfVerify, _a: StubBytesCompat, _b: StubBytesCompat, _c: StubBytesCompat): readonly [bytes<64>, boolean] => { throw new NotImplementedError('vrfVerify') } +/** @internal */ export const EllipticCurve = new Proxy({} as typeof op.EllipticCurve, { get: (_target, prop) => { throw new NotImplementedError(`EllipticCurve.${prop.toString()}`) }, }) +/** @internal */ export const mimc = (_c: MimcConfigurations, _a: StubBytesCompat): bytes => { throw new NotImplementedError('mimc') } diff --git a/src/impl/emit.ts b/src/impl/emit.ts index 5c6930e8..c6a4d3eb 100644 --- a/src/impl/emit.ts +++ b/src/impl/emit.ts @@ -4,6 +4,7 @@ import { sha512_256 } from './crypto' import { getArc4Encoded, getArc4TypeName } from './encoded-types' import { log } from './log' +/** @internal */ export function emit(typeInfoString: string, event: T | string, ...eventProps: unknown[]) { let eventData let eventName diff --git a/src/impl/encoded-types/array-proxy.ts b/src/impl/encoded-types/array-proxy.ts index 4bcc7d03..1e861b53 100644 --- a/src/impl/encoded-types/array-proxy.ts +++ b/src/impl/encoded-types/array-proxy.ts @@ -2,6 +2,7 @@ import type { Uint64Compat } from '@algorandfoundation/algorand-typescript' import { AvmError } from '../../errors' import { arrayUtil } from '../primitives' +/** @internal */ export const arrayProxyHandler = () => ({ get(target: { items: readonly TItem[] }, prop: PropertyKey) { const idx = prop ? parseInt(prop.toString(), 10) : NaN diff --git a/src/impl/encoded-types/constants.ts b/src/impl/encoded-types/constants.ts index f1ef34be..09c497f9 100644 --- a/src/impl/encoded-types/constants.ts +++ b/src/impl/encoded-types/constants.ts @@ -1,4 +1,8 @@ +/** @internal */ export const ABI_LENGTH_SIZE = 2 +/** @internal */ export const TRUE_BIGINT_VALUE = 128n +/** @internal */ export const FALSE_BIGINT_VALUE = 0n +/** @internal */ export const IS_INITIALISING_FROM_BYTES_SYMBOL = Symbol('IsInitialisingFromBytes') diff --git a/src/impl/encoded-types/encoded-types.ts b/src/impl/encoded-types/encoded-types.ts index c862377f..9de0aff1 100644 --- a/src/impl/encoded-types/encoded-types.ts +++ b/src/impl/encoded-types/encoded-types.ts @@ -79,6 +79,7 @@ import type { } from './types' import { getMaxLengthOfStaticContentType } from './utils' +/** @internal */ export class Uint extends _Uint { private value: Uint8Array private bitSize: N @@ -130,6 +131,7 @@ export class Uint extends _Uint { } } +/** @internal */ export class UFixed extends _UFixed { private value: Uint8Array private bitSize: N @@ -190,6 +192,7 @@ export class UFixed extends _UFixed { } } +/** @internal */ export class Byte extends _Byte { typeInfo: TypeInfo private value: Uint<8> @@ -227,6 +230,7 @@ export class Byte extends _Byte { } } +/** @internal */ export class Str extends _Str { typeInfo: TypeInfo private value: Uint8Array @@ -265,6 +269,7 @@ export class Str extends _Str { } } +/** @internal */ export class Bool extends _Bool { private value: Uint8Array typeInfo: TypeInfo @@ -302,6 +307,7 @@ export class Bool extends _Bool { } } +/** @internal */ export class StaticArray extends _StaticArray { private value?: NTuple private uint8ArrayValue?: Uint8Array @@ -413,6 +419,7 @@ export class StaticArray ext } } +/** @internal */ export class Address extends _Address { typeInfo: TypeInfo private value: StaticArray @@ -471,6 +478,7 @@ export class Address extends _Address { } } +/** @internal */ export class DynamicArray extends _DynamicArray { private value?: TItem[] private uint8ArrayValue?: Uint8Array @@ -576,6 +584,7 @@ export class DynamicArray extends _DynamicArray extends _Tuple { private value?: TTuple private uint8ArrayValue?: Uint8Array @@ -657,6 +666,7 @@ export class Tuple extends _Tu } } +/** @internal */ export class Struct extends (_Struct as DeliberateAny) { private uint8ArrayValue?: Uint8Array genericArgs: Record @@ -738,6 +748,7 @@ export class Struct extends (_Struct @@ -799,6 +810,7 @@ export class DynamicBytes extends _DynamicBytes { } } +/** @internal */ export class StaticBytes extends _StaticBytes { private value: StaticArray typeInfo: TypeInfo @@ -860,6 +872,7 @@ export class StaticBytes extends _StaticBytes extends _ReferenceArray { private _values: TItem[] typeInfo: TypeInfo @@ -914,6 +927,7 @@ export class ReferenceArray extends _ReferenceArray { } } +/** @internal */ export class FixedArray extends _FixedArray { private _values: NTuple private size: number @@ -1107,11 +1121,13 @@ const isDynamicLengthType = (value: _ARC4Encoded) => { ) } +/** @internal */ export function encodeArc4(sourceTypeInfoString: string | undefined, source: T): bytes { const arc4Encoded = getArc4Encoded(source, sourceTypeInfoString) return arc4Encoded.bytes } +/** @internal */ export function decodeArc4( sourceTypeInfoString: string, targetTypeInfoString: string, @@ -1125,6 +1141,7 @@ export function decodeArc4( return getNativeValue(source, targetTypeInfo) as T } +/** @internal */ export function interpretAsArc4( typeInfoString: string, bytes: StubBytesCompat, @@ -1134,6 +1151,7 @@ export function interpretAsArc4( return getEncoder(typeInfo)(bytes, typeInfo, prefix) } +/** @internal */ export const getArc4Encoded = (value: DeliberateAny, sourceTypeInfoString?: string): _ARC4Encoded => { if (value instanceof _ARC4Encoded) { return value @@ -1230,6 +1248,7 @@ export const getArc4Encoded = (value: DeliberateAny, sourceTypeInfoString?: stri throw new CodeError(`Unsupported type for encoding: ${typeof value}`) } +/** @internal */ export const toBytes = (val: unknown, sourceTypeInfoString?: string): bytes => { const uint64Val = asMaybeUint64Cls(val, false) if (uint64Val !== undefined) { @@ -1255,6 +1274,7 @@ export const toBytes = (val: unknown, sourceTypeInfoString?: string): bytes => { throw new InternalError(`Invalid type for bytes: ${nameOfType(val)}`) } +/** @internal */ export const getEncoder = (typeInfo: TypeInfo): fromBytes => { const mutableTupleFromBytes = (value: StubBytesCompat | Uint8Array, typeInfo: string | TypeInfo, prefix: 'none' | 'log' = 'none') => { const tuple = Tuple.fromBytes(value, typeInfo, prefix) diff --git a/src/impl/encoded-types/helpers.ts b/src/impl/encoded-types/helpers.ts index bb0bad2d..1c1cc225 100644 --- a/src/impl/encoded-types/helpers.ts +++ b/src/impl/encoded-types/helpers.ts @@ -14,22 +14,31 @@ import { asBytes, asUint8Array } from '../../util' import { BigUint, Uint64 } from '../primitives' import type { fromBytes } from './types' +/** @internal */ export const validBitSizes = [...Array(64).keys()].map((x) => (x + 1) * 8) +/** @internal */ export const maxBigIntValue = (bitSize: number) => 2n ** BigInt(bitSize) - 1n +/** @internal */ export const maxBytesLength = (bitSize: number) => Math.floor(bitSize / BITS_IN_BYTE) +/** @internal */ export const regExpNxM = (maxPrecision: number) => new RegExp(`^\\d*\\.?\\d{0,${maxPrecision}}$`) +/** @internal */ export const trimTrailingDecimalZeros = (v: string) => v.replace(/(\d+\.\d*?)0+$/, '$1').replace(/\.$/, '') +/** @internal */ export const trimGenericTypeName = (typeName: string): string => typeName.replace(/<.*>/, '') +/** @internal */ export const areAllARC4Encoded = (items: T[]): items is T[] => items.every((item) => item instanceof ARC4Encoded) +/** @internal */ export const checkItemTypeName = (type: TypeInfo, value: ARC4Encoded) => { const typeName = trimGenericTypeName(type.name) const validTypeNames = [typeName] assert(validTypeNames.includes(value.constructor.name), `item must be of type ${typeName}, not ${value.constructor.name}`) } +/** @internal */ export const findBoolTypes = (values: TypeInfo[], index: number, delta: number): number => { // Helper function to find consecutive booleans from current index in a tuple. let until = 0 @@ -50,6 +59,7 @@ export const findBoolTypes = (values: TypeInfo[], index: number, delta: number): return until } +/** @internal */ export const getNativeValue = (value: DeliberateAny, targetTypeInfo: TypeInfo | undefined): DeliberateAny => { if (value.typeInfo && value.typeInfo.name === targetTypeInfo?.name) { return value @@ -79,16 +89,19 @@ export const getNativeValue = (value: DeliberateAny, targetTypeInfo: TypeInfo | return native } +/** @internal */ export const readLength = (value: Uint8Array): readonly [number, Uint8Array] => { const length = Number(encodingUtil.uint8ArrayToBigInt(value.slice(0, ABI_LENGTH_SIZE))) const data = value.slice(ABI_LENGTH_SIZE) return [length, data] } +/** @internal */ export const encodeLength = (length: number): BytesCls => { return asBytesCls(encodingUtil.bigIntToUint8Array(BigInt(length), ABI_LENGTH_SIZE)) } +/** @internal */ export const findBool = (values: ARC4Encoded[], index: number, delta: number) => { let until = 0 const length = values.length @@ -108,6 +121,7 @@ export const findBool = (values: ARC4Encoded[], index: number, delta: number) => return until } +/** @internal */ export const compressMultipleBool = (values: Bool[]): number => { let result = 0 if (values.length > 8) { @@ -122,6 +136,7 @@ export const compressMultipleBool = (values: Bool[]): number => { return result } +/** @internal */ export const holdsDynamicLengthContent = (value: TypeInfo): boolean => { const itemTypeName = trimGenericTypeName(value.name) @@ -146,30 +161,37 @@ export const holdsDynamicLengthContent = (value: TypeInfo): boolean => { ) } +/** @internal */ export const booleanFromBytes: fromBytes = (val) => { return encodingUtil.uint8ArrayToBigInt(asUint8Array(val)) > 0n } +/** @internal */ export const bigUintFromBytes: fromBytes = (val) => { return BigUint(encodingUtil.uint8ArrayToBigInt(asUint8Array(val))) } +/** @internal */ export const bytesFromBytes: fromBytes = (val) => { return asBytes(val) } +/** @internal */ export const stringFromBytes: fromBytes = (val) => { return asBytes(val).toString() } +/** @internal */ export const uint64FromBytes: fromBytes = (val) => { return Uint64(encodingUtil.uint8ArrayToBigInt(asUint8Array(val))) } +/** @internal */ export const onCompletionFromBytes: fromBytes = (val) => { return Uint64(encodingUtil.uint8ArrayToBigInt(asUint8Array(val))) as OnCompleteAction } +/** @internal */ export const transactionTypeFromBytes: fromBytes = (val) => { return Uint64(encodingUtil.uint8ArrayToBigInt(asUint8Array(val))) as TransactionType } diff --git a/src/impl/encoded-types/index.ts b/src/impl/encoded-types/index.ts index bd7968d5..f25a93da 100644 --- a/src/impl/encoded-types/index.ts +++ b/src/impl/encoded-types/index.ts @@ -1,3 +1,4 @@ +/** @internal */ export { Address, Bool, @@ -20,5 +21,7 @@ export { UFixed, Uint, } from './encoded-types' +/** @internal */ export { TypeInfo } from './types' +/** @internal */ export { arc4EncodedLength, getArc4TypeName, getMaxLengthOfStaticContentType, minLengthForType } from './utils' diff --git a/src/impl/encoded-types/types.ts b/src/impl/encoded-types/types.ts index bee383b5..ca96b0ae 100644 --- a/src/impl/encoded-types/types.ts +++ b/src/impl/encoded-types/types.ts @@ -3,14 +3,21 @@ import type { ARC4Encoded, BitSize } from '@algorandfoundation/algorand-typescri import type { StubBytesCompat } from '../primitives' +/** @internal */ export type CompatForArc4Int = N extends 8 | 16 | 24 | 32 | 40 | 48 | 56 | 64 ? Uint64Compat : BigUintCompat +/** @internal */ export type uFixedGenericArgs = { n: TypeInfo; m: TypeInfo } +/** @internal */ export type StaticArrayGenericArgs = { elementType: TypeInfo; size: TypeInfo } +/** @internal */ export type DynamicArrayGenericArgs = { elementType: TypeInfo } +/** @internal */ export type StructConstraint = Record +/** @internal */ export type TypeInfo = { name: string genericArgs?: TypeInfo[] | Record } +/** @internal */ export type fromBytes = (val: Uint8Array | StubBytesCompat, typeInfo: TypeInfo, prefix?: 'none' | 'log') => T diff --git a/src/impl/encoded-types/utils.ts b/src/impl/encoded-types/utils.ts index a696dabe..2bc79fc5 100644 --- a/src/impl/encoded-types/utils.ts +++ b/src/impl/encoded-types/utils.ts @@ -5,6 +5,7 @@ import { CodeError } from '../../errors' import { findBoolTypes, trimGenericTypeName } from './helpers' import type { DynamicArrayGenericArgs, StaticArrayGenericArgs, TypeInfo, uFixedGenericArgs } from './types' +/** @internal */ export const getMaxLengthOfStaticContentType = (type: TypeInfo, asArc4Encoded: boolean = true): number => { const getMaxBytesLengthForStaticArray = (typeInfo: { genericArgs: StaticArrayGenericArgs }) => { const genericArgs = typeInfo.genericArgs @@ -83,6 +84,7 @@ export const getMaxLengthOfStaticContentType = (type: TypeInfo, asArc4Encoded: b } } +/** @internal */ export const getArc4TypeName = ( typeInfo: TypeInfo, resourceEncoding: ResourceEncodingOptions | undefined = undefined, @@ -144,11 +146,13 @@ export const getArc4TypeName = ( return undefined } +/** @internal */ export const arc4EncodedLength = (typeInfoString: string): uint64 => { const typeInfo = JSON.parse(typeInfoString) return getMaxLengthOfStaticContentType(typeInfo, true) } +/** @internal */ export const minLengthForType = (typeInfo: TypeInfo): number | undefined => { try { return getMaxLengthOfStaticContentType(typeInfo, false) diff --git a/src/impl/ensure-budget.ts b/src/impl/ensure-budget.ts index 9e37971c..ba1d964b 100644 --- a/src/impl/ensure-budget.ts +++ b/src/impl/ensure-budget.ts @@ -1,6 +1,7 @@ import type { uint64 } from '@algorandfoundation/algorand-typescript' import { OpUpFeeSource } from '@algorandfoundation/algorand-typescript' +/** @internal */ export function ensureBudget(_budget: uint64, _feeSource: OpUpFeeSource = OpUpFeeSource.GroupCredit) { // ensureBudget function is emulated to be a no-op } diff --git a/src/impl/global.ts b/src/impl/global.ts index fd1afe97..7ac1c928 100644 --- a/src/impl/global.ts +++ b/src/impl/global.ts @@ -35,6 +35,7 @@ export class GlobalData { payoutsPercent: uint64 payoutsMinBalance: uint64 + /** @internal */ constructor() { this.minTxnFee = Uint64(MIN_TXN_FEE) this.minBalance = Uint64(DEFAULT_ACCOUNT_MIN_BALANCE) @@ -57,6 +58,7 @@ const getGlobalData = (): GlobalData => { const getMissingValueErrorMessage = (name: keyof GlobalData) => `'Global' object has no value set for attribute named '${name}'. Use \`context.ledger.patchGlobalData({${name}: your_value})\` to set the value in your test setup."` +/** @internal */ export const Global: typeof op.Global = { /** * microalgos diff --git a/src/impl/gtxn.ts b/src/impl/gtxn.ts index 76cb7577..871e0385 100644 --- a/src/impl/gtxn.ts +++ b/src/impl/gtxn.ts @@ -3,218 +3,228 @@ import { lazyContext } from '../context-helpers/internal-context' import { asUint64, asUint64Cls } from '../util' import type { StubUint64Compat } from './primitives' +/** @internal */ export const GTxn: typeof op.GTxn = { sender(t: StubUint64Compat): Account { - return lazyContext.activeGroup.getTransaction(t).sender + return lazyContext.activeGroup.getTransaction(asUint64(t)).sender }, fee(t: StubUint64Compat): uint64 { - return lazyContext.activeGroup.getTransaction(t).fee + return lazyContext.activeGroup.getTransaction(asUint64(t)).fee }, firstValid(t: StubUint64Compat): uint64 { - return lazyContext.activeGroup.getTransaction(t).firstValid + return lazyContext.activeGroup.getTransaction(asUint64(t)).firstValid }, firstValidTime(t: StubUint64Compat): uint64 { - return lazyContext.activeGroup.getTransaction(t).firstValidTime + return lazyContext.activeGroup.getTransaction(asUint64(t)).firstValidTime }, lastValid(t: StubUint64Compat): uint64 { - return lazyContext.activeGroup.getTransaction(t).lastValid + return lazyContext.activeGroup.getTransaction(asUint64(t)).lastValid }, note(t: StubUint64Compat): bytes { - return lazyContext.activeGroup.getTransaction(t).note + return lazyContext.activeGroup.getTransaction(asUint64(t)).note }, lease(t: StubUint64Compat): bytes<32> { - return lazyContext.activeGroup.getTransaction(t).lease + return lazyContext.activeGroup.getTransaction(asUint64(t)).lease }, receiver(t: StubUint64Compat): Account { - return lazyContext.activeGroup.getPaymentTransaction(t).receiver + return lazyContext.activeGroup.getPaymentTransaction(asUint64(t)).receiver }, - amount(t: uint64): uint64 { - return lazyContext.activeGroup.getPaymentTransaction(t).amount + amount(t: StubUint64Compat): uint64 { + return lazyContext.activeGroup.getPaymentTransaction(asUint64(t)).amount }, closeRemainderTo(t: StubUint64Compat): Account { - return lazyContext.activeGroup.getPaymentTransaction(t).closeRemainderTo + return lazyContext.activeGroup.getPaymentTransaction(asUint64(t)).closeRemainderTo }, votePk(t: StubUint64Compat): bytes<32> { - return lazyContext.activeGroup.getKeyRegistrationTransaction(t).voteKey + return lazyContext.activeGroup.getKeyRegistrationTransaction(asUint64(t)).voteKey }, selectionPk(t: StubUint64Compat): bytes<32> { - return lazyContext.activeGroup.getKeyRegistrationTransaction(t).selectionKey + return lazyContext.activeGroup.getKeyRegistrationTransaction(asUint64(t)).selectionKey }, voteFirst(t: StubUint64Compat): uint64 { - return lazyContext.activeGroup.getKeyRegistrationTransaction(t).voteFirst + return lazyContext.activeGroup.getKeyRegistrationTransaction(asUint64(t)).voteFirst }, voteLast(t: StubUint64Compat): uint64 { - return lazyContext.activeGroup.getKeyRegistrationTransaction(t).voteLast + return lazyContext.activeGroup.getKeyRegistrationTransaction(asUint64(t)).voteLast }, voteKeyDilution(t: StubUint64Compat): uint64 { - return lazyContext.activeGroup.getKeyRegistrationTransaction(t).voteKeyDilution + return lazyContext.activeGroup.getKeyRegistrationTransaction(asUint64(t)).voteKeyDilution }, type(t: StubUint64Compat): bytes { - return asUint64Cls(lazyContext.activeGroup.getTransaction(t).type).toBytes().asAlgoTs() + return asUint64Cls(lazyContext.activeGroup.getTransaction(asUint64(t)).type) + .toBytes() + .asAlgoTs() }, - typeEnum(t: uint64): uint64 { - return asUint64(lazyContext.activeGroup.getTransaction(t).type) + typeEnum(t: StubUint64Compat): uint64 { + return asUint64(lazyContext.activeGroup.getTransaction(asUint64(t)).type) }, xferAsset(t: StubUint64Compat): Asset { - return lazyContext.activeGroup.getAssetTransferTransaction(t).xferAsset + return lazyContext.activeGroup.getAssetTransferTransaction(asUint64(t)).xferAsset }, assetAmount(t: StubUint64Compat): uint64 { - return lazyContext.activeGroup.getAssetTransferTransaction(t).assetAmount + return lazyContext.activeGroup.getAssetTransferTransaction(asUint64(t)).assetAmount }, assetSender(t: StubUint64Compat): Account { - return lazyContext.activeGroup.getAssetTransferTransaction(t).assetSender + return lazyContext.activeGroup.getAssetTransferTransaction(asUint64(t)).assetSender }, assetReceiver(t: StubUint64Compat): Account { - return lazyContext.activeGroup.getAssetTransferTransaction(t).assetReceiver + return lazyContext.activeGroup.getAssetTransferTransaction(asUint64(t)).assetReceiver }, assetCloseTo(t: StubUint64Compat): Account { - return lazyContext.activeGroup.getAssetTransferTransaction(t).assetCloseTo + return lazyContext.activeGroup.getAssetTransferTransaction(asUint64(t)).assetCloseTo }, groupIndex(t: StubUint64Compat): uint64 { - return lazyContext.activeGroup.getTransaction(t).groupIndex + return lazyContext.activeGroup.getTransaction(asUint64(t)).groupIndex }, txId(t: StubUint64Compat): bytes<32> { - return lazyContext.activeGroup.getTransaction(t).txnId + return lazyContext.activeGroup.getTransaction(asUint64(t)).txnId }, applicationId(t: StubUint64Compat): Application { - return lazyContext.activeGroup.getApplicationCallTransaction(t).appId + return lazyContext.activeGroup.getApplicationCallTransaction(asUint64(t)).appId }, onCompletion(t: StubUint64Compat): uint64 { - const onCompletion = lazyContext.activeGroup.getApplicationCallTransaction(t).onCompletion + const onCompletion = lazyContext.activeGroup.getApplicationCallTransaction(asUint64(t)).onCompletion return asUint64(onCompletion) }, applicationArgs(a: StubUint64Compat, b: StubUint64Compat): bytes { - return lazyContext.activeGroup.getApplicationCallTransaction(a).appArgs(asUint64(b)) + return lazyContext.activeGroup.getApplicationCallTransaction(asUint64(a)).appArgs(asUint64(b)) }, numAppArgs(t: StubUint64Compat): uint64 { - return lazyContext.activeGroup.getApplicationCallTransaction(t).numAppArgs + return lazyContext.activeGroup.getApplicationCallTransaction(asUint64(t)).numAppArgs }, accounts(a: StubUint64Compat, b: StubUint64Compat): Account { - return lazyContext.activeGroup.getApplicationCallTransaction(a).accounts(asUint64(b)) + return lazyContext.activeGroup.getApplicationCallTransaction(asUint64(a)).accounts(asUint64(b)) }, numAccounts(t: StubUint64Compat): uint64 { - return lazyContext.activeGroup.getApplicationCallTransaction(t).numAccounts + return lazyContext.activeGroup.getApplicationCallTransaction(asUint64(t)).numAccounts }, approvalProgram(t: StubUint64Compat): bytes { - return lazyContext.activeGroup.getApplicationCallTransaction(t).approvalProgram + return lazyContext.activeGroup.getApplicationCallTransaction(asUint64(t)).approvalProgram }, clearStateProgram(t: StubUint64Compat): bytes { - return lazyContext.activeGroup.getApplicationCallTransaction(t).clearStateProgram + return lazyContext.activeGroup.getApplicationCallTransaction(asUint64(t)).clearStateProgram }, rekeyTo(t: StubUint64Compat): Account { - return lazyContext.activeGroup.getTransaction(t).rekeyTo + return lazyContext.activeGroup.getTransaction(asUint64(t)).rekeyTo }, configAsset(t: StubUint64Compat): Asset { - return lazyContext.activeGroup.getAssetConfigTransaction(t).configAsset + return lazyContext.activeGroup.getAssetConfigTransaction(asUint64(t)).configAsset }, configAssetTotal(t: StubUint64Compat): uint64 { - return lazyContext.activeGroup.getAssetConfigTransaction(t).total + return lazyContext.activeGroup.getAssetConfigTransaction(asUint64(t)).total }, configAssetDecimals(t: StubUint64Compat): uint64 { - return lazyContext.activeGroup.getAssetConfigTransaction(t).decimals + return lazyContext.activeGroup.getAssetConfigTransaction(asUint64(t)).decimals }, configAssetDefaultFrozen(t: StubUint64Compat): boolean { - return lazyContext.activeGroup.getAssetConfigTransaction(t).defaultFrozen + return lazyContext.activeGroup.getAssetConfigTransaction(asUint64(t)).defaultFrozen }, configAssetUnitName(t: StubUint64Compat): bytes { - return lazyContext.activeGroup.getAssetConfigTransaction(t).unitName + return lazyContext.activeGroup.getAssetConfigTransaction(asUint64(t)).unitName }, configAssetName(t: StubUint64Compat): bytes { - return lazyContext.activeGroup.getAssetConfigTransaction(t).assetName + return lazyContext.activeGroup.getAssetConfigTransaction(asUint64(t)).assetName }, configAssetUrl(t: StubUint64Compat): bytes { - return lazyContext.activeGroup.getAssetConfigTransaction(t).url + return lazyContext.activeGroup.getAssetConfigTransaction(asUint64(t)).url }, configAssetMetadataHash(t: StubUint64Compat): bytes<32> { - return lazyContext.activeGroup.getAssetConfigTransaction(t).metadataHash + return lazyContext.activeGroup.getAssetConfigTransaction(asUint64(t)).metadataHash }, configAssetManager(t: StubUint64Compat): Account { - return lazyContext.activeGroup.getAssetConfigTransaction(t).manager + return lazyContext.activeGroup.getAssetConfigTransaction(asUint64(t)).manager }, configAssetReserve(t: StubUint64Compat): Account { - return lazyContext.activeGroup.getAssetConfigTransaction(t).reserve + return lazyContext.activeGroup.getAssetConfigTransaction(asUint64(t)).reserve }, configAssetFreeze(t: StubUint64Compat): Account { - return lazyContext.activeGroup.getAssetConfigTransaction(t).freeze + return lazyContext.activeGroup.getAssetConfigTransaction(asUint64(t)).freeze }, configAssetClawback(t: StubUint64Compat): Account { - return lazyContext.activeGroup.getAssetConfigTransaction(t).clawback + return lazyContext.activeGroup.getAssetConfigTransaction(asUint64(t)).clawback }, freezeAsset(t: StubUint64Compat): Asset { - return lazyContext.activeGroup.getAssetFreezeTransaction(t).freezeAsset + return lazyContext.activeGroup.getAssetFreezeTransaction(asUint64(t)).freezeAsset }, freezeAssetAccount(t: StubUint64Compat): Account { - return lazyContext.activeGroup.getAssetFreezeTransaction(t).freezeAccount + return lazyContext.activeGroup.getAssetFreezeTransaction(asUint64(t)).freezeAccount }, freezeAssetFrozen(t: StubUint64Compat): boolean { - return lazyContext.activeGroup.getAssetFreezeTransaction(t).frozen + return lazyContext.activeGroup.getAssetFreezeTransaction(asUint64(t)).frozen }, assets(a: StubUint64Compat, b: StubUint64Compat): Asset { - return lazyContext.activeGroup.getApplicationCallTransaction(a).assets(asUint64(b)) + return lazyContext.activeGroup.getApplicationCallTransaction(asUint64(a)).assets(asUint64(b)) }, numAssets(t: StubUint64Compat): uint64 { - return lazyContext.activeGroup.getApplicationCallTransaction(t).numAssets + return lazyContext.activeGroup.getApplicationCallTransaction(asUint64(t)).numAssets }, applications(a: StubUint64Compat, b: StubUint64Compat): Application { - return lazyContext.activeGroup.getApplicationCallTransaction(a).apps(asUint64(b)) + return lazyContext.activeGroup.getApplicationCallTransaction(asUint64(a)).apps(asUint64(b)) }, numApplications(t: StubUint64Compat): uint64 { - return lazyContext.activeGroup.getApplicationCallTransaction(t).numApps + return lazyContext.activeGroup.getApplicationCallTransaction(asUint64(t)).numApps }, globalNumUint(t: StubUint64Compat): uint64 { - return lazyContext.activeGroup.getApplicationCallTransaction(t).globalNumUint + return lazyContext.activeGroup.getApplicationCallTransaction(asUint64(t)).globalNumUint }, globalNumByteSlice(t: StubUint64Compat): uint64 { - return lazyContext.activeGroup.getApplicationCallTransaction(t).globalNumBytes + return lazyContext.activeGroup.getApplicationCallTransaction(asUint64(t)).globalNumBytes }, localNumUint(t: StubUint64Compat): uint64 { - return lazyContext.activeGroup.getApplicationCallTransaction(t).localNumUint + return lazyContext.activeGroup.getApplicationCallTransaction(asUint64(t)).localNumUint }, localNumByteSlice(t: StubUint64Compat): uint64 { - return lazyContext.activeGroup.getApplicationCallTransaction(t).localNumBytes + return lazyContext.activeGroup.getApplicationCallTransaction(asUint64(t)).localNumBytes }, extraProgramPages(t: StubUint64Compat): uint64 { - return lazyContext.activeGroup.getApplicationCallTransaction(t).extraProgramPages + return lazyContext.activeGroup.getApplicationCallTransaction(asUint64(t)).extraProgramPages }, nonparticipation(t: StubUint64Compat): boolean { - return lazyContext.activeGroup.getKeyRegistrationTransaction(t).nonparticipation + return lazyContext.activeGroup.getKeyRegistrationTransaction(asUint64(t)).nonparticipation }, logs(a: StubUint64Compat, b: StubUint64Compat): bytes { - return lazyContext.activeGroup.getApplicationCallTransaction(a).logs(asUint64(b)) + return lazyContext.activeGroup.getApplicationCallTransaction(asUint64(a)).logs(asUint64(b)) }, numLogs(t: StubUint64Compat): uint64 { - return lazyContext.activeGroup.getApplicationCallTransaction(t).numLogs + return lazyContext.activeGroup.getApplicationCallTransaction(asUint64(t)).numLogs }, createdAssetId(t: StubUint64Compat): Asset { - return lazyContext.activeGroup.getAssetConfigTransaction(t).createdAsset + return lazyContext.activeGroup.getAssetConfigTransaction(asUint64(t)).createdAsset }, createdApplicationId(t: StubUint64Compat): Application { - return lazyContext.activeGroup.getApplicationCallTransaction(t).createdApp + return lazyContext.activeGroup.getApplicationCallTransaction(asUint64(t)).createdApp }, lastLog(t: StubUint64Compat): bytes { - return lazyContext.activeGroup.getApplicationCallTransaction(t).lastLog + return lazyContext.activeGroup.getApplicationCallTransaction(asUint64(t)).lastLog }, stateProofPk(t: StubUint64Compat): bytes { - return lazyContext.activeGroup.getKeyRegistrationTransaction(t).stateProofKey + return lazyContext.activeGroup.getKeyRegistrationTransaction(asUint64(t)).stateProofKey }, approvalProgramPages(a: StubUint64Compat, b: StubUint64Compat): bytes { - return lazyContext.activeGroup.getApplicationCallTransaction(a).approvalProgramPages(asUint64(b)) + return lazyContext.activeGroup.getApplicationCallTransaction(asUint64(a)).approvalProgramPages(asUint64(b)) }, numApprovalProgramPages(t: StubUint64Compat): uint64 { - return lazyContext.activeGroup.getApplicationCallTransaction(t).numApprovalProgramPages + return lazyContext.activeGroup.getApplicationCallTransaction(asUint64(t)).numApprovalProgramPages }, clearStateProgramPages(a: StubUint64Compat, b: StubUint64Compat): bytes { - return lazyContext.activeGroup.getApplicationCallTransaction(a).clearStateProgramPages(asUint64(b)) + return lazyContext.activeGroup.getApplicationCallTransaction(asUint64(a)).clearStateProgramPages(asUint64(b)) }, numClearStateProgramPages(t: StubUint64Compat): uint64 { - return lazyContext.activeGroup.getApplicationCallTransaction(t).numClearStateProgramPages + return lazyContext.activeGroup.getApplicationCallTransaction(asUint64(t)).numClearStateProgramPages }, } +/** @internal */ export const Transaction = (index: uint64) => lazyContext.txn.activeGroup.getTransaction(index) +/** @internal */ export const PaymentTxn = (index: uint64) => lazyContext.txn.activeGroup.getPaymentTransaction(index) +/** @internal */ export const KeyRegistrationTxn = (index: uint64) => lazyContext.txn.activeGroup.getKeyRegistrationTransaction(index) +/** @internal */ export const AssetConfigTxn = (index: uint64) => lazyContext.txn.activeGroup.getAssetConfigTransaction(index) +/** @internal */ export const AssetTransferTxn = (index: uint64) => lazyContext.txn.activeGroup.getAssetTransferTransaction(index) +/** @internal */ export const AssetFreezeTxn = (index: uint64) => lazyContext.txn.activeGroup.getAssetFreezeTransaction(index) +/** @internal */ export const ApplicationCallTxn = (index: uint64) => lazyContext.txn.activeGroup.getApplicationCallTransaction(index) diff --git a/src/impl/index.ts b/src/impl/index.ts index a90600c6..ab0109bc 100644 --- a/src/impl/index.ts +++ b/src/impl/index.ts @@ -1,18 +1,36 @@ +/** @internal */ export { AcctParams, appOptedIn, balance, minBalance } from './acct-params' +/** @internal */ export { AppGlobal } from './app-global' +/** @internal */ export { AppLocal } from './app-local' +/** @internal */ export { AppParams } from './app-params' +/** @internal */ export { AssetHolding } from './asset-holding' +/** @internal */ export { AssetParams } from './asset-params' +/** @internal */ export { Block } from './block' +/** @internal */ export { Box } from './box' +/** @internal */ export * from './crypto' +/** @internal */ export { Global } from './global' +/** @internal */ export { GTxn } from './gtxn' +/** @internal */ export { GITxn, ITxn, ITxnCreate } from './itxn' +/** @internal */ export { arg } from './logicSigArg' +/** @internal */ export { onlineStake } from './online-stake' +/** @internal */ export * from './pure' +/** @internal */ export { gloadBytes, gloadUint64, Scratch } from './scratch' +/** @internal */ export { gaid, Txn } from './txn' +/** @internal */ export { VoterParams } from './voter-params' diff --git a/src/impl/inner-transactions.ts b/src/impl/inner-transactions.ts index 6a0f705c..4a53da2e 100644 --- a/src/impl/inner-transactions.ts +++ b/src/impl/inner-transactions.ts @@ -3,6 +3,7 @@ import type { Application as ApplicationType, Asset as AssetType, bytes, + BytesCompat, itxn, } from '@algorandfoundation/algorand-typescript' import { TransactionType } from '@algorandfoundation/algorand-typescript' @@ -21,7 +22,6 @@ import { getApp } from './app-params' import { getAsset } from './asset-params' import { encodeArc4 } from './encoded-types' import type { InnerTxn, InnerTxnFields } from './itxn' -import type { StubBytesCompat } from './primitives' import { Uint64Cls } from './primitives' import { Account, asAccount, asApplication, asAsset } from './reference' import type { Transaction } from './transactions' @@ -46,6 +46,7 @@ const mapCommonFields = ( ...rest, } } + export class PaymentInnerTxn extends PaymentTransaction implements itxn.PaymentInnerTxn { readonly isItxn?: true @@ -166,6 +167,7 @@ export class AssetFreezeInnerTxn extends AssetFreezeTransaction implements itxn. } } +/** @internal */ export type ApplicationCallFields = itxn.ApplicationCallFields & { createdApp?: ApplicationType appLogs?: Array @@ -200,6 +202,7 @@ export class ApplicationCallInnerTxn extends ApplicationCallTransaction implemen } } +/** @internal */ export const createInnerTxn = (fields: TFields) => { switch (fields.type) { case TransactionType.Payment: @@ -219,28 +222,36 @@ export const createInnerTxn = (fields: TFields) } } +/** @internal */ export function submitGroup(...transactionFields: TFields): itxn.TxnFor { return transactionFields.map((f: (typeof transactionFields)[number]) => f.submit()) as itxn.TxnFor } +/** @internal */ export function payment(fields: itxn.PaymentFields): itxn.PaymentItxnParams { return new ItxnParams(fields, TransactionType.Payment) } +/** @internal */ export function keyRegistration(fields: itxn.KeyRegistrationFields): itxn.KeyRegistrationItxnParams { return new ItxnParams(fields, TransactionType.KeyRegistration) } +/** @internal */ export function assetConfig(fields: itxn.AssetConfigFields): itxn.AssetConfigItxnParams { return new ItxnParams(fields, TransactionType.AssetConfig) } +/** @internal */ export function assetTransfer(fields: itxn.AssetTransferFields): itxn.AssetTransferItxnParams { return new ItxnParams(fields, TransactionType.AssetTransfer) } +/** @internal */ export function assetFreeze(fields: itxn.AssetFreezeFields): itxn.AssetFreezeItxnParams { return new ItxnParams(fields, TransactionType.AssetFreeze) } +/** @internal */ export function applicationCall(fields: itxn.ApplicationCallFields): itxn.ApplicationCallItxnParams { return new ItxnParams(fields, TransactionType.ApplicationCall) } +/** @internal */ export class ItxnParams { #fields: TFields & { type: TransactionType } constructor(fields: TFields, type: TransactionType) { @@ -332,7 +343,7 @@ export class ApplicationCallInnerTxnContext extends Applicati return this.#returnValue === UNSET ? (undefined as TReturn) : this.#returnValue } - override appendLog(value: StubBytesCompat) { + override appendLog(value: BytesCompat) { /* As per https://github.com/algorandfoundation/ARCs/blob/main/ARCs/arc-0004.md#implementing-a-method If the method is non-void, the Application MUST encode the return value as described in the Encoding section and then log it with the diff --git a/src/impl/itxn-compose.ts b/src/impl/itxn-compose.ts index 18f238a3..3d439464 100644 --- a/src/impl/itxn-compose.ts +++ b/src/impl/itxn-compose.ts @@ -67,4 +67,5 @@ class ItxnCompose { } } +/** @internal */ export const itxnCompose: _ItxnCompose = new ItxnCompose() diff --git a/src/impl/itxn.ts b/src/impl/itxn.ts index a99de88d..281e1107 100644 --- a/src/impl/itxn.ts +++ b/src/impl/itxn.ts @@ -13,6 +13,7 @@ export type InnerTxn = | itxn.AssetFreezeInnerTxn | itxn.ApplicationCallInnerTxn +/** @internal */ export type InnerTxnFields = ( | itxn.PaymentFields | itxn.KeyRegistrationFields @@ -22,213 +23,243 @@ export type InnerTxnFields = ( | itxn.ApplicationCallFields ) & { type?: TransactionType } +const getInnerTxn = (index: StubUint64Compat): InnerTxn => { + return lazyContext.activeGroup.getItxnGroup().getInnerTxn(asUint64(index)) +} + +const getPaymentInnerTxn = (index: StubUint64Compat): itxn.PaymentInnerTxn => { + return lazyContext.activeGroup.getItxnGroup().getPaymentInnerTxn(asUint64(index)) +} + +const getKeyRegistrationInnerTxn = (index: StubUint64Compat): itxn.KeyRegistrationInnerTxn => { + return lazyContext.activeGroup.getItxnGroup().getKeyRegistrationInnerTxn(asUint64(index)) +} + +const getAssetTransferInnerTxn = (index: StubUint64Compat): itxn.AssetTransferInnerTxn => { + return lazyContext.activeGroup.getItxnGroup().getAssetTransferInnerTxn(asUint64(index)) +} + +const getAssetConfigInnerTxn = (index: StubUint64Compat): itxn.AssetConfigInnerTxn => { + return lazyContext.activeGroup.getItxnGroup().getAssetConfigInnerTxn(asUint64(index)) +} + +const getAssetFreezeInnerTxn = (index: StubUint64Compat): itxn.AssetFreezeInnerTxn => { + return lazyContext.activeGroup.getItxnGroup().getAssetFreezeInnerTxn(asUint64(index)) +} + +const getApplicationCallInnerTxn = (index: StubUint64Compat): itxn.ApplicationCallInnerTxn => { + return lazyContext.activeGroup.getItxnGroup().getApplicationCallInnerTxn(asUint64(index)) +} + +/** @internal */ export const GITxn: typeof op.GITxn = { sender: function (t: StubUint64Compat): Account { - return lazyContext.activeGroup.getItxnGroup().getInnerTxn(t).sender + return getInnerTxn(t).sender }, fee: function (t: StubUint64Compat): uint64 { - return lazyContext.activeGroup.getItxnGroup().getInnerTxn(t).fee + return getInnerTxn(t).fee }, firstValid: function (t: StubUint64Compat): uint64 { - return lazyContext.activeGroup.getItxnGroup().getInnerTxn(t).firstValid + return getInnerTxn(t).firstValid }, firstValidTime: function (t: StubUint64Compat): uint64 { - return lazyContext.activeGroup.getItxnGroup().getInnerTxn(t).firstValidTime + return getInnerTxn(t).firstValidTime }, lastValid: function (t: StubUint64Compat): uint64 { - return lazyContext.activeGroup.getItxnGroup().getInnerTxn(t).lastValid + return getInnerTxn(t).lastValid }, note: function (t: StubUint64Compat): bytes { - return lazyContext.activeGroup.getItxnGroup().getInnerTxn(t).note + return getInnerTxn(t).note }, lease: function (t: StubUint64Compat): bytes<32> { - return lazyContext.activeGroup.getItxnGroup().getInnerTxn(t).lease + return getInnerTxn(t).lease }, receiver: function (t: StubUint64Compat): Account { - return lazyContext.activeGroup.getItxnGroup().getPaymentInnerTxn(t).receiver + return getPaymentInnerTxn(t).receiver }, amount: function (t: StubUint64Compat): uint64 { - return lazyContext.activeGroup.getItxnGroup().getPaymentInnerTxn(t).amount + return getPaymentInnerTxn(t).amount }, closeRemainderTo: function (t: StubUint64Compat): Account { - return lazyContext.activeGroup.getItxnGroup().getPaymentInnerTxn(t).closeRemainderTo + return getPaymentInnerTxn(t).closeRemainderTo }, votePk: function (t: StubUint64Compat): bytes<32> { - return lazyContext.activeGroup.getItxnGroup().getKeyRegistrationInnerTxn(t).voteKey + return getKeyRegistrationInnerTxn(t).voteKey }, selectionPk: function (t: StubUint64Compat): bytes<32> { - return lazyContext.activeGroup.getItxnGroup().getKeyRegistrationInnerTxn(t).selectionKey + return getKeyRegistrationInnerTxn(t).selectionKey }, voteFirst: function (t: StubUint64Compat): uint64 { - return lazyContext.activeGroup.getItxnGroup().getKeyRegistrationInnerTxn(t).voteFirst + return getKeyRegistrationInnerTxn(t).voteFirst }, voteLast: function (t: StubUint64Compat): uint64 { - return lazyContext.activeGroup.getItxnGroup().getKeyRegistrationInnerTxn(t).voteLast + return getKeyRegistrationInnerTxn(t).voteLast }, voteKeyDilution: function (t: StubUint64Compat): uint64 { - return lazyContext.activeGroup.getItxnGroup().getKeyRegistrationInnerTxn(t).voteKeyDilution + return getKeyRegistrationInnerTxn(t).voteKeyDilution }, type: function (t: StubUint64Compat): bytes { - return asUint64Cls(lazyContext.activeGroup.getItxnGroup().getInnerTxn(t).type).toBytes().asAlgoTs() + return asUint64Cls(getInnerTxn(t).type).toBytes().asAlgoTs() }, typeEnum: function (t: StubUint64Compat): uint64 { - return asUint64(lazyContext.activeGroup.getItxnGroup().getInnerTxn(t).type) + return asUint64(getInnerTxn(t).type) }, xferAsset: function (t: StubUint64Compat): Asset { - return lazyContext.activeGroup.getItxnGroup().getAssetTransferInnerTxn(t).xferAsset + return getAssetTransferInnerTxn(t).xferAsset }, assetAmount: function (t: StubUint64Compat): uint64 { - return lazyContext.activeGroup.getItxnGroup().getAssetTransferInnerTxn(t).assetAmount + return getAssetTransferInnerTxn(t).assetAmount }, assetSender: function (t: StubUint64Compat): Account { - return lazyContext.activeGroup.getItxnGroup().getAssetTransferInnerTxn(t).assetSender + return getAssetTransferInnerTxn(t).assetSender }, assetReceiver: function (t: StubUint64Compat): Account { - return lazyContext.activeGroup.getItxnGroup().getAssetTransferInnerTxn(t).assetReceiver + return getAssetTransferInnerTxn(t).assetReceiver }, assetCloseTo: function (t: StubUint64Compat): Account { - return lazyContext.activeGroup.getItxnGroup().getAssetTransferInnerTxn(t).assetCloseTo + return getAssetTransferInnerTxn(t).assetCloseTo }, groupIndex: function (t: StubUint64Compat): uint64 { - return lazyContext.activeGroup.getItxnGroup().getInnerTxn(t).groupIndex + return getInnerTxn(t).groupIndex }, txId: function (t: StubUint64Compat): bytes<32> { - return lazyContext.activeGroup.getItxnGroup().getInnerTxn(t).txnId + return getInnerTxn(t).txnId }, applicationId: function (t: StubUint64Compat): Application { - return lazyContext.activeGroup.getItxnGroup().getApplicationCallInnerTxn(t).appId + return getApplicationCallInnerTxn(t).appId }, onCompletion: function (t: StubUint64Compat): uint64 { - const onCompletion = lazyContext.activeGroup.getItxnGroup().getApplicationCallInnerTxn(t).onCompletion + const onCompletion = getApplicationCallInnerTxn(t).onCompletion return asUint64(onCompletion) }, applicationArgs: function (t: StubUint64Compat, a: StubUint64Compat): bytes { - return lazyContext.activeGroup.getItxnGroup().getApplicationCallInnerTxn(t).appArgs(asUint64(a)) + return getApplicationCallInnerTxn(t).appArgs(asUint64(a)) }, numAppArgs: function (t: StubUint64Compat): uint64 { - return lazyContext.activeGroup.getItxnGroup().getApplicationCallInnerTxn(t).numAppArgs + return getApplicationCallInnerTxn(t).numAppArgs }, accounts: function (t: StubUint64Compat, a: StubUint64Compat): Account { - return lazyContext.activeGroup.getItxnGroup().getApplicationCallInnerTxn(t).accounts(asUint64(a)) + return getApplicationCallInnerTxn(t).accounts(asUint64(a)) }, numAccounts: function (t: StubUint64Compat): uint64 { - return lazyContext.activeGroup.getItxnGroup().getApplicationCallInnerTxn(t).numAccounts + return getApplicationCallInnerTxn(t).numAccounts }, approvalProgram: function (t: StubUint64Compat): bytes { - return lazyContext.activeGroup.getItxnGroup().getApplicationCallInnerTxn(t).approvalProgram + return getApplicationCallInnerTxn(t).approvalProgram }, clearStateProgram: function (t: StubUint64Compat): bytes { - return lazyContext.activeGroup.getItxnGroup().getApplicationCallInnerTxn(t).clearStateProgram + return getApplicationCallInnerTxn(t).clearStateProgram }, rekeyTo: function (t: StubUint64Compat): Account { - return lazyContext.activeGroup.getItxnGroup().getInnerTxn(t).rekeyTo + return getInnerTxn(t).rekeyTo }, configAsset: function (t: StubUint64Compat): Asset { - return lazyContext.activeGroup.getItxnGroup().getAssetConfigInnerTxn(t).configAsset + return getAssetConfigInnerTxn(t).configAsset }, configAssetTotal: function (t: StubUint64Compat): uint64 { - return lazyContext.activeGroup.getItxnGroup().getAssetConfigInnerTxn(t).total + return getAssetConfigInnerTxn(t).total }, configAssetDecimals: function (t: StubUint64Compat): uint64 { - return lazyContext.activeGroup.getItxnGroup().getAssetConfigInnerTxn(t).decimals + return getAssetConfigInnerTxn(t).decimals }, configAssetDefaultFrozen: function (t: StubUint64Compat): boolean { - return lazyContext.activeGroup.getItxnGroup().getAssetConfigInnerTxn(t).defaultFrozen + return getAssetConfigInnerTxn(t).defaultFrozen }, configAssetUnitName: function (t: StubUint64Compat): bytes { - return lazyContext.activeGroup.getItxnGroup().getAssetConfigInnerTxn(t).unitName + return getAssetConfigInnerTxn(t).unitName }, configAssetName: function (t: StubUint64Compat): bytes { - return lazyContext.activeGroup.getItxnGroup().getAssetConfigInnerTxn(t).assetName + return getAssetConfigInnerTxn(t).assetName }, configAssetUrl: function (t: StubUint64Compat): bytes { - return lazyContext.activeGroup.getItxnGroup().getAssetConfigInnerTxn(t).url + return getAssetConfigInnerTxn(t).url }, configAssetMetadataHash: function (t: StubUint64Compat): bytes<32> { - return lazyContext.activeGroup.getItxnGroup().getAssetConfigInnerTxn(t).metadataHash + return getAssetConfigInnerTxn(t).metadataHash }, configAssetManager: function (t: StubUint64Compat): Account { - return lazyContext.activeGroup.getItxnGroup().getAssetConfigInnerTxn(t).manager + return getAssetConfigInnerTxn(t).manager }, configAssetReserve: function (t: StubUint64Compat): Account { - return lazyContext.activeGroup.getItxnGroup().getAssetConfigInnerTxn(t).reserve + return getAssetConfigInnerTxn(t).reserve }, configAssetFreeze: function (t: StubUint64Compat): Account { - return lazyContext.activeGroup.getItxnGroup().getAssetConfigInnerTxn(t).freeze + return getAssetConfigInnerTxn(t).freeze }, configAssetClawback: function (t: StubUint64Compat): Account { - return lazyContext.activeGroup.getItxnGroup().getAssetConfigInnerTxn(t).clawback + return getAssetConfigInnerTxn(t).clawback }, freezeAsset: function (t: StubUint64Compat): Asset { - return lazyContext.activeGroup.getItxnGroup().getAssetFreezeInnerTxn(t).freezeAsset + return getAssetFreezeInnerTxn(t).freezeAsset }, freezeAssetAccount: function (t: StubUint64Compat): Account { - return lazyContext.activeGroup.getItxnGroup().getAssetFreezeInnerTxn(t).freezeAccount + return getAssetFreezeInnerTxn(t).freezeAccount }, freezeAssetFrozen: function (t: StubUint64Compat): boolean { - return lazyContext.activeGroup.getItxnGroup().getAssetFreezeInnerTxn(t).frozen + return getAssetFreezeInnerTxn(t).frozen }, assets: function (t: StubUint64Compat, a: StubUint64Compat): Asset { - return lazyContext.activeGroup.getItxnGroup().getApplicationCallInnerTxn(t).assets(asUint64(a)) + return getApplicationCallInnerTxn(t).assets(asUint64(a)) }, numAssets: function (t: StubUint64Compat): uint64 { - return lazyContext.activeGroup.getItxnGroup().getApplicationCallInnerTxn(t).numAssets + return getApplicationCallInnerTxn(t).numAssets }, applications: function (t: StubUint64Compat, a: StubUint64Compat): Application { - return lazyContext.activeGroup.getItxnGroup().getApplicationCallInnerTxn(t).apps(asUint64(a)) + return getApplicationCallInnerTxn(t).apps(asUint64(a)) }, numApplications: function (t: StubUint64Compat): uint64 { - return lazyContext.activeGroup.getItxnGroup().getApplicationCallInnerTxn(t).numApps + return getApplicationCallInnerTxn(t).numApps }, globalNumUint: function (t: StubUint64Compat): uint64 { - return lazyContext.activeGroup.getItxnGroup().getApplicationCallInnerTxn(t).globalNumUint + return getApplicationCallInnerTxn(t).globalNumUint }, globalNumByteSlice: function (t: StubUint64Compat): uint64 { - return lazyContext.activeGroup.getItxnGroup().getApplicationCallInnerTxn(t).globalNumBytes + return getApplicationCallInnerTxn(t).globalNumBytes }, localNumUint: function (t: StubUint64Compat): uint64 { - return lazyContext.activeGroup.getItxnGroup().getApplicationCallInnerTxn(t).localNumUint + return getApplicationCallInnerTxn(t).localNumUint }, localNumByteSlice: function (t: StubUint64Compat): uint64 { - return lazyContext.activeGroup.getItxnGroup().getApplicationCallInnerTxn(t).localNumBytes + return getApplicationCallInnerTxn(t).localNumBytes }, extraProgramPages: function (t: StubUint64Compat): uint64 { - return lazyContext.activeGroup.getItxnGroup().getApplicationCallInnerTxn(t).extraProgramPages + return getApplicationCallInnerTxn(t).extraProgramPages }, nonparticipation: function (t: StubUint64Compat): boolean { - return lazyContext.activeGroup.getItxnGroup().getKeyRegistrationInnerTxn(t).nonparticipation + return getKeyRegistrationInnerTxn(t).nonparticipation }, logs: function (t: StubUint64Compat, a: StubUint64Compat): bytes { - return lazyContext.activeGroup.getItxnGroup().getApplicationCallInnerTxn(t).logs(asUint64(a)) + return getApplicationCallInnerTxn(t).logs(asUint64(a)) }, numLogs: function (t: StubUint64Compat): uint64 { - return lazyContext.activeGroup.getItxnGroup().getApplicationCallInnerTxn(t).numLogs + return getApplicationCallInnerTxn(t).numLogs }, createdAssetId: function (t: StubUint64Compat): Asset { - return lazyContext.activeGroup.getItxnGroup().getAssetConfigInnerTxn(t).createdAsset + return getAssetConfigInnerTxn(t).createdAsset }, createdApplicationId: function (t: StubUint64Compat): Application { - return lazyContext.activeGroup.getItxnGroup().getApplicationCallInnerTxn(t).createdApp + return getApplicationCallInnerTxn(t).createdApp }, lastLog: function (t: StubUint64Compat): bytes { - return lazyContext.activeGroup.getItxnGroup().getApplicationCallInnerTxn(t).lastLog + return getApplicationCallInnerTxn(t).lastLog }, stateProofPk: function (t: StubUint64Compat): bytes { - return lazyContext.activeGroup.getItxnGroup().getKeyRegistrationInnerTxn(t).stateProofKey + return getKeyRegistrationInnerTxn(t).stateProofKey }, approvalProgramPages: function (t: StubUint64Compat, a: StubUint64Compat): bytes { - return lazyContext.activeGroup.getItxnGroup().getApplicationCallInnerTxn(t).approvalProgramPages(asUint64(a)) + return getApplicationCallInnerTxn(t).approvalProgramPages(asUint64(a)) }, numApprovalProgramPages: function (t: StubUint64Compat): uint64 { - return lazyContext.activeGroup.getItxnGroup().getApplicationCallInnerTxn(t).numApprovalProgramPages + return getApplicationCallInnerTxn(t).numApprovalProgramPages }, clearStateProgramPages: function (t: StubUint64Compat, a: StubUint64Compat): bytes { - return lazyContext.activeGroup.getItxnGroup().getApplicationCallInnerTxn(t).clearStateProgramPages(asUint64(a)) + return getApplicationCallInnerTxn(t).clearStateProgramPages(asUint64(a)) }, numClearStateProgramPages: function (t: StubUint64Compat): uint64 { - return lazyContext.activeGroup.getItxnGroup().getApplicationCallInnerTxn(t).numClearStateProgramPages + return getApplicationCallInnerTxn(t).numClearStateProgramPages }, } +/** @internal */ export const ITxn: typeof op.ITxn = { /** * 32 byte address @@ -649,6 +680,7 @@ const getConstructingItxn = (): T => { return lazyContext.activeGroup.constructingItxn as T } +/** @internal */ export const ITxnCreate: typeof op.ITxnCreate = { begin: function (): void { lazyContext.activeGroup.beginInnerTransactionGroup() diff --git a/src/impl/log.ts b/src/impl/log.ts index 01beb4c4..8633130a 100644 --- a/src/impl/log.ts +++ b/src/impl/log.ts @@ -4,6 +4,7 @@ import { lazyContext } from '../context-helpers/internal-context' import { toBytes } from './encoded-types' import type { StubBigUintCompat, StubBytesCompat, StubUint64Compat } from './primitives' +/** @internal */ export function log(...args: Array): void { lazyContext.txn.appendLog(args.map((a) => toBytes(a)).reduce((left, right) => left.concat(right))) } diff --git a/src/impl/logicSigArg.ts b/src/impl/logicSigArg.ts index e96ec9ca..30c7b986 100644 --- a/src/impl/logicSigArg.ts +++ b/src/impl/logicSigArg.ts @@ -3,6 +3,7 @@ import { lazyContext } from '../context-helpers/internal-context' import { asNumber } from '../util' import type { StubUint64Compat } from './primitives' +/** @internal */ export const arg = (a: StubUint64Compat): bytes => { const index = asNumber(a) return lazyContext.value.activeLogicSigArgs[index] diff --git a/src/impl/match.ts b/src/impl/match.ts index dda748f4..bdf78545 100644 --- a/src/impl/match.ts +++ b/src/impl/match.ts @@ -7,6 +7,7 @@ import { FixedArray } from './encoded-types/encoded-types' import type { StubBytesCompat, Uint64Cls } from './primitives' import { BytesCls } from './primitives' +/** @internal */ export const match: typeof _match = (subject, test): boolean => { if (Object.hasOwn(test, 'not')) { return !match(subject, (test as DeliberateAny).not) @@ -56,6 +57,7 @@ export const match: typeof _match = (subject, test): boolean => { return false } +/** @internal */ export const assertMatch: typeof _assertMatch = (subject, test, message): void => { const isMatching = match(subject, test) assert(isMatching, message) diff --git a/src/impl/method-selector.ts b/src/impl/method-selector.ts index 75f4a798..9d8e4a9c 100644 --- a/src/impl/method-selector.ts +++ b/src/impl/method-selector.ts @@ -6,6 +6,7 @@ import type { Contract } from './contract' import { sha512_256 } from './crypto' import { Bytes } from './primitives' +/** @internal */ export const methodSelector = ( methodSignature: Parameters>[0], contract?: TContract | { new (): TContract }, diff --git a/src/impl/online-stake.ts b/src/impl/online-stake.ts index 4c474378..b3875f06 100644 --- a/src/impl/online-stake.ts +++ b/src/impl/online-stake.ts @@ -1,6 +1,7 @@ import type { op } from '@algorandfoundation/algorand-typescript' import { lazyContext } from '../context-helpers/internal-context' +/** @internal */ export const onlineStake: typeof op.onlineStake = () => { return lazyContext.ledger.onlineStake } diff --git a/src/impl/primitives.ts b/src/impl/primitives.ts index 37113b52..1a751f50 100644 --- a/src/impl/primitives.ts +++ b/src/impl/primitives.ts @@ -8,27 +8,35 @@ import { base32ToUint8Array } from './base-32' const MAX_UINT8 = 2 ** 8 - 1 const MAX_BYTES_SIZE = 4096 +/** @internal */ export type StubBigUintCompat = BigUintCompat | BigUintCls | Uint64Cls +/** @internal */ export type StubBytesCompat = BytesCompat | BytesCls +/** @internal */ export type StubUint64Compat = Uint64Compat | Uint64Cls /** * Create a uint64 with the default value of 0 + * @internal */ export function Uint64(): uint64 /** + * @internal * Create a uint64 from a string literal */ export function Uint64(v: string): uint64 /** + * @internal * Create a uint64 from a bigint literal */ export function Uint64(v: bigint): uint64 /** + * @internal * Create a uint64 from a number literal */ export function Uint64(v: number): uint64 /** + * @internal * Create a uint64 from a boolean value. True is 1, False is 0 */ export function Uint64(v: boolean): uint64 @@ -40,30 +48,37 @@ export function Uint64(v?: Uint64Compat | string): uint64 { } /** + * @internal * Create a biguint from a bigint literal */ export function BigUint(v: bigint): biguint /** + * @internal * Create a biguint from a boolean value (true = 1, false = 0) */ export function BigUint(v: boolean): biguint /** + * @internal * Create a biguint from a uint64 value */ export function BigUint(v: uint64): biguint /** + * @internal * Create a biguint from a number literal */ export function BigUint(v: number): biguint /** + * @internal * Create a biguint from a byte array interpreted as a big-endian number */ export function BigUint(v: bytes): biguint /** + * @internal * Create a biguint from a string literal containing the decimal digits */ export function BigUint(v: string): biguint /** + * @internal * Create a biguint with the default value of 0 */ export function BigUint(): biguint @@ -74,9 +89,10 @@ export function BigUint(v?: BigUintCompat | string): biguint { } /** + * @internal * Create a byte array from a string interpolation template and compatible replacements - * @param value - * @param replacements + * @param value * + * @param replacements * */ export function FixedBytes( length: TLength, @@ -84,26 +100,32 @@ export function FixedBytes( ...replacements: BytesCompat[] ): bytes /** + * @internal * Create a byte array from a utf8 string */ export function FixedBytes(length: TLength, value: string): bytes /** + * @internal * No op, returns the provided byte array. */ export function FixedBytes(length: TLength, value: bytes): bytes /** - * Create a byte array from a biguint value encoded as a variable length big-endian number + * @internal + * Create a byte array from a biguint value encoded as a variable length big-endian number * */ export function FixedBytes(length: TLength, value: biguint): bytes /** + * @internal * Create a byte array from a uint64 value encoded as a fixed length 64-bit number */ export function FixedBytes(length: TLength, value: uint64): bytes /** + * @internal * Create a byte array from an Iterable where each item is interpreted as a single byte and must be between 0 and 255 inclusively */ export function FixedBytes(length: TLength, value: Iterable): bytes /** + * @internal * Create an empty byte array */ export function FixedBytes(length: TLength): bytes @@ -120,6 +142,7 @@ export function FixedBytes( } /** + * @internal * Create a new bytes value from a hexadecimal encoded string * @param hex */ @@ -131,6 +154,7 @@ FixedBytes.fromHex = (length: TLength, hex: str return result.toFixed({ length }) } /** + * @internal * Create a new bytes value from a base 64 encoded string * @param b64 */ @@ -143,6 +167,7 @@ FixedBytes.fromBase64 = (length: TLength, b64: } /** + * @internal * Create a new bytes value from a base 32 encoded string * @param b32 */ @@ -155,32 +180,39 @@ FixedBytes.fromBase32 = (length: TLength, b32: } /** + * @internal * Create a byte array from a string interpolation template and compatible replacements * @param value * @param replacements */ export function Bytes(value: TemplateStringsArray, ...replacements: BytesCompat[]): bytes /** + * @internal * Create a byte array from a utf8 string */ export function Bytes(value: string): bytes /** + * @internal * No op, returns the provided byte array. */ export function Bytes(value: bytes): bytes /** + * @internal * Create a byte array from a biguint value encoded as a variable length big-endian number */ export function Bytes(value: biguint): bytes /** + * @internal * Create a byte array from a uint64 value encoded as a fixed length 64-bit number */ export function Bytes(value: uint64): bytes /** + * @internal * Create a byte array from an Iterable where each item is interpreted as a single byte and must be between 0 and 255 inclusively */ export function Bytes(value: Iterable): bytes /** + * @internal * Create an empty byte array */ export function Bytes(): bytes @@ -207,6 +239,7 @@ export function Bytes( } /** + * @internal * Create a new bytes value from a hexadecimal encoded string * @param hex */ @@ -214,6 +247,7 @@ Bytes.fromHex = (hex: string): bytes => { return BytesCls.fromHex(hex).asAlgoTs() } /** + * @internal * Create a new bytes value from a base 64 encoded string * @param b64 */ @@ -222,6 +256,7 @@ Bytes.fromBase64 = (b64: string): bytes => { } /** + * @internal * Create a new bytes value from a base 32 encoded string * @param b32 */ @@ -230,6 +265,7 @@ Bytes.fromBase32 = (b32: string): bytes => { } /** + * @internal * Convert a StubUint64Compat value into a 'number' if possible. * This value may be negative * @param v @@ -248,33 +284,39 @@ export const getNumber = (v: StubUint64Compat): number => { throw new InternalError(`Cannot convert ${v} to number`) } +/** @internal */ export const getUint8Array = (v: StubBytesCompat): Uint8Array => { return BytesCls.fromCompat(v).asUint8Array() } +/** @internal */ export const isBytes = (v: unknown): v is StubBytesCompat => { if (typeof v === 'string') return true if (v instanceof BytesCls) return true return v instanceof Uint8Array } +/** @internal */ export const isUint64 = (v: unknown): v is StubUint64Compat => { if (typeof v == 'number') return true if (typeof v == 'bigint') return true return v instanceof Uint64Cls } +/** @internal */ export const checkUint64 = (v: bigint): bigint => { const u64 = BigInt.asUintN(64, v) if (u64 !== v) throw new AvmError(`Uint64 overflow or underflow`) return u64 } +/** @internal */ export const checkBigUint = (v: bigint): bigint => { const uBig = BigInt.asUintN(64 * 8, v) if (uBig !== v) throw new AvmError(`BigUint overflow or underflow`) return uBig } +/** @internal */ export const checkBytes = (v: Uint8Array): Uint8Array => { if (v.length > MAX_BYTES_SIZE) throw new AvmError(`Bytes length ${v.length} exceeds maximum length ${MAX_BYTES_SIZE}`) return v @@ -299,6 +341,7 @@ function isInstanceOfTypeByName(subject: unknown, typeCtor: { name: string }): b return false } +/** @internal */ export abstract class AlgoTsPrimitiveCls { static [Symbol.hasInstance](x: unknown): x is AlgoTsPrimitiveCls { return isInstanceOfTypeByName(x, AlgoTsPrimitiveCls) @@ -308,6 +351,7 @@ export abstract class AlgoTsPrimitiveCls { abstract toBytes(): BytesCls } +/** @internal */ export class Uint64Cls extends AlgoTsPrimitiveCls { readonly #value: bigint constructor(value: bigint | number | string) { @@ -357,6 +401,7 @@ export class Uint64Cls extends AlgoTsPrimitiveCls { return this.#value.toString() } } +/** @internal */ export class BigUintCls extends AlgoTsPrimitiveCls { readonly #value: bigint constructor(value: bigint) { @@ -407,6 +452,7 @@ function isTemplateStringsArray(v: unknown): v is TemplateStringsArray { return Boolean(v) && Array.isArray(v) && typeof v[0] === 'string' } +/** @internal */ export class BytesCls extends AlgoTsPrimitiveCls { readonly #v: Uint8Array constructor( @@ -558,6 +604,7 @@ export class BytesCls extends AlgoTsPrimitiveCls { } } +/** @internal */ export const arrayUtil = new (class ArrayUtil { arrayAt(arrayLike: Uint8Array, index: StubUint64Compat): Uint8Array arrayAt(arrayLike: readonly T[], index: StubUint64Compat): T diff --git a/src/impl/pure.ts b/src/impl/pure.ts index a27b2f04..0089a640 100644 --- a/src/impl/pure.ts +++ b/src/impl/pure.ts @@ -6,6 +6,7 @@ import { asBigUint, asBytes, asBytesCls, asMaybeBytesCls, asMaybeUint64Cls, asUi import type { StubBigUintCompat, StubBytesCompat, StubUint64Compat } from './primitives' import { BigUintCls, Bytes, BytesCls, checkBigUint, isUint64, Uint64, Uint64Cls } from './primitives' +/** @internal */ export const addw = (a: StubUint64Compat, b: StubUint64Compat): readonly [uint64, uint64] => { const uint64A = Uint64Cls.fromCompat(a) const uint64B = Uint64Cls.fromCompat(b) @@ -13,6 +14,7 @@ export const addw = (a: StubUint64Compat, b: StubUint64Compat): readonly [uint64 return toUint128(sum) } +/** @internal */ export const base64Decode = (e: Base64, a: StubBytesCompat): bytes => { const encoding = e === Base64.StdEncoding ? 'base64' : 'base64url' const bytesValue = BytesCls.fromCompat(a) @@ -27,6 +29,7 @@ export const base64Decode = (e: Base64, a: StubBytesCompat): bytes => { return Bytes(uint8ArrayResult) } +/** @internal */ export const bitLength = (a: StubUint64Compat | StubBytesCompat): uint64 => { const uint64Cls = asMaybeUint64Cls(a) const bigUintCls = asMaybeBytesCls(a)?.toBigUint() @@ -35,6 +38,7 @@ export const bitLength = (a: StubUint64Compat | StubBytesCompat): uint64 => { return Uint64(binaryValue.length) } +/** @internal */ export const bsqrt = (a: StubBigUintCompat): biguint => { const bigUintClsValue = BigUintCls.fromCompat(a) const bigintValue = checkBigUint(bigUintClsValue.asBigInt()) @@ -42,6 +46,7 @@ export const bsqrt = (a: StubBigUintCompat): biguint => { return asBigUint(sqrtValue) } +/** @internal */ export const btoi = (a: StubBytesCompat): uint64 => { const bytesValue = BytesCls.fromCompat(a) if (bytesValue.length.asAlgoTs() > BITS_IN_BYTE) { @@ -50,6 +55,7 @@ export const btoi = (a: StubBytesCompat): uint64 => { return bytesValue.toUint64().asAlgoTs() } +/** @internal */ export const bzero = (a: StubUint64Compat): bytes => { const size = Uint64Cls.fromCompat(a).asBigInt() if (size > MAX_BYTES_SIZE) { @@ -58,12 +64,14 @@ export const bzero = (a: StubUint64Compat): bytes => { return Bytes(new Uint8Array(Number(size))) } +/** @internal */ export const concat = (a: StubBytesCompat, b: StubBytesCompat): bytes => { const bytesA = BytesCls.fromCompat(a) const bytesB = BytesCls.fromCompat(b) return bytesA.concat(bytesB).asAlgoTs() } +/** @internal */ export const divmodw = ( a: StubUint64Compat, b: StubUint64Compat, @@ -78,12 +86,14 @@ export const divmodw = ( return [...toUint128(div), ...toUint128(mod)] } +/** @internal */ export const divw = (a: StubUint64Compat, b: StubUint64Compat, c: StubUint64Compat): uint64 => { const i = uint128ToBigInt(a, b) const j = Uint64Cls.fromCompat(c).asBigInt() return Uint64(i / j) } +/** @internal */ export const exp = (a: StubUint64Compat, b: StubUint64Compat): uint64 => { const base = Uint64Cls.fromCompat(a).asBigInt() const exponent = Uint64Cls.fromCompat(b).asBigInt() @@ -93,6 +103,7 @@ export const exp = (a: StubUint64Compat, b: StubUint64Compat): uint64 => { return Uint64(base ** exponent) } +/** @internal */ export const expw = (a: StubUint64Compat, b: StubUint64Compat): readonly [uint64, uint64] => { const base = Uint64Cls.fromCompat(a).asBigInt() const exponent = Uint64Cls.fromCompat(b).asBigInt() @@ -104,6 +115,7 @@ export const expw = (a: StubUint64Compat, b: StubUint64Compat): readonly [uint64 type ExtractType = ((a: StubBytesCompat, b: StubUint64Compat) => bytes) & ((a: StubBytesCompat, b: StubUint64Compat, c: StubUint64Compat) => bytes) +/** @internal */ export const extract = ((a: StubBytesCompat, b: StubUint64Compat, c?: StubUint64Compat): bytes => { const bytesValue = BytesCls.fromCompat(a) const bytesLength = bytesValue.length.asBigInt() @@ -122,24 +134,28 @@ export const extract = ((a: StubBytesCompat, b: StubUint64Compat, c?: StubUint64 return bytesValue.slice(start, end).asAlgoTs() }) as ExtractType +/** @internal */ export const extractUint16 = (a: StubBytesCompat, b: StubUint64Compat): uint64 => { const result = extract(a, b, 2) const bytesResult = BytesCls.fromCompat(result) return bytesResult.toUint64().asAlgoTs() } +/** @internal */ export const extractUint32 = (a: StubBytesCompat, b: StubUint64Compat): uint64 => { const result = extract(a, b, 4) const bytesResult = BytesCls.fromCompat(result) return bytesResult.toUint64().asAlgoTs() } +/** @internal */ export const extractUint64 = (a: StubBytesCompat, b: StubUint64Compat): uint64 => { const result = extract(a, b, 8) const bytesResult = BytesCls.fromCompat(result) return bytesResult.toUint64().asAlgoTs() } +/** @internal */ export const getBit = (a: StubUint64Compat | StubBytesCompat, b: StubUint64Compat): uint64 => { const binaryString = toBinaryString(isUint64(a) ? asUint64Cls(a).toBytes().asAlgoTs() : asBytes(a)) const index = Uint64Cls.fromCompat(b).asNumber() @@ -150,6 +166,7 @@ export const getBit = (a: StubUint64Compat | StubBytesCompat, b: StubUint64Compa return binaryString[adjustedIndex] === '1' ? 1 : 0 } +/** @internal */ export const getByte = (a: StubBytesCompat, b: StubUint64Compat): uint64 => { const bytesValue = BytesCls.fromCompat(a) const index = Uint64Cls.fromCompat(b).asNumber() @@ -159,14 +176,17 @@ export const getByte = (a: StubBytesCompat, b: StubUint64Compat): uint64 => { return bytesValue.at(index).toUint64().asAlgoTs() } +/** @internal */ export const itob = (a: StubUint64Compat): bytes => { return asUint64Cls(a).toBytes().asAlgoTs() } +/** @internal */ export const len = (a: StubBytesCompat): uint64 => { return asBytesCls(a).length.asAlgoTs() } +/** @internal */ export const mulw = (a: StubUint64Compat, b: StubUint64Compat): readonly [uint64, uint64] => { const uint64A = Uint64Cls.fromCompat(a) const uint64B = Uint64Cls.fromCompat(b) @@ -174,6 +194,7 @@ export const mulw = (a: StubUint64Compat, b: StubUint64Compat): readonly [uint64 return toUint128(product) } +/** @internal */ export const replace = (a: StubBytesCompat, b: StubUint64Compat, c: StubBytesCompat): bytes => { const bytesValue = BytesCls.fromCompat(a) const index = Uint64Cls.fromCompat(b).asNumber() @@ -194,6 +215,7 @@ export const replace = (a: StubBytesCompat, b: StubUint64Compat, c: StubBytesCom type selectType = ((a: StubBytesCompat, b: StubBytesCompat, c: StubUint64Compat) => bytes) & ((a: StubUint64Compat, b: StubUint64Compat, c: StubUint64Compat) => uint64) +/** @internal */ export const select = (( a: StubUint64Compat | StubBytesCompat, b: StubUint64Compat | StubBytesCompat, @@ -211,6 +233,7 @@ export const select = (( type SetBitType = ((target: StubBytesCompat, n: StubUint64Compat, c: StubUint64Compat) => bytes) & ((target: StubUint64Compat, n: StubUint64Compat, c: StubUint64Compat) => uint64) +/** @internal */ export const setBit = ((a: StubUint64Compat | StubBytesCompat, b: StubUint64Compat, c: StubUint64Compat) => { const uint64Cls = asMaybeUint64Cls(a) const indexParam = Uint64Cls.fromCompat(b).asNumber() @@ -229,6 +252,7 @@ export const setBit = ((a: StubUint64Compat | StubBytesCompat, b: StubUint64Comp } }) as SetBitType +/** @internal */ export const setByte = (a: StubBytesCompat, b: StubUint64Compat, c: StubUint64Compat): bytes => { const binaryString = toBinaryString(BytesCls.fromCompat(a).asAlgoTs()) @@ -249,6 +273,7 @@ export const setByte = (a: StubBytesCompat, b: StubUint64Compat, c: StubUint64Co return updatedBytes.asAlgoTs() } +/** @internal */ export const shl = (a: StubUint64Compat, b: StubUint64Compat): uint64 => { const uint64A = Uint64Cls.fromCompat(a) const uint64B = Uint64Cls.fromCompat(b) @@ -261,6 +286,7 @@ export const shl = (a: StubUint64Compat, b: StubUint64Compat): uint64 => { return Uint64(shifted) } +/** @internal */ export const shr = (a: StubUint64Compat, b: StubUint64Compat): uint64 => { const uint64A = Uint64Cls.fromCompat(a) const uint64B = Uint64Cls.fromCompat(b) @@ -273,12 +299,14 @@ export const shr = (a: StubUint64Compat, b: StubUint64Compat): uint64 => { return Uint64(shifted) } +/** @internal */ export const sqrt = (a: StubUint64Compat): uint64 => { const bigIntValue = Uint64Cls.fromCompat(a).asBigInt() const sqrtValue = squareroot(bigIntValue) return Uint64(sqrtValue) } +/** @internal */ export const substring = (a: StubBytesCompat, b: StubUint64Compat, c: StubUint64Compat): bytes => { const bytesValue = BytesCls.fromCompat(a) const start = Uint64Cls.fromCompat(b).asBigInt() @@ -292,6 +320,7 @@ export const substring = (a: StubBytesCompat, b: StubUint64Compat, c: StubUint64 return bytesValue.slice(start, end).asAlgoTs() } +/** @internal */ export const JsonRef = new Proxy({} as typeof op.JsonRef, { get: (_target, prop) => { throw new NotImplementedError(`JsonRef.${prop.toString()}`) diff --git a/src/impl/reference.ts b/src/impl/reference.ts index 2850acfe..a15fe79b 100644 --- a/src/impl/reference.ts +++ b/src/impl/reference.ts @@ -32,6 +32,8 @@ import type { GlobalStateCls, LocalStateCls } from './state' export class AssetHolding { balance: uint64 frozen: boolean + + /** @internal */ constructor(balance: StubUint64Compat, frozen: boolean) { this.balance = asUint64(balance) this.frozen = frozen @@ -46,6 +48,7 @@ export class AccountData { lastHeartbeat?: uint64 account: Mutable> + /** @internal */ constructor() { this.account = { totalAppsCreated: 0, @@ -64,10 +67,12 @@ export class AccountData { } } +/** @internal */ export function Account(address?: bytes): AccountType { return new AccountCls(address) } +/** @internal */ export class AccountCls extends BytesBackedCls implements AccountType { constructor(address?: bytes) { const addressBytes = address ?? ZERO_ADDRESS @@ -143,8 +148,10 @@ export class ApplicationData { materialisedBoxes: BytesMap } + /** @internal */ isCreating: boolean = false + /** @internal */ constructor() { this.application = { approvalProgram: ALWAYS_APPROVE_TEAL_PROGRAM, @@ -165,10 +172,12 @@ export class ApplicationData { } } +/** @internal */ export function Application(id?: uint64): ApplicationType { return new ApplicationCls(id) } +/** @internal */ export class ApplicationCls extends Uint64BackedCls implements ApplicationType { get id() { return this.uint64 @@ -215,6 +224,7 @@ export class ApplicationCls extends Uint64BackedCls implements ApplicationType { } export type AssetData = Mutable> +/** @internal */ export const getDefaultAssetData = (): AssetData => ({ total: lazyContext.any.uint64(), decimals: lazyContext.any.uint64(1, 6), @@ -230,10 +240,12 @@ export const getDefaultAssetData = (): AssetData => ({ reserve: Account(ZERO_ADDRESS), }) +/** @internal */ export function Asset(id?: uint64): AssetType { return new AssetCls(id) } +/** @internal */ export class AssetCls extends Uint64BackedCls implements AssetType { get id(): uint64 { return this.uint64 @@ -302,10 +314,12 @@ export class AssetCls extends Uint64BackedCls implements AssetType { } } +/** @internal */ export const checksumFromPublicKey = (pk: Uint8Array): Uint8Array => { return Uint8Array.from(js_sha512.sha512_256.array(pk).slice(HASH_BYTES_LENGTH - ALGORAND_CHECKSUM_BYTE_LENGTH, HASH_BYTES_LENGTH)) } +/** @internal */ export const getApplicationAddress = (appId: StubUint64Compat): AccountType => { const toBeSigned = conactUint8Arrays(asUint8Array(APP_ID_PREFIX), encodingUtil.bigIntToUint8Array(asBigInt(appId), 8)) const appIdHash = js_sha512.sha512_256.array(toBeSigned) @@ -314,16 +328,19 @@ export const getApplicationAddress = (appId: StubUint64Compat): AccountType => { return Account(Bytes.fromBase32(address)) } +/** @internal */ export const encodeAddress = (address: Uint8Array): string => { const checksum = checksumFromPublicKey(address) return encodingUtil.uint8ArrayToBase32(conactUint8Arrays(address, checksum)).slice(0, ALGORAND_ADDRESS_LENGTH) } +/** @internal */ export const decodePublicKey = (address: string): Uint8Array => { const decoded = encodingUtil.base32ToUint8Array(address) return decoded.slice(0, ALGORAND_ADDRESS_BYTE_LENGTH - ALGORAND_CHECKSUM_BYTE_LENGTH) } +/** @internal */ export const asAccount = (val: AccountType | bytes | string | undefined): AccountType | undefined => { return val instanceof AccountCls ? val @@ -334,10 +351,12 @@ export const asAccount = (val: AccountType | bytes | string | undefined): Accoun : undefined } +/** @internal */ export const asAsset = (val: AssetType | uint64 | undefined): AssetType | undefined => { return val instanceof Uint64Cls ? Asset(val.asAlgoTs()) : val instanceof AssetCls ? val : undefined } +/** @internal */ export const asApplication = (val: ApplicationType | uint64 | undefined): ApplicationType | undefined => { return val instanceof Uint64Cls ? Application(val.asAlgoTs()) : val instanceof ApplicationCls ? val : undefined } diff --git a/src/impl/scratch.ts b/src/impl/scratch.ts index 4f48d8d1..b9659f88 100644 --- a/src/impl/scratch.ts +++ b/src/impl/scratch.ts @@ -1,43 +1,47 @@ -import type { bytes, op, uint64 } from '@algorandfoundation/algorand-typescript' +import type { bytes, BytesCompat, op, uint64, Uint64Compat } from '@algorandfoundation/algorand-typescript' import { lazyContext } from '../context-helpers/internal-context' import { InternalError } from '../errors' +import { asUint64 } from '../util' import type { StubBytesCompat, StubUint64Compat } from './primitives' import { BytesCls, Uint64Cls } from './primitives' +/** @internal */ export const gloadUint64: typeof op.gloadUint64 = (a: StubUint64Compat, b: StubUint64Compat): uint64 => { - const txn = lazyContext.activeGroup.getTransaction(a) - const result = txn.getScratchSlot(b) + const txn = lazyContext.activeGroup.getTransaction(asUint64(a)) + const result = txn.getScratchSlot(asUint64(b)) if (result instanceof Uint64Cls) { return result.asAlgoTs() } throw new InternalError('invalid scratch slot type') } +/** @internal */ export const gloadBytes: typeof op.gloadBytes = (a: StubUint64Compat, b: StubUint64Compat): bytes => { - const txn = lazyContext.activeGroup.getTransaction(a) - const result = txn.getScratchSlot(b) + const txn = lazyContext.activeGroup.getTransaction(asUint64(a)) + const result = txn.getScratchSlot(asUint64(b)) if (result instanceof BytesCls) { return result.asAlgoTs() } throw new InternalError('invalid scratch slot type') } +/** @internal */ export const Scratch: typeof op.Scratch = { loadBytes: function (a: StubUint64Compat): bytes { - const result = lazyContext.activeGroup.activeTransaction.getScratchSlot(a) + const result = lazyContext.activeGroup.activeTransaction.getScratchSlot(asUint64(a)) if (result instanceof BytesCls) { return result as bytes } throw new InternalError('invalid scratch slot type') }, loadUint64: function (a: StubUint64Compat): uint64 { - const result = lazyContext.activeGroup.activeTransaction.getScratchSlot(a) + const result = lazyContext.activeGroup.activeTransaction.getScratchSlot(asUint64(a)) if (result instanceof Uint64Cls) { return result as uint64 } throw new InternalError('invalid scratch slot type') }, store: function (a: StubUint64Compat, b: StubUint64Compat | StubBytesCompat): void { - lazyContext.activeGroup.activeTransaction.setScratchSlot(a, b) + lazyContext.activeGroup.activeTransaction.setScratchSlot(asUint64(a), b as unknown as Uint64Compat | BytesCompat) }, } diff --git a/src/impl/state.ts b/src/impl/state.ts index 9fdb4c3b..c7cd694c 100644 --- a/src/impl/state.ts +++ b/src/impl/state.ts @@ -24,9 +24,12 @@ import type { StubBytesCompat, StubUint64Compat } from './primitives' import { Bytes, Uint64, Uint64Cls } from './primitives' export class GlobalStateCls { + /** @internal */ private readonly _type: string = GlobalStateCls.name + /** @internal */ #value: ValueType | undefined + key: bytes | undefined get hasKey(): boolean { @@ -60,6 +63,7 @@ export class GlobalStateCls { return this.#value !== undefined } + /** @internal */ constructor(key?: bytes | string, value?: ValueType) { this.key = key !== undefined ? asBytes(key) : undefined this.#value = value @@ -67,7 +71,9 @@ export class GlobalStateCls { } export class LocalStateCls { + /** @internal */ #value: ValueType | undefined + delete: () => void = () => { if (this.#value instanceof Uint64Cls) { this.#value = Uint64(0) as ValueType @@ -92,8 +98,10 @@ export class LocalStateCls { } export class LocalStateMapCls { + /** @internal */ private applicationId: uint64 + /** @internal */ constructor() { this.applicationId = lazyContext.activeGroup.activeApplicationId } @@ -107,6 +115,7 @@ export class LocalStateMapCls { return localStateMap.getOrFail(account) as LocalStateCls } + /** @internal */ private ensureApplicationLocalStateMap(key: bytes | string) { const applicationData = lazyContext.ledger.applicationDataMap.getOrFail(this.applicationId)!.application if (!applicationData.localStateMaps.has(key)) { @@ -116,10 +125,12 @@ export class LocalStateMapCls { } } +/** @internal */ export function GlobalState(options?: GlobalStateOptions): GlobalStateType { return new GlobalStateCls(options?.key, options?.initialValue) } +/** @internal */ export function LocalState(options?: { key?: bytes | string }): LocalStateType { function localStateInternal(account: Account): LocalStateForAccount { return localStateInternal.map.getValue(localStateInternal.key, account) @@ -130,6 +141,7 @@ export function LocalState(options?: { key?: bytes | string }): Local return localStateInternal } +/** @internal */ export class BoxCls { #key: bytes | undefined #app: Application @@ -191,7 +203,7 @@ export class BoxCls { } const original = lazyContext.ledger.getBox(this.#app, this.key) materialised = this.fromBytes(original) - lazyContext.ledger.setMatrialisedBox(this.#app, this.key, materialised) + lazyContext.ledger.setMaterialisedBox(this.#app, this.key, materialised) return materialised } set value(v: TValue) { @@ -204,7 +216,7 @@ export class BoxCls { } } lazyContext.ledger.setBox(this.#app, this.key, newValueBytes) - lazyContext.ledger.setMatrialisedBox(this.#app, this.key, v) + lazyContext.ledger.setMaterialisedBox(this.#app, this.key, v) } get hasKey(): boolean { @@ -252,6 +264,7 @@ export class BoxCls { } } +/** @internal */ export class BoxMapCls { private _keyPrefix: bytes | undefined #app: Application @@ -293,6 +306,7 @@ export class BoxMapCls { } } +/** @internal */ export class BoxRefCls { #key: bytes | undefined #app: Application @@ -462,10 +476,12 @@ export class BoxRefCls { } } +/** @internal */ export function Box(options?: { key: bytes | string }): BoxType { return new BoxCls(options?.key) } +/** @internal */ export function BoxMap(options?: { keyPrefix: bytes | string }): BoxMapType { const boxMap = new BoxMapCls() if (options?.keyPrefix !== undefined) { @@ -476,6 +492,7 @@ export function BoxMap(options?: { keyPrefix: bytes | string }): B return Object.setPrototypeOf(x, boxMap) } +/** @internal */ export function BoxRef(options?: { key: bytes | string }): BoxRefType { return new BoxRefCls(options?.key) } diff --git a/src/impl/template-var.ts b/src/impl/template-var.ts index ee9df5b1..4904fd5b 100644 --- a/src/impl/template-var.ts +++ b/src/impl/template-var.ts @@ -2,6 +2,7 @@ import { DEFAULT_TEMPLATE_VAR_PREFIX } from '../constants' import { lazyContext } from '../context-helpers/internal-context' import { CodeError } from '../errors' +/** @internal */ export function TemplateVar(variableName: string, prefix = DEFAULT_TEMPLATE_VAR_PREFIX): T { const key = prefix + variableName if (!Object.hasOwn(lazyContext.value.templateVars, key)) { diff --git a/src/impl/transactions.ts b/src/impl/transactions.ts index 1c7aacfa..144f5655 100644 --- a/src/impl/transactions.ts +++ b/src/impl/transactions.ts @@ -3,8 +3,10 @@ import type { Application as ApplicationType, Asset as AssetType, bytes, + BytesCompat, gtxn, uint64, + Uint64Compat, } from '@algorandfoundation/algorand-typescript' import { OnCompleteAction, TransactionType } from '@algorandfoundation/algorand-typescript' import { ABI_RETURN_VALUE_LOG_PREFIX, MAX_ITEMS_IN_LOG } from '../constants' @@ -13,7 +15,7 @@ import { InternalError } from '../errors' import type { Mutable, ObjectKeys } from '../typescript-helpers' import { asBytes, asMaybeBytesCls, asMaybeUint64Cls, asNumber, asUint64Cls, combineIntoMaxBytePages, getRandomBytes } from '../util' import { toBytes } from './encoded-types' -import { Bytes, Uint64, type StubBytesCompat, type StubUint64Compat } from './primitives' +import { Bytes, Uint64, type StubBytesCompat } from './primitives' import { Account, Application, Asset } from './reference' const baseDefaultFields = () => ({ @@ -59,7 +61,7 @@ abstract class TransactionBase { readonly rekeyTo: AccountType readonly scratchSpace: Array - setScratchSlot(index: StubUint64Compat, value: StubBytesCompat | StubUint64Compat): void { + setScratchSlot(index: Uint64Compat, value: BytesCompat | Uint64Compat): void { const i = asNumber(index) if (i >= this.scratchSpace.length) { throw new InternalError('invalid scratch slot') @@ -69,7 +71,7 @@ abstract class TransactionBase { this.scratchSpace[i] = bytesValue?.asAlgoTs() ?? uint64Value?.asAlgoTs() ?? Uint64(0) } - getScratchSlot(index: StubUint64Compat): bytes | uint64 { + getScratchSlot(index: Uint64Compat): bytes | uint64 { const i = asNumber(index) if (i >= this.scratchSpace.length) { throw new InternalError('invalid scratch slot') @@ -324,25 +326,25 @@ export class ApplicationCallTransaction extends TransactionBase implements gtxn. get apfa() { return this.#apps } - appArgs(index: StubUint64Compat): bytes { + appArgs(index: Uint64Compat): bytes { return toBytes(this.args[asNumber(index)]) } - accounts(index: StubUint64Compat): AccountType { + accounts(index: Uint64Compat): AccountType { return this.#accounts[asNumber(index)] } - assets(index: StubUint64Compat): AssetType { + assets(index: Uint64Compat): AssetType { return this.#assets[asNumber(index)] } - apps(index: StubUint64Compat): ApplicationType { + apps(index: Uint64Compat): ApplicationType { return this.#apps[asNumber(index)] } - approvalProgramPages(index: StubUint64Compat): bytes { + approvalProgramPages(index: Uint64Compat): bytes { return combineIntoMaxBytePages(this.#approvalProgramPages)[asNumber(index)] } - clearStateProgramPages(index: StubUint64Compat): bytes { + clearStateProgramPages(index: Uint64Compat): bytes { return combineIntoMaxBytePages(this.#clearStateProgramPages)[asNumber(index)] } - logs(index: StubUint64Compat): bytes { + logs(index: Uint64Compat): bytes { const i = asNumber(index) return this.appLogs[i] ?? lazyContext.getApplicationData(this.appId.id).application.appLogs ?? Bytes() } diff --git a/src/impl/txn.ts b/src/impl/txn.ts index f449a898..cae045c1 100644 --- a/src/impl/txn.ts +++ b/src/impl/txn.ts @@ -5,9 +5,10 @@ import { InternalError } from '../errors' import { asNumber, asUint64, asUint64Cls } from '../util' import type { StubUint64Compat } from './primitives' +/** @internal */ export const gaid = (a: StubUint64Compat): uint64 => { const group = lazyContext.activeGroup - const transaction = group.getTransaction(a) + const transaction = group.getTransaction(asUint64(a)) if (transaction.type === TransactionType.ApplicationCall) { return transaction.createdApp.id } else if (transaction.type === TransactionType.AssetConfig) { @@ -17,6 +18,7 @@ export const gaid = (a: StubUint64Compat): uint64 => { } } +/** @internal */ export const Txn: typeof op.Txn = { get sender(): Account { return lazyContext.activeGroup.getTransaction().sender diff --git a/src/impl/urange.ts b/src/impl/urange.ts index 6deee538..d661e663 100644 --- a/src/impl/urange.ts +++ b/src/impl/urange.ts @@ -1,6 +1,7 @@ import { asBigInt, asUint64 } from '../util' import type { StubUint64Compat } from './primitives' +/** @internal */ export function* urange(a: StubUint64Compat, b?: StubUint64Compat, c?: StubUint64Compat) { const start = b ? asBigInt(a) : BigInt(0) const end = b ? asBigInt(b) : asBigInt(a) diff --git a/src/impl/voter-params.ts b/src/impl/voter-params.ts index 2f48522f..a6499eb4 100644 --- a/src/impl/voter-params.ts +++ b/src/impl/voter-params.ts @@ -7,6 +7,7 @@ export class VoterData { balance: uint64 incentiveEligible: boolean + /** @internal */ constructor() { this.balance = 0 this.incentiveEligible = false @@ -18,6 +19,7 @@ const getVoterData = (a: Account | StubUint64Compat): VoterData => { return lazyContext.getVoterData(acct) } +/** @internal */ export const VoterParams: typeof op.VoterParams = { voterBalance: function (a: Account | StubUint64Compat): readonly [uint64, boolean] { const data = getVoterData(a) diff --git a/src/internal/arc4.ts b/src/internal/arc4.ts index 8c1fa9c7..6336db70 100644 --- a/src/internal/arc4.ts +++ b/src/internal/arc4.ts @@ -1,6 +1,10 @@ +/** @internal */ export * from '@algorandfoundation/algorand-typescript/arc4' +/** @internal */ export { abiCall, compileArc4 } from '../impl/c2c' +/** @internal */ export { abimethod, baremethod, Contract } from '../impl/contract' +/** @internal */ export { Address, arc4EncodedLength, @@ -21,4 +25,5 @@ export { UFixed, Uint, } from '../impl/encoded-types' +/** @internal */ export { methodSelector } from '../impl/method-selector' diff --git a/src/internal/index.ts b/src/internal/index.ts index 4a8878a3..3ea1c662 100644 --- a/src/internal/index.ts +++ b/src/internal/index.ts @@ -1,21 +1,40 @@ +/** @internal */ export * from '@algorandfoundation/algorand-typescript' +/** @internal */ export { BaseContract, contract } from '../impl/base-contract' +/** @internal */ export { clone } from '../impl/clone' +/** @internal */ export { compile } from '../impl/compiled' +/** @internal */ export { abimethod, baremethod, Contract } from '../impl/contract' +/** @internal */ export { emit } from '../impl/emit' +/** @internal */ export { ensureBudget } from '../impl/ensure-budget' +/** @internal */ export { Global } from '../impl/global' +/** @internal */ export { log } from '../impl/log' +/** @internal */ export { assertMatch, match } from '../impl/match' +/** @internal */ export { BigUint, Bytes, Uint64 } from '../impl/primitives' +/** @internal */ export { Account, Application, Asset } from '../impl/reference' +/** @internal */ export { Box, BoxMap, BoxRef, GlobalState, LocalState } from '../impl/state' +/** @internal */ export { TemplateVar } from '../impl/template-var' +/** @internal */ export { Txn } from '../impl/txn' +/** @internal */ export { urange } from '../impl/urange' +/** @internal */ export { assert, err } from '../util' +/** @internal */ export * as arc4 from './arc4' +/** @internal */ export * as op from './op' import { ApplicationCallTxn, @@ -26,6 +45,7 @@ import { PaymentTxn, Transaction, } from '../impl/gtxn' +/** @internal */ export const gtxn = { Transaction, PaymentTxn, @@ -37,6 +57,7 @@ export const gtxn = { } import { applicationCall, assetConfig, assetFreeze, assetTransfer, keyRegistration, payment, submitGroup } from '../impl/inner-transactions' +/** @internal */ export const itxn = { submitGroup, payment, @@ -47,4 +68,5 @@ export const itxn = { applicationCall, } +/** @internal */ export { itxnCompose } from '../impl/itxn-compose' diff --git a/src/internal/op.ts b/src/internal/op.ts index b1fe52d3..67dec9cc 100644 --- a/src/internal/op.ts +++ b/src/internal/op.ts @@ -1,12 +1,22 @@ +/** @internal */ export * from '@algorandfoundation/algorand-typescript/op' +/** @internal */ export { AcctParams, appOptedIn, balance, minBalance } from '../impl/acct-params' +/** @internal */ export { AppGlobal } from '../impl/app-global' +/** @internal */ export { AppLocal } from '../impl/app-local' +/** @internal */ export { AppParams } from '../impl/app-params' +/** @internal */ export { AssetHolding } from '../impl/asset-holding' +/** @internal */ export { AssetParams } from '../impl/asset-params' +/** @internal */ export { Block } from '../impl/block' +/** @internal */ export { Box } from '../impl/box' +/** @internal */ export { ecdsaPkDecompress, ecdsaPkRecover, @@ -21,11 +31,17 @@ export { sha512_256, vrfVerify, } from '../impl/crypto' +/** @internal */ export { Global } from '../impl/global' +/** @internal */ export { GTxn } from '../impl/gtxn' +/** @internal */ export { GITxn, ITxn, ITxnCreate } from '../impl/itxn' +/** @internal */ export { arg } from '../impl/logicSigArg' +/** @internal */ export { onlineStake } from '../impl/online-stake' +/** @internal */ export { addw, base64Decode, @@ -57,6 +73,9 @@ export { sqrt, substring, } from '../impl/pure' +/** @internal */ export { gloadBytes, gloadUint64, Scratch } from '../impl/scratch' +/** @internal */ export { gaid, Txn } from '../impl/txn' +/** @internal */ export { VoterParams } from '../impl/voter-params' diff --git a/src/runtime-helpers.ts b/src/runtime-helpers.ts index 13064e88..b3f32ad1 100644 --- a/src/runtime-helpers.ts +++ b/src/runtime-helpers.ts @@ -9,9 +9,12 @@ import { AccountCls } from './impl/reference' import { nameOfType, type DeliberateAny } from './typescript-helpers' import { flattenAsBytes } from './util' +/** @internal */ export { attachAbiMetadata } from './abi-metadata' +/** @internal */ export { FixedBytes } from './impl/primitives' +/** @internal */ export function switchableValue(x: unknown): bigint | string | boolean { if (typeof x === 'boolean') return x if (typeof x === 'bigint') return x @@ -31,6 +34,7 @@ function tryGetBigInt(value: unknown): bigint | undefined { return undefined } +/** @internal */ export function binaryOp(left: unknown, right: unknown, op: BinaryOps) { if (left instanceof ARC4Encoded && right instanceof ARC4Encoded) { return arc4EncodedOp(left, right, op) @@ -68,6 +72,7 @@ export function binaryOp(left: unknown, right: unknown, op: BinaryOps) { return defaultBinaryOp(left, right, op) } +/** @internal */ export function unaryOp(operand: unknown, op: UnaryOps) { if (operand instanceof Uint64Cls) { return uint64UnaryOp(operand, op) @@ -316,11 +321,13 @@ function defaultUnaryOp(_operand: DeliberateAny, op: UnaryOps): DeliberateAny { } const genericTypeMap = new WeakMap() +/** @internal */ export function captureGenericTypeInfo(target: DeliberateAny, t: string) { genericTypeMap.set(target, JSON.parse(t)) return target } +/** @internal */ export function getGenericTypeInfo(target: DeliberateAny): TypeInfo | undefined { return genericTypeMap.get(target) } diff --git a/src/subcontexts/contract-context.ts b/src/subcontexts/contract-context.ts index 608b945b..297d3a8f 100644 --- a/src/subcontexts/contract-context.ts +++ b/src/subcontexts/contract-context.ts @@ -1,3 +1,4 @@ +import type { BaseContract as BaseContractType } from '@algorandfoundation/algorand-typescript' import { OnCompleteAction, type Account, @@ -89,7 +90,9 @@ const extractStates = (contract: BaseContract, contractOptions: ContractOptionsP const getUint8 = (value: number) => new Uint({ name: 'Uint<8>', genericArgs: [{ name: '8' }] }, value) -/** @ignore */ +/** @ignore + * @internal + */ export const extractArraysFromArgs = ( app: Application, methodSelector: Uint8Array, @@ -171,7 +174,7 @@ export class ContractContext { * const ctx = new TestExecutionContext(); * const contract = ctx.contract.create(MyContract); */ - create(type: IConstructor, ...args: DeliberateAny[]): T { + create(type: IConstructor, ...args: DeliberateAny[]): T { const proxy = new Proxy(type, this.getContractProxyHandler(this.isArc4(type))) return new proxy(...args) } @@ -211,6 +214,7 @@ export class ContractContext { return txns } + /** @internal */ private isArc4(type: IConstructor): boolean { const result = (type as DeliberateAny as typeof BaseContract).isArc4 if (result !== undefined && result !== null) { @@ -230,6 +234,7 @@ export class ContractContext { return this.isArc4(proto) } + /** @internal */ private getContractProxyHandler(isArc4: boolean): ProxyHandler> { const onConstructed = (application: Application, instance: T, conrtactOptions: ContractOptionsParameter | undefined) => { const states = extractStates(instance, conrtactOptions) diff --git a/src/subcontexts/ledger-context.ts b/src/subcontexts/ledger-context.ts index ed02fe97..b9acef47 100644 --- a/src/subcontexts/ledger-context.ts +++ b/src/subcontexts/ledger-context.ts @@ -4,8 +4,10 @@ import type { Asset as AssetType, BaseContract, bytes, + BytesCompat, LocalStateForAccount, uint64, + Uint64Compat, } from '@algorandfoundation/algorand-typescript' import { AccountMap, Uint64Map } from '../collections/custom-key-map' import { MAX_UINT64 } from '../constants' @@ -13,7 +15,7 @@ import { InternalError } from '../errors' import { BlockData } from '../impl/block' import { toBytes } from '../impl/encoded-types' import { GlobalData } from '../impl/global' -import { Uint64Cls, type StubBytesCompat, type StubUint64Compat } from '../impl/primitives' +import { Uint64Cls } from '../impl/primitives' import type { AssetData } from '../impl/reference' import { AccountCls, @@ -31,14 +33,23 @@ import type { PickPartial } from '../typescript-helpers' import { asBigInt, asBytes, asMaybeBytesCls, asMaybeUint64Cls, asUint64, asUint64Cls, asUint8Array, iterBigInt } from '../util' export class LedgerContext { + /** @internal */ appIdIter = iterBigInt(1001n, MAX_UINT64) + /** @internal */ assetIdIter = iterBigInt(1001n, MAX_UINT64) + /** @internal */ applicationDataMap = new Uint64Map() + /** @internal */ appIdContractMap = new Uint64Map() + /** @internal */ accountDataMap = new AccountMap() + /** @internal */ assetDataMap = new Uint64Map() + /** @internal */ voterDataMap = new AccountMap() + /** @internal */ blocks = new Uint64Map() + /** @internal */ globalData = new GlobalData() onlineStake = 0 @@ -48,7 +59,7 @@ export class LedgerContext { * @param appId - The application ID. * @param contract - The contract to add. */ - addAppIdContractMap(appId: StubUint64Compat, contract: BaseContract): void { + addAppIdContractMap(appId: Uint64Compat, contract: BaseContract): void { this.appIdContractMap.set(appId, contract) } @@ -57,8 +68,8 @@ export class LedgerContext { * @param address - The account address. * @returns The account. */ - getAccount(address: AccountType | StubBytesCompat): AccountType { - return new AccountCls(address instanceof AccountCls ? address.bytes : asBytes(address as StubBytesCompat)) + getAccount(address: AccountType | BytesCompat): AccountType { + return new AccountCls(address instanceof AccountCls ? address.bytes : asBytes(address as BytesCompat)) } /** @@ -67,7 +78,7 @@ export class LedgerContext { * @returns The asset. * @throws If the asset is unknown. */ - getAsset(assetId: StubUint64Compat): AssetType { + getAsset(assetId: Uint64Compat): AssetType { if (this.assetDataMap.has(assetId)) { return Asset(asUint64(assetId)) } @@ -80,7 +91,7 @@ export class LedgerContext { * @returns The application. * @throws If the application is unknown. */ - getApplication(applicationId: StubUint64Compat): ApplicationType { + getApplication(applicationId: Uint64Compat): ApplicationType { if (this.applicationDataMap.has(applicationId)) { return Application(asUint64(applicationId)) } @@ -139,8 +150,8 @@ export class LedgerContext { * @param balance * @param frozen */ - updateAssetHolding(account: AccountType, assetId: StubUint64Compat | AssetType, balance?: StubUint64Compat, frozen?: boolean): void { - const id = asMaybeUint64Cls(assetId) ?? asUint64Cls((assetId as AssetType).id) + updateAssetHolding(account: AccountType, assetId: Uint64Compat | AssetType, balance?: Uint64Compat, frozen?: boolean): void { + const id = (asMaybeUint64Cls(assetId) ?? asUint64Cls((assetId as AssetType).id)).asAlgoTs() const accountData = this.accountDataMap.get(account)! const asset = this.assetDataMap.get(id)! const holding = accountData.optedAssets.get(id) ?? new AssetHolding(0n, asset.defaultFrozen) @@ -228,7 +239,7 @@ export class LedgerContext { * @param index - The block index. * @param data - The partial block data. */ - patchBlockData(index: StubUint64Compat, data: Partial): void { + patchBlockData(index: Uint64Compat, data: Partial): void { const i = asUint64(index) const blockData = this.blocks.get(i) ?? new BlockData() this.blocks.set(i, { @@ -243,7 +254,7 @@ export class LedgerContext { * @returns The block data. * @throws If the block is not set. */ - getBlockData(index: StubUint64Compat): BlockData { + getBlockData(index: Uint64Compat): BlockData { const i = asBigInt(index) if (this.blocks.has(i)) { return this.blocks.get(i)! @@ -257,7 +268,7 @@ export class LedgerContext { * @param key - The key. * @returns The global state and a boolean indicating if it was found. */ - getGlobalState(app: ApplicationType | BaseContract, key: StubBytesCompat): [GlobalStateCls, true] | [undefined, false] { + getGlobalState(app: ApplicationType | BaseContract, key: BytesCompat): [GlobalStateCls, true] | [undefined, false] { const appId = this.getAppId(app) const appData = this.applicationDataMap.get(appId) if (!appData?.application.globalStates.has(key)) { @@ -272,7 +283,7 @@ export class LedgerContext { * @param key - The key. * @param value - The value (optional). */ - setGlobalState(app: ApplicationType | BaseContract, key: StubBytesCompat, value: StubUint64Compat | StubBytesCompat | undefined): void { + setGlobalState(app: ApplicationType | BaseContract, key: BytesCompat, value: Uint64Compat | BytesCompat | undefined): void { const appId = this.getAppId(app) const appData = this.applicationDataMap.getOrFail(appId) const globalState = appData.application.globalStates.get(key) @@ -295,9 +306,9 @@ export class LedgerContext { getLocalState( app: ApplicationType | BaseContract | uint64, account: AccountType, - key: StubBytesCompat, + key: BytesCompat, ): [LocalStateForAccount, true] | [undefined, false] { - const appId = app instanceof Uint64Cls ? app : this.getAppId(app as ApplicationType | BaseContract) + const appId = app instanceof Uint64Cls ? app.asAlgoTs() : this.getAppId(app as ApplicationType | BaseContract) const appData = this.applicationDataMap.get(appId) if (!appData?.application.localStates.has(key)) { return [undefined, false] @@ -314,8 +325,8 @@ export class LedgerContext { * @param key - The key. * @param value - The value (optional). */ - setLocalState(app: ApplicationType | BaseContract | uint64, account: AccountType, key: StubBytesCompat, value: T | undefined): void { - const appId = app instanceof Uint64Cls ? app : this.getAppId(app as ApplicationType | BaseContract) + setLocalState(app: ApplicationType | BaseContract | uint64, account: AccountType, key: BytesCompat, value: T | undefined): void { + const appId = app instanceof Uint64Cls ? app.asAlgoTs() : this.getAppId(app as ApplicationType | BaseContract) const appData = this.applicationDataMap.getOrFail(appId) if (!appData.application.localStateMaps.has(key)) { appData.application.localStateMaps.set(key, new AccountMap()) @@ -338,7 +349,7 @@ export class LedgerContext { * @param key - The key. * @returns The box data. */ - getBox(app: ApplicationType | BaseContract, key: StubBytesCompat): Uint8Array { + getBox(app: ApplicationType | BaseContract, key: BytesCompat): Uint8Array { const appId = this.getAppId(app) const appData = this.applicationDataMap.getOrFail(appId) const materialised = appData.application.materialisedBoxes.get(key) @@ -355,7 +366,7 @@ export class LedgerContext { * @param key - The key. * @returns The materialised box data if exists or undefined. */ - getMaterialisedBox(app: ApplicationType | BaseContract, key: StubBytesCompat): T | undefined { + getMaterialisedBox(app: ApplicationType | BaseContract, key: BytesCompat): T | undefined { const appId = this.getAppId(app) const appData = this.applicationDataMap.getOrFail(appId) return appData.application.materialisedBoxes.get(key) as T | undefined @@ -367,7 +378,7 @@ export class LedgerContext { * @param key - The key. * @param value - The box data. */ - setBox(app: ApplicationType | BaseContract, key: StubBytesCompat, value: StubBytesCompat | Uint8Array): void { + setBox(app: ApplicationType | BaseContract, key: BytesCompat, value: BytesCompat | Uint8Array): void { const appId = this.getAppId(app) const appData = this.applicationDataMap.getOrFail(appId) const uint8ArrayValue = value instanceof Uint8Array ? value : asUint8Array(value) @@ -383,7 +394,7 @@ export class LedgerContext { * @param key - The key. * @param value - The box data. */ - setMatrialisedBox(app: ApplicationType | BaseContract, key: StubBytesCompat, value: TValue | undefined): void { + setMaterialisedBox(app: ApplicationType | BaseContract, key: BytesCompat, value: TValue | undefined): void { const appId = this.getAppId(app) const appData = this.applicationDataMap.getOrFail(appId) appData.application.materialisedBoxes.set(key, value) @@ -395,7 +406,7 @@ export class LedgerContext { * @param key - The key. * @returns True if the box was deleted, false otherwise. */ - deleteBox(app: ApplicationType | BaseContract, key: StubBytesCompat): boolean { + deleteBox(app: ApplicationType | BaseContract, key: BytesCompat): boolean { const appId = this.getAppId(app) const appData = this.applicationDataMap.getOrFail(appId) appData.application.materialisedBoxes.delete(key) @@ -408,12 +419,13 @@ export class LedgerContext { * @param key - The key. * @returns True if the box exists, false otherwise. */ - boxExists(app: ApplicationType | BaseContract, key: StubBytesCompat): boolean { + boxExists(app: ApplicationType | BaseContract, key: BytesCompat): boolean { const appId = this.getAppId(app) const appData = this.applicationDataMap.getOrFail(appId) return appData.application.boxes.has(key) } + /** @internal */ private getAppId(app: ApplicationType | BaseContract): uint64 { return app instanceof ApplicationCls ? app.id : this.getApplicationForContract(app as BaseContract).id } diff --git a/src/subcontexts/transaction-context.ts b/src/subcontexts/transaction-context.ts index ca14585c..90a6f058 100644 --- a/src/subcontexts/transaction-context.ts +++ b/src/subcontexts/transaction-context.ts @@ -1,4 +1,4 @@ -import type { bytes, Contract, uint64 } from '@algorandfoundation/algorand-typescript' +import type { bytes, Contract, uint64, Uint64Compat } from '@algorandfoundation/algorand-typescript' import { OnCompleteAction, TransactionType } from '@algorandfoundation/algorand-typescript' import { getContractAbiMetadata, type AbiMetadata } from '../abi-metadata' import { TRANSACTION_GROUP_MAX_SIZE } from '../constants' @@ -17,7 +17,7 @@ import type { } from '../impl/inner-transactions' import { ApplicationCallInnerTxnContext, createInnerTxn, ItxnParams } from '../impl/inner-transactions' import type { InnerTxn, InnerTxnFields } from '../impl/itxn' -import type { StubBytesCompat, StubUint64Compat } from '../impl/primitives' +import type { StubBytesCompat } from '../impl/primitives' import type { AllTransactionFields, ApplicationCallTransaction, @@ -57,6 +57,7 @@ interface ExecutionScope { * Represents a deferred application call. */ export class DeferredAppCall { + /** @internal */ constructor( private readonly appId: uint64, readonly txns: Transaction[], @@ -231,12 +232,15 @@ export class TransactionContext { * Represents a group of transactions. */ export class TransactionGroup { - activeTransactionIndex: number latestTimestamp: number transactions: Transaction[] itxnGroups: ItxnGroup[] = [] + /** @internal */ + activeTransactionIndex: number + /** @internal */ constructingItxnGroup: InnerTxnFields[] = [] + /** @internal */ constructor(transactions: Transaction[], activeTransactionIndex?: number) { this.latestTimestamp = Date.now() if (transactions.length > TRANSACTION_GROUP_MAX_SIZE) { @@ -290,7 +294,7 @@ export class TransactionGroup { * @param index The index of the scratch slot. * @returns The scratch slot value. */ - getScratchSlot(index: StubUint64Compat): bytes | uint64 { + getScratchSlot(index: Uint64Compat): bytes | uint64 { return this.activeTransaction.getScratchSlot(index) } @@ -384,7 +388,7 @@ export class TransactionGroup { * @returns The inner transaction group. * @throws If the index is invalid or there are no previous inner transactions. */ - getItxnGroup(index?: StubUint64Compat): ItxnGroup { + getItxnGroup(index?: Uint64Compat): ItxnGroup { const i = index !== undefined ? asNumber(index) : undefined invariant(this.itxnGroups.length > 0, 'no previous inner transactions') @@ -402,7 +406,7 @@ export class TransactionGroup { * @param index The index of the transaction. * @returns The application transaction. */ - getApplicationCallTransaction(index?: StubUint64Compat): ApplicationCallTransaction { + getApplicationCallTransaction(index?: Uint64Compat): ApplicationCallTransaction { return this._getTransaction({ type: TransactionType.ApplicationCall, index }) as ApplicationCallTransaction } @@ -411,7 +415,7 @@ export class TransactionGroup { * @param index The index of the transaction. * @returns The asset configuration transaction. */ - getAssetConfigTransaction(index?: StubUint64Compat): AssetConfigTransaction { + getAssetConfigTransaction(index?: Uint64Compat): AssetConfigTransaction { return this._getTransaction({ type: TransactionType.AssetConfig, index }) as AssetConfigTransaction } @@ -420,7 +424,7 @@ export class TransactionGroup { * @param index The index of the transaction. * @returns The asset transfer transaction. */ - getAssetTransferTransaction(index?: StubUint64Compat): AssetTransferTransaction { + getAssetTransferTransaction(index?: Uint64Compat): AssetTransferTransaction { return this._getTransaction({ type: TransactionType.AssetTransfer, index }) as AssetTransferTransaction } @@ -429,7 +433,7 @@ export class TransactionGroup { * @param index The index of the transaction. * @returns The asset freeze transaction. */ - getAssetFreezeTransaction(index?: StubUint64Compat): AssetFreezeTransaction { + getAssetFreezeTransaction(index?: Uint64Compat): AssetFreezeTransaction { return this._getTransaction({ type: TransactionType.AssetFreeze, index }) as AssetFreezeTransaction } @@ -438,7 +442,7 @@ export class TransactionGroup { * @param index The index of the transaction. * @returns The key registration transaction. */ - getKeyRegistrationTransaction(index?: StubUint64Compat): KeyRegistrationTransaction { + getKeyRegistrationTransaction(index?: Uint64Compat): KeyRegistrationTransaction { return this._getTransaction({ type: TransactionType.KeyRegistration, index }) as KeyRegistrationTransaction } @@ -447,7 +451,7 @@ export class TransactionGroup { * @param index The index of the transaction. * @returns The payment transaction. */ - getPaymentTransaction(index?: StubUint64Compat): PaymentTransaction { + getPaymentTransaction(index?: Uint64Compat): PaymentTransaction { return this._getTransaction({ type: TransactionType.Payment, index }) as PaymentTransaction } @@ -456,10 +460,11 @@ export class TransactionGroup { * @param index The index of the transaction. * @returns The transaction. */ - getTransaction(index?: StubUint64Compat): Transaction { + getTransaction(index?: Uint64Compat): Transaction { return this._getTransaction({ index }) } - private _getTransaction({ type, index }: { type?: TransactionType; index?: StubUint64Compat }) { + /** @internal */ + private _getTransaction({ type, index }: { type?: TransactionType; index?: Uint64Compat }) { const i = index !== undefined ? asNumber(index) : undefined if (i !== undefined && i >= lazyContext.activeGroup.transactions.length) { throw new InternalError('Invalid group index') @@ -495,6 +500,7 @@ export class TransactionGroup { */ export class ItxnGroup { itxns: InnerTxn[] = [] + /** @internal */ constructor(itxns: InnerTxn[]) { this.itxns = itxns } @@ -504,7 +510,7 @@ export class ItxnGroup { * @param index The index of the transaction. * @returns The application inner transaction. */ - getApplicationCallInnerTxn(index?: StubUint64Compat): ApplicationCallInnerTxn { + getApplicationCallInnerTxn(index?: Uint64Compat): ApplicationCallInnerTxn { return this._getInnerTxn({ type: TransactionType.ApplicationCall, index }) as ApplicationCallInnerTxn } @@ -513,7 +519,7 @@ export class ItxnGroup { * @param index The index of the transaction. * @returns The asset configuration inner transaction. */ - getAssetConfigInnerTxn(index?: StubUint64Compat): AssetConfigInnerTxn { + getAssetConfigInnerTxn(index?: Uint64Compat): AssetConfigInnerTxn { return this._getInnerTxn({ type: TransactionType.AssetConfig, index }) as AssetConfigInnerTxn } @@ -522,7 +528,7 @@ export class ItxnGroup { * @param index The index of the transaction. * @returns The asset transfer inner transaction. */ - getAssetTransferInnerTxn(index?: StubUint64Compat): AssetTransferInnerTxn { + getAssetTransferInnerTxn(index?: Uint64Compat): AssetTransferInnerTxn { return this._getInnerTxn({ type: TransactionType.AssetTransfer, index }) as AssetTransferInnerTxn } @@ -531,7 +537,7 @@ export class ItxnGroup { * @param index The index of the transaction. * @returns The asset freeze inner transaction. */ - getAssetFreezeInnerTxn(index?: StubUint64Compat): AssetFreezeInnerTxn { + getAssetFreezeInnerTxn(index?: Uint64Compat): AssetFreezeInnerTxn { return this._getInnerTxn({ type: TransactionType.AssetFreeze, index }) as AssetFreezeInnerTxn } @@ -540,7 +546,7 @@ export class ItxnGroup { * @param index The index of the transaction. * @returns The key registration inner transaction. */ - getKeyRegistrationInnerTxn(index?: StubUint64Compat): KeyRegistrationInnerTxn { + getKeyRegistrationInnerTxn(index?: Uint64Compat): KeyRegistrationInnerTxn { return this._getInnerTxn({ type: TransactionType.KeyRegistration, index }) as KeyRegistrationInnerTxn } @@ -549,7 +555,7 @@ export class ItxnGroup { * @param index The index of the transaction. * @returns The payment inner transaction. */ - getPaymentInnerTxn(index?: StubUint64Compat): PaymentInnerTxn { + getPaymentInnerTxn(index?: Uint64Compat): PaymentInnerTxn { return this._getInnerTxn({ type: TransactionType.Payment, index }) as PaymentInnerTxn } @@ -558,11 +564,12 @@ export class ItxnGroup { * @param index The index of the transaction. * @returns The inner transaction. */ - getInnerTxn(index?: StubUint64Compat): InnerTxn { + getInnerTxn(index?: Uint64Compat): InnerTxn { return this._getInnerTxn({ index }) } - private _getInnerTxn({ type, index }: { type?: TransactionType; index?: StubUint64Compat }) { + /** @internal */ + private _getInnerTxn({ type, index }: { type?: TransactionType; index?: Uint64Compat }) { invariant(this.itxns.length > 0, 'no previous inner transactions') const i = index !== undefined ? asNumber(index) : undefined if (i !== undefined && i >= this.itxns.length) { diff --git a/src/test-transformer/errors.ts b/src/test-transformer/errors.ts index 5f2f7d76..5a3f9f51 100644 --- a/src/test-transformer/errors.ts +++ b/src/test-transformer/errors.ts @@ -1,3 +1,4 @@ +/** @internal */ export class TransformerError extends Error { constructor(message: string) { super(message) diff --git a/src/test-transformer/helpers.ts b/src/test-transformer/helpers.ts index 51b17160..ef70c03c 100644 --- a/src/test-transformer/helpers.ts +++ b/src/test-transformer/helpers.ts @@ -1,6 +1,7 @@ import ts from 'typescript' import { TransformerError } from './errors' +/** @internal */ export const getPropertyNameAsString = (name: ts.PropertyName): ts.Identifier | ts.StringLiteral | ts.NoSubstitutionTemplateLiteral => { if (ts.isStringLiteralLike(name)) { return name @@ -11,4 +12,5 @@ export const getPropertyNameAsString = (name: ts.PropertyName): ts.Identifier | throw new TransformerError(`Node ${name.kind} cannot be converted to a static string`) } +/** @internal */ export const trimGenericTypeName = (typeName: string) => typeName.replace(/<.*>/, '') diff --git a/src/test-transformer/node-factory.ts b/src/test-transformer/node-factory.ts index 63e7fb63..7a3b9986 100644 --- a/src/test-transformer/node-factory.ts +++ b/src/test-transformer/node-factory.ts @@ -5,6 +5,7 @@ import type { DeliberateAny } from '../typescript-helpers' import { getPropertyNameAsString, trimGenericTypeName } from './helpers' const factory = ts.factory +/** @internal */ export const nodeFactory = { importHelpers(testingPackageName: string) { return [ diff --git a/src/test-transformer/program-factory.ts b/src/test-transformer/program-factory.ts index c9ceda7f..d00fe652 100644 --- a/src/test-transformer/program-factory.ts +++ b/src/test-transformer/program-factory.ts @@ -6,11 +6,13 @@ export interface TransformerConfig { includeExt: string[] testingPackageName: string } +/** @internal */ export const defaultTransformerConfig: TransformerConfig = { includeExt: ['.algo.ts', '.algo.spec.ts', '.algo.test.ts'], testingPackageName: '@algorandfoundation/algorand-typescript-testing', } +/** @internal */ export function programFactory(config: TransformerConfig, program: ts.Program): ts.TransformerFactory { registerPTypes(typeRegistry) return (context) => { diff --git a/src/test-transformer/supported-binary-op-string.ts b/src/test-transformer/supported-binary-op-string.ts index 7f51847a..555cecff 100644 --- a/src/test-transformer/supported-binary-op-string.ts +++ b/src/test-transformer/supported-binary-op-string.ts @@ -1,6 +1,7 @@ import type { BinaryOperator, PrefixUnaryOperator } from 'typescript' import ts from 'typescript' +/** @internal */ export function supportedBinaryOpString(x: BinaryOperator): string | undefined { switch (x) { case ts.SyntaxKind.MinusToken: @@ -60,6 +61,7 @@ export function supportedBinaryOpString(x: BinaryOperator): string | undefined { } } +/** @internal */ export function supportedAugmentedAssignmentBinaryOpString(x: BinaryOperator): string | undefined { switch (x) { case ts.SyntaxKind.PlusEqualsToken: @@ -79,6 +81,7 @@ export function supportedAugmentedAssignmentBinaryOpString(x: BinaryOperator): s } } +/** @internal */ export function supportedPrefixUnaryOpString(x: PrefixUnaryOperator): string | undefined { switch (x) { case ts.SyntaxKind.TildeToken: diff --git a/src/test-transformer/visitors.ts b/src/test-transformer/visitors.ts index cbeb9526..02aca3d9 100644 --- a/src/test-transformer/visitors.ts +++ b/src/test-transformer/visitors.ts @@ -39,6 +39,7 @@ type VisitorHelper = { getConfig(): TransformerConfig } +/** @internal */ export class SourceFileVisitor { private helper: VisitorHelper diff --git a/src/util.ts b/src/util.ts index 24aa9509..41c006e3 100644 --- a/src/util.ts +++ b/src/util.ts @@ -46,31 +46,42 @@ export function toExternalValue(val: uint64 | biguint | bytes | string) { if (typeof val === 'string') return val } +/** @internal */ export function* iterBigInt(start: bigint, end: bigint): Generator { for (let i = start; i < end; i++) { yield BigInt(i) } } +/** @internal */ export const asBigInt = (v: StubUint64Compat): bigint => asUint64Cls(v).asBigInt() +/** @internal */ export const asNumber = (v: StubUint64Compat): number => asUint64Cls(v).asNumber() +/** @internal */ export const asUint64Cls = (val: StubUint64Compat) => Uint64Cls.fromCompat(val) +/** @internal */ export const asBigUintCls = (val: StubBigUintCompat | Uint8Array) => BigUintCls.fromCompat(val instanceof Uint8Array ? asBytes(val) : Array.isArray(val) ? asBytes(new Uint8Array(val)) : val) +/** @internal */ export const asBytesCls = (val: StubBytesCompat | Uint8Array) => BytesCls.fromCompat(val) +/** @internal */ export const asUint64 = (val: StubUint64Compat) => asUint64Cls(val).asAlgoTs() +/** @internal */ export const asBigUint = (val: StubBigUintCompat | Uint8Array) => asBigUintCls(val).asAlgoTs() +/** @internal */ export const asBytes = (val: StubBytesCompat | Uint8Array) => asBytesCls(val).asAlgoTs() +/** @internal */ export const asUint8Array = (val: StubBytesCompat | Uint8Array) => asBytesCls(val).asUint8Array() +/** @internal */ export const asMaybeUint64Cls = (val: DeliberateAny, throwsOverflow: boolean = true) => { try { return Uint64Cls.fromCompat(val) @@ -86,6 +97,7 @@ export const asMaybeUint64Cls = (val: DeliberateAny, throwsOverflow: boolean = t return undefined } +/** @internal */ export const asMaybeBigUintCls = (val: DeliberateAny) => { try { return BigUintCls.fromCompat(val) @@ -98,6 +110,7 @@ export const asMaybeBigUintCls = (val: DeliberateAny) => { } return undefined } +/** @internal */ export const asMaybeBytesCls = (val: DeliberateAny) => { try { return BytesCls.fromCompat(val) @@ -111,13 +124,16 @@ export const asMaybeBytesCls = (val: DeliberateAny) => { return undefined } +/** @internal */ export const binaryStringToBytes = (s: string): BytesCls => BytesCls.fromCompat(new Uint8Array(s.match(/.{1,8}/g)!.map((x) => parseInt(x, 2)))) +/** @internal */ export const getRandomNumber = (min: number, max: number): number => { return Math.floor(Math.random() * (max - min + 1)) + min } +/** @internal */ export const getRandomBigInt = (min: number | bigint, max: number | bigint): bigint => { const bigIntMin = BigInt(min) const bigIntMax = BigInt(max) @@ -127,14 +143,17 @@ export const getRandomBigInt = (min: number | bigint, max: number | bigint): big return (randomValue % (bigIntMax - bigIntMin)) + bigIntMin } +/** @internal */ export const getRandomBytes = (length: number): BytesCls => asBytesCls(Bytes(randomBytes(length))) +/** @internal */ export const flattenAsBytes = (arr: StubBytesCompat | StubBytesCompat[]): bytes => { return (Array.isArray(arr) ? arr : [arr]).map((x) => asBytes(x)).reduce((acc, x) => acc.concat(x), Bytes()) } const NoValue = Symbol('no-value') type LazyInstance = () => T +/** @internal */ export const Lazy = (factory: () => T): LazyInstance => { let val: T | typeof NoValue = NoValue @@ -148,6 +167,7 @@ export const Lazy = (factory: () => T): LazyInstance => { const ObjectReferenceSymbol = Symbol('ObjectReference') const objectRefIter = iterBigInt(1001n, MAX_UINT512) +/** @internal */ export const getObjectReference = (obj: DeliberateAny): bigint => { const tryGetReference = (obj: DeliberateAny): bigint | undefined => { const s = Object.getOwnPropertySymbols(obj).find((s) => s.toString() === ObjectReferenceSymbol.toString()) @@ -167,6 +187,7 @@ export const getObjectReference = (obj: DeliberateAny): bigint => { return ref } +/** @internal */ export const combineIntoMaxBytePages = (pages: bytes[]): bytes[] => { const combined = pages.reduce((acc, x) => acc.concat(x), asBytesCls('')) const totalPages = (asNumber(combined.length) + MAX_BYTES_SIZE - 1) / MAX_BYTES_SIZE @@ -180,6 +201,7 @@ export const combineIntoMaxBytePages = (pages: bytes[]): bytes[] => { return result } +/** @internal */ export const conactUint8Arrays = (...values: Uint8Array[]): Uint8Array => { const result = new Uint8Array(values.reduce((acc, value) => acc + value.length, 0)) let index = 0 @@ -190,6 +212,7 @@ export const conactUint8Arrays = (...values: Uint8Array[]): Uint8Array => { return result } +/** @internal */ export const uint8ArrayToNumber = (value: Uint8Array): number => { return value.reduce((acc, x) => acc * 256 + x, 0) } @@ -209,6 +232,7 @@ export const uint8ArrayToNumber = (value: Uint8Array): number => { * assert(false, "This will throw"); // throws AssertError: This will throw * ``` */ +/** @internal */ export function assert(condition: unknown, message?: string): asserts condition { if (!condition) { throw new AssertError(message ?? 'Assertion failed') @@ -230,6 +254,7 @@ export function assert(condition: unknown, message?: string): asserts condition * } * ``` */ +/** @internal */ export function err(message?: string): never { throw new AvmError(message ?? 'err opcode executed') } diff --git a/src/value-generators/avm.ts b/src/value-generators/avm.ts index 526561c0..1dcd4ec8 100644 --- a/src/value-generators/avm.ts +++ b/src/value-generators/avm.ts @@ -3,39 +3,41 @@ import type { Application as ApplicationType, Asset as AssetType, biguint, + BigUintCompat, bytes, uint64, + Uint64Compat, } from '@algorandfoundation/algorand-typescript' import { randomBytes } from 'crypto' import { MAX_BYTES_SIZE, MAX_UINT512, MAX_UINT64 } from '../constants' import { lazyContext } from '../context-helpers/internal-context' import { InternalError } from '../errors' -import { BigUint, Bytes, Uint64, type StubBigUintCompat, type StubUint64Compat } from '../impl/primitives' +import { BigUint, Bytes, Uint64 } from '../impl/primitives' import type { AssetData } from '../impl/reference' import { Account, AccountData, ApplicationCls, ApplicationData, AssetCls, getDefaultAssetData } from '../impl/reference' -import { asBigInt, asBigUintCls, asUint64Cls, getRandomBigInt, getRandomBytes } from '../util' +import { asBigInt, asBigUintCls, asUint64, asUint64Cls, getRandomBigInt, getRandomBytes } from '../util' type AccountContextData = Partial & { address?: bytes incentiveEligible?: boolean lastProposed?: uint64 lastHeartbeat?: uint64 - optedAssetBalances?: Map + optedAssetBalances?: Map optedApplications?: ApplicationType[] } -type AssetContextData = Partial & { assetId?: StubUint64Compat } +type AssetContextData = Partial & { assetId?: Uint64Compat } -type ApplicationContextData = Partial & { applicationId?: StubUint64Compat } +type ApplicationContextData = Partial & { applicationId?: Uint64Compat } export class AvmValueGenerator { /** * Generates a random uint64 value within the specified range. - * @param {StubUint64Compat} [minValue=0n] - The minimum value (inclusive). - * @param {StubUint64Compat} [maxValue=MAX_UINT64] - The maximum value (inclusive). + * @param {Uint64Compat} [minValue=0n] - The minimum value (inclusive). + * @param {Uint64Compat} [maxValue=MAX_UINT64] - The maximum value (inclusive). * @returns {uint64} - A random uint64 value. */ - uint64(minValue: StubUint64Compat = 0n, maxValue: StubUint64Compat = MAX_UINT64): uint64 { + uint64(minValue: Uint64Compat = 0n, maxValue: Uint64Compat = MAX_UINT64): uint64 { const min = asBigInt(minValue) const max = asBigInt(maxValue) if (max > MAX_UINT64) { @@ -52,10 +54,10 @@ export class AvmValueGenerator { /** * Generates a random biguint value within the specified range. - * @param {StubBigUintCompat} [minValue=0n] - The minimum value (inclusive). + * @param {BigUintCompat} [minValue=0n] - The minimum value (inclusive). * @returns {biguint} - A random biguint value. */ - biguint(minValue: StubBigUintCompat = 0n): biguint { + biguint(minValue: BigUintCompat = 0n): biguint { const min = asBigUintCls(minValue).asBigInt() if (min < 0n) { throw new InternalError('minValue must be greater than or equal to 0') @@ -132,17 +134,17 @@ export class AvmValueGenerator { */ asset(input?: AssetContextData): AssetType { const id = input?.assetId - if (id && lazyContext.ledger.assetDataMap.has(id)) { + if (id && lazyContext.ledger.assetDataMap.has(asUint64(id))) { throw new InternalError('Asset with such ID already exists in testing context!') } - const assetId = asUint64Cls(id ?? lazyContext.ledger.assetIdIter.next().value) + const assetId = asUint64Cls(id ?? lazyContext.ledger.assetIdIter.next().value).asAlgoTs() const defaultAssetData = getDefaultAssetData() const { assetId: _, ...assetData } = input ?? {} lazyContext.ledger.assetDataMap.set(assetId, { ...defaultAssetData, ...assetData, }) - return new AssetCls(assetId.asAlgoTs()) + return new AssetCls(assetId) } /** @@ -155,7 +157,7 @@ export class AvmValueGenerator { if (id && lazyContext.ledger.applicationDataMap.has(id)) { throw new InternalError('Application with such ID already exists in testing context!') } - const applicationId = asUint64Cls(id ?? lazyContext.ledger.appIdIter.next().value) + const applicationId = asUint64Cls(id ?? lazyContext.ledger.appIdIter.next().value).asAlgoTs() const data = new ApplicationData() const { applicationId: _, ...applicationData } = input ?? {} data.application = { @@ -163,6 +165,6 @@ export class AvmValueGenerator { ...applicationData, } lazyContext.ledger.applicationDataMap.set(applicationId, data) - return new ApplicationCls(applicationId.asAlgoTs()) + return new ApplicationCls(applicationId) } } diff --git a/src/value-generators/txn.ts b/src/value-generators/txn.ts index 9864632e..1be276ae 100644 --- a/src/value-generators/txn.ts +++ b/src/value-generators/txn.ts @@ -1,4 +1,4 @@ -import type { Application as ApplicationType, gtxn } from '@algorandfoundation/algorand-typescript' +import type { Application as ApplicationType, BaseContract as BaseContractType, gtxn } from '@algorandfoundation/algorand-typescript' import { lazyContext } from '../context-helpers/internal-context' import { InternalError } from '../errors' import { BaseContract } from '../impl/base-contract' @@ -20,7 +20,7 @@ export class TxnValueGenerator { * @returns {ApplicationCallTransaction} - A random application call transaction. */ applicationCall( - fields?: Partial & { appId: ApplicationType | BaseContract }>, + fields?: Partial & { appId: ApplicationType | BaseContractType }>, ): ApplicationCallTransaction { const params = fields ?? {} let appId = From b18ef44c74b8ea61f6a2b9cdba2a8ad15a477309 Mon Sep 17 00:00:00 2001 From: Bobby Lat Date: Wed, 13 Aug 2025 17:38:34 +0700 Subject: [PATCH 36/68] refactor: remove checked in generated doc and publish html doc to github pages --- .github/workflows/gh-pages.yml | 35 + .gitignore | 1 + docs/algots.md | 4 + docs/api.md | 26 +- docs/code/README.md | 18 - docs/code/index/README.md | 22 - docs/code/index/classes/ApplicationSpy.md | 170 ---- docs/code/index/classes/AssertError.md | 157 ---- docs/code/index/classes/AvmError.md | 162 ---- docs/code/index/classes/CodeError.md | 161 ---- docs/code/index/classes/InternalError.md | 161 ---- .../code/index/classes/NotImplementedError.md | 155 ---- .../index/classes/TestExecutionContext.md | 424 --------- .../index/functions/addEqualityTesters.md | 42 - docs/code/index/functions/toExternalValue.md | 135 --- .../subcontexts/contract-context/README.md | 11 - .../classes/ContractContext.md | 124 --- .../code/subcontexts/ledger-context/README.md | 11 - .../ledger-context/classes/LedgerContext.md | 832 ------------------ .../subcontexts/transaction-context/README.md | 14 - .../classes/DeferredAppCall.md | 79 -- .../transaction-context/classes/ItxnGroup.md | 205 ----- .../classes/TransactionContext.md | 273 ------ .../classes/TransactionGroup.md | 485 ---------- .../jest-transformer/README.md | 13 - .../jest-transformer/variables/factory.md | 52 -- .../jest-transformer/variables/name.md | 11 - .../jest-transformer/variables/version.md | 11 - .../vitest-transformer/README.md | 11 - .../variables/puyaTsTransformer.md | 50 -- docs/code/value-generators/README.md | 11 - docs/code/value-generators/arc4/README.md | 11 - .../arc4/classes/Arc4ValueGenerator.md | 242 ----- docs/code/value-generators/avm/README.md | 11 - .../avm/classes/AvmValueGenerator.md | 197 ----- .../classes/ValueGenerator.md | 247 ------ docs/code/value-generators/txn/README.md | 11 - .../txn/classes/TxnValueGenerator.md | 163 ---- docs/coverage.md | 6 +- docs/examples.md | 4 + docs/faq.md | 10 +- docs/readme.md | 293 ++++++ .../index.md => testing-guide.md} | 31 +- ...plication-spy.md => tg-application-spy.md} | 4 + .../arc4-types.md => tg-arc4-types.md} | 8 +- .../avm-types.md => tg-avm-types.md} | 8 +- .../concepts.md => tg-concepts.md} | 24 +- ...ract-testing.md => tg-contract-testing.md} | 12 +- .../opcodes.md => tg-opcodes.md} | 30 +- ...ure-testing.md => tg-signature-testing.md} | 6 +- ...e-management.md => tg-state-management.md} | 6 +- .../transactions.md => tg-transactions.md} | 14 +- package-lock.json | 87 +- package.json | 5 +- src/application-spy.ts | 16 +- src/impl/inner-transactions.ts | 34 +- src/impl/transactions.ts | 19 +- src/subcontexts/contract-context.ts | 12 +- src/test-execution-context.ts | 2 +- src/value-generators/arc4.ts | 42 +- src/value-generators/index.ts | 1 + typedoc.json | 28 +- 62 files changed, 609 insertions(+), 4841 deletions(-) create mode 100644 .github/workflows/gh-pages.yml delete mode 100644 docs/code/README.md delete mode 100644 docs/code/index/README.md delete mode 100644 docs/code/index/classes/ApplicationSpy.md delete mode 100644 docs/code/index/classes/AssertError.md delete mode 100644 docs/code/index/classes/AvmError.md delete mode 100644 docs/code/index/classes/CodeError.md delete mode 100644 docs/code/index/classes/InternalError.md delete mode 100644 docs/code/index/classes/NotImplementedError.md delete mode 100644 docs/code/index/classes/TestExecutionContext.md delete mode 100644 docs/code/index/functions/addEqualityTesters.md delete mode 100644 docs/code/index/functions/toExternalValue.md delete mode 100644 docs/code/subcontexts/contract-context/README.md delete mode 100644 docs/code/subcontexts/contract-context/classes/ContractContext.md delete mode 100644 docs/code/subcontexts/ledger-context/README.md delete mode 100644 docs/code/subcontexts/ledger-context/classes/LedgerContext.md delete mode 100644 docs/code/subcontexts/transaction-context/README.md delete mode 100644 docs/code/subcontexts/transaction-context/classes/DeferredAppCall.md delete mode 100644 docs/code/subcontexts/transaction-context/classes/ItxnGroup.md delete mode 100644 docs/code/subcontexts/transaction-context/classes/TransactionContext.md delete mode 100644 docs/code/subcontexts/transaction-context/classes/TransactionGroup.md delete mode 100644 docs/code/test-transformer/jest-transformer/README.md delete mode 100644 docs/code/test-transformer/jest-transformer/variables/factory.md delete mode 100644 docs/code/test-transformer/jest-transformer/variables/name.md delete mode 100644 docs/code/test-transformer/jest-transformer/variables/version.md delete mode 100644 docs/code/test-transformer/vitest-transformer/README.md delete mode 100644 docs/code/test-transformer/vitest-transformer/variables/puyaTsTransformer.md delete mode 100644 docs/code/value-generators/README.md delete mode 100644 docs/code/value-generators/arc4/README.md delete mode 100644 docs/code/value-generators/arc4/classes/Arc4ValueGenerator.md delete mode 100644 docs/code/value-generators/avm/README.md delete mode 100644 docs/code/value-generators/avm/classes/AvmValueGenerator.md delete mode 100644 docs/code/value-generators/classes/ValueGenerator.md delete mode 100644 docs/code/value-generators/txn/README.md delete mode 100644 docs/code/value-generators/txn/classes/TxnValueGenerator.md create mode 100644 docs/readme.md rename docs/{testing-guide/index.md => testing-guide.md} (78%) rename docs/{testing-guide/application-spy.md => tg-application-spy.md} (99%) rename docs/{testing-guide/arc4-types.md => tg-arc4-types.md} (98%) rename docs/{testing-guide/avm-types.md => tg-avm-types.md} (99%) rename docs/{testing-guide/concepts.md => tg-concepts.md} (77%) rename docs/{testing-guide/contract-testing.md => tg-contract-testing.md} (96%) rename docs/{testing-guide/opcodes.md => tg-opcodes.md} (97%) rename docs/{testing-guide/signature-testing.md => tg-signature-testing.md} (93%) rename docs/{testing-guide/state-management.md => tg-state-management.md} (95%) rename docs/{testing-guide/transactions.md => tg-transactions.md} (95%) diff --git a/.github/workflows/gh-pages.yml b/.github/workflows/gh-pages.yml new file mode 100644 index 00000000..895b0c9a --- /dev/null +++ b/.github/workflows/gh-pages.yml @@ -0,0 +1,35 @@ +name: 'Run typedoc and publish to pages' + +on: + push: + branches: + - main +jobs: + build-and-publish-docs: + runs-on: ubuntu-latest + permissions: + contents: read + pages: write + id-token: write + steps: + - name: Checkout source code + uses: actions/checkout@v4 + + - name: Use Node.js 21.x + uses: actions/setup-node@v4 + with: + node-version: 21.x + + - name: Npm install + run: npm ci --ignore-scripts + + - name: Build doc + run: npm run script:documentation + + - name: Upload to GitHub pages + uses: actions/upload-pages-artifact@v3 + with: + path: docs/_html + + - name: Deploy to GitHub Pages + uses: actions/deploy-pages@v4 diff --git a/.gitignore b/.gitignore index 119c7fb8..4b408762 100644 --- a/.gitignore +++ b/.gitignore @@ -55,6 +55,7 @@ test-results.xml # Website & Code docs generation code-docs/ out/ +docs/_html # dotenv environment variable files .env diff --git a/docs/algots.md b/docs/algots.md index 7efd44f5..8a5b2661 100644 --- a/docs/algots.md +++ b/docs/algots.md @@ -1,3 +1,7 @@ +--- +title: Algorand TypeScript +--- + # Algorand TypeScript Algorand TypeScript is a partial implementation of the TypeScript programming language that runs on the Algorand Virtual Machine (AVM). It includes a statically typed framework for development of Algorand smart contracts and logic signatures, with TypeScript interfaces to underlying AVM functionality that works with standard TypeScript tooling. diff --git a/docs/api.md b/docs/api.md index c6ea0a7d..dda092bd 100644 --- a/docs/api.md +++ b/docs/api.md @@ -1,29 +1,33 @@ +--- +title: API Reference +--- + # API Reference An overview of the `algorand-typescript-testing` package - covering the main classes and functions. -```{hint} +``` Spotted a typo in documentation? This project is open source, please submit an issue or a PR on [GitHub](https://github.com/algorandfoundation/algorand-typescript-testing). ``` ## Contexts -- [TestExecutionContext](./code/index/classes/TestExecutionContext.md) -- [ContractContext](./code/subcontexts/contract-context/classes/ContractContext.md) -- [LedgerContext](./code/subcontexts/ledger-context/classes/LedgerContext.md) -- [TransactionContext](./code/subcontexts/transaction-context/classes/TransactionContext.md) +- [TestExecutionContext](../classes/index.TestExecutionContext.html) +- [ContractContext](../classes/index._internal_.ContractContext.html) +- [LedgerContext](../classes/index._internal_.LedgerContext.html) +- [TransactionContext](../classes/index._internal_.TransactionContext.html) ## Value Generators -- [AvmValueGenerator](./code/value-generators/avm/classes/AvmValueGenerator.md) -- [Arc4ValueGenerator](./code/value-generators/arc4/classes/Arc4ValueGenerator.md) -- [TxnValueGenerator](./code/value-generators/txn/classes/TxnValueGenerator.md) +- [AvmValueGenerator](../classes/value-generators._internal_.AvmValueGenerator.html) +- [Arc4ValueGenerator](../classes/value-generators._internal_.Arc4ValueGenerator.html) +- [TxnValueGenerator](../classes/value-generators._internal_.TxnValueGenerator.html) ## Utils -- [addEqualityTesters](./code/index/functions/addEqualityTesters.md) -- [encodingUtils](./code/index/variables/encodingUtil.md) +- [addEqualityTesters](../functions/index.addEqualityTesters.html) +- [toExternalValue](../functions/index.toExternalValue.html) ## Reference documentation -We also have [auto-generated reference documentation for the code](./code/README.md). +We also have [auto-generated reference documentation for the code](../modules/index.html). diff --git a/docs/code/README.md b/docs/code/README.md deleted file mode 100644 index a729f30b..00000000 --- a/docs/code/README.md +++ /dev/null @@ -1,18 +0,0 @@ -**@algorandfoundation/algorand-typescript-testing** - -*** - -# @algorandfoundation/algorand-typescript-testing - -## Modules - -- [index](index/README.md) -- [subcontexts/contract-context](subcontexts/contract-context/README.md) -- [subcontexts/ledger-context](subcontexts/ledger-context/README.md) -- [subcontexts/transaction-context](subcontexts/transaction-context/README.md) -- [test-transformer/jest-transformer](test-transformer/jest-transformer/README.md) -- [test-transformer/vitest-transformer](test-transformer/vitest-transformer/README.md) -- [value-generators](value-generators/README.md) -- [value-generators/arc4](value-generators/arc4/README.md) -- [value-generators/avm](value-generators/avm/README.md) -- [value-generators/txn](value-generators/txn/README.md) diff --git a/docs/code/index/README.md b/docs/code/index/README.md deleted file mode 100644 index 99345445..00000000 --- a/docs/code/index/README.md +++ /dev/null @@ -1,22 +0,0 @@ -[**@algorandfoundation/algorand-typescript-testing**](../README.md) - -*** - -[@algorandfoundation/algorand-typescript-testing](../README.md) / index - -# index - -## Classes - -- [ApplicationSpy](classes/ApplicationSpy.md) -- [AssertError](classes/AssertError.md) -- [AvmError](classes/AvmError.md) -- [CodeError](classes/CodeError.md) -- [InternalError](classes/InternalError.md) -- [NotImplementedError](classes/NotImplementedError.md) -- [TestExecutionContext](classes/TestExecutionContext.md) - -## Functions - -- [addEqualityTesters](functions/addEqualityTesters.md) -- [toExternalValue](functions/toExternalValue.md) diff --git a/docs/code/index/classes/ApplicationSpy.md b/docs/code/index/classes/ApplicationSpy.md deleted file mode 100644 index 7504b178..00000000 --- a/docs/code/index/classes/ApplicationSpy.md +++ /dev/null @@ -1,170 +0,0 @@ -[**@algorandfoundation/algorand-typescript-testing**](../../README.md) - -*** - -[@algorandfoundation/algorand-typescript-testing](../../README.md) / [index](../README.md) / ApplicationSpy - -# Class: ApplicationSpy\ - -Defined in: [src/application-spy.ts:34](https://github.com/algorandfoundation/algorand-typescript-testing/blob/main/src/application-spy.ts#L34) - -## Type Parameters - -### TContract - -`TContract` *extends* `Contract` = `Contract` - -## Constructors - -### Constructor - -> **new ApplicationSpy**\<`TContract`\>(`contract`?): `ApplicationSpy`\<`TContract`\> - -Defined in: [src/application-spy.ts:46](https://github.com/algorandfoundation/algorand-typescript-testing/blob/main/src/application-spy.ts#L46) - -#### Parameters - -##### contract? - -`TContract` | `ConstructorFor`\<`TContract`\> - -#### Returns - -`ApplicationSpy`\<`TContract`\> - -## Properties - -### contract? - -> `optional` **contract**: `TContract` \| `ConstructorFor`\<`TContract`\> - -Defined in: [src/application-spy.ts:44](https://github.com/algorandfoundation/algorand-typescript-testing/blob/main/src/application-spy.ts#L44) - -*** - -### on - -> `readonly` **on**: `_TypedApplicationSpyCallBacks`\<`TContract`\> - -Defined in: [src/application-spy.ts:41](https://github.com/algorandfoundation/algorand-typescript-testing/blob/main/src/application-spy.ts#L41) - -The `on` property is a proxy that allows you to register callbacks for specific method signatures. -It dynamically creates methods based on the contract's methods. - -## Methods - -### notify() - -> **notify**(`itxn`): `void` - -Defined in: [src/application-spy.ts:52](https://github.com/algorandfoundation/algorand-typescript-testing/blob/main/src/application-spy.ts#L52) - -#### Parameters - -##### itxn - -`ApplicationCallInnerTxnContext` - -#### Returns - -`void` - -*** - -### onAbiCall() - -#### Call Signature - -> **onAbiCall**(`methodSignature`, `callback`): `void` - -Defined in: [src/application-spy.ts:80](https://github.com/algorandfoundation/algorand-typescript-testing/blob/main/src/application-spy.ts#L80) - -Registers a callback for a specific method signature. - -##### Parameters - -###### methodSignature - -`bytes` - -###### callback - -`AppSpyCb` - -##### Returns - -`void` - -#### Call Signature - -> **onAbiCall**(`methodSignature`, `ocas`, `callback`): `void` - -Defined in: [src/application-spy.ts:81](https://github.com/algorandfoundation/algorand-typescript-testing/blob/main/src/application-spy.ts#L81) - -Registers a callback for a specific method signature. - -##### Parameters - -###### methodSignature - -`bytes` - -###### ocas - -`OnCompleteAction`[] - -###### callback - -`AppSpyCb` - -##### Returns - -`void` - -*** - -### onBareCall() - -#### Call Signature - -> **onBareCall**(`callback`): `void` - -Defined in: [src/application-spy.ts:62](https://github.com/algorandfoundation/algorand-typescript-testing/blob/main/src/application-spy.ts#L62) - -Registers a callback for a bare call (no arguments). - -##### Parameters - -###### callback - -`AppSpyCb` - -The callback to be executed when a bare call is detected. - -##### Returns - -`void` - -#### Call Signature - -> **onBareCall**(`ocas`, `callback`): `void` - -Defined in: [src/application-spy.ts:63](https://github.com/algorandfoundation/algorand-typescript-testing/blob/main/src/application-spy.ts#L63) - -Registers a callback for a bare call (no arguments). - -##### Parameters - -###### ocas - -`OnCompleteAction`[] - -###### callback - -`AppSpyCb` - -The callback to be executed when a bare call is detected. - -##### Returns - -`void` diff --git a/docs/code/index/classes/AssertError.md b/docs/code/index/classes/AssertError.md deleted file mode 100644 index ce76224c..00000000 --- a/docs/code/index/classes/AssertError.md +++ /dev/null @@ -1,157 +0,0 @@ -[**@algorandfoundation/algorand-typescript-testing**](../../README.md) - -*** - -[@algorandfoundation/algorand-typescript-testing](../../README.md) / [index](../README.md) / AssertError - -# Class: AssertError - -Defined in: [src/errors.ts:22](https://github.com/algorandfoundation/algorand-typescript-testing/blob/main/src/errors.ts#L22) - -Raised when an assertion fails - -## Extends - -- [`AvmError`](AvmError.md) - -## Constructors - -### Constructor - -> **new AssertError**(`message`): `AssertError` - -Defined in: [src/errors.ts:23](https://github.com/algorandfoundation/algorand-typescript-testing/blob/main/src/errors.ts#L23) - -#### Parameters - -##### message - -`string` - -#### Returns - -`AssertError` - -#### Overrides - -[`AvmError`](AvmError.md).[`constructor`](AvmError.md#constructor) - -## Properties - -### cause? - -> `optional` **cause**: `unknown` - -Defined in: node\_modules/typescript/lib/lib.es2022.error.d.ts:26 - -#### Inherited from - -[`AvmError`](AvmError.md).[`cause`](AvmError.md#cause) - -*** - -### message - -> **message**: `string` - -Defined in: node\_modules/typescript/lib/lib.es5.d.ts:1077 - -#### Inherited from - -[`AvmError`](AvmError.md).[`message`](AvmError.md#message) - -*** - -### name - -> **name**: `string` - -Defined in: node\_modules/typescript/lib/lib.es5.d.ts:1076 - -#### Inherited from - -[`AvmError`](AvmError.md).[`name`](AvmError.md#name) - -*** - -### stack? - -> `optional` **stack**: `string` - -Defined in: node\_modules/typescript/lib/lib.es5.d.ts:1078 - -#### Inherited from - -[`AvmError`](AvmError.md).[`stack`](AvmError.md#stack) - -*** - -### prepareStackTrace()? - -> `static` `optional` **prepareStackTrace**: (`err`, `stackTraces`) => `any` - -Defined in: node\_modules/@types/node/globals.d.ts:143 - -Optional override for formatting stack traces - -#### Parameters - -##### err - -`Error` - -##### stackTraces - -`CallSite`[] - -#### Returns - -`any` - -#### See - -https://v8.dev/docs/stack-trace-api#customizing-stack-traces - -#### Inherited from - -[`AvmError`](AvmError.md).[`prepareStackTrace`](AvmError.md#preparestacktrace) - -*** - -### stackTraceLimit - -> `static` **stackTraceLimit**: `number` - -Defined in: node\_modules/@types/node/globals.d.ts:145 - -#### Inherited from - -[`AvmError`](AvmError.md).[`stackTraceLimit`](AvmError.md#stacktracelimit) - -## Methods - -### captureStackTrace() - -> `static` **captureStackTrace**(`targetObject`, `constructorOpt`?): `void` - -Defined in: node\_modules/@types/node/globals.d.ts:136 - -Create .stack property on a target object - -#### Parameters - -##### targetObject - -`object` - -##### constructorOpt? - -`Function` - -#### Returns - -`void` - -#### Inherited from - -[`AvmError`](AvmError.md).[`captureStackTrace`](AvmError.md#capturestacktrace) diff --git a/docs/code/index/classes/AvmError.md b/docs/code/index/classes/AvmError.md deleted file mode 100644 index 62516959..00000000 --- a/docs/code/index/classes/AvmError.md +++ /dev/null @@ -1,162 +0,0 @@ -[**@algorandfoundation/algorand-typescript-testing**](../../README.md) - -*** - -[@algorandfoundation/algorand-typescript-testing](../../README.md) / [index](../README.md) / AvmError - -# Class: AvmError - -Defined in: [src/errors.ts:5](https://github.com/algorandfoundation/algorand-typescript-testing/blob/main/src/errors.ts#L5) - -Raised when an `err` op is encountered, or when the testing VM is asked to do something that would cause -the AVM to fail. - -## Extends - -- `Error` - -## Extended by - -- [`AssertError`](AssertError.md) - -## Constructors - -### Constructor - -> **new AvmError**(`message`): `AvmError` - -Defined in: [src/errors.ts:6](https://github.com/algorandfoundation/algorand-typescript-testing/blob/main/src/errors.ts#L6) - -#### Parameters - -##### message - -`string` - -#### Returns - -`AvmError` - -#### Overrides - -`Error.constructor` - -## Properties - -### cause? - -> `optional` **cause**: `unknown` - -Defined in: node\_modules/typescript/lib/lib.es2022.error.d.ts:26 - -#### Inherited from - -`Error.cause` - -*** - -### message - -> **message**: `string` - -Defined in: node\_modules/typescript/lib/lib.es5.d.ts:1077 - -#### Inherited from - -`Error.message` - -*** - -### name - -> **name**: `string` - -Defined in: node\_modules/typescript/lib/lib.es5.d.ts:1076 - -#### Inherited from - -`Error.name` - -*** - -### stack? - -> `optional` **stack**: `string` - -Defined in: node\_modules/typescript/lib/lib.es5.d.ts:1078 - -#### Inherited from - -`Error.stack` - -*** - -### prepareStackTrace()? - -> `static` `optional` **prepareStackTrace**: (`err`, `stackTraces`) => `any` - -Defined in: node\_modules/@types/node/globals.d.ts:143 - -Optional override for formatting stack traces - -#### Parameters - -##### err - -`Error` - -##### stackTraces - -`CallSite`[] - -#### Returns - -`any` - -#### See - -https://v8.dev/docs/stack-trace-api#customizing-stack-traces - -#### Inherited from - -`Error.prepareStackTrace` - -*** - -### stackTraceLimit - -> `static` **stackTraceLimit**: `number` - -Defined in: node\_modules/@types/node/globals.d.ts:145 - -#### Inherited from - -`Error.stackTraceLimit` - -## Methods - -### captureStackTrace() - -> `static` **captureStackTrace**(`targetObject`, `constructorOpt`?): `void` - -Defined in: node\_modules/@types/node/globals.d.ts:136 - -Create .stack property on a target object - -#### Parameters - -##### targetObject - -`object` - -##### constructorOpt? - -`Function` - -#### Returns - -`void` - -#### Inherited from - -`Error.captureStackTrace` diff --git a/docs/code/index/classes/CodeError.md b/docs/code/index/classes/CodeError.md deleted file mode 100644 index e93e9d4b..00000000 --- a/docs/code/index/classes/CodeError.md +++ /dev/null @@ -1,161 +0,0 @@ -[**@algorandfoundation/algorand-typescript-testing**](../../README.md) - -*** - -[@algorandfoundation/algorand-typescript-testing](../../README.md) / [index](../README.md) / CodeError - -# Class: CodeError - -Defined in: [src/errors.ts:40](https://github.com/algorandfoundation/algorand-typescript-testing/blob/main/src/errors.ts#L40) - -Raised when unsupported user code is encountered - -## Extends - -- `Error` - -## Constructors - -### Constructor - -> **new CodeError**(`message`, `options`?): `CodeError` - -Defined in: [src/errors.ts:41](https://github.com/algorandfoundation/algorand-typescript-testing/blob/main/src/errors.ts#L41) - -#### Parameters - -##### message - -`string` - -##### options? - -`ErrorOptions` - -#### Returns - -`CodeError` - -#### Overrides - -`Error.constructor` - -## Properties - -### cause? - -> `optional` **cause**: `unknown` - -Defined in: node\_modules/typescript/lib/lib.es2022.error.d.ts:26 - -#### Inherited from - -`Error.cause` - -*** - -### message - -> **message**: `string` - -Defined in: node\_modules/typescript/lib/lib.es5.d.ts:1077 - -#### Inherited from - -`Error.message` - -*** - -### name - -> **name**: `string` - -Defined in: node\_modules/typescript/lib/lib.es5.d.ts:1076 - -#### Inherited from - -`Error.name` - -*** - -### stack? - -> `optional` **stack**: `string` - -Defined in: node\_modules/typescript/lib/lib.es5.d.ts:1078 - -#### Inherited from - -`Error.stack` - -*** - -### prepareStackTrace()? - -> `static` `optional` **prepareStackTrace**: (`err`, `stackTraces`) => `any` - -Defined in: node\_modules/@types/node/globals.d.ts:143 - -Optional override for formatting stack traces - -#### Parameters - -##### err - -`Error` - -##### stackTraces - -`CallSite`[] - -#### Returns - -`any` - -#### See - -https://v8.dev/docs/stack-trace-api#customizing-stack-traces - -#### Inherited from - -`Error.prepareStackTrace` - -*** - -### stackTraceLimit - -> `static` **stackTraceLimit**: `number` - -Defined in: node\_modules/@types/node/globals.d.ts:145 - -#### Inherited from - -`Error.stackTraceLimit` - -## Methods - -### captureStackTrace() - -> `static` **captureStackTrace**(`targetObject`, `constructorOpt`?): `void` - -Defined in: node\_modules/@types/node/globals.d.ts:136 - -Create .stack property on a target object - -#### Parameters - -##### targetObject - -`object` - -##### constructorOpt? - -`Function` - -#### Returns - -`void` - -#### Inherited from - -`Error.captureStackTrace` diff --git a/docs/code/index/classes/InternalError.md b/docs/code/index/classes/InternalError.md deleted file mode 100644 index 1ea98693..00000000 --- a/docs/code/index/classes/InternalError.md +++ /dev/null @@ -1,161 +0,0 @@ -[**@algorandfoundation/algorand-typescript-testing**](../../README.md) - -*** - -[@algorandfoundation/algorand-typescript-testing](../../README.md) / [index](../README.md) / InternalError - -# Class: InternalError - -Defined in: [src/errors.ts:31](https://github.com/algorandfoundation/algorand-typescript-testing/blob/main/src/errors.ts#L31) - -Raised when testing code errors - -## Extends - -- `Error` - -## Constructors - -### Constructor - -> **new InternalError**(`message`, `options`?): `InternalError` - -Defined in: [src/errors.ts:32](https://github.com/algorandfoundation/algorand-typescript-testing/blob/main/src/errors.ts#L32) - -#### Parameters - -##### message - -`string` - -##### options? - -`ErrorOptions` - -#### Returns - -`InternalError` - -#### Overrides - -`Error.constructor` - -## Properties - -### cause? - -> `optional` **cause**: `unknown` - -Defined in: node\_modules/typescript/lib/lib.es2022.error.d.ts:26 - -#### Inherited from - -`Error.cause` - -*** - -### message - -> **message**: `string` - -Defined in: node\_modules/typescript/lib/lib.es5.d.ts:1077 - -#### Inherited from - -`Error.message` - -*** - -### name - -> **name**: `string` - -Defined in: node\_modules/typescript/lib/lib.es5.d.ts:1076 - -#### Inherited from - -`Error.name` - -*** - -### stack? - -> `optional` **stack**: `string` - -Defined in: node\_modules/typescript/lib/lib.es5.d.ts:1078 - -#### Inherited from - -`Error.stack` - -*** - -### prepareStackTrace()? - -> `static` `optional` **prepareStackTrace**: (`err`, `stackTraces`) => `any` - -Defined in: node\_modules/@types/node/globals.d.ts:143 - -Optional override for formatting stack traces - -#### Parameters - -##### err - -`Error` - -##### stackTraces - -`CallSite`[] - -#### Returns - -`any` - -#### See - -https://v8.dev/docs/stack-trace-api#customizing-stack-traces - -#### Inherited from - -`Error.prepareStackTrace` - -*** - -### stackTraceLimit - -> `static` **stackTraceLimit**: `number` - -Defined in: node\_modules/@types/node/globals.d.ts:145 - -#### Inherited from - -`Error.stackTraceLimit` - -## Methods - -### captureStackTrace() - -> `static` **captureStackTrace**(`targetObject`, `constructorOpt`?): `void` - -Defined in: node\_modules/@types/node/globals.d.ts:136 - -Create .stack property on a target object - -#### Parameters - -##### targetObject - -`object` - -##### constructorOpt? - -`Function` - -#### Returns - -`void` - -#### Inherited from - -`Error.captureStackTrace` diff --git a/docs/code/index/classes/NotImplementedError.md b/docs/code/index/classes/NotImplementedError.md deleted file mode 100644 index ca4788bd..00000000 --- a/docs/code/index/classes/NotImplementedError.md +++ /dev/null @@ -1,155 +0,0 @@ -[**@algorandfoundation/algorand-typescript-testing**](../../README.md) - -*** - -[@algorandfoundation/algorand-typescript-testing](../../README.md) / [index](../README.md) / NotImplementedError - -# Class: NotImplementedError - -Defined in: [src/errors.ts:46](https://github.com/algorandfoundation/algorand-typescript-testing/blob/main/src/errors.ts#L46) - -## Extends - -- `Error` - -## Constructors - -### Constructor - -> **new NotImplementedError**(`feature`): `NotImplementedError` - -Defined in: [src/errors.ts:47](https://github.com/algorandfoundation/algorand-typescript-testing/blob/main/src/errors.ts#L47) - -#### Parameters - -##### feature - -`string` - -#### Returns - -`NotImplementedError` - -#### Overrides - -`Error.constructor` - -## Properties - -### cause? - -> `optional` **cause**: `unknown` - -Defined in: node\_modules/typescript/lib/lib.es2022.error.d.ts:26 - -#### Inherited from - -`Error.cause` - -*** - -### message - -> **message**: `string` - -Defined in: node\_modules/typescript/lib/lib.es5.d.ts:1077 - -#### Inherited from - -`Error.message` - -*** - -### name - -> **name**: `string` - -Defined in: node\_modules/typescript/lib/lib.es5.d.ts:1076 - -#### Inherited from - -`Error.name` - -*** - -### stack? - -> `optional` **stack**: `string` - -Defined in: node\_modules/typescript/lib/lib.es5.d.ts:1078 - -#### Inherited from - -`Error.stack` - -*** - -### prepareStackTrace()? - -> `static` `optional` **prepareStackTrace**: (`err`, `stackTraces`) => `any` - -Defined in: node\_modules/@types/node/globals.d.ts:143 - -Optional override for formatting stack traces - -#### Parameters - -##### err - -`Error` - -##### stackTraces - -`CallSite`[] - -#### Returns - -`any` - -#### See - -https://v8.dev/docs/stack-trace-api#customizing-stack-traces - -#### Inherited from - -`Error.prepareStackTrace` - -*** - -### stackTraceLimit - -> `static` **stackTraceLimit**: `number` - -Defined in: node\_modules/@types/node/globals.d.ts:145 - -#### Inherited from - -`Error.stackTraceLimit` - -## Methods - -### captureStackTrace() - -> `static` **captureStackTrace**(`targetObject`, `constructorOpt`?): `void` - -Defined in: node\_modules/@types/node/globals.d.ts:136 - -Create .stack property on a target object - -#### Parameters - -##### targetObject - -`object` - -##### constructorOpt? - -`Function` - -#### Returns - -`void` - -#### Inherited from - -`Error.captureStackTrace` diff --git a/docs/code/index/classes/TestExecutionContext.md b/docs/code/index/classes/TestExecutionContext.md deleted file mode 100644 index 7a379395..00000000 --- a/docs/code/index/classes/TestExecutionContext.md +++ /dev/null @@ -1,424 +0,0 @@ -[**@algorandfoundation/algorand-typescript-testing**](../../README.md) - -*** - -[@algorandfoundation/algorand-typescript-testing](../../README.md) / [index](../README.md) / TestExecutionContext - -# Class: TestExecutionContext - -Defined in: [src/test-execution-context.ts:22](https://github.com/algorandfoundation/algorand-typescript-testing/blob/main/src/test-execution-context.ts#L22) - -The `TestExecutionContext` class provides a context for executing tests in an Algorand environment. -It manages various contexts such as contract, ledger, and transaction contexts, and provides utilities -for generating values, managing accounts, and handling logic signatures. - -## Constructors - -### Constructor - -> **new TestExecutionContext**(`defaultSenderAddress`?): `TestExecutionContext` - -Defined in: [src/test-execution-context.ts:39](https://github.com/algorandfoundation/algorand-typescript-testing/blob/main/src/test-execution-context.ts#L39) - -Creates an instance of `TestExecutionContext`. - -#### Parameters - -##### defaultSenderAddress? - -`bytes` - -The default sender address. - -#### Returns - -`TestExecutionContext` - -## Accessors - -### activeLogicSigArgs - -#### Get Signature - -> **get** **activeLogicSigArgs**(): `bytes`[] - -Defined in: [src/test-execution-context.ts:120](https://github.com/algorandfoundation/algorand-typescript-testing/blob/main/src/test-execution-context.ts#L120) - -Returns the active logic signature arguments. - -##### Returns - -`bytes`[] - -*** - -### any - -#### Get Signature - -> **get** **any**(): [`ValueGenerator`](../../value-generators/classes/ValueGenerator.md) - -Defined in: [src/test-execution-context.ts:93](https://github.com/algorandfoundation/algorand-typescript-testing/blob/main/src/test-execution-context.ts#L93) - -Returns the value generator. - -##### Returns - -[`ValueGenerator`](../../value-generators/classes/ValueGenerator.md) - -*** - -### contract - -#### Get Signature - -> **get** **contract**(): [`ContractContext`](../../subcontexts/contract-context/classes/ContractContext.md) - -Defined in: [src/test-execution-context.ts:66](https://github.com/algorandfoundation/algorand-typescript-testing/blob/main/src/test-execution-context.ts#L66) - -Returns the contract context. - -##### Returns - -[`ContractContext`](../../subcontexts/contract-context/classes/ContractContext.md) - -*** - -### defaultSender - -#### Get Signature - -> **get** **defaultSender**(): `Account` - -Defined in: [src/test-execution-context.ts:102](https://github.com/algorandfoundation/algorand-typescript-testing/blob/main/src/test-execution-context.ts#L102) - -Returns the default sender account. - -##### Returns - -`Account` - -#### Set Signature - -> **set** **defaultSender**(`val`): `void` - -Defined in: [src/test-execution-context.ts:111](https://github.com/algorandfoundation/algorand-typescript-testing/blob/main/src/test-execution-context.ts#L111) - -Sets the default sender account. - -##### Parameters - -###### val - -The default sender account. - -`bytes` | `Account` - -##### Returns - -`void` - -*** - -### ledger - -#### Get Signature - -> **get** **ledger**(): [`LedgerContext`](../../subcontexts/ledger-context/classes/LedgerContext.md) - -Defined in: [src/test-execution-context.ts:75](https://github.com/algorandfoundation/algorand-typescript-testing/blob/main/src/test-execution-context.ts#L75) - -Returns the ledger context. - -##### Returns - -[`LedgerContext`](../../subcontexts/ledger-context/classes/LedgerContext.md) - -*** - -### templateVars - -#### Get Signature - -> **get** **templateVars**(): `Record`\<`string`, `any`\> - -Defined in: [src/test-execution-context.ts:129](https://github.com/algorandfoundation/algorand-typescript-testing/blob/main/src/test-execution-context.ts#L129) - -Returns the template variables. - -##### Returns - -`Record`\<`string`, `any`\> - -*** - -### txn - -#### Get Signature - -> **get** **txn**(): [`TransactionContext`](../../subcontexts/transaction-context/classes/TransactionContext.md) - -Defined in: [src/test-execution-context.ts:84](https://github.com/algorandfoundation/algorand-typescript-testing/blob/main/src/test-execution-context.ts#L84) - -Returns the transaction context. - -##### Returns - -[`TransactionContext`](../../subcontexts/transaction-context/classes/TransactionContext.md) - -## Methods - -### addApplicationSpy() - -> **addApplicationSpy**\<`TContract`\>(`spy`): `void` - -Defined in: [src/test-execution-context.ts:197](https://github.com/algorandfoundation/algorand-typescript-testing/blob/main/src/test-execution-context.ts#L197) - -Adds an application spy to the context. - -#### Type Parameters - -##### TContract - -`TContract` *extends* `Contract` - -#### Parameters - -##### spy - -[`ApplicationSpy`](ApplicationSpy.md)\<`TContract`\> - -The application spy to add. - -#### Returns - -`void` - -*** - -### executeLogicSig() - -> **executeLogicSig**(`logicSig`, ...`args`): `boolean` \| `uint64` - -Defined in: [src/test-execution-context.ts:140](https://github.com/algorandfoundation/algorand-typescript-testing/blob/main/src/test-execution-context.ts#L140) - -Executes a logic signature with the given arguments. - -#### Parameters - -##### logicSig - -`LogicSig` - -The logic signature to execute. - -##### args - -...`bytes`[] - -The arguments for the logic signature. - -#### Returns - -`boolean` \| `uint64` - -*** - -### exportLogs() - -> **exportLogs**\<`T`\>(`appId`, ...`decoding`): `DecodedLogs`\<`T`\> - -Defined in: [src/test-execution-context.ts:57](https://github.com/algorandfoundation/algorand-typescript-testing/blob/main/src/test-execution-context.ts#L57) - -Exports logs for a given application ID and decoding. - -#### Type Parameters - -##### T - -`T` *extends* `LogDecoding`[] - -#### Parameters - -##### appId - -`uint64` - -The application ID. - -##### decoding - -...`T` - -The log decoding. - -#### Returns - -`DecodedLogs`\<`T`\> - -*** - -### getCompiledAppEntry() - -> **getCompiledAppEntry**(`contract`): `undefined` \| \{ `key`: `ConstructorFor`\<`BaseContract`\>; `value`: `uint64`; \} - -Defined in: [src/test-execution-context.ts:166](https://github.com/algorandfoundation/algorand-typescript-testing/blob/main/src/test-execution-context.ts#L166) - -Gets a compiled application by contract. - -#### Parameters - -##### contract - -`ConstructorFor`\<`BaseContract`\> - -The contract class. - -#### Returns - -`undefined` \| \{ `key`: `ConstructorFor`\<`BaseContract`\>; `value`: `uint64`; \} - -*** - -### getCompiledLogicSigEntry() - -> **getCompiledLogicSigEntry**(`logicsig`): `undefined` \| \{ `key`: `ConstructorFor`\<`LogicSig`\>; `value`: `Account`; \} - -Defined in: [src/test-execution-context.ts:207](https://github.com/algorandfoundation/algorand-typescript-testing/blob/main/src/test-execution-context.ts#L207) - -Gets a compiled logic signature. - -#### Parameters - -##### logicsig - -`ConstructorFor`\<`LogicSig`\> - -The logic signature class. - -#### Returns - -`undefined` \| \{ `key`: `ConstructorFor`\<`LogicSig`\>; `value`: `Account`; \} - -*** - -### notifyApplicationSpies() - -> **notifyApplicationSpies**(`itxn`): `void` - -Defined in: [src/test-execution-context.ts:186](https://github.com/algorandfoundation/algorand-typescript-testing/blob/main/src/test-execution-context.ts#L186) - -#### Parameters - -##### itxn - -`ApplicationCallInnerTxnContext` - -#### Returns - -`void` - -*** - -### reset() - -> **reset**(): `void` - -Defined in: [src/test-execution-context.ts:230](https://github.com/algorandfoundation/algorand-typescript-testing/blob/main/src/test-execution-context.ts#L230) - -Reinitializes the execution context, clearing all state variables and resetting internal components. -Invoked between test cases to ensure isolation. - -#### Returns - -`void` - -*** - -### setCompiledApp() - -> **setCompiledApp**(`c`, `appId`): `void` - -Defined in: [src/test-execution-context.ts:176](https://github.com/algorandfoundation/algorand-typescript-testing/blob/main/src/test-execution-context.ts#L176) - -Sets a compiled application. - -#### Parameters - -##### c - -`ConstructorFor`\<`BaseContract`\> - -The contract class. - -##### appId - -`uint64` - -The application ID. - -#### Returns - -`void` - -*** - -### setCompiledLogicSig() - -> **setCompiledLogicSig**(`c`, `account`): `void` - -Defined in: [src/test-execution-context.ts:217](https://github.com/algorandfoundation/algorand-typescript-testing/blob/main/src/test-execution-context.ts#L217) - -Sets a compiled logic signature. - -#### Parameters - -##### c - -`ConstructorFor`\<`LogicSig`\> - -The logic signature class. - -##### account - -`Account` - -The account associated with the logic signature. - -#### Returns - -`void` - -*** - -### setTemplateVar() - -> **setTemplateVar**(`name`, `value`, `prefix`?): `void` - -Defined in: [src/test-execution-context.ts:156](https://github.com/algorandfoundation/algorand-typescript-testing/blob/main/src/test-execution-context.ts#L156) - -Sets a template variable. - -#### Parameters - -##### name - -`string` - -The name of the template variable. - -##### value - -`any` - -The value of the template variable. - -##### prefix? - -`string` - -The prefix for the template variable. - -#### Returns - -`void` diff --git a/docs/code/index/functions/addEqualityTesters.md b/docs/code/index/functions/addEqualityTesters.md deleted file mode 100644 index b67e49a2..00000000 --- a/docs/code/index/functions/addEqualityTesters.md +++ /dev/null @@ -1,42 +0,0 @@ -[**@algorandfoundation/algorand-typescript-testing**](../../README.md) - -*** - -[@algorandfoundation/algorand-typescript-testing](../../README.md) / [index](../README.md) / addEqualityTesters - -# Function: addEqualityTesters() - -> **addEqualityTesters**(`params`): `void` - -Defined in: [src/set-up.ts:159](https://github.com/algorandfoundation/algorand-typescript-testing/blob/main/src/set-up.ts#L159) - -Adds custom equality testers for Algorand types to vitest's expect function. -This allows vitest to properly compare Algorand types such as uint64, biguint, and bytes -against JS native types such as number, bigint and Uint8Array, in tests. - -## Parameters - -### params - -The parameters object - -#### expect - -`ExpectObj` - -vitest's expect object to extend with custom equality testers - -## Returns - -`void` - -## Example - -```ts -import { beforeAll, expect } from 'vitest' -import { addEqualityTesters } from '@algorandfoundation/algorand-typescript-testing'; - -beforeAll(() => { - addEqualityTesters({ expect }); -}); -``` diff --git a/docs/code/index/functions/toExternalValue.md b/docs/code/index/functions/toExternalValue.md deleted file mode 100644 index b0bb3dca..00000000 --- a/docs/code/index/functions/toExternalValue.md +++ /dev/null @@ -1,135 +0,0 @@ -[**@algorandfoundation/algorand-typescript-testing**](../../README.md) - -*** - -[@algorandfoundation/algorand-typescript-testing](../../README.md) / [index](../README.md) / toExternalValue - -# Function: toExternalValue() - -## Call Signature - -> **toExternalValue**(`val`): `bigint` - -Defined in: [src/impl/primitives.ts:43](https://github.com/algorandfoundation/algorand-typescript-testing/blob/main/src/impl/primitives.ts#L43) - -Converts internal Algorand type representations to their external primitive values. - -### Parameters - -#### val - -`uint64` - -A uint64 value to convert - -### Returns - -`bigint` - -The uint64 value as a bigint - -### Example - -```ts -const uint64Val = Uint64(123n) -toExternalValue(uint64Val) // returns 123n - -const bytesVal = Bytes.fromBase64("SGVsbG8="); -toExternalValue(bytesVal) // returns Uint8Array([72, 101, 108, 108, 111]) -``` - -## Call Signature - -> **toExternalValue**(`val`): `bigint` - -Defined in: [src/impl/primitives.ts:44](https://github.com/algorandfoundation/algorand-typescript-testing/blob/main/src/impl/primitives.ts#L44) - -Converts internal Algorand type representations to their external primitive values. - -### Parameters - -#### val - -`biguint` - -A uint64 value to convert - -### Returns - -`bigint` - -The uint64 value as a bigint - -### Example - -```ts -const uint64Val = Uint64(123n) -toExternalValue(uint64Val) // returns 123n - -const bytesVal = Bytes.fromBase64("SGVsbG8="); -toExternalValue(bytesVal) // returns Uint8Array([72, 101, 108, 108, 111]) -``` - -## Call Signature - -> **toExternalValue**(`val`): `Uint8Array` - -Defined in: [src/impl/primitives.ts:45](https://github.com/algorandfoundation/algorand-typescript-testing/blob/main/src/impl/primitives.ts#L45) - -Converts internal Algorand type representations to their external primitive values. - -### Parameters - -#### val - -`bytes` - -A uint64 value to convert - -### Returns - -`Uint8Array` - -The uint64 value as a bigint - -### Example - -```ts -const uint64Val = Uint64(123n) -toExternalValue(uint64Val) // returns 123n - -const bytesVal = Bytes.fromBase64("SGVsbG8="); -toExternalValue(bytesVal) // returns Uint8Array([72, 101, 108, 108, 111]) -``` - -## Call Signature - -> **toExternalValue**(`val`): `string` - -Defined in: [src/impl/primitives.ts:46](https://github.com/algorandfoundation/algorand-typescript-testing/blob/main/src/impl/primitives.ts#L46) - -Converts internal Algorand type representations to their external primitive values. - -### Parameters - -#### val - -`string` - -A uint64 value to convert - -### Returns - -`string` - -The uint64 value as a bigint - -### Example - -```ts -const uint64Val = Uint64(123n) -toExternalValue(uint64Val) // returns 123n - -const bytesVal = Bytes.fromBase64("SGVsbG8="); -toExternalValue(bytesVal) // returns Uint8Array([72, 101, 108, 108, 111]) -``` diff --git a/docs/code/subcontexts/contract-context/README.md b/docs/code/subcontexts/contract-context/README.md deleted file mode 100644 index 0e52d7d2..00000000 --- a/docs/code/subcontexts/contract-context/README.md +++ /dev/null @@ -1,11 +0,0 @@ -[**@algorandfoundation/algorand-typescript-testing**](../../README.md) - -*** - -[@algorandfoundation/algorand-typescript-testing](../../README.md) / subcontexts/contract-context - -# subcontexts/contract-context - -## Classes - -- [ContractContext](classes/ContractContext.md) diff --git a/docs/code/subcontexts/contract-context/classes/ContractContext.md b/docs/code/subcontexts/contract-context/classes/ContractContext.md deleted file mode 100644 index f95ae0a5..00000000 --- a/docs/code/subcontexts/contract-context/classes/ContractContext.md +++ /dev/null @@ -1,124 +0,0 @@ -[**@algorandfoundation/algorand-typescript-testing**](../../../README.md) - -*** - -[@algorandfoundation/algorand-typescript-testing](../../../README.md) / [subcontexts/contract-context](../README.md) / ContractContext - -# Class: ContractContext - -Defined in: [src/subcontexts/contract-context.ts:162](https://github.com/algorandfoundation/algorand-typescript-testing/blob/main/src/subcontexts/contract-context.ts#L162) - -Provides a context for creating contracts and registering created contract instances -with test execution context. - -## Constructors - -### Constructor - -> **new ContractContext**(): `ContractContext` - -#### Returns - -`ContractContext` - -## Methods - -### create() - -> **create**\<`T`\>(`type`, ...`args`): `T` - -Defined in: [src/subcontexts/contract-context.ts:174](https://github.com/algorandfoundation/algorand-typescript-testing/blob/main/src/subcontexts/contract-context.ts#L174) - -Creates a new contract instance and register the created instance with test execution context. - -#### Type Parameters - -##### T - -`T` *extends* `BaseContract` - -Type of contract extending BaseContract - -#### Parameters - -##### type - -`IConstructor`\<`T`\> - -The contract class constructor - -##### args - -...`any`[] - -Constructor arguments for the contract - -#### Returns - -`T` - -Proxied instance of the contract - -#### Example - -```ts -const ctx = new TestExecutionContext(); -const contract = ctx.contract.create(MyContract); -``` - -*** - -### createMethodCallTxns() - -> `static` **createMethodCallTxns**\<`TParams`\>(`contract`, `abiMetadata`, ...`args`): `Transaction`[] - -Defined in: [src/subcontexts/contract-context.ts:196](https://github.com/algorandfoundation/algorand-typescript-testing/blob/main/src/subcontexts/contract-context.ts#L196) - -**`Internal`** - -Creates an array of transactions for calling a contract method. - -#### Type Parameters - -##### TParams - -`TParams` *extends* `unknown`[] - -Array of parameter types - -#### Parameters - -##### contract - -`BaseContract` - -The contract instance - -##### abiMetadata - -ABI metadata for the method - -`undefined` | `AbiMetadata` - -##### args - -...`TParams` - -Method arguments - -#### Returns - -`Transaction`[] - -Array of transactions needed to execute the method - -#### Example - -```ts -const txns = ContractContext.createMethodCallTxns( - myContract, - methodAbiMetadata, - arg1, - arg2 -); -``` diff --git a/docs/code/subcontexts/ledger-context/README.md b/docs/code/subcontexts/ledger-context/README.md deleted file mode 100644 index 3572c51a..00000000 --- a/docs/code/subcontexts/ledger-context/README.md +++ /dev/null @@ -1,11 +0,0 @@ -[**@algorandfoundation/algorand-typescript-testing**](../../README.md) - -*** - -[@algorandfoundation/algorand-typescript-testing](../../README.md) / subcontexts/ledger-context - -# subcontexts/ledger-context - -## Classes - -- [LedgerContext](classes/LedgerContext.md) diff --git a/docs/code/subcontexts/ledger-context/classes/LedgerContext.md b/docs/code/subcontexts/ledger-context/classes/LedgerContext.md deleted file mode 100644 index 2085c7fb..00000000 --- a/docs/code/subcontexts/ledger-context/classes/LedgerContext.md +++ /dev/null @@ -1,832 +0,0 @@ -[**@algorandfoundation/algorand-typescript-testing**](../../../README.md) - -*** - -[@algorandfoundation/algorand-typescript-testing](../../../README.md) / [subcontexts/ledger-context](../README.md) / LedgerContext - -# Class: LedgerContext - -Defined in: [src/subcontexts/ledger-context.ts:33](https://github.com/algorandfoundation/algorand-typescript-testing/blob/main/src/subcontexts/ledger-context.ts#L33) - -## Constructors - -### Constructor - -> **new LedgerContext**(): `LedgerContext` - -#### Returns - -`LedgerContext` - -## Properties - -### accountDataMap - -> **accountDataMap**: `AccountMap`\<`AccountData`\> - -Defined in: [src/subcontexts/ledger-context.ts:38](https://github.com/algorandfoundation/algorand-typescript-testing/blob/main/src/subcontexts/ledger-context.ts#L38) - -*** - -### appIdContractMap - -> **appIdContractMap**: `Uint64Map`\<`BaseContract`\> - -Defined in: [src/subcontexts/ledger-context.ts:37](https://github.com/algorandfoundation/algorand-typescript-testing/blob/main/src/subcontexts/ledger-context.ts#L37) - -*** - -### appIdIter - -> **appIdIter**: `Generator`\<`bigint`, `any`, `any`\> - -Defined in: [src/subcontexts/ledger-context.ts:34](https://github.com/algorandfoundation/algorand-typescript-testing/blob/main/src/subcontexts/ledger-context.ts#L34) - -*** - -### applicationDataMap - -> **applicationDataMap**: `Uint64Map`\<`ApplicationData`\> - -Defined in: [src/subcontexts/ledger-context.ts:36](https://github.com/algorandfoundation/algorand-typescript-testing/blob/main/src/subcontexts/ledger-context.ts#L36) - -*** - -### assetDataMap - -> **assetDataMap**: `Uint64Map`\<`Mutable`\<`Omit`\<`Asset`, `"id"` \| `"balance"` \| `"frozen"`\>\>\> - -Defined in: [src/subcontexts/ledger-context.ts:39](https://github.com/algorandfoundation/algorand-typescript-testing/blob/main/src/subcontexts/ledger-context.ts#L39) - -*** - -### assetIdIter - -> **assetIdIter**: `Generator`\<`bigint`, `any`, `any`\> - -Defined in: [src/subcontexts/ledger-context.ts:35](https://github.com/algorandfoundation/algorand-typescript-testing/blob/main/src/subcontexts/ledger-context.ts#L35) - -*** - -### blocks - -> **blocks**: `Uint64Map`\<`BlockData`\> - -Defined in: [src/subcontexts/ledger-context.ts:41](https://github.com/algorandfoundation/algorand-typescript-testing/blob/main/src/subcontexts/ledger-context.ts#L41) - -*** - -### globalData - -> **globalData**: `GlobalData` - -Defined in: [src/subcontexts/ledger-context.ts:42](https://github.com/algorandfoundation/algorand-typescript-testing/blob/main/src/subcontexts/ledger-context.ts#L42) - -*** - -### onlineStake - -> **onlineStake**: `number` = `0` - -Defined in: [src/subcontexts/ledger-context.ts:43](https://github.com/algorandfoundation/algorand-typescript-testing/blob/main/src/subcontexts/ledger-context.ts#L43) - -*** - -### voterDataMap - -> **voterDataMap**: `AccountMap`\<`VoterData`\> - -Defined in: [src/subcontexts/ledger-context.ts:40](https://github.com/algorandfoundation/algorand-typescript-testing/blob/main/src/subcontexts/ledger-context.ts#L40) - -## Methods - -### addAppIdContractMap() - -> **addAppIdContractMap**(`appId`, `contract`): `void` - -Defined in: [src/subcontexts/ledger-context.ts:51](https://github.com/algorandfoundation/algorand-typescript-testing/blob/main/src/subcontexts/ledger-context.ts#L51) - -**`Internal`** - -Adds a contract to the application ID contract map. - -#### Parameters - -##### appId - -`StubUint64Compat` - -The application ID. - -##### contract - -`BaseContract` - -The contract to add. - -#### Returns - -`void` - -*** - -### boxExists() - -> **boxExists**(`app`, `key`): `boolean` - -Defined in: [src/subcontexts/ledger-context.ts:411](https://github.com/algorandfoundation/algorand-typescript-testing/blob/main/src/subcontexts/ledger-context.ts#L411) - -Checks if a box exists for an application by key. - -#### Parameters - -##### app - -The application. - -`BaseContract` | `Application` - -##### key - -`StubBytesCompat` - -The key. - -#### Returns - -`boolean` - -True if the box exists, false otherwise. - -*** - -### deleteBox() - -> **deleteBox**(`app`, `key`): `boolean` - -Defined in: [src/subcontexts/ledger-context.ts:398](https://github.com/algorandfoundation/algorand-typescript-testing/blob/main/src/subcontexts/ledger-context.ts#L398) - -Deletes a box for an application by key. - -#### Parameters - -##### app - -The application. - -`BaseContract` | `Application` - -##### key - -`StubBytesCompat` - -The key. - -#### Returns - -`boolean` - -True if the box was deleted, false otherwise. - -*** - -### getAccount() - -> **getAccount**(`address`): `Account` - -Defined in: [src/subcontexts/ledger-context.ts:60](https://github.com/algorandfoundation/algorand-typescript-testing/blob/main/src/subcontexts/ledger-context.ts#L60) - -Retrieves an account by address. - -#### Parameters - -##### address - -The account address. - -`StubBytesCompat` | `Account` - -#### Returns - -`Account` - -The account. - -*** - -### getApplication() - -> **getApplication**(`applicationId`): `Application` - -Defined in: [src/subcontexts/ledger-context.ts:83](https://github.com/algorandfoundation/algorand-typescript-testing/blob/main/src/subcontexts/ledger-context.ts#L83) - -Retrieves an application by application ID. - -#### Parameters - -##### applicationId - -`StubUint64Compat` - -The application ID. - -#### Returns - -`Application` - -The application. - -#### Throws - -If the application is unknown. - -*** - -### getApplicationForApprovalProgram() - -> **getApplicationForApprovalProgram**(`approvalProgram`): `undefined` \| `Application` - -Defined in: [src/subcontexts/ledger-context.ts:112](https://github.com/algorandfoundation/algorand-typescript-testing/blob/main/src/subcontexts/ledger-context.ts#L112) - -Retrieves an application for a given approval program. - -#### Parameters - -##### approvalProgram - -The approval program. - -`undefined` | `bytes` | readonly `bytes`[] - -#### Returns - -`undefined` \| `Application` - -The application or undefined if not found. - -*** - -### getApplicationForContract() - -> **getApplicationForContract**(`contract`): `Application` - -Defined in: [src/subcontexts/ledger-context.ts:96](https://github.com/algorandfoundation/algorand-typescript-testing/blob/main/src/subcontexts/ledger-context.ts#L96) - -Retrieves an application for a given contract. - -#### Parameters - -##### contract - -`BaseContract` - -The contract. - -#### Returns - -`Application` - -The application. - -#### Throws - -If the contract is unknown. - -*** - -### getAsset() - -> **getAsset**(`assetId`): `Asset` - -Defined in: [src/subcontexts/ledger-context.ts:70](https://github.com/algorandfoundation/algorand-typescript-testing/blob/main/src/subcontexts/ledger-context.ts#L70) - -Retrieves an asset by asset ID. - -#### Parameters - -##### assetId - -`StubUint64Compat` - -The asset ID. - -#### Returns - -`Asset` - -The asset. - -#### Throws - -If the asset is unknown. - -*** - -### getBlockData() - -> **getBlockData**(`index`): `BlockData` - -Defined in: [src/subcontexts/ledger-context.ts:246](https://github.com/algorandfoundation/algorand-typescript-testing/blob/main/src/subcontexts/ledger-context.ts#L246) - -Retrieves block data by index. - -#### Parameters - -##### index - -`StubUint64Compat` - -The block index. - -#### Returns - -`BlockData` - -The block data. - -#### Throws - -If the block is not set. - -*** - -### getBox() - -> **getBox**(`app`, `key`): `Uint8Array` - -Defined in: [src/subcontexts/ledger-context.ts:341](https://github.com/algorandfoundation/algorand-typescript-testing/blob/main/src/subcontexts/ledger-context.ts#L341) - -Retrieves a box for an application by key. - -#### Parameters - -##### app - -The application. - -`BaseContract` | `Application` - -##### key - -`StubBytesCompat` - -The key. - -#### Returns - -`Uint8Array` - -The box data. - -*** - -### getGlobalState() - -> **getGlobalState**(`app`, `key`): \[`GlobalStateCls`\<`unknown`\>, `true`\] \| \[`undefined`, `false`\] - -Defined in: [src/subcontexts/ledger-context.ts:260](https://github.com/algorandfoundation/algorand-typescript-testing/blob/main/src/subcontexts/ledger-context.ts#L260) - -Retrieves global state for an application by key. - -#### Parameters - -##### app - -The application. - -`BaseContract` | `Application` - -##### key - -`StubBytesCompat` - -The key. - -#### Returns - -\[`GlobalStateCls`\<`unknown`\>, `true`\] \| \[`undefined`, `false`\] - -The global state and a boolean indicating if it was found. - -*** - -### getLocalState() - -> **getLocalState**(`app`, `account`, `key`): \[`undefined`, `false`\] \| \[`LocalStateForAccount`\<`unknown`\>, `true`\] - -Defined in: [src/subcontexts/ledger-context.ts:295](https://github.com/algorandfoundation/algorand-typescript-testing/blob/main/src/subcontexts/ledger-context.ts#L295) - -Retrieves local state for an application and account by key. - -#### Parameters - -##### app - -The application. - -`uint64` | `BaseContract` | `Application` - -##### account - -`Account` - -The account. - -##### key - -`StubBytesCompat` - -The key. - -#### Returns - -\[`undefined`, `false`\] \| \[`LocalStateForAccount`\<`unknown`\>, `true`\] - -The local state and a boolean indicating if it was found. - -*** - -### getMaterialisedBox() - -> **getMaterialisedBox**\<`T`\>(`app`, `key`): `undefined` \| `T` - -Defined in: [src/subcontexts/ledger-context.ts:358](https://github.com/algorandfoundation/algorand-typescript-testing/blob/main/src/subcontexts/ledger-context.ts#L358) - -**`Internal`** - -Retrieves a materialised box for an application by key. - -#### Type Parameters - -##### T - -`T` - -#### Parameters - -##### app - -The application. - -`BaseContract` | `Application` - -##### key - -`StubBytesCompat` - -The key. - -#### Returns - -`undefined` \| `T` - -The materialised box data if exists or undefined. - -*** - -### patchAccountData() - -> **patchAccountData**(`account`, `data`): `void` - -Defined in: [src/subcontexts/ledger-context.ts:168](https://github.com/algorandfoundation/algorand-typescript-testing/blob/main/src/subcontexts/ledger-context.ts#L168) - -Patches account data with the provided partial data. - -#### Parameters - -##### account - -`Account` - -The account. - -##### data - -`Partial`\<`Omit`\<`AccountData`, `"account"`\>\> & `Partial`\<`PickPartial`\<`AccountData`, `"account"`\>\> - -The partial account data. - -#### Returns - -`void` - -*** - -### patchApplicationData() - -> **patchApplicationData**(`app`, `data`): `void` - -Defined in: [src/subcontexts/ledger-context.ts:185](https://github.com/algorandfoundation/algorand-typescript-testing/blob/main/src/subcontexts/ledger-context.ts#L185) - -Patches application data with the provided partial data. - -#### Parameters - -##### app - -`Application` - -The application. - -##### data - -`Partial`\<`Omit`\<`ApplicationData`, `"application"`\>\> & `Partial`\<`PickPartial`\<`ApplicationData`, `"application"`\>\> - -The partial application data. - -#### Returns - -`void` - -*** - -### patchAssetData() - -> **patchAssetData**(`asset`, `data`): `void` - -Defined in: [src/subcontexts/ledger-context.ts:205](https://github.com/algorandfoundation/algorand-typescript-testing/blob/main/src/subcontexts/ledger-context.ts#L205) - -Patches asset data with the provided partial data. - -#### Parameters - -##### asset - -`Asset` - -##### data - -`Partial`\<`Mutable`\<`Omit`\<`Asset`, `"id"` \| `"balance"` \| `"frozen"`\>\>\> - -The partial asset data. - -#### Returns - -`void` - -*** - -### patchBlockData() - -> **patchBlockData**(`index`, `data`): `void` - -Defined in: [src/subcontexts/ledger-context.ts:231](https://github.com/algorandfoundation/algorand-typescript-testing/blob/main/src/subcontexts/ledger-context.ts#L231) - -Patches block data with the provided partial data. - -#### Parameters - -##### index - -`StubUint64Compat` - -The block index. - -##### data - -`Partial`\<`BlockData`\> - -The partial block data. - -#### Returns - -`void` - -*** - -### patchGlobalData() - -> **patchGlobalData**(`data`): `void` - -Defined in: [src/subcontexts/ledger-context.ts:156](https://github.com/algorandfoundation/algorand-typescript-testing/blob/main/src/subcontexts/ledger-context.ts#L156) - -Patches global data with the provided partial data. - -#### Parameters - -##### data - -`Partial`\<`GlobalData`\> - -The partial global data. - -#### Returns - -`void` - -*** - -### patchVoterData() - -> **patchVoterData**(`account`, `data`): `void` - -Defined in: [src/subcontexts/ledger-context.ts:218](https://github.com/algorandfoundation/algorand-typescript-testing/blob/main/src/subcontexts/ledger-context.ts#L218) - -Patches voter data with the provided partial data. - -#### Parameters - -##### account - -`Account` - -The account. - -##### data - -`Partial`\<`VoterData`\> - -The partial voter data. - -#### Returns - -`void` - -*** - -### setBox() - -> **setBox**(`app`, `key`, `value`): `void` - -Defined in: [src/subcontexts/ledger-context.ts:370](https://github.com/algorandfoundation/algorand-typescript-testing/blob/main/src/subcontexts/ledger-context.ts#L370) - -Sets a box for an application by key. - -#### Parameters - -##### app - -The application. - -`BaseContract` | `Application` - -##### key - -`StubBytesCompat` - -The key. - -##### value - -The box data. - -`Uint8Array`\<`ArrayBufferLike`\> | `StubBytesCompat` - -#### Returns - -`void` - -*** - -### setGlobalState() - -> **setGlobalState**(`app`, `key`, `value`): `void` - -Defined in: [src/subcontexts/ledger-context.ts:275](https://github.com/algorandfoundation/algorand-typescript-testing/blob/main/src/subcontexts/ledger-context.ts#L275) - -Sets global state for an application by key. - -#### Parameters - -##### app - -The application. - -`BaseContract` | `Application` - -##### key - -`StubBytesCompat` - -The key. - -##### value - -The value (optional). - -`undefined` | `StubBytesCompat` | `StubUint64Compat` - -#### Returns - -`void` - -*** - -### setLocalState() - -> **setLocalState**\<`T`\>(`app`, `account`, `key`, `value`): `void` - -Defined in: [src/subcontexts/ledger-context.ts:317](https://github.com/algorandfoundation/algorand-typescript-testing/blob/main/src/subcontexts/ledger-context.ts#L317) - -Sets local state for an application and account by key. - -#### Type Parameters - -##### T - -`T` - -#### Parameters - -##### app - -The application. - -`uint64` | `BaseContract` | `Application` - -##### account - -`Account` - -The account. - -##### key - -`StubBytesCompat` - -The key. - -##### value - -The value (optional). - -`undefined` | `T` - -#### Returns - -`void` - -*** - -### setMatrialisedBox() - -> **setMatrialisedBox**\<`TValue`\>(`app`, `key`, `value`): `void` - -Defined in: [src/subcontexts/ledger-context.ts:386](https://github.com/algorandfoundation/algorand-typescript-testing/blob/main/src/subcontexts/ledger-context.ts#L386) - -**`Internal`** - -Cache the materialised box for an application by key. - -#### Type Parameters - -##### TValue - -`TValue` - -#### Parameters - -##### app - -The application. - -`BaseContract` | `Application` - -##### key - -`StubBytesCompat` - -The key. - -##### value - -The box data. - -`undefined` | `TValue` - -#### Returns - -`void` - -*** - -### updateAssetHolding() - -> **updateAssetHolding**(`account`, `assetId`, `balance`?, `frozen`?): `void` - -Defined in: [src/subcontexts/ledger-context.ts:142](https://github.com/algorandfoundation/algorand-typescript-testing/blob/main/src/subcontexts/ledger-context.ts#L142) - -Update asset holdings for account, only specified values will be updated. -AccountType will also be opted-in to asset - -#### Parameters - -##### account - -`Account` - -##### assetId - -`StubUint64Compat` | `Asset` - -##### balance? - -`StubUint64Compat` - -##### frozen? - -`boolean` - -#### Returns - -`void` diff --git a/docs/code/subcontexts/transaction-context/README.md b/docs/code/subcontexts/transaction-context/README.md deleted file mode 100644 index 32e8d084..00000000 --- a/docs/code/subcontexts/transaction-context/README.md +++ /dev/null @@ -1,14 +0,0 @@ -[**@algorandfoundation/algorand-typescript-testing**](../../README.md) - -*** - -[@algorandfoundation/algorand-typescript-testing](../../README.md) / subcontexts/transaction-context - -# subcontexts/transaction-context - -## Classes - -- [DeferredAppCall](classes/DeferredAppCall.md) -- [ItxnGroup](classes/ItxnGroup.md) -- [TransactionContext](classes/TransactionContext.md) -- [TransactionGroup](classes/TransactionGroup.md) diff --git a/docs/code/subcontexts/transaction-context/classes/DeferredAppCall.md b/docs/code/subcontexts/transaction-context/classes/DeferredAppCall.md deleted file mode 100644 index 11014f8e..00000000 --- a/docs/code/subcontexts/transaction-context/classes/DeferredAppCall.md +++ /dev/null @@ -1,79 +0,0 @@ -[**@algorandfoundation/algorand-typescript-testing**](../../../README.md) - -*** - -[@algorandfoundation/algorand-typescript-testing](../../../README.md) / [subcontexts/transaction-context](../README.md) / DeferredAppCall - -# Class: DeferredAppCall\ - -Defined in: [src/subcontexts/transaction-context.ts:59](https://github.com/algorandfoundation/algorand-typescript-testing/blob/main/src/subcontexts/transaction-context.ts#L59) - -Represents a deferred application call. - -## Type Parameters - -### TParams - -`TParams` *extends* `unknown`[] - -### TReturn - -`TReturn` - -## Constructors - -### Constructor - -> **new DeferredAppCall**\<`TParams`, `TReturn`\>(`appId`, `txns`, `method`, `abiMetadata`, `args`): `DeferredAppCall`\<`TParams`, `TReturn`\> - -Defined in: [src/subcontexts/transaction-context.ts:60](https://github.com/algorandfoundation/algorand-typescript-testing/blob/main/src/subcontexts/transaction-context.ts#L60) - -#### Parameters - -##### appId - -`uint64` - -##### txns - -`Transaction`[] - -##### method - -(...`args`) => `TReturn` - -##### abiMetadata - -`AbiMetadata` - -##### args - -`TParams` - -#### Returns - -`DeferredAppCall`\<`TParams`, `TReturn`\> - -## Properties - -### txns - -> `readonly` **txns**: `Transaction`[] - -Defined in: [src/subcontexts/transaction-context.ts:62](https://github.com/algorandfoundation/algorand-typescript-testing/blob/main/src/subcontexts/transaction-context.ts#L62) - -## Methods - -### submit() - -> **submit**(): `TReturn` - -Defined in: [src/subcontexts/transaction-context.ts:72](https://github.com/algorandfoundation/algorand-typescript-testing/blob/main/src/subcontexts/transaction-context.ts#L72) - -Submits the deferred application call. - -#### Returns - -`TReturn` - -The result of the application call. diff --git a/docs/code/subcontexts/transaction-context/classes/ItxnGroup.md b/docs/code/subcontexts/transaction-context/classes/ItxnGroup.md deleted file mode 100644 index 10a5bf2e..00000000 --- a/docs/code/subcontexts/transaction-context/classes/ItxnGroup.md +++ /dev/null @@ -1,205 +0,0 @@ -[**@algorandfoundation/algorand-typescript-testing**](../../../README.md) - -*** - -[@algorandfoundation/algorand-typescript-testing](../../../README.md) / [subcontexts/transaction-context](../README.md) / ItxnGroup - -# Class: ItxnGroup - -Defined in: [src/subcontexts/transaction-context.ts:496](https://github.com/algorandfoundation/algorand-typescript-testing/blob/main/src/subcontexts/transaction-context.ts#L496) - -Represents a group of inner transactions. - -## Constructors - -### Constructor - -> **new ItxnGroup**(`itxns`): `ItxnGroup` - -Defined in: [src/subcontexts/transaction-context.ts:498](https://github.com/algorandfoundation/algorand-typescript-testing/blob/main/src/subcontexts/transaction-context.ts#L498) - -#### Parameters - -##### itxns - -`InnerTxn`[] - -#### Returns - -`ItxnGroup` - -## Properties - -### itxns - -> **itxns**: `InnerTxn`[] = `[]` - -Defined in: [src/subcontexts/transaction-context.ts:497](https://github.com/algorandfoundation/algorand-typescript-testing/blob/main/src/subcontexts/transaction-context.ts#L497) - -## Methods - -### getApplicationCallInnerTxn() - -> **getApplicationCallInnerTxn**(`index`?): `ApplicationCallInnerTxn` - -Defined in: [src/subcontexts/transaction-context.ts:507](https://github.com/algorandfoundation/algorand-typescript-testing/blob/main/src/subcontexts/transaction-context.ts#L507) - -Gets an application inner transaction by index. - -#### Parameters - -##### index? - -`StubUint64Compat` - -The index of the transaction. - -#### Returns - -`ApplicationCallInnerTxn` - -The application inner transaction. - -*** - -### getAssetConfigInnerTxn() - -> **getAssetConfigInnerTxn**(`index`?): `AssetConfigInnerTxn` - -Defined in: [src/subcontexts/transaction-context.ts:516](https://github.com/algorandfoundation/algorand-typescript-testing/blob/main/src/subcontexts/transaction-context.ts#L516) - -Gets an asset configuration inner transaction by index. - -#### Parameters - -##### index? - -`StubUint64Compat` - -The index of the transaction. - -#### Returns - -`AssetConfigInnerTxn` - -The asset configuration inner transaction. - -*** - -### getAssetFreezeInnerTxn() - -> **getAssetFreezeInnerTxn**(`index`?): `AssetFreezeInnerTxn` - -Defined in: [src/subcontexts/transaction-context.ts:534](https://github.com/algorandfoundation/algorand-typescript-testing/blob/main/src/subcontexts/transaction-context.ts#L534) - -Gets an asset freeze inner transaction by index. - -#### Parameters - -##### index? - -`StubUint64Compat` - -The index of the transaction. - -#### Returns - -`AssetFreezeInnerTxn` - -The asset freeze inner transaction. - -*** - -### getAssetTransferInnerTxn() - -> **getAssetTransferInnerTxn**(`index`?): `AssetTransferInnerTxn` - -Defined in: [src/subcontexts/transaction-context.ts:525](https://github.com/algorandfoundation/algorand-typescript-testing/blob/main/src/subcontexts/transaction-context.ts#L525) - -Gets an asset transfer inner transaction by index. - -#### Parameters - -##### index? - -`StubUint64Compat` - -The index of the transaction. - -#### Returns - -`AssetTransferInnerTxn` - -The asset transfer inner transaction. - -*** - -### getInnerTxn() - -> **getInnerTxn**(`index`?): `InnerTxn` - -Defined in: [src/subcontexts/transaction-context.ts:561](https://github.com/algorandfoundation/algorand-typescript-testing/blob/main/src/subcontexts/transaction-context.ts#L561) - -Gets an inner transaction by index. - -#### Parameters - -##### index? - -`StubUint64Compat` - -The index of the transaction. - -#### Returns - -`InnerTxn` - -The inner transaction. - -*** - -### getKeyRegistrationInnerTxn() - -> **getKeyRegistrationInnerTxn**(`index`?): `KeyRegistrationInnerTxn` - -Defined in: [src/subcontexts/transaction-context.ts:543](https://github.com/algorandfoundation/algorand-typescript-testing/blob/main/src/subcontexts/transaction-context.ts#L543) - -Gets a key registration inner transaction by index. - -#### Parameters - -##### index? - -`StubUint64Compat` - -The index of the transaction. - -#### Returns - -`KeyRegistrationInnerTxn` - -The key registration inner transaction. - -*** - -### getPaymentInnerTxn() - -> **getPaymentInnerTxn**(`index`?): `PaymentInnerTxn` - -Defined in: [src/subcontexts/transaction-context.ts:552](https://github.com/algorandfoundation/algorand-typescript-testing/blob/main/src/subcontexts/transaction-context.ts#L552) - -Gets a payment inner transaction by index. - -#### Parameters - -##### index? - -`StubUint64Compat` - -The index of the transaction. - -#### Returns - -`PaymentInnerTxn` - -The payment inner transaction. diff --git a/docs/code/subcontexts/transaction-context/classes/TransactionContext.md b/docs/code/subcontexts/transaction-context/classes/TransactionContext.md deleted file mode 100644 index 7d27b907..00000000 --- a/docs/code/subcontexts/transaction-context/classes/TransactionContext.md +++ /dev/null @@ -1,273 +0,0 @@ -[**@algorandfoundation/algorand-typescript-testing**](../../../README.md) - -*** - -[@algorandfoundation/algorand-typescript-testing](../../../README.md) / [subcontexts/transaction-context](../README.md) / TransactionContext - -# Class: TransactionContext - -Defined in: [src/subcontexts/transaction-context.ts:81](https://github.com/algorandfoundation/algorand-typescript-testing/blob/main/src/subcontexts/transaction-context.ts#L81) - -Manages transaction contexts and groups. - -## Constructors - -### Constructor - -> **new TransactionContext**(): `TransactionContext` - -#### Returns - -`TransactionContext` - -## Properties - -### groups - -> `readonly` **groups**: [`TransactionGroup`](TransactionGroup.md)[] = `[]` - -Defined in: [src/subcontexts/transaction-context.ts:82](https://github.com/algorandfoundation/algorand-typescript-testing/blob/main/src/subcontexts/transaction-context.ts#L82) - -## Accessors - -### activeGroup - -#### Get Signature - -> **get** **activeGroup**(): [`TransactionGroup`](TransactionGroup.md) - -Defined in: [src/subcontexts/transaction-context.ts:148](https://github.com/algorandfoundation/algorand-typescript-testing/blob/main/src/subcontexts/transaction-context.ts#L148) - -Gets the active transaction group. - -##### Throws - -If there is no active transaction group. - -##### Returns - -[`TransactionGroup`](TransactionGroup.md) - -The active transaction group. - -*** - -### lastActive - -#### Get Signature - -> **get** **lastActive**(): `Transaction` - -Defined in: [src/subcontexts/transaction-context.ts:171](https://github.com/algorandfoundation/algorand-typescript-testing/blob/main/src/subcontexts/transaction-context.ts#L171) - -Gets the last active transaction. - -##### Returns - -`Transaction` - -The last active transaction. - -*** - -### lastGroup - -#### Get Signature - -> **get** **lastGroup**(): [`TransactionGroup`](TransactionGroup.md) - -Defined in: [src/subcontexts/transaction-context.ts:160](https://github.com/algorandfoundation/algorand-typescript-testing/blob/main/src/subcontexts/transaction-context.ts#L160) - -Gets the last transaction group. - -##### Throws - -If there are no transaction groups. - -##### Returns - -[`TransactionGroup`](TransactionGroup.md) - -The last transaction group. - -## Methods - -### appendLog() - -> **appendLog**(`value`): `void` - -Defined in: [src/subcontexts/transaction-context.ts:181](https://github.com/algorandfoundation/algorand-typescript-testing/blob/main/src/subcontexts/transaction-context.ts#L181) - -**`Internal`** - -Appends a log to the active transaction. - -#### Parameters - -##### value - -`StubBytesCompat` - -The log value. - -#### Returns - -`void` - -#### Throws - -If the active transaction is not an application call. - -*** - -### createScope() - -> **createScope**(`group`, `activeTransactionIndex`?): `ExecutionScope` - -Defined in: [src/subcontexts/transaction-context.ts:91](https://github.com/algorandfoundation/algorand-typescript-testing/blob/main/src/subcontexts/transaction-context.ts#L91) - -Creates a new execution scope for a group of transactions. - -#### Parameters - -##### group - -(`Transaction` \| [`DeferredAppCall`](DeferredAppCall.md)\<`any`[], `any`\>)[] - -The group of transactions or deferred application calls. - -##### activeTransactionIndex? - -`number` - -The index of the active transaction. - -#### Returns - -`ExecutionScope` - -The execution scope. - -*** - -### deferAppCall() - -> **deferAppCall**\<`TContract`, `TParams`, `TReturn`\>(`contract`, `method`, `methodName`, ...`args`): [`DeferredAppCall`](DeferredAppCall.md)\<`TParams`, `TReturn`\> - -Defined in: [src/subcontexts/transaction-context.ts:197](https://github.com/algorandfoundation/algorand-typescript-testing/blob/main/src/subcontexts/transaction-context.ts#L197) - -Defers an application call. - -#### Type Parameters - -##### TContract - -`TContract` *extends* `Contract` - -##### TParams - -`TParams` *extends* `unknown`[] - -##### TReturn - -`TReturn` - -#### Parameters - -##### contract - -`TContract` - -The contract. - -##### method - -(...`args`) => `TReturn` - -The method to call. - -##### methodName - -`FunctionKeys`\<`TContract`\> - -The name of the method. - -##### args - -...`TParams` - -The arguments for the method. - -#### Returns - -[`DeferredAppCall`](DeferredAppCall.md)\<`TParams`, `TReturn`\> - -The deferred application call. - -*** - -### ensureScope() - -> **ensureScope**(`group`, `activeTransactionIndex`?): `ExecutionScope` - -Defined in: [src/subcontexts/transaction-context.ts:132](https://github.com/algorandfoundation/algorand-typescript-testing/blob/main/src/subcontexts/transaction-context.ts#L132) - -**`Internal`** - -Ensures that a scope is created for the given group of transactions. - -#### Parameters - -##### group - -`Transaction`[] - -The group of transactions. - -##### activeTransactionIndex? - -`number` - -The index of the active transaction. - -#### Returns - -`ExecutionScope` - -The execution scope. - -*** - -### exportLogs() - -> **exportLogs**\<`T`\>(`appId`, ...`decoding`): `DecodedLogs`\<`T`\> - -Defined in: [src/subcontexts/transaction-context.ts:215](https://github.com/algorandfoundation/algorand-typescript-testing/blob/main/src/subcontexts/transaction-context.ts#L215) - -Exports logs for the given application ID. - -#### Type Parameters - -##### T - -`T` *extends* `LogDecoding`[] - -#### Parameters - -##### appId - -`uint64` - -The application ID. - -##### decoding - -...`T` - -The log decoding. - -#### Returns - -`DecodedLogs`\<`T`\> - -The decoded logs. diff --git a/docs/code/subcontexts/transaction-context/classes/TransactionGroup.md b/docs/code/subcontexts/transaction-context/classes/TransactionGroup.md deleted file mode 100644 index b16e01ff..00000000 --- a/docs/code/subcontexts/transaction-context/classes/TransactionGroup.md +++ /dev/null @@ -1,485 +0,0 @@ -[**@algorandfoundation/algorand-typescript-testing**](../../../README.md) - -*** - -[@algorandfoundation/algorand-typescript-testing](../../../README.md) / [subcontexts/transaction-context](../README.md) / TransactionGroup - -# Class: TransactionGroup - -Defined in: [src/subcontexts/transaction-context.ts:233](https://github.com/algorandfoundation/algorand-typescript-testing/blob/main/src/subcontexts/transaction-context.ts#L233) - -Represents a group of transactions. - -## Constructors - -### Constructor - -> **new TransactionGroup**(`transactions`, `activeTransactionIndex`?): `TransactionGroup` - -Defined in: [src/subcontexts/transaction-context.ts:240](https://github.com/algorandfoundation/algorand-typescript-testing/blob/main/src/subcontexts/transaction-context.ts#L240) - -#### Parameters - -##### transactions - -`Transaction`[] - -##### activeTransactionIndex? - -`number` - -#### Returns - -`TransactionGroup` - -## Properties - -### activeTransactionIndex - -> **activeTransactionIndex**: `number` - -Defined in: [src/subcontexts/transaction-context.ts:234](https://github.com/algorandfoundation/algorand-typescript-testing/blob/main/src/subcontexts/transaction-context.ts#L234) - -*** - -### constructingItxnGroup - -> **constructingItxnGroup**: `InnerTxnFields`[] = `[]` - -Defined in: [src/subcontexts/transaction-context.ts:238](https://github.com/algorandfoundation/algorand-typescript-testing/blob/main/src/subcontexts/transaction-context.ts#L238) - -*** - -### itxnGroups - -> **itxnGroups**: [`ItxnGroup`](ItxnGroup.md)[] = `[]` - -Defined in: [src/subcontexts/transaction-context.ts:237](https://github.com/algorandfoundation/algorand-typescript-testing/blob/main/src/subcontexts/transaction-context.ts#L237) - -*** - -### latestTimestamp - -> **latestTimestamp**: `number` - -Defined in: [src/subcontexts/transaction-context.ts:235](https://github.com/algorandfoundation/algorand-typescript-testing/blob/main/src/subcontexts/transaction-context.ts#L235) - -*** - -### transactions - -> **transactions**: `Transaction`[] - -Defined in: [src/subcontexts/transaction-context.ts:236](https://github.com/algorandfoundation/algorand-typescript-testing/blob/main/src/subcontexts/transaction-context.ts#L236) - -## Accessors - -### activeApplicationId - -#### Get Signature - -> **get** **activeApplicationId**(): `uint64` - -Defined in: [src/subcontexts/transaction-context.ts:263](https://github.com/algorandfoundation/algorand-typescript-testing/blob/main/src/subcontexts/transaction-context.ts#L263) - -Gets the active application ID. - -##### Throws - -If there are no transactions in the group or the active transaction is not an application call. - -##### Returns - -`uint64` - -The active application ID. - -*** - -### activeTransaction - -#### Get Signature - -> **get** **activeTransaction**(): `Transaction` - -Defined in: [src/subcontexts/transaction-context.ts:254](https://github.com/algorandfoundation/algorand-typescript-testing/blob/main/src/subcontexts/transaction-context.ts#L254) - -Gets the active transaction. - -##### Returns - -`Transaction` - -The active transaction. - -*** - -### constructingItxn - -#### Get Signature - -> **get** **constructingItxn**(): `InnerTxnFields` - -Defined in: [src/subcontexts/transaction-context.ts:273](https://github.com/algorandfoundation/algorand-typescript-testing/blob/main/src/subcontexts/transaction-context.ts#L273) - -##### Returns - -`InnerTxnFields` - -## Methods - -### addInnerTransactionGroup() - -> **addInnerTransactionGroup**(...`itxns`): `void` - -Defined in: [src/subcontexts/transaction-context.ts:312](https://github.com/algorandfoundation/algorand-typescript-testing/blob/main/src/subcontexts/transaction-context.ts#L312) - -**`Internal`** - -Adds a group of inner transactions. - -#### Parameters - -##### itxns - -...`InnerTxn`[] - -The inner transactions. - -#### Returns - -`void` - -*** - -### appendInnerTransactionGroup() - -> **appendInnerTransactionGroup**(): `void` - -Defined in: [src/subcontexts/transaction-context.ts:337](https://github.com/algorandfoundation/algorand-typescript-testing/blob/main/src/subcontexts/transaction-context.ts#L337) - -**`Internal`** - -Appends a new inner transaction to the current group. - -#### Returns - -`void` - -#### Throws - -If there is no inner transaction group being constructed. - -*** - -### beginInnerTransactionGroup() - -> **beginInnerTransactionGroup**(): `void` - -Defined in: [src/subcontexts/transaction-context.ts:321](https://github.com/algorandfoundation/algorand-typescript-testing/blob/main/src/subcontexts/transaction-context.ts#L321) - -**`Internal`** - -Begins a new inner transaction group. - -#### Returns - -`void` - -#### Throws - -If there is already an inner transaction group being constructed or the active transaction is not an application call. - -*** - -### getApplicationCallTransaction() - -> **getApplicationCallTransaction**(`index`?): `ApplicationCallTransaction` - -Defined in: [src/subcontexts/transaction-context.ts:405](https://github.com/algorandfoundation/algorand-typescript-testing/blob/main/src/subcontexts/transaction-context.ts#L405) - -Gets an application transaction by index. - -#### Parameters - -##### index? - -`StubUint64Compat` - -The index of the transaction. - -#### Returns - -`ApplicationCallTransaction` - -The application transaction. - -*** - -### getAssetConfigTransaction() - -> **getAssetConfigTransaction**(`index`?): `AssetConfigTransaction` - -Defined in: [src/subcontexts/transaction-context.ts:414](https://github.com/algorandfoundation/algorand-typescript-testing/blob/main/src/subcontexts/transaction-context.ts#L414) - -Gets an asset configuration transaction by index. - -#### Parameters - -##### index? - -`StubUint64Compat` - -The index of the transaction. - -#### Returns - -`AssetConfigTransaction` - -The asset configuration transaction. - -*** - -### getAssetFreezeTransaction() - -> **getAssetFreezeTransaction**(`index`?): `AssetFreezeTransaction` - -Defined in: [src/subcontexts/transaction-context.ts:432](https://github.com/algorandfoundation/algorand-typescript-testing/blob/main/src/subcontexts/transaction-context.ts#L432) - -Gets an asset freeze transaction by index. - -#### Parameters - -##### index? - -`StubUint64Compat` - -The index of the transaction. - -#### Returns - -`AssetFreezeTransaction` - -The asset freeze transaction. - -*** - -### getAssetTransferTransaction() - -> **getAssetTransferTransaction**(`index`?): `AssetTransferTransaction` - -Defined in: [src/subcontexts/transaction-context.ts:423](https://github.com/algorandfoundation/algorand-typescript-testing/blob/main/src/subcontexts/transaction-context.ts#L423) - -Gets an asset transfer transaction by index. - -#### Parameters - -##### index? - -`StubUint64Compat` - -The index of the transaction. - -#### Returns - -`AssetTransferTransaction` - -The asset transfer transaction. - -*** - -### getItxnGroup() - -> **getItxnGroup**(`index`?): [`ItxnGroup`](ItxnGroup.md) - -Defined in: [src/subcontexts/transaction-context.ts:387](https://github.com/algorandfoundation/algorand-typescript-testing/blob/main/src/subcontexts/transaction-context.ts#L387) - -Gets an inner transaction group by index. - -#### Parameters - -##### index? - -`StubUint64Compat` - -The index of the group. If not provided, the last group is returned. - -#### Returns - -[`ItxnGroup`](ItxnGroup.md) - -The inner transaction group. - -#### Throws - -If the index is invalid or there are no previous inner transactions. - -*** - -### getKeyRegistrationTransaction() - -> **getKeyRegistrationTransaction**(`index`?): `KeyRegistrationTransaction` - -Defined in: [src/subcontexts/transaction-context.ts:441](https://github.com/algorandfoundation/algorand-typescript-testing/blob/main/src/subcontexts/transaction-context.ts#L441) - -Gets a key registration transaction by index. - -#### Parameters - -##### index? - -`StubUint64Compat` - -The index of the transaction. - -#### Returns - -`KeyRegistrationTransaction` - -The key registration transaction. - -*** - -### getPaymentTransaction() - -> **getPaymentTransaction**(`index`?): `PaymentTransaction` - -Defined in: [src/subcontexts/transaction-context.ts:450](https://github.com/algorandfoundation/algorand-typescript-testing/blob/main/src/subcontexts/transaction-context.ts#L450) - -Gets a payment transaction by index. - -#### Parameters - -##### index? - -`StubUint64Compat` - -The index of the transaction. - -#### Returns - -`PaymentTransaction` - -The payment transaction. - -*** - -### getScratchSlot() - -> **getScratchSlot**(`index`): `uint64` \| `bytes` - -Defined in: [src/subcontexts/transaction-context.ts:293](https://github.com/algorandfoundation/algorand-typescript-testing/blob/main/src/subcontexts/transaction-context.ts#L293) - -Gets the scratch slot of the active transaction. - -#### Parameters - -##### index - -`StubUint64Compat` - -The index of the scratch slot. - -#### Returns - -`uint64` \| `bytes` - -The scratch slot value. - -*** - -### getScratchSpace() - -> **getScratchSpace**(): (`uint64` \| `bytes`)[] - -Defined in: [src/subcontexts/transaction-context.ts:284](https://github.com/algorandfoundation/algorand-typescript-testing/blob/main/src/subcontexts/transaction-context.ts#L284) - -Gets the scratch space of the active transaction. - -#### Returns - -(`uint64` \| `bytes`)[] - -The scratch space. - -*** - -### getTransaction() - -> **getTransaction**(`index`?): `Transaction` - -Defined in: [src/subcontexts/transaction-context.ts:459](https://github.com/algorandfoundation/algorand-typescript-testing/blob/main/src/subcontexts/transaction-context.ts#L459) - -Gets a transaction by index. - -#### Parameters - -##### index? - -`StubUint64Compat` - -The index of the transaction. - -#### Returns - -`Transaction` - -The transaction. - -*** - -### lastItxnGroup() - -> **lastItxnGroup**(): [`ItxnGroup`](ItxnGroup.md) - -Defined in: [src/subcontexts/transaction-context.ts:377](https://github.com/algorandfoundation/algorand-typescript-testing/blob/main/src/subcontexts/transaction-context.ts#L377) - -Gets the last inner transaction group. - -#### Returns - -[`ItxnGroup`](ItxnGroup.md) - -The last inner transaction group. - -*** - -### patchActiveTransactionFields() - -> **patchActiveTransactionFields**(`fields`): `void` - -Defined in: [src/subcontexts/transaction-context.ts:301](https://github.com/algorandfoundation/algorand-typescript-testing/blob/main/src/subcontexts/transaction-context.ts#L301) - -Patches the fields of the active transaction. - -#### Parameters - -##### fields - -`AllTransactionFields` - -The fields to patch. - -#### Returns - -`void` - -*** - -### submitInnerTransactionGroup() - -> **submitInnerTransactionGroup**(): `void` - -Defined in: [src/subcontexts/transaction-context.ts:349](https://github.com/algorandfoundation/algorand-typescript-testing/blob/main/src/subcontexts/transaction-context.ts#L349) - -**`Internal`** - -Submits the current inner transaction group. - -#### Returns - -`void` - -#### Throws - -If there is no inner transaction group being constructed or the group exceeds the maximum size. diff --git a/docs/code/test-transformer/jest-transformer/README.md b/docs/code/test-transformer/jest-transformer/README.md deleted file mode 100644 index c00fdef0..00000000 --- a/docs/code/test-transformer/jest-transformer/README.md +++ /dev/null @@ -1,13 +0,0 @@ -[**@algorandfoundation/algorand-typescript-testing**](../../README.md) - -*** - -[@algorandfoundation/algorand-typescript-testing](../../README.md) / test-transformer/jest-transformer - -# test-transformer/jest-transformer - -## Variables - -- [factory](variables/factory.md) -- [name](variables/name.md) -- [version](variables/version.md) diff --git a/docs/code/test-transformer/jest-transformer/variables/factory.md b/docs/code/test-transformer/jest-transformer/variables/factory.md deleted file mode 100644 index 99a090e2..00000000 --- a/docs/code/test-transformer/jest-transformer/variables/factory.md +++ /dev/null @@ -1,52 +0,0 @@ -[**@algorandfoundation/algorand-typescript-testing**](../../../README.md) - -*** - -[@algorandfoundation/algorand-typescript-testing](../../../README.md) / [test-transformer/jest-transformer](../README.md) / factory - -# Variable: factory() - -> `const` **factory**: (`compilerInstance`) => `TransformerFactory`\<`SourceFile`\> - -Defined in: [src/test-transformer/jest-transformer.ts:49](https://github.com/algorandfoundation/algorand-typescript-testing/blob/main/src/test-transformer/jest-transformer.ts#L49) - -Factory function that creates TypeScript program transformers for Jest. -Used by ts-jest to transform TypeScript files during test execution. -Initialized with default transformer configuration. - -## Parameters - -### compilerInstance - -#### program - -`Program` - -## Returns - -`TransformerFactory`\<`SourceFile`\> - -## Example - -```ts -// Use as before stage transformer with custom config in jest.config.ts -import { createDefaultEsmPreset, type JestConfigWithTsJest } from 'ts-jest' - -const presetConfig = createDefaultEsmPreset({}) -const jestConfig: JestConfigWithTsJest = { - ...presetConfig, - transform: { - '^.+\\.tsx?$': [ - 'ts-jest', - { - useESM: true, - astTransformers: { - before: ['node_modules/@algorandfoundation/algorand-typescript-testing/test-transformer/jest-transformer.mjs'], - }, - }, - ], - }, - extensionsToTreatAsEsm: ['.ts'], -} -export default jestConfig -``` diff --git a/docs/code/test-transformer/jest-transformer/variables/name.md b/docs/code/test-transformer/jest-transformer/variables/name.md deleted file mode 100644 index 4e5c23f8..00000000 --- a/docs/code/test-transformer/jest-transformer/variables/name.md +++ /dev/null @@ -1,11 +0,0 @@ -[**@algorandfoundation/algorand-typescript-testing**](../../../README.md) - -*** - -[@algorandfoundation/algorand-typescript-testing](../../../README.md) / [test-transformer/jest-transformer](../README.md) / name - -# Variable: name - -> `const` **name**: `"puyaTsTransformer"` = `'puyaTsTransformer'` - -Defined in: [src/test-transformer/jest-transformer.ts:15](https://github.com/algorandfoundation/algorand-typescript-testing/blob/main/src/test-transformer/jest-transformer.ts#L15) diff --git a/docs/code/test-transformer/jest-transformer/variables/version.md b/docs/code/test-transformer/jest-transformer/variables/version.md deleted file mode 100644 index 85d81f7e..00000000 --- a/docs/code/test-transformer/jest-transformer/variables/version.md +++ /dev/null @@ -1,11 +0,0 @@ -[**@algorandfoundation/algorand-typescript-testing**](../../../README.md) - -*** - -[@algorandfoundation/algorand-typescript-testing](../../../README.md) / [test-transformer/jest-transformer](../README.md) / version - -# Variable: version - -> `const` **version**: `"0.1.0"` = `'0.1.0'` - -Defined in: [src/test-transformer/jest-transformer.ts:16](https://github.com/algorandfoundation/algorand-typescript-testing/blob/main/src/test-transformer/jest-transformer.ts#L16) diff --git a/docs/code/test-transformer/vitest-transformer/README.md b/docs/code/test-transformer/vitest-transformer/README.md deleted file mode 100644 index dead2fa4..00000000 --- a/docs/code/test-transformer/vitest-transformer/README.md +++ /dev/null @@ -1,11 +0,0 @@ -[**@algorandfoundation/algorand-typescript-testing**](../../README.md) - -*** - -[@algorandfoundation/algorand-typescript-testing](../../README.md) / test-transformer/vitest-transformer - -# test-transformer/vitest-transformer - -## Variables - -- [puyaTsTransformer](variables/puyaTsTransformer.md) diff --git a/docs/code/test-transformer/vitest-transformer/variables/puyaTsTransformer.md b/docs/code/test-transformer/vitest-transformer/variables/puyaTsTransformer.md deleted file mode 100644 index dfae2e98..00000000 --- a/docs/code/test-transformer/vitest-transformer/variables/puyaTsTransformer.md +++ /dev/null @@ -1,50 +0,0 @@ -[**@algorandfoundation/algorand-typescript-testing**](../../../README.md) - -*** - -[@algorandfoundation/algorand-typescript-testing](../../../README.md) / [test-transformer/vitest-transformer](../README.md) / puyaTsTransformer - -# Variable: puyaTsTransformer - -> `const` **puyaTsTransformer**: `ts.TransformerFactory`\<`ts.SourceFile`\> & (`config`) => `ts.TransformerFactory`\<`ts.SourceFile`\> - -Defined in: [src/test-transformer/vitest-transformer.ts:54](https://github.com/algorandfoundation/algorand-typescript-testing/blob/main/src/test-transformer/vitest-transformer.ts#L54) - -TypeScript transformer for Algorand TypeScript smart contracts and testing files -which is mainly responsilbe for swapping in stub implementations of op codes, -and capturing TypeScript type information for the Node.js runtime. - -* - -## Param - -Configuration options - -## Param - -File extensions to process - -## Param - -Package name for testing imports - -## Example - -```ts -// Use as before stage transformer with custom config in vitest.config.mts -import typescript from '@rollup/plugin-typescript' -import { defineConfig } from 'vitest/config' -import { puyaTsTransformer } from '@algorandfoundation/algorand-typescript-testing/vitest-transformer' - -export default defineConfig({ - esbuild: {}, - plugins: [ - typescript({ - tsconfig: './tsconfig.json', - transformers: { - before: [puyaTsTransformer], - }, - }), - ], -}) -``` diff --git a/docs/code/value-generators/README.md b/docs/code/value-generators/README.md deleted file mode 100644 index 5e7671a8..00000000 --- a/docs/code/value-generators/README.md +++ /dev/null @@ -1,11 +0,0 @@ -[**@algorandfoundation/algorand-typescript-testing**](../README.md) - -*** - -[@algorandfoundation/algorand-typescript-testing](../README.md) / value-generators - -# value-generators - -## Classes - -- [ValueGenerator](classes/ValueGenerator.md) diff --git a/docs/code/value-generators/arc4/README.md b/docs/code/value-generators/arc4/README.md deleted file mode 100644 index 6ef802d1..00000000 --- a/docs/code/value-generators/arc4/README.md +++ /dev/null @@ -1,11 +0,0 @@ -[**@algorandfoundation/algorand-typescript-testing**](../../README.md) - -*** - -[@algorandfoundation/algorand-typescript-testing](../../README.md) / value-generators/arc4 - -# value-generators/arc4 - -## Classes - -- [Arc4ValueGenerator](classes/Arc4ValueGenerator.md) diff --git a/docs/code/value-generators/arc4/classes/Arc4ValueGenerator.md b/docs/code/value-generators/arc4/classes/Arc4ValueGenerator.md deleted file mode 100644 index 8baa1b21..00000000 --- a/docs/code/value-generators/arc4/classes/Arc4ValueGenerator.md +++ /dev/null @@ -1,242 +0,0 @@ -[**@algorandfoundation/algorand-typescript-testing**](../../../README.md) - -*** - -[@algorandfoundation/algorand-typescript-testing](../../../README.md) / [value-generators/arc4](../README.md) / Arc4ValueGenerator - -# Class: Arc4ValueGenerator - -Defined in: [src/value-generators/arc4.ts:7](https://github.com/algorandfoundation/algorand-typescript-testing/blob/main/src/value-generators/arc4.ts#L7) - -## Constructors - -### Constructor - -> **new Arc4ValueGenerator**(): `Arc4ValueGenerator` - -#### Returns - -`Arc4ValueGenerator` - -## Methods - -### address() - -> **address**(): `Address` - -Defined in: [src/value-generators/arc4.ts:12](https://github.com/algorandfoundation/algorand-typescript-testing/blob/main/src/value-generators/arc4.ts#L12) - -Generate a random Algorand address. -@returns: A new, random Algorand address. - -#### Returns - -`Address` - -*** - -### dynamicBytes() - -> **dynamicBytes**(`n`): `DynamicBytes` - -Defined in: [src/value-generators/arc4.ts:97](https://github.com/algorandfoundation/algorand-typescript-testing/blob/main/src/value-generators/arc4.ts#L97) - -Generate a random dynamic bytes of size `n` bits. - -#### Parameters - -##### n - -`number` - -#### Returns - -`DynamicBytes` - -*** - -### str() - -> **str**(`n`): `Str` - -Defined in: [src/value-generators/arc4.ts:109](https://github.com/algorandfoundation/algorand-typescript-testing/blob/main/src/value-generators/arc4.ts#L109) - -Generate a random dynamic string of size `n` bits. - -#### Parameters - -##### n - -`number` - -#### Returns - -`Str` - -*** - -### uint128() - -> **uint128**(`minValue`, `maxValue`): `Uint128` - -Defined in: [src/value-generators/arc4.ts:67](https://github.com/algorandfoundation/algorand-typescript-testing/blob/main/src/value-generators/arc4.ts#L67) - -Generate a random Uint128 within the specified range. - -#### Parameters - -##### minValue - -`number` | `bigint` - -##### maxValue - -`number` | `bigint` - -#### Returns - -`Uint128` - -*** - -### uint16() - -> **uint16**(`minValue`, `maxValue`): `Uint16` - -Defined in: [src/value-generators/arc4.ts:37](https://github.com/algorandfoundation/algorand-typescript-testing/blob/main/src/value-generators/arc4.ts#L37) - -Generate a random Uint16 within the specified range. - -#### Parameters - -##### minValue - -`number` | `bigint` - -##### maxValue - -`number` | `bigint` - -#### Returns - -`Uint16` - -*** - -### uint256() - -> **uint256**(`minValue`, `maxValue`): `Uint256` - -Defined in: [src/value-generators/arc4.ts:77](https://github.com/algorandfoundation/algorand-typescript-testing/blob/main/src/value-generators/arc4.ts#L77) - -Generate a random Uint256 within the specified range. - -#### Parameters - -##### minValue - -`number` | `bigint` - -##### maxValue - -`number` | `bigint` - -#### Returns - -`Uint256` - -*** - -### uint32() - -> **uint32**(`minValue`, `maxValue`): `Uint32` - -Defined in: [src/value-generators/arc4.ts:47](https://github.com/algorandfoundation/algorand-typescript-testing/blob/main/src/value-generators/arc4.ts#L47) - -Generate a random Uint32 within the specified range. - -#### Parameters - -##### minValue - -`number` | `bigint` - -##### maxValue - -`number` | `bigint` - -#### Returns - -`Uint32` - -*** - -### uint512() - -> **uint512**(`minValue`, `maxValue`): `Uint`\<`512`\> - -Defined in: [src/value-generators/arc4.ts:87](https://github.com/algorandfoundation/algorand-typescript-testing/blob/main/src/value-generators/arc4.ts#L87) - -Generate a random Uint512 within the specified range. - -#### Parameters - -##### minValue - -`number` | `bigint` - -##### maxValue - -`number` | `bigint` - -#### Returns - -`Uint`\<`512`\> - -*** - -### uint64() - -> **uint64**(`minValue`, `maxValue`): `Uint64` - -Defined in: [src/value-generators/arc4.ts:57](https://github.com/algorandfoundation/algorand-typescript-testing/blob/main/src/value-generators/arc4.ts#L57) - -Generate a random Uint64 within the specified range. - -#### Parameters - -##### minValue - -`number` | `bigint` - -##### maxValue - -`number` | `bigint` - -#### Returns - -`Uint64` - -*** - -### uint8() - -> **uint8**(`minValue`, `maxValue`): `Uint8` - -Defined in: [src/value-generators/arc4.ts:27](https://github.com/algorandfoundation/algorand-typescript-testing/blob/main/src/value-generators/arc4.ts#L27) - -Generate a random Uint8 within the specified range. - -#### Parameters - -##### minValue - -`number` | `bigint` - -##### maxValue - -`number` | `bigint` - -#### Returns - -`Uint8` diff --git a/docs/code/value-generators/avm/README.md b/docs/code/value-generators/avm/README.md deleted file mode 100644 index b528e26b..00000000 --- a/docs/code/value-generators/avm/README.md +++ /dev/null @@ -1,11 +0,0 @@ -[**@algorandfoundation/algorand-typescript-testing**](../../README.md) - -*** - -[@algorandfoundation/algorand-typescript-testing](../../README.md) / value-generators/avm - -# value-generators/avm - -## Classes - -- [AvmValueGenerator](classes/AvmValueGenerator.md) diff --git a/docs/code/value-generators/avm/classes/AvmValueGenerator.md b/docs/code/value-generators/avm/classes/AvmValueGenerator.md deleted file mode 100644 index 1b5fb7e0..00000000 --- a/docs/code/value-generators/avm/classes/AvmValueGenerator.md +++ /dev/null @@ -1,197 +0,0 @@ -[**@algorandfoundation/algorand-typescript-testing**](../../../README.md) - -*** - -[@algorandfoundation/algorand-typescript-testing](../../../README.md) / [value-generators/avm](../README.md) / AvmValueGenerator - -# Class: AvmValueGenerator - -Defined in: [src/value-generators/avm.ts:31](https://github.com/algorandfoundation/algorand-typescript-testing/blob/main/src/value-generators/avm.ts#L31) - -## Extended by - -- [`ValueGenerator`](../../classes/ValueGenerator.md) - -## Constructors - -### Constructor - -> **new AvmValueGenerator**(): `AvmValueGenerator` - -#### Returns - -`AvmValueGenerator` - -## Methods - -### account() - -> **account**(`input`?): `Account` - -Defined in: [src/value-generators/avm.ts:95](https://github.com/algorandfoundation/algorand-typescript-testing/blob/main/src/value-generators/avm.ts#L95) - -Generates a random account with the specified context data. - -#### Parameters - -##### input? - -`AccountContextData` - -The context data for the account. - -#### Returns - -`Account` - -- A random account. - -*** - -### application() - -> **application**(`input`?): `Application` - -Defined in: [src/value-generators/avm.ts:153](https://github.com/algorandfoundation/algorand-typescript-testing/blob/main/src/value-generators/avm.ts#L153) - -Generates a random application with the specified context data. - -#### Parameters - -##### input? - -`ApplicationContextData` - -The context data for the application. - -#### Returns - -`Application` - -- A random application. - -*** - -### asset() - -> **asset**(`input`?): `Asset` - -Defined in: [src/value-generators/avm.ts:133](https://github.com/algorandfoundation/algorand-typescript-testing/blob/main/src/value-generators/avm.ts#L133) - -Generates a random asset with the specified context data. - -#### Parameters - -##### input? - -`AssetContextData` - -The context data for the asset. - -#### Returns - -`Asset` - -- A random asset. - -*** - -### biguint() - -> **biguint**(`minValue`?): `biguint` - -Defined in: [src/value-generators/avm.ts:58](https://github.com/algorandfoundation/algorand-typescript-testing/blob/main/src/value-generators/avm.ts#L58) - -Generates a random biguint value within the specified range. - -#### Parameters - -##### minValue? - -`StubBigUintCompat` = `0n` - -The minimum value (inclusive). - -#### Returns - -`biguint` - -- A random biguint value. - -*** - -### bytes() - -> **bytes**(`length`?): `bytes` - -Defined in: [src/value-generators/avm.ts:72](https://github.com/algorandfoundation/algorand-typescript-testing/blob/main/src/value-generators/avm.ts#L72) - -Generates a random bytes of the specified length. - -#### Parameters - -##### length? - -`number` = `MAX_BYTES_SIZE` - -The length of the bytes. - -#### Returns - -`bytes` - -- A random bytes. - -*** - -### string() - -> **string**(`length`?): `string` - -Defined in: [src/value-generators/avm.ts:81](https://github.com/algorandfoundation/algorand-typescript-testing/blob/main/src/value-generators/avm.ts#L81) - -Generates a random string of the specified length. - -#### Parameters - -##### length? - -`number` = `11` - -The length of the string. - -#### Returns - -`string` - -- A random string. - -*** - -### uint64() - -> **uint64**(`minValue`?, `maxValue`?): `uint64` - -Defined in: [src/value-generators/avm.ts:38](https://github.com/algorandfoundation/algorand-typescript-testing/blob/main/src/value-generators/avm.ts#L38) - -Generates a random uint64 value within the specified range. - -#### Parameters - -##### minValue? - -`StubUint64Compat` = `0n` - -The minimum value (inclusive). - -##### maxValue? - -`StubUint64Compat` = `MAX_UINT64` - -The maximum value (inclusive). - -#### Returns - -`uint64` - -- A random uint64 value. diff --git a/docs/code/value-generators/classes/ValueGenerator.md b/docs/code/value-generators/classes/ValueGenerator.md deleted file mode 100644 index 55686288..00000000 --- a/docs/code/value-generators/classes/ValueGenerator.md +++ /dev/null @@ -1,247 +0,0 @@ -[**@algorandfoundation/algorand-typescript-testing**](../../README.md) - -*** - -[@algorandfoundation/algorand-typescript-testing](../../README.md) / [value-generators](../README.md) / ValueGenerator - -# Class: ValueGenerator - -Defined in: [src/value-generators/index.ts:5](https://github.com/algorandfoundation/algorand-typescript-testing/blob/main/src/value-generators/index.ts#L5) - -## Extends - -- [`AvmValueGenerator`](../avm/classes/AvmValueGenerator.md) - -## Constructors - -### Constructor - -> **new ValueGenerator**(): `ValueGenerator` - -Defined in: [src/value-generators/index.ts:9](https://github.com/algorandfoundation/algorand-typescript-testing/blob/main/src/value-generators/index.ts#L9) - -#### Returns - -`ValueGenerator` - -#### Overrides - -[`AvmValueGenerator`](../avm/classes/AvmValueGenerator.md).[`constructor`](../avm/classes/AvmValueGenerator.md#constructor) - -## Properties - -### arc4 - -> **arc4**: [`Arc4ValueGenerator`](../arc4/classes/Arc4ValueGenerator.md) - -Defined in: [src/value-generators/index.ts:7](https://github.com/algorandfoundation/algorand-typescript-testing/blob/main/src/value-generators/index.ts#L7) - -*** - -### txn - -> **txn**: [`TxnValueGenerator`](../txn/classes/TxnValueGenerator.md) - -Defined in: [src/value-generators/index.ts:6](https://github.com/algorandfoundation/algorand-typescript-testing/blob/main/src/value-generators/index.ts#L6) - -## Methods - -### account() - -> **account**(`input`?): `Account` - -Defined in: [src/value-generators/avm.ts:95](https://github.com/algorandfoundation/algorand-typescript-testing/blob/main/src/value-generators/avm.ts#L95) - -Generates a random account with the specified context data. - -#### Parameters - -##### input? - -`AccountContextData` - -The context data for the account. - -#### Returns - -`Account` - -- A random account. - -#### Inherited from - -[`AvmValueGenerator`](../avm/classes/AvmValueGenerator.md).[`account`](../avm/classes/AvmValueGenerator.md#account) - -*** - -### application() - -> **application**(`input`?): `Application` - -Defined in: [src/value-generators/avm.ts:153](https://github.com/algorandfoundation/algorand-typescript-testing/blob/main/src/value-generators/avm.ts#L153) - -Generates a random application with the specified context data. - -#### Parameters - -##### input? - -`ApplicationContextData` - -The context data for the application. - -#### Returns - -`Application` - -- A random application. - -#### Inherited from - -[`AvmValueGenerator`](../avm/classes/AvmValueGenerator.md).[`application`](../avm/classes/AvmValueGenerator.md#application) - -*** - -### asset() - -> **asset**(`input`?): `Asset` - -Defined in: [src/value-generators/avm.ts:133](https://github.com/algorandfoundation/algorand-typescript-testing/blob/main/src/value-generators/avm.ts#L133) - -Generates a random asset with the specified context data. - -#### Parameters - -##### input? - -`AssetContextData` - -The context data for the asset. - -#### Returns - -`Asset` - -- A random asset. - -#### Inherited from - -[`AvmValueGenerator`](../avm/classes/AvmValueGenerator.md).[`asset`](../avm/classes/AvmValueGenerator.md#asset) - -*** - -### biguint() - -> **biguint**(`minValue`?): `biguint` - -Defined in: [src/value-generators/avm.ts:58](https://github.com/algorandfoundation/algorand-typescript-testing/blob/main/src/value-generators/avm.ts#L58) - -Generates a random biguint value within the specified range. - -#### Parameters - -##### minValue? - -`StubBigUintCompat` = `0n` - -The minimum value (inclusive). - -#### Returns - -`biguint` - -- A random biguint value. - -#### Inherited from - -[`AvmValueGenerator`](../avm/classes/AvmValueGenerator.md).[`biguint`](../avm/classes/AvmValueGenerator.md#biguint) - -*** - -### bytes() - -> **bytes**(`length`?): `bytes` - -Defined in: [src/value-generators/avm.ts:72](https://github.com/algorandfoundation/algorand-typescript-testing/blob/main/src/value-generators/avm.ts#L72) - -Generates a random bytes of the specified length. - -#### Parameters - -##### length? - -`number` = `MAX_BYTES_SIZE` - -The length of the bytes. - -#### Returns - -`bytes` - -- A random bytes. - -#### Inherited from - -[`AvmValueGenerator`](../avm/classes/AvmValueGenerator.md).[`bytes`](../avm/classes/AvmValueGenerator.md#bytes) - -*** - -### string() - -> **string**(`length`?): `string` - -Defined in: [src/value-generators/avm.ts:81](https://github.com/algorandfoundation/algorand-typescript-testing/blob/main/src/value-generators/avm.ts#L81) - -Generates a random string of the specified length. - -#### Parameters - -##### length? - -`number` = `11` - -The length of the string. - -#### Returns - -`string` - -- A random string. - -#### Inherited from - -[`AvmValueGenerator`](../avm/classes/AvmValueGenerator.md).[`string`](../avm/classes/AvmValueGenerator.md#string) - -*** - -### uint64() - -> **uint64**(`minValue`?, `maxValue`?): `uint64` - -Defined in: [src/value-generators/avm.ts:38](https://github.com/algorandfoundation/algorand-typescript-testing/blob/main/src/value-generators/avm.ts#L38) - -Generates a random uint64 value within the specified range. - -#### Parameters - -##### minValue? - -`StubUint64Compat` = `0n` - -The minimum value (inclusive). - -##### maxValue? - -`StubUint64Compat` = `MAX_UINT64` - -The maximum value (inclusive). - -#### Returns - -`uint64` - -- A random uint64 value. - -#### Inherited from - -[`AvmValueGenerator`](../avm/classes/AvmValueGenerator.md).[`uint64`](../avm/classes/AvmValueGenerator.md#uint64) diff --git a/docs/code/value-generators/txn/README.md b/docs/code/value-generators/txn/README.md deleted file mode 100644 index bf67a306..00000000 --- a/docs/code/value-generators/txn/README.md +++ /dev/null @@ -1,11 +0,0 @@ -[**@algorandfoundation/algorand-typescript-testing**](../../README.md) - -*** - -[@algorandfoundation/algorand-typescript-testing](../../README.md) / value-generators/txn - -# value-generators/txn - -## Classes - -- [TxnValueGenerator](classes/TxnValueGenerator.md) diff --git a/docs/code/value-generators/txn/classes/TxnValueGenerator.md b/docs/code/value-generators/txn/classes/TxnValueGenerator.md deleted file mode 100644 index b9dcf1ad..00000000 --- a/docs/code/value-generators/txn/classes/TxnValueGenerator.md +++ /dev/null @@ -1,163 +0,0 @@ -[**@algorandfoundation/algorand-typescript-testing**](../../../README.md) - -*** - -[@algorandfoundation/algorand-typescript-testing](../../../README.md) / [value-generators/txn](../README.md) / TxnValueGenerator - -# Class: TxnValueGenerator - -Defined in: [src/value-generators/txn.ts:16](https://github.com/algorandfoundation/algorand-typescript-testing/blob/main/src/value-generators/txn.ts#L16) - -## Constructors - -### Constructor - -> **new TxnValueGenerator**(): `TxnValueGenerator` - -#### Returns - -`TxnValueGenerator` - -## Methods - -### applicationCall() - -> **applicationCall**(`fields`?): `ApplicationCallTransaction` - -Defined in: [src/value-generators/txn.ts:22](https://github.com/algorandfoundation/algorand-typescript-testing/blob/main/src/value-generators/txn.ts#L22) - -Generates a random application call transaction with the specified fields. - -#### Parameters - -##### fields? - -`Partial`\<`Omit`\<`ApplicationCallTransactionFields`, `"appId"`\> & `object`\> - -The fields for the application call transaction where `appId` value can be instance of Application or BaseContract. - -#### Returns - -`ApplicationCallTransaction` - -- A random application call transaction. - -*** - -### assetConfig() - -> **assetConfig**(`fields`?): `AssetConfigTransaction` - -Defined in: [src/value-generators/txn.ts:65](https://github.com/algorandfoundation/algorand-typescript-testing/blob/main/src/value-generators/txn.ts#L65) - -Generates a random asset configuration transaction with the specified fields. - -#### Parameters - -##### fields? - -`Partial`\<`Mutable`\<`Pick`\<`AssetConfigTxn`, keyof `AssetConfigTxn`\>\>\> - -The fields for the asset configuration transaction. - -#### Returns - -`AssetConfigTransaction` - -- A random asset configuration transaction. - -*** - -### assetFreeze() - -> **assetFreeze**(`fields`?): `AssetFreezeTransaction` - -Defined in: [src/value-generators/txn.ts:83](https://github.com/algorandfoundation/algorand-typescript-testing/blob/main/src/value-generators/txn.ts#L83) - -Generates a random asset freeze transaction with the specified fields. - -#### Parameters - -##### fields? - -`Partial`\<`Mutable`\<`Pick`\<`AssetFreezeTxn`, keyof `AssetFreezeTxn`\>\>\> - -The fields for the asset freeze transaction. - -#### Returns - -`AssetFreezeTransaction` - -- A random asset freeze transaction. - -*** - -### assetTransfer() - -> **assetTransfer**(`fields`?): `AssetTransferTransaction` - -Defined in: [src/value-generators/txn.ts:74](https://github.com/algorandfoundation/algorand-typescript-testing/blob/main/src/value-generators/txn.ts#L74) - -Generates a random asset transfer transaction with the specified fields. - -#### Parameters - -##### fields? - -`Partial`\<`Mutable`\<`Pick`\<`AssetTransferTxn`, keyof `AssetTransferTxn`\>\>\> - -The fields for the asset transfer transaction. - -#### Returns - -`AssetTransferTransaction` - -- A random asset transfer transaction. - -*** - -### keyRegistration() - -> **keyRegistration**(`fields`?): `KeyRegistrationTransaction` - -Defined in: [src/value-generators/txn.ts:56](https://github.com/algorandfoundation/algorand-typescript-testing/blob/main/src/value-generators/txn.ts#L56) - -Generates a random key registration transaction with the specified fields. - -#### Parameters - -##### fields? - -`Partial`\<`Mutable`\<`Pick`\<`KeyRegistrationTxn`, keyof `KeyRegistrationTxn`\>\>\> - -The fields for the key registration transaction. - -#### Returns - -`KeyRegistrationTransaction` - -- A random key registration transaction. - -*** - -### payment() - -> **payment**(`fields`?): `PaymentTransaction` - -Defined in: [src/value-generators/txn.ts:47](https://github.com/algorandfoundation/algorand-typescript-testing/blob/main/src/value-generators/txn.ts#L47) - -Generates a random payment transaction with the specified fields. - -#### Parameters - -##### fields? - -`Partial`\<`Mutable`\<`Pick`\<`PaymentTxn`, keyof `PaymentTxn`\>\>\> - -The fields for the payment transaction. - -#### Returns - -`PaymentTransaction` - -- A random payment transaction. diff --git a/docs/coverage.md b/docs/coverage.md index c8a29cba..03848443 100644 --- a/docs/coverage.md +++ b/docs/coverage.md @@ -1,6 +1,10 @@ +--- +title: Coverage +--- + # Coverage -See which `algorand-typescript` stubs are implemented by the `algorand-typescript-testing` library. See the [Concepts](testing-guide/concepts.md#types-of-algorand-typescript-stub-implementations) section for more details on the implementation categories. Refer to the [`algorand-typescript` stubs API](api.md) for the full list of the stubs for which the `algorand-typescript-testing` library provides implementations referenced in the table below. +See which `algorand-typescript` stubs are implemented by the `algorand-typescript-testing` library. See the [Concepts](tg-concepts.md#types-of-algorand-typescript-stub-implementations) section for more details on the implementation categories. Refer to the [`algorand-typescript` stubs API](api.md) for the full list of the stubs for which the `algorand-typescript-testing` library provides implementations referenced in the table below. | Name | Implementation type | | ---------------------------- | ------------------- | diff --git a/docs/examples.md b/docs/examples.md index 84bbe69e..7cbb0b96 100644 --- a/docs/examples.md +++ b/docs/examples.md @@ -1,3 +1,7 @@ +--- +title: Examples +--- + # Examples Below is a showcase of various examples of unit testing real and sample Algorand Python smart contracts using `algorand-typescript-testing`. diff --git a/docs/faq.md b/docs/faq.md index f93119a2..5275425d 100644 --- a/docs/faq.md +++ b/docs/faq.md @@ -1,8 +1,12 @@ +--- +title: FAQ +--- + # FAQ ## What is a Test Context? -A Test Context is a context manager that provides a simulated Algorand environment for testing TypeScript smart contracts. It allows developers to create and manipulate a virtual Algorand ecosystem for testing purposes. For more details, see the [Test Context section](testing-guide/concepts.md#test-context) in our documentation. +A Test Context is a context manager that provides a simulated Algorand environment for testing TypeScript smart contracts. It allows developers to create and manipulate a virtual Algorand ecosystem for testing purposes. For more details, see the [Test Context section](tg-concepts.md#test-context) in our documentation. ## What is the Algorand Virtual Machine (AVM)? @@ -14,7 +18,7 @@ Operational Codes, or opcodes, are AVM instructions that are executed directly b ## What are Value Generators? -Value Generators are helper methods that generate randomized values for testing when the specific value of the tested type is not important. In the context of Algorand TypeScript testing, these are represented by property on the context manager, accessed via `any.*` (`any.txn.*`, or `any.arc4.*`. in the case of ARC 4 types). To understand how to use Value Generators effectively, check out our [Value Generators section](testing-guide/concepts.md#value-generators) in the documentation. +Value Generators are helper methods that generate randomized values for testing when the specific value of the tested type is not important. In the context of Algorand TypeScript testing, these are represented by property on the context manager, accessed via `any.*` (`any.txn.*`, or `any.arc4.*`. in the case of ARC 4 types). To understand how to use Value Generators effectively, check out our [Value Generators section](tg-concepts.md#value-generators) in the documentation. ## What are the limitations of the Algorand TypeScript Testing framework? @@ -52,7 +56,7 @@ While this framework is useful for unit testing and local development, it should Yes, the `algokit-typescript-template`, accessible via `algokit init`, provides a working example of how to structure `algorand-typecript-testing` along with regular integration tests against localnet. -```{hint} +``` An `algokit-typescript-template` accessible via `algokit init -t typescript`, provides a comprehensive and customizable working example of how to structure `algorand-typescript-testing` along with regular integration tests against localnet. ``` diff --git a/docs/readme.md b/docs/readme.md new file mode 100644 index 00000000..3e3d7c6b --- /dev/null +++ b/docs/readme.md @@ -0,0 +1,293 @@ +[![docs-repository](https://img.shields.io/badge/url-repository-74dfdc?logo=github&style=flat.svg)](https://github.com/algorandfoundation/algorand-typescript-testing/) +[![learn-AlgoKit](https://img.shields.io/badge/learn-AlgoKit-74dfdc?logo=algorand&mac=flat.svg)](https://developer.algorand.org/algokit/) +[![github-stars](https://img.shields.io/github/stars/algorandfoundation/algorand-typescript-testing?color=74dfdc&logo=star&style=flat)](https://github.com/algorandfoundation/algorand-typescript-testing) +[![visitor-badge](https://api.visitorbadge.io/api/visitors?path=https%3A%2F%2Fgithub.com%2Falgorandfoundation%2Falgorand-typescript-testing&countColor=%2374dfdc&style=flat)](https://github.com/algorandfoundation/algorand-typescript-testing/) + +`algorand-typescript-testing` is a companion package to [Algorand Typescript](https://github.com/algorandfoundation/puya-ts/tree/main/packages/algo-ts) that enables efficient unit testing of Algorand TypeScript smart contracts in an offline environment. This package emulates key AVM behaviors without requiring a network connection, offering fast and reliable testing capabilities with a familiar TypeScript interface. + +The `algorand-typescript-testing` package provides: + +- A simple interface for fast and reliable unit testing +- An offline testing environment that simulates core AVM functionality +- A familiar TypeScript experience, compatible with testing frameworks like [vitest](https://vitest.dev/), and [jest](https://jestjs.io/) + +## Quick Start + +`algorand-typescript` is a prerequisite for `algorand-typescript-testing`, providing stubs and type annotations for Algorand TypeScript syntax. It enhances code completion and type checking when writing smart contracts. Note that this code isn't directly executable in standard Node.js environment; it's compiled by `puya-ts` into TEAL for Algorand Network deployment. + +Traditionally, testing Algorand smart contracts involved deployment on sandboxed networks and interacting with live instances. While robust, this approach can be inefficient and lacks versatility for testing Algorand TypeScript code. + +Enter `algorand-typescript-testing`: it leverages TypeScript's rich testing ecosystem for unit testing without network deployment. This enables rapid iteration and granular logic testing. + +> **NOTE**: While `algorand-typescript-testing` offers valuable unit testing capabilities, it's not a replacement for comprehensive testing. Use it alongside other test types, particularly those running against the actual Algorand Network, for thorough contract validation. + +### Prerequisites + +- Python 3.12 or later +- [Algorand Python](https://github.com/algorandfoundation/puya) +- Node.js 20.x or later +- [Algorand TypeScript](https://github.com/algorandfoundation/puya-ts) + +### Installation + +`algorand-typescript-testing` is distributed via [npm](https://www.npmjs.com/package/@algorandfoundation/algorand-typescript-testing/). Install the package using `npm`: + +```bash +npm i @algorandfoundation/algorand-typescript-testing +``` + +### Testing your first contract + +Let's write a simple contract and test it using the `algorand-typescript-testing` framework. + +#### Simulating AVM + +`algorand-typescript-testing` includes a TypeScript transformer (`puyaTsTransformer`) that ensures contracts (with `.algo.ts` extension) and tests (with `.algo.spec.ts` or `.algo.test.ts` extensions) behave consistently between Node.js and AVM environments. + +The transformer replicates AVM behavior, such as integer-only arithmetic where `3 / 2` produces `1`. For code requiring standard Node.js behaviour (e.g., `3 / 2` produces `1.5`), place it in separate `.ts` files and reference them from test files. + +The transformer also redirects `@algorandfoundation/algorand-typescript` imports to `@algorandfoundation/algorand-typescript-testing/internal` to provide executable implementations of Algorand TypeScript constructs like `Global`, `Box`, `Uint64`, and `clone`. + +If there are tests which do not need to be executed in the AVM context such as end to end tests, simply use `.test.ts` or `.spec.ts` file extensions without `.algo` part and the transformer would skip them. + +#### Configuring vitest + +If you are using [vitest](https://vitest.dev/) with [@rollup/plugin-typescript](https://www.npmjs.com/package/@rollup/plugin-typescript) plugin, configure `puyaTsTransformer` as a `before` stage transformer of the `typescript` plugin in `vitest.config.mts` file. + +```typescript +import typescript from '@rollup/plugin-typescript' +import { defineConfig } from 'vitest/config' +import { puyaTsTransformer } from '@algorandfoundation/algorand-typescript-testing/vitest-transformer' + +export default defineConfig({ + esbuild: {}, + test: { + setupFiles: 'vitest.setup.ts', + }, + plugins: [ + typescript({ + transformers: { + before: [puyaTsTransformer], + }, + }), + ], +}) +``` + +`algorand-typescript-testing` package also exposes additional equality testers which enables the smart contract developers to write terser test by avoiding type casting in assertions. It can setup in `beforeAll` hook point in the setup file, `vitest.setup.ts`. + +```typescript +import { beforeAll, expect } from 'vitest' +import { addEqualityTesters } from '@algorandfoundation/algorand-typescript-testing' + +beforeAll(() => { + addEqualityTesters({ expect }) +}) +``` + +#### Configuring jest + +If you are using [jest](https://jestjs.io/) with [ts-jest](https://www.npmjs.com/package/ts-jest), [@jest/globals](https://www.npmjs.com/package/@jest/globals) and [ts-node](https://www.npmjs.com/package/ts-node) plugins, configure `puyaTsTransformer` as a `before` stage transformer of the `typescript` plugin in `jest.config.ts` file. + +```typescript +import { createDefaultEsmPreset, type JestConfigWithTsJest } from 'ts-jest' + +const presetConfig = createDefaultEsmPreset({}) +const jestConfig: JestConfigWithTsJest = { + ...presetConfig, + testMatch: ['**/*.algo.test.ts'], + setupFilesAfterEnv: ['/jest.setup.ts'], + transform: { + '^.+\\.tsx?$': [ + 'ts-jest', + { + useESM: true, + astTransformers: { + before: ['node_modules/@algorandfoundation/algorand-typescript-testing/test-transformer/jest-transformer.mjs'], + }, + }, + ], + }, + extensionsToTreatAsEsm: ['.ts'], +} +export default jestConfig +``` + +`algorand-typescript-testing` package also exposes additional equality testers which enables the smart contract developers to write terser test by avoiding type casting in assertions. It can setup in `beforeAll` hook point in the setup file, `jest.setup.ts`. + +```typescript +import { beforeAll, expect } from '@jest/globals' +import { addEqualityTesters } from '@algorandfoundation/algorand-typescript-testing' + +beforeAll(() => { + addEqualityTesters({ expect }) +}) +``` + +You'll also need to run `jest` with the `--experimental-vm-modules` and `--experimental-require-module` flags in the `package.json`. This requires node 20.17 or greater. + +```json +{ + "name": "puya-ts-demo", + "scripts": { + "test:jest": "tsc && node --experimental-vm-modules --experimental-require-module node_modules/jest/bin/jest" + }, + "engines": { + "node": ">=20.17" + } +} +``` + +There is also a patch file `ts-jest+29.2.5.patch` that needs to be applied to `ts-jest` package to for the `puyaTsTransformer` to work with the test files. + +1. Place the file in `\patches` folder. +1. Install [patch-package](https://www.npmjs.com/package/patch-package) package as a dev dependency. +1. Add `"postinstall": "patch-package",` script in `package.json` file. + The patch will then be applied with every `npm install` call. + +```patch +diff --git a/node_modules/ts-jest/dist/legacy/compiler/ts-compiler.js b/node_modules/ts-jest/dist/legacy/compiler/ts-compiler.js +index 5198f8f..addb47c 100644 +--- a/node_modules/ts-jest/dist/legacy/compiler/ts-compiler.js ++++ b/node_modules/ts-jest/dist/legacy/compiler/ts-compiler.js +@@ -234,7 +234,7 @@ var TsCompiler = /** @class */ (function () { + var _a; + // Initialize memory cache for typescript compiler + this._parsedTsConfig.fileNames +- .filter(function (fileName) { return constants_1.TS_TSX_REGEX.test(fileName) && !_this.configSet.isTestFile(fileName); }) ++ .filter(function (fileName) { return constants_1.TS_TSX_REGEX.test(fileName); }) + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion + .forEach(function (fileName) { return _this._fileVersionCache.set(fileName, 0); }); + /* istanbul ignore next */ + +``` + +After the setup, the examples provided using `vitest` can be converted to work with `jest` by simply swapping the `import {...} from 'vitest'` with `import {...} from '@jest/globals'`. + +#### Contract Definition + +```typescript +import { arc4, assert, Bytes, GlobalState, gtxn, LocalState, op, Txn, uint64, Uint64 } from '@algorandfoundation/algorand-typescript' + +export default class VotingContract extends arc4.Contract { + topic = GlobalState({ initialValue: 'default_topic', key: Bytes('topic') }) + votes = GlobalState({ initialValue: Uint64(0), key: Bytes('votes') }) + voted = LocalState({ key: Bytes('voted') }) + + @arc4.abimethod() + public setTopic(topic: string): void { + this.topic.value = topic + } + @arc4.abimethod() + public vote(pay: gtxn.PaymentTxn): boolean { + assert(op.Global.groupSize === 2, 'Expected 2 transactions') + assert(pay.amount === 10_000, 'Incorrect payment amount') + assert(pay.sender === Txn.sender, 'Payment sender must match transaction sender') + + if (this.voted(Txn.sender).hasValue) { + return false // Already voted + } + + this.votes.value = this.votes.value + 1 + this.voted(Txn.sender).value = 1 + return true + } + + @arc4.abimethod({ readonly: true }) + public getVotes(): uint64 { + return this.votes.value + } + + public clearStateProgram(): boolean { + return true + } +} +``` + +#### Test Definition + +```typescript +import { Uint64 } from '@algorandfoundation/algorand-typescript' +import { TestExecutionContext } from '@algorandfoundation/algorand-typescript-testing' +import { afterEach, describe, expect, test } from 'vitest' +import VotingContract from './contract.algo' + +describe('Voting contract', () => { + const ctx = new TestExecutionContext() + afterEach(() => { + ctx.reset() + }) + + test('vote function', () => { + // Initialize the contract within the testing context + const contract = ctx.contract.create(VotingContract) + + const voter = ctx.defaultSender + const payment = ctx.any.txn.payment({ + sender: voter, + amount: 10_000, + }) + + const result = contract.vote(payment) + expect(result).toEqual(true) + expect(contract.votes.value).toEqual(1) + expect(contract.voted(voter).value).toEqual(1) + }) + + test('setTopic function', () => { + // Initialize the contract within the testing context + const contract = ctx.contract.create(VotingContract) + + const newTopic = ctx.any.string(10) + contract.setTopic(newTopic) + expect(contract.topic.value).toEqual(newTopic) + }) + + test('getVotes function', () => { + // Initialize the contract within the testing context + const contract = ctx.contract.create(VotingContract) + + contract.votes.value = 5 + const votes = contract.getVotes() + expect(votes).toEqual(5) + }) +}) +``` + +This example demonstrates key aspects of testing with `algorand-typescript-testing` for ARC4-based contracts: + +1. ARC4 Contract Features: + + - Use of `arc4.Contract` as the base class for the contract. + - ABI methods defined using the `@arc4.abimethod` decorator. + - Readonly method annotation with `@arc4.abimethod({readonly: true})`. + +2. Testing ARC4 Contracts: + + - Creation of an `arc4.Contract` instance within the test context. + - Use of `ctx.any` for generating random test data. + - Direct invocation of ABI methods on the contract instance. + +3. Transaction Handling: + + - Use of `ctx.any.txn` to create test transactions. + - Passing transaction objects as parameters to contract methods. + +4. State Verification: + - Checking global and local state changes after method execution. + - Verifying return values from ABI methods. + +> **NOTE**: Thorough testing is crucial in smart contract development due to their immutable nature post-deployment. Comprehensive unit and integration tests ensure contract validity and reliability. Optimizing for efficiency can significantly improve user experience by reducing transaction fees and simplifying interactions. Investing in robust testing and optimization practices is crucial and offers many benefits in the long run. + +### Next steps + +To dig deeper into the capabilities of `algorand-typescript-testing`, continue with the following sections. + +#### Contents + +- [Testing Guide](./testing-guide.md) +- [Examples](./examples.md) +- [Coverage](./coverage.md) +- [FQA](./faq.md) +- [API Reference](./api.md) +- [Algorand TypeScript](./algots.md) diff --git a/docs/testing-guide/index.md b/docs/testing-guide.md similarity index 78% rename from docs/testing-guide/index.md rename to docs/testing-guide.md index e004e522..4fe60264 100644 --- a/docs/testing-guide/index.md +++ b/docs/testing-guide.md @@ -1,8 +1,21 @@ +--- +title: Algorand TypeScript Testing Guide +children: + - ./tg-concepts.md + - ./tg-avm-types.md + - ./tg-arc4-types.md + - ./tg-transactions.md + - ./tg-contract-testing.md + - ./tg-signature-testing.md + - ./tg-state-management.md + - ./tg-opcodes.md +--- + # Testing Guide The Algorand TypeScript Testing framework provides powerful tools for testing Algorand TypeScript smart contracts within a Node.js environment. This guide covers the main features and concepts of the framework, helping you write effective tests for your Algorand applications. -```{note} +``` For all code examples in the _Testing Guide_ section, assume `context` is an instance of `TestExecutionContext` obtained using the initialising an instance of `TestExecutionContext` class. All subsequent code is executed within this context. ``` @@ -31,11 +44,11 @@ The framework is designed to work seamlessly with Algorand TypeScript smart cont ## Table of Contents -- [Concepts](./concepts.md) -- [AVM Types](./avm-types.md) -- [ARC4 Types](./arc4-types.md) -- [Transactions](./transactions.md) -- [Smart Contract Testing](./contract-testing.md) -- [Smart Signature Testing](./signature-testing.md) -- [State Management](./state-management.md) -- [AVM Opcodes](./opcodes.md) +- [Concepts](./tg-concepts.md) +- [AVM Types](./tg-avm-types.md) +- [ARC4 Types](./tg-arc4-types.md) +- [Transactions](./tg-transactions.md) +- [Smart Contract Testing](./tg-contract-testing.md) +- [Smart Signature Testing](./tg-signature-testing.md) +- [State Management](./tg-state-management.md) +- [AVM Opcodes](./tg-opcodes.md) diff --git a/docs/testing-guide/application-spy.md b/docs/tg-application-spy.md similarity index 99% rename from docs/testing-guide/application-spy.md rename to docs/tg-application-spy.md index e67bd894..462ba221 100644 --- a/docs/testing-guide/application-spy.md +++ b/docs/tg-application-spy.md @@ -1,3 +1,7 @@ +--- +title: Application Spy +--- + # ApplicationSpy The `ApplicationSpy` class provides a way to mock making method calls for from within contracts. This is particularly useful when testing contracts that deploy and interact with other contracts in a type safe manner. It can be used with all the approaches for making method calls supported by `algorand-typescript`. diff --git a/docs/testing-guide/arc4-types.md b/docs/tg-arc4-types.md similarity index 98% rename from docs/testing-guide/arc4-types.md rename to docs/tg-arc4-types.md index d3397347..b0429a54 100644 --- a/docs/testing-guide/arc4-types.md +++ b/docs/tg-arc4-types.md @@ -1,12 +1,16 @@ +--- +title: ARC4 Types +--- + # ARC4 Types These types are available under the `arc4` namespace. Refer to the [ARC4 specification](https://arc.algorand.foundation/ARCs/arc-0004) for more details on the spec. -```{hint} +``` Test execution context provides _value generators_ for ARC4 types. To access their _value generators_, use `{context_instance}.any.arc4` property. See more examples below. ``` -```{note} +``` For all `arc4` types with and without respective _value generator_, instantiation can be performed directly. If you have a suggestion for a new _value generator_ implementation, please open an issue in the [`algorand-typescript-testing`](https://github.com/algorandfoundation/algorand-typescript-testing) repository or contribute by following the [contribution guide](https://github.com/algorandfoundation/algorand-typescript-testing/blob/main/CONTRIBUTING.md). ``` diff --git a/docs/testing-guide/avm-types.md b/docs/tg-avm-types.md similarity index 99% rename from docs/testing-guide/avm-types.md rename to docs/tg-avm-types.md index 53753c96..b65cea6b 100644 --- a/docs/testing-guide/avm-types.md +++ b/docs/tg-avm-types.md @@ -1,8 +1,12 @@ +--- +title: AVM Types +--- + # AVM Types These types are available directly under the `algorand-typescript` namespace. They represent the basic AVM primitive types and can be instantiated directly or via _value generators_: -```{note} +``` For 'primitive `algorand-typescript` types such as `Account`, `Application`, `Asset`, `uint64`, `biguint`, `bytes`, `string` with and without respective _value generator_, instantiation can be performed directly. If you have a suggestion for a new _value generator_ implementation, please open an issue in the [`algorand-typescript-testing`](https://github.com/algorandfoundation/algorand-typescript-testing) repository or contribute by following the [contribution guide](https://github.com/algorandfoundation/algorand-typescript-testing/blob/main/CONTRIBUTING.md). ``` @@ -119,7 +123,7 @@ const account = algots.Account(rawAddress) // zero address by default const randomAccount = ctx.any.account({ address: rawAddress, // Optional: Specify a custom address, defaults to a random address optedAssetBalances: new Map([]), // Optional: Specify opted asset balances as dict of assets to balance - optedApplications: [], // Optional: Specify opted apps as sequence of algopy.Application objects + optedApplications: [], // Optional: Specify opted apps as sequence of Application objects totalAppsCreated: 0, // Optional: Specify the total number of created applications totalAppsOptedIn: 0, // Optional: Specify the total number of applications opted into totalAssets: 0, // Optional: Specify the total number of assets diff --git a/docs/testing-guide/concepts.md b/docs/tg-concepts.md similarity index 77% rename from docs/testing-guide/concepts.md rename to docs/tg-concepts.md index c892fea3..fc322fa9 100644 --- a/docs/testing-guide/concepts.md +++ b/docs/tg-concepts.md @@ -1,10 +1,14 @@ +--- +title: Concepts +--- + # Concepts The following sections provide an overview of key concepts and features in the Algorand TypeScript Testing framework. ## Test Context -The main abstraction for interacting with the testing framework is the [`TestExecutionContext`](../api.md#contexts). It creates an emulated Algorand environment that closely mimics AVM behavior relevant to unit testing the contracts and provides a TypeScript interface for interacting with the emulated environment. +The main abstraction for interacting with the testing framework is the [`TestExecutionContext`](../classes/index.TestExecutionContext.html). It creates an emulated Algorand environment that closely mimics AVM behavior relevant to unit testing the contracts and provides a TypeScript interface for interacting with the emulated environment. ```typescript import { TestExecutionContext } from '@algorandfoundation/algorand-typescript-testing' @@ -29,14 +33,16 @@ The context manager interface exposes four main properties: 1. `contract`: An instance of `ContractContext` for creating instances of Contract under test and register them with the test execution context. 1. `ledger`: An instance of `LedgerContext` for interacting with and querying the emulated Algorand ledger state. 1. `txn`: An instance of `TransactionContext` for creating and managing transaction groups, submitting transactions, and accessing transaction results. -1. `any`: An instance of `AlgopyValueGenerator` for generating randomized test data. +1. `any`: An instance of `ValueGenerator` for generating randomized test data. For detailed method signatures, parameters, and return types, refer to the following API sections: -- [`ContractContext`](../code/subcontexts/contract-context/classes/ContractContext.md) -- [`LedgerContext`](../code/subcontexts/ledger-context/classes/LedgerContext.md) -- [`TransactionContext`](../code/subcontexts/transaction-context/classes/TransactionContext.md) -- [`AvmValueGenerator`, `TxnValueGenerator`, `Arc4ValueGenerator`](../api.md) +- [`ContractContext`](../classes/index._internal_.ContractContext.html) +- [`LedgerContext`](../classes/index._internal_.LedgerContext.html) +- [`TransactionContext`](../classes/index._internal_.TransactionContext.html) +- [`AvmValueGenerator`, `TxnValueGenerator`, `Arc4ValueGenerator`](../classes/value-generators.ValueGenerator.html) + +### Value generators The `any` property provides access to different value generators: @@ -46,7 +52,7 @@ The `any` property provides access to different value generators: These generators allow creation of constrained random values for various AVM entities (accounts, assets, applications, etc.) when specific values are not required. -```{hint} +``` Value generators are powerful tools for generating test data for specified AVM types. They allow further constraints on random value generation via arguments, making it easier to generate test data when exact values are not necessary. When used with the 'Arrange, Act, Assert' pattern, value generators can be especially useful in setting up clear and concise test data in arrange steps. @@ -55,7 +61,7 @@ When used with the 'Arrange, Act, Assert' pattern, value generators can be espec ## Types of `algorand-typescript` stub implementations -As explained in the [introduction](index.md), `algorand-typescript-testing` _injects_ test implementations for stubs available in the `algorand-typescript` package. However, not all of the stubs are implemented in the same manner: +As explained in the [introduction](testing-guide.md), `algorand-typescript-testing` _injects_ test implementations for stubs available in the `algorand-typescript` package. However, not all of the stubs are implemented in the same manner: 1. **Native**: Fully matches AVM computation in Python. For example, `op.sha256` and other cryptographic operations behave identically in AVM and unit tests. This implies that the majority of opcodes that are 'pure' functions in AVM also have a native TypeScript implementation provided by this package. These abstractions and opcodes can be used within and outside of the testing context. @@ -63,4 +69,4 @@ As explained in the [introduction](index.md), `algorand-typescript-testing` _inj 3. **Mockable**: Not implemented, but can be mocked or patched. For example, `op.onlineStake` can be mocked to return specific values or behaviors; otherwise, it raises a `NotImplementedError`. This category covers cases where native or emulated implementation in a unit test context is impractical or overly complex. -For a full list of all public `algorand-typescript` types and their corresponding implementation category, refer to the [Coverage](../coverage.md) section. +For a full list of all public `algorand-typescript` types and their corresponding implementation category, refer to the [Coverage](./coverage.md) section. diff --git a/docs/testing-guide/contract-testing.md b/docs/tg-contract-testing.md similarity index 96% rename from docs/testing-guide/contract-testing.md rename to docs/tg-contract-testing.md index 5303a72b..43c399d2 100644 --- a/docs/testing-guide/contract-testing.md +++ b/docs/tg-contract-testing.md @@ -1,8 +1,12 @@ +--- +title: Smart Contract Testing +--- + # Smart Contract Testing This guide provides an overview of how to test smart contracts using the [Algorand Typescript Testing package](https://www.npmjs.com/package/@algorandfoundation/algorand-typescript-testing). We will cover the basics of testing `arc4.Contract` and `BaseContract` classes, focusing on `abimethod` and `baremethod` decorators. -```{note} +``` The code snippets showcasing the contract testing capabilities are using [vitest](https://vitest.dev/) as the test framework. However, note that the `algorand-typescript-testing` package can be used with any other test framework that supports TypeScript. `vitest` is used for demonstration purposes in this documentation. ``` @@ -18,7 +22,7 @@ const ctx = new TestExecutionContext() Subclasses of `arc4.Contract` are **required** to be instantiated with an active test context. As part of instantiation, the test context will automatically create a matching `Application` object instance. -Within the class implementation, methods decorated with `arc4.abimethod` and `arc4.baremethod` will automatically assemble an `gtxn.ApplicationCallTxn` transaction to emulate the AVM application call. This behavior can be overriden by setting the transaction group manually as part of test setup, this is done via implicit invocation of `ctx.any.txn.applicationCall` _value generator_ (refer to [APIs](../apis.md) for more details). +Within the class implementation, methods decorated with `arc4.abimethod` and `arc4.baremethod` will automatically assemble an `gtxn.ApplicationCallTxn` transaction to emulate the AVM application call. This behavior can be overriden by setting the transaction group manually as part of test setup, this is done via implicit invocation of `ctx.any.txn.applicationCall` _value generator_ (refer to [APIs](../modules/index.html) for more details). ```ts class SimpleVotingContract extends arc4.Contract { @@ -71,7 +75,7 @@ expect(contract.topic.value).toEqual(initialTopic) expect(contract.votes.value).toEqual(Uint64(0)) // Act - Vote -// The method `.vote()` is decorated with `algopy.arc4.abimethod`, which means it will assemble a transaction to emulate the AVM application call +// The method `.vote()` is decorated with `arc4.abimethod`, which means it will assemble a transaction to emulate the AVM application call const result = contract.vote() // Assert - you can access the corresponding auto generated application call transaction via test context @@ -98,7 +102,7 @@ const votes = contract.getVotes() expect(votes).toEqual(0) ``` -For more examples of tests using `arc4.Contract`, see the [examples](../examples.md) section. +For more examples of tests using `arc4.Contract`, see the [examples](./examples.md) section. ## `BaseContract`` diff --git a/docs/testing-guide/opcodes.md b/docs/tg-opcodes.md similarity index 97% rename from docs/testing-guide/opcodes.md rename to docs/tg-opcodes.md index 9ccb9b5a..8eec5fde 100644 --- a/docs/testing-guide/opcodes.md +++ b/docs/tg-opcodes.md @@ -1,3 +1,7 @@ +--- +title: AVM Opcodes +--- + # AVM Opcodes The [coverage](coverage.md) file provides a comprehensive list of all opcodes and their respective types, categorized as _Mockable_, _Emulated_, or _Native_ within the `algorand-typescript-testing` package. This section highlights a **subset** of opcodes and types that typically require interaction with the test execution context. @@ -66,13 +70,13 @@ const isBitSet = op.getBit(value, 3) const newValue = op.setBit(value, 2, 1) ``` -For a comprehensive list of all opcodes and types, refer to the [coverage](../coverage.md) page. +For a comprehensive list of all opcodes and types, refer to the [coverage](./coverage.md) page. ## Emulated Types Requiring Transaction Context These types necessitate interaction with the transaction context: -### algopy.op.Global +### op.Global ```ts import { TestExecutionContext } from '@algorandfoundation/algorand-typescript-testing' @@ -98,7 +102,7 @@ const result = contract.checkGlobals() expect(result).toEqual(101000) ``` -### algopy.op.Txn +### op.Txn ```ts import { op, arc4 } from '@algorandfoundation/algorand-typescript' @@ -122,7 +126,7 @@ ctx.txn.createScope([ctx.any.txn.applicationCall({ sender: customSender })]).exe }) ``` -### algopy.op.AssetHoldingGet +### op.AssetHoldingGet ```ts import { Account, arc4, Asset, op, uint64, Uint64 } from '@algorandfoundation/algorand-typescript' @@ -147,7 +151,7 @@ const result = contract.checkAssetHolding(account, asset) expect(result).toEqual(5000) ``` -### algopy.op.AppGlobal +### op.AppGlobal ```ts import { arc4, bytes, Bytes, op, uint64, Uint64 } from '@algorandfoundation/algorand-typescript' @@ -174,7 +178,7 @@ const [storedValue, _] = ctx.ledger.getGlobalState(contract, key) expect(storedValue?.value).toEqual(42) ``` -### algopy.op.Block +### op.Block ```ts import { arc4, bytes, op } from '@algorandfoundation/algorand-typescript' @@ -196,7 +200,7 @@ const seed = contract.getBlockSeed() expect(seed).toEqual(op.itob(123456)) ``` -### algopy.op.AcctParamsGet +### op.AcctParamsGet ```ts import type { Account, uint64 } from '@algorandfoundation/algorand-typescript' @@ -221,7 +225,7 @@ const balance = contract.getAccountBalance(account) expect(balance).toEqual(Uint64(1000000)) ``` -### algopy.op.AppParamsGet +### op.AppParamsGet ```ts import type { Application } from '@algorandfoundation/algorand-typescript' @@ -245,7 +249,7 @@ const creator = contract.getAppCreator(app) expect(creator).toEqual(ctx.defaultSender) ``` -### algopy.op.AssetParamsGet +### op.AssetParamsGet ```ts import type { uint64 } from '@algorandfoundation/algorand-typescript' @@ -269,7 +273,7 @@ const total = contract.getAssetTotal(asset.id) expect(total).toEqual(1000000) ``` -### algopy.op.Box +### op.Box ```ts import type { bytes } from '@algorandfoundation/algorand-typescript' @@ -299,7 +303,7 @@ const storedValue = ctx.ledger.getBox(contract, key) expect(storedValue).toEqual(value) ``` -### algopy.compile_contract +### compile_contract ```ts import { arc4, compile, uint64 } from '@algorandfoundation/algorand-typescript' @@ -329,7 +333,7 @@ expect(result).toBe(4) These opcodes are mockable in `algorand-typescript-testing`, allowing for controlled testing of complex operations. Note that the module being mocked is `@algorandfoundation/algorand-typescript-testing/internal` which holds the stub implementations of `algorand-typescript` functions to be executed in Node.js environment. -### algopy.op.vrf_verify +### op.vrf_verify ```ts import { expect, Mock, test, vi } from 'vitest' @@ -356,7 +360,7 @@ test('mock vrfVerify', () => { }) ``` -### algopy.op.EllipticCurve +### op.EllipticCurve ```ts import { expect, Mock, test, vi } from 'vitest' diff --git a/docs/testing-guide/signature-testing.md b/docs/tg-signature-testing.md similarity index 93% rename from docs/testing-guide/signature-testing.md rename to docs/tg-signature-testing.md index 47cd1441..d53d32e5 100644 --- a/docs/testing-guide/signature-testing.md +++ b/docs/tg-signature-testing.md @@ -1,3 +1,7 @@ +--- +title: Smart Signature Testing +--- + # Smart Signature Testing Test Algorand smart signatures (LogicSigs) with ease using the Algorand TypeScript Testing framework. @@ -68,7 +72,7 @@ const secret = algots.Bytes('secret') expect(ctx.executeLogicSig(new HashedTimeLockedLogicSig(), secret)) ``` -For more details on available operations, see the [coverage](../coverage.md). +For more details on available operations, see the [coverage](./coverage.md). ```ts // test cleanup diff --git a/docs/testing-guide/state-management.md b/docs/tg-state-management.md similarity index 95% rename from docs/testing-guide/state-management.md rename to docs/tg-state-management.md index e97749f5..5d24cfa8 100644 --- a/docs/testing-guide/state-management.md +++ b/docs/tg-state-management.md @@ -1,3 +1,7 @@ +--- +title: State Management +--- + # State Management `algorand-typescript-testing` provides tools to test state-related abstractions in Algorand smart contracts. This guide covers global state, local state, boxes, and scratch space management. @@ -98,7 +102,7 @@ const scratchSpace = ctx.txn.lastGroup.getScratchSpace() expect(scratchSpace[1]).toEqual(5) ``` -For more detailed information, explore the example contracts in the `examples/` directory, the [coverage](../coverage.md) page, and the [API documentation](../api.md). +For more detailed information, explore the example contracts in the `examples/` directory, the [coverage](./coverage.md) page, and the [API documentation](../modules/index.html). ```ts // test cleanup diff --git a/docs/testing-guide/transactions.md b/docs/tg-transactions.md similarity index 95% rename from docs/testing-guide/transactions.md rename to docs/tg-transactions.md index 2e793609..25adbb1c 100644 --- a/docs/testing-guide/transactions.md +++ b/docs/tg-transactions.md @@ -1,3 +1,7 @@ +--- +title: Transactions +--- + # Transactions The testing framework follows the Transaction definitions described in [`algorand-typescript` docs](https://github.com/algorandfoundation/puya-ts/blob/main/docs/lg-transactions.md). This section focuses on _value generators_ and interactions with inner transactions, it also explains how the framework identifies _active_ transaction group during contract method/subroutine/logicsig invocation. @@ -12,7 +16,7 @@ const ctx = new TestExecutionContext() ## Group Transactions -Refers to test implementation of transaction stubs available under `algots.gtxn.*` namespace. Available under [`TxnValueGenerator`](../code/value-generators/txn/classes/TxnValueGenerator.md) instance accessible via `ctx.any.txn` property: +Refers to test implementation of transaction stubs available under `algots.gtxn.*` namespace. Available under [`TxnValueGenerator`](../classes/value-generators._internal_.TxnValueGenerator.html) instance accessible via `ctx.any.txn` property: ```ts // Generate a random payment transaction @@ -82,7 +86,7 @@ const assetFreezeTxn = ctx.any.txn.assetFreeze({ When a smart contract instance (application) is interacted with on the Algorand network, it must be performed in relation to a specific transaction or transaction group where one or many transactions are application calls to target smart contract instances. -To emulate this behaviour, the `createScope` context manager is available on [`TransactionContext`](../code/subcontexts/transaction-context/classes/TransactionContext.md) instance that allows setting temporary transaction fields within a specific scope, passing in emulated transaction objects and identifying the active transaction index within the transaction group +To emulate this behaviour, the `createScope` context manager is available on [`TransactionContext`](../classes/index._internal_.TransactionContext.html) instance that allows setting temporary transaction fields within a specific scope, passing in emulated transaction objects and identifying the active transaction index within the transaction group ```ts import { arc4, Txn } from '@algorandfoundation/algorand-typescript' @@ -408,9 +412,9 @@ describe('pre compiled typed app calls', () => { ## References -- [API](../api.md) for more details on the test context manager and inner transactions related methods that perform implicit inner transaction type validation. -- [Examples](../examples.md) for more examples of smart contracts and associated tests that interact with inner transactions. -- [ApplicationSpy](./application-spy.md) for detailed explanation on the usage of it +- [API](../modules/index.html) for more details on the test context manager and inner transactions related methods that perform implicit inner transaction type validation. +- [Examples](./examples.md) for more examples of smart contracts and associated tests that interact with inner transactions. +- [ApplicationSpy](./tg-application-spy.md) for detailed explanation on the usage of it ```ts // test cleanup diff --git a/package-lock.json b/package-lock.json index 5b78a10c..06f054d7 100644 --- a/package-lock.json +++ b/package-lock.json @@ -50,8 +50,9 @@ "rollup": "^4.38.0", "semantic-release": "^24.2.3", "tsx": "4.19.3", - "typedoc": "^0.28.1", - "typedoc-plugin-markdown": "^4.6.0", + "typedoc": "^0.28.7", + "typedoc-plugin-markdown": "^4.7.1", + "typedoc-plugin-missing-exports": "^4.0.0", "typescript": "^5.8.3", "upath": "^2.0.1", "vitest": "3.2.4" @@ -1278,14 +1279,16 @@ } }, "node_modules/@gerrit0/mini-shiki": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/@gerrit0/mini-shiki/-/mini-shiki-3.2.1.tgz", - "integrity": "sha512-HbzRC6MKB6U8kQhczz0APKPIzFHTrcqhaC7es2EXInq1SpjPVnpVSIsBe6hNoLWqqCx1n5VKiPXq6PfXnHZKOQ==", + "version": "3.9.2", + "resolved": "https://registry.npmjs.org/@gerrit0/mini-shiki/-/mini-shiki-3.9.2.tgz", + "integrity": "sha512-Tvsj+AOO4Z8xLRJK900WkyfxHsZQu+Zm1//oT1w443PO6RiYMoq/4NGOhaNuZoUMYsjKIAPVQ6eOFMddj6yphQ==", "dev": true, "license": "MIT", "dependencies": { - "@shikijs/engine-oniguruma": "^3.2.1", - "@shikijs/types": "^3.2.1", + "@shikijs/engine-oniguruma": "^3.9.2", + "@shikijs/langs": "^3.9.2", + "@shikijs/themes": "^3.9.2", + "@shikijs/types": "^3.9.2", "@shikijs/vscode-textmate": "^10.0.2" } }, @@ -2557,20 +2560,40 @@ } }, "node_modules/@shikijs/engine-oniguruma": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/@shikijs/engine-oniguruma/-/engine-oniguruma-3.2.1.tgz", - "integrity": "sha512-wZZAkayEn6qu2+YjenEoFqj0OyQI64EWsNR6/71d1EkG4sxEOFooowKivsWPpaWNBu3sxAG+zPz5kzBL/SsreQ==", + "version": "3.9.2", + "resolved": "https://registry.npmjs.org/@shikijs/engine-oniguruma/-/engine-oniguruma-3.9.2.tgz", + "integrity": "sha512-Vn/w5oyQ6TUgTVDIC/BrpXwIlfK6V6kGWDVVz2eRkF2v13YoENUvaNwxMsQU/t6oCuZKzqp9vqtEtEzKl9VegA==", "dev": true, "license": "MIT", "dependencies": { - "@shikijs/types": "3.2.1", + "@shikijs/types": "3.9.2", "@shikijs/vscode-textmate": "^10.0.2" } }, + "node_modules/@shikijs/langs": { + "version": "3.9.2", + "resolved": "https://registry.npmjs.org/@shikijs/langs/-/langs-3.9.2.tgz", + "integrity": "sha512-X1Q6wRRQXY7HqAuX3I8WjMscjeGjqXCg/Sve7J2GWFORXkSrXud23UECqTBIdCSNKJioFtmUGJQNKtlMMZMn0w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@shikijs/types": "3.9.2" + } + }, + "node_modules/@shikijs/themes": { + "version": "3.9.2", + "resolved": "https://registry.npmjs.org/@shikijs/themes/-/themes-3.9.2.tgz", + "integrity": "sha512-6z5lBPBMRfLyyEsgf6uJDHPa6NAGVzFJqH4EAZ+03+7sedYir2yJBRu2uPZOKmj43GyhVHWHvyduLDAwJQfDjA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@shikijs/types": "3.9.2" + } + }, "node_modules/@shikijs/types": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/@shikijs/types/-/types-3.2.1.tgz", - "integrity": "sha512-/NTWAk4KE2M8uac0RhOsIhYQf4pdU0OywQuYDGIGAJ6Mjunxl2cGiuLkvu4HLCMn+OTTLRWkjZITp+aYJv60yA==", + "version": "3.9.2", + "resolved": "https://registry.npmjs.org/@shikijs/types/-/types-3.9.2.tgz", + "integrity": "sha512-/M5L0Uc2ljyn2jKvj4Yiah7ow/W+DJSglVafvWAJ/b8AZDeeRAdMu3c2riDzB7N42VD+jSnWxeP9AKtd4TfYVw==", "dev": true, "license": "MIT", "dependencies": { @@ -13677,17 +13700,17 @@ } }, "node_modules/typedoc": { - "version": "0.28.1", - "resolved": "https://registry.npmjs.org/typedoc/-/typedoc-0.28.1.tgz", - "integrity": "sha512-Mn2VPNMaxoe/hlBiLriG4U55oyAa3Xo+8HbtEwV7F5WEOPXqtxzGuMZhJYHaqFJpajeQ6ZDUC2c990NAtTbdgw==", + "version": "0.28.10", + "resolved": "https://registry.npmjs.org/typedoc/-/typedoc-0.28.10.tgz", + "integrity": "sha512-zYvpjS2bNJ30SoNYfHSRaFpBMZAsL7uwKbWwqoCNFWjcPnI3e/mPLh2SneH9mX7SJxtDpvDgvd9/iZxGbo7daw==", "dev": true, "license": "Apache-2.0", "dependencies": { - "@gerrit0/mini-shiki": "^3.2.1", + "@gerrit0/mini-shiki": "^3.9.0", "lunr": "^2.3.9", "markdown-it": "^14.1.0", "minimatch": "^9.0.5", - "yaml": "^2.7.0 " + "yaml": "^2.8.0" }, "bin": { "typedoc": "bin/typedoc" @@ -13697,13 +13720,13 @@ "pnpm": ">= 10" }, "peerDependencies": { - "typescript": "5.0.x || 5.1.x || 5.2.x || 5.3.x || 5.4.x || 5.5.x || 5.6.x || 5.7.x || 5.8.x" + "typescript": "5.0.x || 5.1.x || 5.2.x || 5.3.x || 5.4.x || 5.5.x || 5.6.x || 5.7.x || 5.8.x || 5.9.x" } }, "node_modules/typedoc-plugin-markdown": { - "version": "4.6.0", - "resolved": "https://registry.npmjs.org/typedoc-plugin-markdown/-/typedoc-plugin-markdown-4.6.0.tgz", - "integrity": "sha512-RG90uC1QqGN9kPBjzEckEf0v9yIYlLoNYKm4OqjwEGFJJGOLUDs5pIEQQDR2tAv1RD7D8GUSakRlcHMTipyaOA==", + "version": "4.8.1", + "resolved": "https://registry.npmjs.org/typedoc-plugin-markdown/-/typedoc-plugin-markdown-4.8.1.tgz", + "integrity": "sha512-ug7fc4j0SiJxSwBGLncpSo8tLvrT9VONvPUQqQDTKPxCoFQBADLli832RGPtj6sfSVJebNSrHZQRUdEryYH/7g==", "dev": true, "license": "MIT", "engines": { @@ -13713,6 +13736,16 @@ "typedoc": "0.28.x" } }, + "node_modules/typedoc-plugin-missing-exports": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/typedoc-plugin-missing-exports/-/typedoc-plugin-missing-exports-4.1.0.tgz", + "integrity": "sha512-p1M5jXnEbQ4qqy0erJz41BBZEDb8XDrbLjndlH4RhYEcymbdQr0xLF6yUw1GWBrhSIfkU98m3BELMAiQh+R1zA==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "typedoc": "^0.28.1" + } + }, "node_modules/typedoc/node_modules/minimatch": { "version": "9.0.5", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", @@ -14434,16 +14467,16 @@ } }, "node_modules/yaml": { - "version": "2.7.1", - "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.7.1.tgz", - "integrity": "sha512-10ULxpnOCQXxJvBgxsn9ptjq6uviG/htZKk9veJGhlqn3w/DxQ631zFF+nlQXLwmImeS5amR2dl2U8sg6U9jsQ==", + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.8.1.tgz", + "integrity": "sha512-lcYcMxX2PO9XMGvAJkJ3OsNMw+/7FKes7/hgerGUYWIoWu5j/+YQqcZr5JnPZWzOsEBgMbSbiSTn/dv/69Mkpw==", "dev": true, "license": "ISC", "bin": { "yaml": "bin.mjs" }, "engines": { - "node": ">= 14" + "node": ">= 14.6" } }, "node_modules/yargs": { diff --git a/package.json b/package.json index 83328e5f..f3e4bd36 100644 --- a/package.json +++ b/package.json @@ -60,8 +60,9 @@ "rollup": "^4.38.0", "semantic-release": "^24.2.3", "tsx": "4.19.3", - "typedoc": "^0.28.1", - "typedoc-plugin-markdown": "^4.6.0", + "typedoc": "^0.28.7", + "typedoc-plugin-markdown": "^4.7.1", + "typedoc-plugin-missing-exports": "^4.0.0", "typescript": "^5.8.3", "upath": "^2.0.1", "vitest": "3.2.4" diff --git a/src/application-spy.ts b/src/application-spy.ts index 125430ee..7086d20b 100644 --- a/src/application-spy.ts +++ b/src/application-spy.ts @@ -38,9 +38,9 @@ export class ApplicationSpy { * The `on` property is a proxy that allows you to register callbacks for specific method signatures. * It dynamically creates methods based on the contract's methods. */ - readonly on: _TypedApplicationSpyCallBacks + readonly on: TypedApplicationSpyCallBacks - /* @internal */ + /** @internal */ contract?: TContract | ConstructorFor constructor(contract?: TContract | ConstructorFor) { @@ -48,7 +48,7 @@ export class ApplicationSpy { this.on = this.createOnProxy() } - /* @internal */ + /** @internal */ notify(itxn: ApplicationCallInnerTxnContext) { for (const cb of this.#spyFns) { cb(itxn) @@ -100,9 +100,9 @@ export class ApplicationSpy { } } - private createOnProxy(spy: ApplicationSpy = this): _TypedApplicationSpyCallBacks { - return new Proxy({} as _TypedApplicationSpyCallBacks, { - get(_: _TypedApplicationSpyCallBacks, methodName) { + private createOnProxy(spy: ApplicationSpy = this): TypedApplicationSpyCallBacks { + return new Proxy({} as TypedApplicationSpyCallBacks, { + get(_: TypedApplicationSpyCallBacks, methodName) { const fn = spy._tryGetMethod(methodName) if (fn === undefined) return fn return function (callback: AppSpyCb) { @@ -116,11 +116,11 @@ export class ApplicationSpy { spy.onAbiCall(selector, ocas, callback) } }, - }) as _TypedApplicationSpyCallBacks + }) as TypedApplicationSpyCallBacks } } -type _TypedApplicationSpyCallBacks = { +export type TypedApplicationSpyCallBacks = { [key in keyof TContract as key extends 'approvalProgram' | 'clearStateProgram' ? never : TContract[key] extends AnyFunction diff --git a/src/impl/inner-transactions.ts b/src/impl/inner-transactions.ts index 4a53da2e..cebbdbe8 100644 --- a/src/impl/inner-transactions.ts +++ b/src/impl/inner-transactions.ts @@ -50,12 +50,12 @@ const mapCommonFields = ( export class PaymentInnerTxn extends PaymentTransaction implements itxn.PaymentInnerTxn { readonly isItxn?: true - /* @internal */ + /** @internal */ static create(fields: itxn.PaymentFields) { return new PaymentInnerTxn(fields) } - /* @internal */ + /** @internal */ constructor(fields: itxn.PaymentFields) { super({ ...mapCommonFields(fields), @@ -68,12 +68,12 @@ export class PaymentInnerTxn extends PaymentTransaction implements itxn.PaymentI export class KeyRegistrationInnerTxn extends KeyRegistrationTransaction implements itxn.KeyRegistrationInnerTxn { readonly isItxn?: true - /* @internal */ + /** @internal */ static create(fields: itxn.KeyRegistrationFields) { return new KeyRegistrationInnerTxn(fields) } - /* @internal */ + /** @internal */ constructor(fields: itxn.KeyRegistrationFields) { super(mapCommonFields(fields)) } @@ -82,12 +82,12 @@ export class KeyRegistrationInnerTxn extends KeyRegistrationTransaction implemen export class AssetConfigInnerTxn extends AssetConfigTransaction implements itxn.AssetConfigInnerTxn { readonly isItxn?: true - /* @internal */ + /** @internal */ static create(fields: itxn.AssetConfigFields) { return new AssetConfigInnerTxn(fields) } - /* @internal */ + /** @internal */ constructor(fields: itxn.AssetConfigFields) { const { assetName, unitName, url, manager, reserve, freeze, clawback, configAsset, ...rest } = mapCommonFields(fields) const createdAsset = @@ -122,7 +122,7 @@ export class AssetConfigInnerTxn extends AssetConfigTransaction implements itxn. export class AssetTransferInnerTxn extends AssetTransferTransaction implements itxn.AssetTransferInnerTxn { readonly isItxn?: true - /* @internal */ + /** @internal */ static create(fields: Partial) { if (fields.xferAsset === undefined) { throw new Error('xferAsset is required') @@ -130,7 +130,7 @@ export class AssetTransferInnerTxn extends AssetTransferTransaction implements i return new AssetTransferInnerTxn(fields as itxn.AssetTransferFields) } - /* @internal */ + /** @internal */ constructor(fields: itxn.AssetTransferFields) { super({ ...mapCommonFields(fields), @@ -145,7 +145,7 @@ export class AssetTransferInnerTxn extends AssetTransferTransaction implements i export class AssetFreezeInnerTxn extends AssetFreezeTransaction implements itxn.AssetFreezeInnerTxn { readonly isItxn?: true - /* @internal */ + /** @internal */ static create(fields: Partial) { if (fields.freezeAsset === undefined) { throw new Error('freezeAsset is required') @@ -153,7 +153,7 @@ export class AssetFreezeInnerTxn extends AssetFreezeTransaction implements itxn. return new AssetFreezeInnerTxn(fields as itxn.AssetFreezeFields) } - /* @internal */ + /** @internal */ constructor(fields: itxn.AssetFreezeFields) { const { freezeAsset, freezeAccount, ...rest } = mapCommonFields(fields) const asset: AssetType | undefined = freezeAsset instanceof Uint64Cls ? getAsset(freezeAsset) : (freezeAsset as AssetType) @@ -176,12 +176,12 @@ export type ApplicationCallFields = itxn.ApplicationCallFields & { export class ApplicationCallInnerTxn extends ApplicationCallTransaction implements itxn.ApplicationCallInnerTxn { readonly isItxn?: true - /* @internal */ + /** @internal */ static create(fields: Partial) { return new ApplicationCallInnerTxn(fields) } - /* @internal */ + /** @internal */ constructor(fields: Partial) { const { appId, approvalProgram, clearStateProgram, onCompletion, appArgs, accounts, assets, apps, ...rest } = mapCommonFields(fields) super({ @@ -293,17 +293,19 @@ export class ItxnParams(this.#fields, this.#fields.type) } } + +/** @internal */ const UNSET = Symbol('UNSET_SYMBOL') /** * The ApplicationCallInnerTxnContext class is a specialized version of the ApplicationCallInnerTxn class. * It is used to handle the context of an application call transaction, including managing the return value. */ export class ApplicationCallInnerTxnContext extends ApplicationCallInnerTxn { - /* @internal */ + /** @internal */ static createFromFields(fields: ApplicationCallFields) { return new ApplicationCallInnerTxnContext(fields) } - /* @internal */ + /** @internal */ static createFromTypedApplicationCallFields( methodArgs: TypedApplicationCallFields, methodSelector: bytes, @@ -321,7 +323,7 @@ export class ApplicationCallInnerTxnContext extends Applicati } return new ApplicationCallInnerTxnContext(fields, transactions) } - /* @internal */ + /** @internal */ static createFromBareCreateApplicationCallFields(methodArgs: BareCreateApplicationCallFields) { return new ApplicationCallInnerTxnContext(methodArgs) } @@ -338,7 +340,7 @@ export class ApplicationCallInnerTxnContext extends Applicati this.appendLog(ABI_RETURN_VALUE_LOG_PREFIX.concat(encodeArc4(undefined, value))) this.#returnValue = value } - /* @internal */ + /** @internal */ get loggedReturnValue(): TReturn { return this.#returnValue === UNSET ? (undefined as TReturn) : this.#returnValue } diff --git a/src/impl/transactions.ts b/src/impl/transactions.ts index 144f5655..57eb73d4 100644 --- a/src/impl/transactions.ts +++ b/src/impl/transactions.ts @@ -18,6 +18,7 @@ import { toBytes } from './encoded-types' import { Bytes, Uint64, type StubBytesCompat } from './primitives' import { Account, Application, Asset } from './reference' +/** @internal */ const baseDefaultFields = () => ({ sender: lazyContext.defaultSender, fee: Uint64(0), @@ -81,7 +82,7 @@ abstract class TransactionBase { } export class PaymentTransaction extends TransactionBase implements gtxn.PaymentTxn { - /* @internal */ + /** @internal */ static create(fields: TxnFields) { return new PaymentTransaction(fields) } @@ -101,7 +102,7 @@ export class PaymentTransaction extends TransactionBase implements gtxn.PaymentT } export class KeyRegistrationTransaction extends TransactionBase implements gtxn.KeyRegistrationTxn { - /* @internal */ + /** @internal */ static create(fields: TxnFields) { return new KeyRegistrationTransaction(fields) } @@ -135,7 +136,7 @@ export class KeyRegistrationTransaction extends TransactionBase implements gtxn. } export class AssetConfigTransaction extends TransactionBase implements gtxn.AssetConfigTxn { - /* @internal */ + /** @internal */ static create(fields: TxnFields) { return new AssetConfigTransaction(fields) } @@ -175,7 +176,7 @@ export class AssetConfigTransaction extends TransactionBase implements gtxn.Asse } export class AssetTransferTransaction extends TransactionBase implements gtxn.AssetTransferTxn { - /* @internal */ + /** @internal */ static create(fields: TxnFields) { return new AssetTransferTransaction(fields) } @@ -200,7 +201,7 @@ export class AssetTransferTransaction extends TransactionBase implements gtxn.As } export class AssetFreezeTransaction extends TransactionBase implements gtxn.AssetFreezeTxn { - /* @internal */ + /** @internal */ static create(fields: TxnFields) { return new AssetFreezeTransaction(fields) } @@ -233,7 +234,7 @@ export type ApplicationCallTransactionFields = TxnFields export class ApplicationCallTransaction extends TransactionBase implements gtxn.ApplicationCallTxn { - /* @internal */ + /** @internal */ static create(fields: ApplicationCallTransactionFields) { return new ApplicationCallTransaction(fields) } @@ -351,18 +352,18 @@ export class ApplicationCallTransaction extends TransactionBase implements gtxn. readonly type: TransactionType.ApplicationCall = TransactionType.ApplicationCall readonly typeBytes: bytes = asUint64Cls(TransactionType.ApplicationCall).toBytes().asAlgoTs() - /* @internal */ + /** @internal */ get appLogs() { return this.#appLogs } - /* @internal */ + /** @internal */ appendLog(value: StubBytesCompat): void { if (this.#appLogs.length + 1 > MAX_ITEMS_IN_LOG) { throw new InternalError(`Too many log calls in program, up to ${MAX_ITEMS_IN_LOG} is allowed`) } this.#appLogs.push(asBytes(value)) } - /* @internal */ + /** @internal */ logArc4ReturnValue(value: unknown): void { this.appendLog(ABI_RETURN_VALUE_LOG_PREFIX.concat(toBytes(value))) } diff --git a/src/subcontexts/contract-context.ts b/src/subcontexts/contract-context.ts index 297d3a8f..fb7783f4 100644 --- a/src/subcontexts/contract-context.ts +++ b/src/subcontexts/contract-context.ts @@ -90,8 +90,8 @@ const extractStates = (contract: BaseContract, contractOptions: ContractOptionsP const getUint8 = (value: number) => new Uint({ name: 'Uint<8>', genericArgs: [{ name: '8' }] }, value) -/** @ignore - * @internal +/** + * @internal */ export const extractArraysFromArgs = ( app: Application, @@ -214,7 +214,9 @@ export class ContractContext { return txns } - /** @internal */ + /** + * @internal + */ private isArc4(type: IConstructor): boolean { const result = (type as DeliberateAny as typeof BaseContract).isArc4 if (result !== undefined && result !== null) { @@ -234,7 +236,9 @@ export class ContractContext { return this.isArc4(proto) } - /** @internal */ + /** + * @internal + */ private getContractProxyHandler(isArc4: boolean): ProxyHandler> { const onConstructed = (application: Application, instance: T, conrtactOptions: ContractOptionsParameter | undefined) => { const states = extractStates(instance, conrtactOptions) diff --git a/src/test-execution-context.ts b/src/test-execution-context.ts index 56f244c2..87e7a0cf 100644 --- a/src/test-execution-context.ts +++ b/src/test-execution-context.ts @@ -182,7 +182,7 @@ export class TestExecutionContext { } } - /* @internal */ + /** @internal */ notifyApplicationSpies(itxn: ApplicationCallInnerTxnContext) { for (const spy of this.#applicationSpies) { spy.notify(itxn) diff --git a/src/value-generators/arc4.ts b/src/value-generators/arc4.ts index 119032c8..4c1fef4e 100644 --- a/src/value-generators/arc4.ts +++ b/src/value-generators/arc4.ts @@ -20,8 +20,8 @@ export class Arc4ValueGenerator { /** * Generate a random Uint8 within the specified range. - * @param minValue: Minimum value (inclusive). Defaults to 0. - * @param maxValue: Maximum value (inclusive). Defaults to 2 ** 8 - 1. + * @param minValue Minimum value (inclusive). Defaults to 0. + * @param maxValue Maximum value (inclusive). Defaults to 2 ** 8 - 1. * @returns: A random Uint8 value. * */ uint8(minValue: number | bigint = 0, maxValue: number | bigint = MAX_UINT8): arc4.Uint8 { @@ -30,8 +30,8 @@ export class Arc4ValueGenerator { /** * Generate a random Uint16 within the specified range. - * @param minValue: Minimum value (inclusive). Defaults to 0. - * @param maxValue: Maximum value (inclusive). Defaults to 2 ** 16 - 1. + * @param minValue Minimum value (inclusive). Defaults to 0. + * @param maxValue Maximum value (inclusive). Defaults to 2 ** 16 - 1. * @returns: A random Uint16 value. * */ uint16(minValue: number | bigint = 0, maxValue: number | bigint = MAX_UINT16): arc4.Uint16 { @@ -40,8 +40,8 @@ export class Arc4ValueGenerator { /** * Generate a random Uint32 within the specified range. - * @param minValue: Minimum value (inclusive). Defaults to 0. - * @param maxValue: Maximum value (inclusive). Defaults to 2 ** 32 - 1. + * @param minValue Minimum value (inclusive). Defaults to 0. + * @param maxValue Maximum value (inclusive). Defaults to 2 ** 32 - 1. * @returns: A random Uint32 value. * */ uint32(minValue: number | bigint = 0, maxValue: number | bigint = MAX_UINT32): arc4.Uint32 { @@ -50,8 +50,8 @@ export class Arc4ValueGenerator { /** * Generate a random Uint64 within the specified range. - * @param minValue: Minimum value (inclusive). Defaults to 0. - * @param maxValue: Maximum value (inclusive). Defaults to 2n ** 64n - 1n. + * @param minValue Minimum value (inclusive). Defaults to 0. + * @param maxValue Maximum value (inclusive). Defaults to 2n ** 64n - 1n. * @returns: A random Uint64 value. * */ uint64(minValue: number | bigint = 0, maxValue: number | bigint = MAX_UINT64): arc4.Uint64 { @@ -60,9 +60,9 @@ export class Arc4ValueGenerator { /** * Generate a random Uint128 within the specified range. - * @param minValue: Minimum value (inclusive). Defaults to 0. - * @param maxValue: Maximum value (inclusive). Defaults to 2n ** 128n - 1n. - * @returns: A random Uint128 value. + * @param minValue Minimum value (inclusive). Defaults to 0. + * @param maxValue Maximum value (inclusive). Defaults to 2n ** 128n - 1n. + * @returns A random Uint128 value. * */ uint128(minValue: number | bigint = 0, maxValue: number | bigint = MAX_UINT128): arc4.Uint128 { return new Uint({ name: 'Uint', genericArgs: [{ name: '128' }] }, getRandomBigInt(minValue, maxValue)) as arc4.Uint128 @@ -70,9 +70,9 @@ export class Arc4ValueGenerator { /** * Generate a random Uint256 within the specified range. - * @param minValue: Minimum value (inclusive). Defaults to 0. - * @param maxValue: Maximum value (inclusive). Defaults to 2n ** 256n - 1n. - * @returns: A random Uint256 value. + * @param minValue Minimum value (inclusive). Defaults to 0. + * @param maxValue Maximum value (inclusive). Defaults to 2n ** 256n - 1n. + * @returns A random Uint256 value. * */ uint256(minValue: number | bigint = 0, maxValue: number | bigint = MAX_UINT256): arc4.Uint256 { return new Uint({ name: 'Uint', genericArgs: [{ name: '256' }] }, getRandomBigInt(minValue, maxValue)) as arc4.Uint256 @@ -80,9 +80,9 @@ export class Arc4ValueGenerator { /** * Generate a random Uint512 within the specified range. - * @param minValue: Minimum value (inclusive). Defaults to 0. - * @param maxValue: Maximum value (inclusive). Defaults to 2n ** 512n - 1n. - * @returns: A random Uint512 value. + * @param minValue Minimum value (inclusive). Defaults to 0. + * @param maxValue Maximum value (inclusive). Defaults to 2n ** 512n - 1n. + * @returns A random Uint512 value. * */ uint512(minValue: number | bigint = 0, maxValue: number | bigint = MAX_UINT512): arc4.Uint<512> { return new Uint({ name: 'Uint', genericArgs: [{ name: '512' }] }, getRandomBigInt(minValue, maxValue)) as arc4.Uint<512> @@ -90,9 +90,9 @@ export class Arc4ValueGenerator { /** * Generate a random dynamic bytes of size `n` bits. - * @param n: The number of bits for the dynamic bytes. Must be a multiple of 8, otherwise + * @param n The number of bits for the dynamic bytes. Must be a multiple of 8, otherwise * the last byte will be truncated. - * @returns: A new, random dynamic bytes of size `n` bits. + * @returns A new, random dynamic bytes of size `n` bits. * */ dynamicBytes(n: number): arc4.DynamicBytes { return new DynamicBytes( @@ -103,8 +103,8 @@ export class Arc4ValueGenerator { /** * Generate a random dynamic string of size `n` bits. - * @param n: The number of bits for the string. - * @returns: A new, random string of size `n` bits. + * @param n The number of bits for the string. + * @returns A new, random string of size `n` bits. * */ str(n: number): arc4.Str { // Calculate the number of characters needed (rounding up) diff --git a/src/value-generators/index.ts b/src/value-generators/index.ts index f0779453..cd7ca278 100644 --- a/src/value-generators/index.ts +++ b/src/value-generators/index.ts @@ -6,6 +6,7 @@ export class ValueGenerator extends AvmValueGenerator { txn: TxnValueGenerator arc4: Arc4ValueGenerator + /** @internal */ constructor() { super() this.txn = new TxnValueGenerator() diff --git a/typedoc.json b/typedoc.json index 103799eb..c1b5e8af 100644 --- a/typedoc.json +++ b/typedoc.json @@ -2,18 +2,26 @@ "$schema": "https://typedoc.org/schema.json", "entryPoints": [ "./src/index.ts", - "./src/value-generators/*.ts", - "./src/subcontexts/*.ts", "./src/test-transformer/jest-transformer.ts", - "./src/test-transformer/vitest-transformer.ts" + "./src/test-transformer/vitest-transformer.ts", + "./src/value-generators/index.ts" ], - "exclude": ["types/*.spec.ts"], - "entryPointStrategy": "expand", - "out": "docs/code", - "plugin": ["typedoc-plugin-markdown"], - "theme": "default", + "excludeInternal": true, + "excludeReferences": true, + "excludeExternals": true, + "excludePrivate": true, + "excludeProtected": true, + "includeHierarchySummary": false, + "outputs": [ + { + "name": "html", + "path": "docs/_html" + } + ], + "name": "Algorand TypeScript Testing", + "projectDocuments": ["docs/testing-guide.md", "docs/algots.md", "docs/api.md", "docs/coverage.md", "docs/examples.md", "docs/faq.md"], + "plugin": ["typedoc-plugin-missing-exports", "typedoc-plugin-markdown"], "cleanOutputDir": true, - "githubPages": false, - "readme": "none", + "readme": "docs/readme.md", "gitRevision": "main" } From 9e66da5167458ccb2243b156917d2af5ec518883 Mon Sep 17 00:00:00 2001 From: Bobby Lat Date: Fri, 15 Aug 2025 08:35:06 +0700 Subject: [PATCH 37/68] docs: fix testing guide link --- README.md | 2 +- src/subcontexts/ledger-context.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 47f169c7..1620f2bf 100644 --- a/README.md +++ b/README.md @@ -287,7 +287,7 @@ To dig deeper into the capabilities of `algorand-typescript-testing`, continue w #### Contents -- [Testing Guide](./docs/testing-guide/index.md) +- [Testing Guide](./docs/testing-guide.md) - [Examples](./docs/examples.md) - [Coverage](./docs/coverage.md) - [FQA](./docs/faq.md) diff --git a/src/subcontexts/ledger-context.ts b/src/subcontexts/ledger-context.ts index b9acef47..ede9f378 100644 --- a/src/subcontexts/ledger-context.ts +++ b/src/subcontexts/ledger-context.ts @@ -210,7 +210,7 @@ export class LedgerContext { /** * Patches asset data with the provided partial data. - * @param account - The asset. + * @param asset - The asset. * @param data - The partial asset data. */ patchAssetData(asset: AssetType, data: Partial) { From 8ce9d46af3c973bc98bbd16aa47def70e0f59221 Mon Sep 17 00:00:00 2001 From: Bobby Lat Date: Fri, 15 Aug 2025 09:19:52 +0700 Subject: [PATCH 38/68] chore: add more @internal flags --- src/collections/custom-key-map.ts | 5 +++++ src/impl/inner-transactions.ts | 1 + src/impl/transactions.ts | 8 ++++++++ src/subcontexts/transaction-context.ts | 5 +++++ 4 files changed, 19 insertions(+) diff --git a/src/collections/custom-key-map.ts b/src/collections/custom-key-map.ts index 7aeb590e..b2589dab 100644 --- a/src/collections/custom-key-map.ts +++ b/src/collections/custom-key-map.ts @@ -8,6 +8,7 @@ export abstract class CustomKeyMap implements Map { #keySerializer: (key: TKey) => Primitive #map = new Map() + /** @internal */ constructor(keySerializer: (key: TKey) => number | bigint | string) { this.#keySerializer = keySerializer } @@ -65,22 +66,26 @@ export abstract class CustomKeyMap implements Map { } export class AccountMap extends CustomKeyMap { + /** @internal */ constructor() { super(AccountMap.getAddressStrFromAccount) } + /** @internal */ private static getAddressStrFromAccount = (acc: Account) => { return asBytesCls(acc.bytes).valueOf() } } export class BytesMap extends CustomKeyMap { + /** @internal */ constructor() { super((bytes) => asBytesCls(bytes).valueOf()) } } export class Uint64Map extends CustomKeyMap { + /** @internal */ constructor() { super((uint64) => asUint64Cls(uint64).valueOf()) } diff --git a/src/impl/inner-transactions.ts b/src/impl/inner-transactions.ts index cebbdbe8..e88b71d2 100644 --- a/src/impl/inner-transactions.ts +++ b/src/impl/inner-transactions.ts @@ -355,6 +355,7 @@ export class ApplicationCallInnerTxnContext extends Applicati super.appendLog(value) } + /** @internal */ private constructor( fields: ApplicationCallFields, public itxns?: Transaction[], diff --git a/src/impl/transactions.ts b/src/impl/transactions.ts index 57eb73d4..84c59d59 100644 --- a/src/impl/transactions.ts +++ b/src/impl/transactions.ts @@ -35,6 +35,7 @@ const baseDefaultFields = () => ({ export type TxnFields = Partial>>> abstract class TransactionBase { + /** @internal */ protected constructor(fields: Partial>) { const baseDefaults = baseDefaultFields() this.sender = fields.sender ?? baseDefaults.sender @@ -87,6 +88,7 @@ export class PaymentTransaction extends TransactionBase implements gtxn.PaymentT return new PaymentTransaction(fields) } + /** @internal */ protected constructor(fields: TxnFields) { super(fields) this.receiver = fields.receiver ?? Account() @@ -107,6 +109,7 @@ export class KeyRegistrationTransaction extends TransactionBase implements gtxn. return new KeyRegistrationTransaction(fields) } + /** @internal */ protected constructor(fields: TxnFields) { super(fields) this.voteKey = fields.voteKey ?? (Bytes() as bytes<32>) @@ -141,6 +144,7 @@ export class AssetConfigTransaction extends TransactionBase implements gtxn.Asse return new AssetConfigTransaction(fields) } + /** @internal */ protected constructor(fields: TxnFields) { super(fields) this.configAsset = fields.configAsset ?? Asset() @@ -181,6 +185,7 @@ export class AssetTransferTransaction extends TransactionBase implements gtxn.As return new AssetTransferTransaction(fields) } + /** @internal */ protected constructor(fields: TxnFields) { super(fields) this.xferAsset = fields.xferAsset ?? Asset() @@ -206,6 +211,7 @@ export class AssetFreezeTransaction extends TransactionBase implements gtxn.Asse return new AssetFreezeTransaction(fields) } + /** @internal */ protected constructor(fields: TxnFields) { super(fields) this.freezeAsset = fields.freezeAsset ?? Asset() @@ -238,6 +244,7 @@ export class ApplicationCallTransaction extends TransactionBase implements gtxn. static create(fields: ApplicationCallTransactionFields) { return new ApplicationCallTransaction(fields) } + /** @internal */ private args: Array #accounts: Array #assets: Array @@ -247,6 +254,7 @@ export class ApplicationCallTransaction extends TransactionBase implements gtxn. #appLogs: Array #appId: ApplicationType + /** @internal */ protected constructor(fields: ApplicationCallTransactionFields) { super(fields) this.#appId = fields.appId ?? Application() diff --git a/src/subcontexts/transaction-context.ts b/src/subcontexts/transaction-context.ts index 90a6f058..93ee04d9 100644 --- a/src/subcontexts/transaction-context.ts +++ b/src/subcontexts/transaction-context.ts @@ -59,10 +59,15 @@ interface ExecutionScope { export class DeferredAppCall { /** @internal */ constructor( + /** @internal */ private readonly appId: uint64, + /** @internal */ readonly txns: Transaction[], + /** @internal */ private readonly method: (...args: TParams) => TReturn, + /** @internal */ private readonly abiMetadata: AbiMetadata, + /** @internal */ private readonly args: TParams, ) {} From b81f4b0d1bd69c822117b59dda237119f0c70d0f Mon Sep 17 00:00:00 2001 From: Bobby Lat Date: Fri, 15 Aug 2025 10:04:00 +0700 Subject: [PATCH 39/68] docs: fix more broken links --- docs/algots.md | 2 +- docs/testing-guide.md | 1 + docs/tg-transactions.md | 2 +- 3 files changed, 3 insertions(+), 2 deletions(-) diff --git a/docs/algots.md b/docs/algots.md index 8a5b2661..440ec236 100644 --- a/docs/algots.md +++ b/docs/algots.md @@ -12,4 +12,4 @@ and debugged on a Node.js virtual machine with transpilation to EcmaScript and r Algorand TypeScript is compiled for execution on the AVM by PuyaTs, a TypeScript frontend for the [Puya](https://github.com/algorandfoundation/puya) optimising compiler that ensures the resulting AVM bytecode execution semantics that match the given TypeScript code. PuyaTs produces output that is directly compatible with AlgoKit typed clients to make deployment and calling easy. -[Documentation](https://github.com/algorandfoundation/puya-ts/blob/main/README.md) +[Documentation](https://algorandfoundation.github.io/puya-ts/index.html) diff --git a/docs/testing-guide.md b/docs/testing-guide.md index 4fe60264..a2e52bf7 100644 --- a/docs/testing-guide.md +++ b/docs/testing-guide.md @@ -2,6 +2,7 @@ title: Algorand TypeScript Testing Guide children: - ./tg-concepts.md + - ./tg-application-spy.md - ./tg-avm-types.md - ./tg-arc4-types.md - ./tg-transactions.md diff --git a/docs/tg-transactions.md b/docs/tg-transactions.md index 25adbb1c..ce1a2720 100644 --- a/docs/tg-transactions.md +++ b/docs/tg-transactions.md @@ -4,7 +4,7 @@ title: Transactions # Transactions -The testing framework follows the Transaction definitions described in [`algorand-typescript` docs](https://github.com/algorandfoundation/puya-ts/blob/main/docs/lg-transactions.md). This section focuses on _value generators_ and interactions with inner transactions, it also explains how the framework identifies _active_ transaction group during contract method/subroutine/logicsig invocation. +The testing framework follows the Transaction definitions described in [`algorand-typescript` docs](https://algorandfoundation.github.io/puya-ts/documents/Algorand_TypeScript_Language_Guide.Types.html#group-transactions). This section focuses on _value generators_ and interactions with inner transactions, it also explains how the framework identifies _active_ transaction group during contract method/subroutine/logicsig invocation. ```ts import * as algots from '@algorandfoundation/algorand-typescript' From fa2f3afced850ad140f6f04f66a0ac2fbba87012 Mon Sep 17 00:00:00 2001 From: Bobby Lat Date: Fri, 15 Aug 2025 17:03:36 +0700 Subject: [PATCH 40/68] fix: do not encode reference types as foreign array index --- src/context-helpers/internal-context.ts | 4 ++++ src/impl/app-global.ts | 6 ++++-- src/impl/app-local.ts | 6 ++++-- src/impl/encoded-types/encoded-types.ts | 11 +++-------- src/impl/state.ts | 6 +++--- src/subcontexts/transaction-context.ts | 4 ++++ tests/artifacts/state-ops/contract.algo.ts | 13 +++++++++++++ tests/state-op-codes.algo.spec.ts | 13 +++++++++++++ 8 files changed, 48 insertions(+), 15 deletions(-) diff --git a/src/context-helpers/internal-context.ts b/src/context-helpers/internal-context.ts index 27773046..13a77e01 100644 --- a/src/context-helpers/internal-context.ts +++ b/src/context-helpers/internal-context.ts @@ -43,6 +43,10 @@ class InternalContext { return this.ledger.getApplication(this.activeGroup.activeApplicationId) } + get hasActiveGroup(): boolean { + return this.value.txn.hasActiveGroup + } + get activeGroup(): TransactionGroup { return this.value.txn.activeGroup } diff --git a/src/impl/app-global.ts b/src/impl/app-global.ts index 67ec19f3..0dad6cdb 100644 --- a/src/impl/app-global.ts +++ b/src/impl/app-global.ts @@ -11,10 +11,12 @@ export const AppGlobal: typeof op.AppGlobal = { lazyContext.ledger.setGlobalState(lazyContext.activeApplication, asBytes(a), undefined) }, getBytes(a: StubBytesCompat): bytes { - return this.getExBytes(0, asBytes(a))[0] + const app = lazyContext.activeApplication + return this.getExBytes(app, asBytes(a))[0] }, getUint64(a: StubBytesCompat): uint64 { - return this.getExUint64(0, asBytes(a))[0] + const app = lazyContext.activeApplication + return this.getExUint64(app, asBytes(a))[0] }, getExBytes(a: Application | StubUint64Compat, b: StubBytesCompat): readonly [bytes, boolean] { const app = getApp(a) diff --git a/src/impl/app-local.ts b/src/impl/app-local.ts index 0f56b053..d15db8eb 100644 --- a/src/impl/app-local.ts +++ b/src/impl/app-local.ts @@ -14,12 +14,14 @@ export const AppLocal: typeof op.AppLocal = { lazyContext.ledger.setLocalState(app, account, asBytes(b), undefined) }, getBytes: function (a: Account | StubUint64Compat, b: StubBytesCompat): bytes { + const app = lazyContext.activeApplication const account = getAccount(a) - return this.getExBytes(account, 0, asBytes(b))[0] + return this.getExBytes(account, app, asBytes(b))[0] }, getUint64: function (a: Account | StubUint64Compat, b: StubBytesCompat): uint64 { + const app = lazyContext.activeApplication const account = getAccount(a) - return this.getExUint64(account, 0, asBytes(b))[0] + return this.getExUint64(account, app, asBytes(b))[0] }, getExBytes: function (a: Account | StubUint64Compat, b: Application | StubUint64Compat, c: StubBytesCompat): readonly [bytes, boolean] { const app = getApp(b) diff --git a/src/impl/encoded-types/encoded-types.ts b/src/impl/encoded-types/encoded-types.ts index 9de0aff1..0e33392b 100644 --- a/src/impl/encoded-types/encoded-types.ts +++ b/src/impl/encoded-types/encoded-types.ts @@ -19,7 +19,6 @@ import { import { encodingUtil } from '@algorandfoundation/puya-ts' import assert from 'assert' import { ABI_RETURN_VALUE_LOG_PREFIX, ALGORAND_ADDRESS_BYTE_LENGTH, ALGORAND_CHECKSUM_BYTE_LENGTH, UINT64_SIZE } from '../../constants' -import { lazyContext } from '../../context-helpers/internal-context' import { AvmError, avmInvariant, CodeError, InternalError } from '../../errors' import { nameOfType, type DeliberateAny } from '../../typescript-helpers' import { @@ -42,7 +41,6 @@ import { BytesBackedCls, Uint64BackedCls } from '../base' import type { StubBytesCompat } from '../primitives' import { BigUintCls, Bytes, BytesCls, getUint8Array, isBytes, Uint64Cls } from '../primitives' import { Account, AccountCls, ApplicationCls, AssetCls } from '../reference' -import type { ApplicationCallTransaction } from '../transactions' import { arrayProxyHandler } from './array-proxy' import { ABI_LENGTH_SIZE, FALSE_BIGINT_VALUE, IS_INITIALISING_FROM_BYTES_SYMBOL, TRUE_BIGINT_VALUE } from './constants' import { @@ -1157,16 +1155,13 @@ export const getArc4Encoded = (value: DeliberateAny, sourceTypeInfoString?: stri return value } if (value instanceof AccountCls) { - const index = (lazyContext.activeGroup.activeTransaction as ApplicationCallTransaction).apat.indexOf(value) - return index >= 0 ? new Uint({ name: 'Uint<64>', genericArgs: [{ name: '64' }] }, asBigInt(index)) : getArc4Encoded(value.bytes) + return getArc4Encoded(value.bytes) } if (value instanceof AssetCls) { - const index = (lazyContext.activeGroup.activeTransaction as ApplicationCallTransaction).apas.indexOf(value) - return index >= 0 ? new Uint({ name: 'Uint<64>', genericArgs: [{ name: '64' }] }, asBigInt(index)) : getArc4Encoded(value.id) + return getArc4Encoded(value.id) } if (value instanceof ApplicationCls) { - const index = (lazyContext.activeGroup.activeTransaction as ApplicationCallTransaction).apfa.indexOf(value) - return index >= 0 ? new Uint({ name: 'Uint<64>', genericArgs: [{ name: '64' }] }, asBigInt(index)) : getArc4Encoded(value.id) + return getArc4Encoded(value.id) } if (typeof value === 'boolean') { return new Bool({ name: 'Bool' }, value) diff --git a/src/impl/state.ts b/src/impl/state.ts index c7cd694c..b734f7e6 100644 --- a/src/impl/state.ts +++ b/src/impl/state.ts @@ -246,7 +246,7 @@ export class BoxCls { } get ref(): BoxRefCls { - return new BoxRefCls(this.key) + return new BoxRefCls(this.key, this.#app) } get(options: { default: TValue }): TValue { @@ -317,9 +317,9 @@ export class BoxRefCls { return x instanceof Object && '_type' in x && (x as { _type: string })['_type'] === BoxRefCls.name } - constructor(key?: StubBytesCompat) { + constructor(key?: StubBytesCompat, app?: Application) { this.#key = key ? asBytes(key) : undefined - this.#app = lazyContext.activeApplication + this.#app = app ?? lazyContext.activeApplication } get hasKey(): boolean { diff --git a/src/subcontexts/transaction-context.ts b/src/subcontexts/transaction-context.ts index 93ee04d9..fb2f432a 100644 --- a/src/subcontexts/transaction-context.ts +++ b/src/subcontexts/transaction-context.ts @@ -146,6 +146,10 @@ export class TransactionContext { } } + get hasActiveGroup(): boolean { + return !!this.#activeGroup + } + /** * Gets the active transaction group. * @returns The active transaction group. diff --git a/tests/artifacts/state-ops/contract.algo.ts b/tests/artifacts/state-ops/contract.algo.ts index d539d309..2542f0b5 100644 --- a/tests/artifacts/state-ops/contract.algo.ts +++ b/tests/artifacts/state-ops/contract.algo.ts @@ -3,6 +3,7 @@ import { arc4, assert, BaseContract, + BoxMap, Bytes, clone, contract, @@ -907,3 +908,15 @@ export class LocalStateContract extends arc4.Contract { return this.arc4DynamicBytes(a).value } } + +export class BoxMapContract extends arc4.Contract { + allowedCreators = BoxMap<[Account, Account], boolean>({ + keyPrefix: Bytes(), + }) + + allowOptInsFrom(creator: Account): void { + assert(Txn.accounts(0) === Txn.sender) + assert(Txn.applications(0) === Global.currentApplicationId) + this.allowedCreators([Txn.sender, creator]).value = true + } +} diff --git a/tests/state-op-codes.algo.spec.ts b/tests/state-op-codes.algo.spec.ts index 461a2772..dd6681b1 100644 --- a/tests/state-op-codes.algo.spec.ts +++ b/tests/state-op-codes.algo.spec.ts @@ -17,6 +17,7 @@ import type { DeliberateAny } from '../src/typescript-helpers' import { asBigInt, asNumber, asUint64Cls, asUint8Array, getRandomBytes } from '../src/util' import { AppExpectingEffects } from './artifacts/created-app-asset/contract.algo' import { + BoxMapContract, ItxnDemoContract, ITxnOpsContract, StateAcctParamsGetContract, @@ -767,6 +768,18 @@ describe('State op codes', async () => { expect(bytesResult).toEqual(bytesAvmResult) }) }) + + describe('BoxMap', async () => { + test('should be able to use tuple of reference types as key', () => { + const creatorVerifier = ctx.contract.create(BoxMapContract) + const creator = ctx.any.arc4.address() + + creatorVerifier.allowOptInsFrom(creator.native) + + const isAllowed = creatorVerifier.allowedCreators([ctx.defaultSender, creator.native]).value + expect(isAllowed).toBe(true) + }) + }) }) const tryOptIn = async (client: AppClient) => { try { From 0f1335592664e29bddfbea5aaa6ae2f730d09acc Mon Sep 17 00:00:00 2001 From: Bobby Lat Date: Tue, 2 Sep 2025 16:02:34 +0700 Subject: [PATCH 41/68] feat: add stub implementations for additional 1.0 features - add @readonly decorator, - allow abiCall with type import, - rename arc4EncodedLength, - add strategy param to .toFixed method --- README.md | 18 ++++++-- docs/coverage.md | 2 +- docs/readme.md | 18 ++++++-- docs/tg-contract-testing.md | 2 +- examples/arc4-simple-voting/contract.algo.ts | 4 +- examples/local-storage/contract.algo.ts | 4 +- examples/marketplace/contract.algo.ts | 4 +- .../precompiled/precompiled-typed.algo.ts | 7 ++-- examples/proof-of-attendance/contract.algo.ts | 9 ++-- examples/voting/contract.algo.ts | 3 +- package-lock.json | 21 +++++----- package.json | 7 ++-- src/abi-metadata.ts | 8 +++- src/impl/c2c.ts | 13 ++++-- src/impl/contract.ts | 11 +++++ src/impl/encoded-types/index.ts | 2 +- src/impl/encoded-types/utils.ts | 2 +- src/impl/primitives.ts | 4 +- src/impl/pure.ts | 4 +- src/internal/arc4.ts | 4 +- src/internal/index.ts | 2 +- src/test-transformer/helpers.ts | 26 ++++++++++++ src/test-transformer/node-factory.ts | 19 +++++++-- src/test-transformer/visitors.ts | 41 +++++++++++-------- tests/arc4/encode-decode-arc4.algo.spec.ts | 26 ++++++------ .../miscellaneous-ops/contract.algo.ts | 4 +- .../resource-encoding/contract.algo.ts | 7 ++-- tests/primitives/bytes.algo.spec.ts | 6 +-- tests/state-op-codes.algo.spec.ts | 6 +-- 29 files changed, 190 insertions(+), 94 deletions(-) diff --git a/README.md b/README.md index 1620f2bf..7f28d88c 100644 --- a/README.md +++ b/README.md @@ -169,7 +169,19 @@ After the setup, the examples provided using `vitest` can be converted to work w #### Contract Definition ```typescript -import { arc4, assert, Bytes, GlobalState, gtxn, LocalState, op, Txn, uint64, Uint64 } from '@algorandfoundation/algorand-typescript' +import { + arc4, + assert, + Bytes, + GlobalState, + gtxn, + LocalState, + op, + readonly, + Txn, + uint64, + Uint64, +} from '@algorandfoundation/algorand-typescript' export default class VotingContract extends arc4.Contract { topic = GlobalState({ initialValue: 'default_topic', key: Bytes('topic') }) @@ -195,7 +207,7 @@ export default class VotingContract extends arc4.Contract { return true } - @arc4.abimethod({ readonly: true }) + @readonly public getVotes(): uint64 { return this.votes.value } @@ -262,7 +274,7 @@ This example demonstrates key aspects of testing with `algorand-typescript-testi - Use of `arc4.Contract` as the base class for the contract. - ABI methods defined using the `@arc4.abimethod` decorator. - - Readonly method annotation with `@arc4.abimethod({readonly: true})`. + - Readonly method annotation with `@arc4.abimethod({readonly: true})` or `@readonly`. 2. Testing ARC4 Contracts: diff --git a/docs/coverage.md b/docs/coverage.md index 03848443..5a15567b 100644 --- a/docs/coverage.md +++ b/docs/coverage.md @@ -34,7 +34,7 @@ See which `algorand-typescript` stubs are implemented by the `algorand-typescrip | Txn | Emulated | | Uint64 | Native | | abiCall | Mockable | -| arc4EncodedLength | Native | +| sizeOf | Native | | compile | Mockable | | compileArc4 | Mockable | | decodeArc4 | Native | diff --git a/docs/readme.md b/docs/readme.md index 3e3d7c6b..86aa7e89 100644 --- a/docs/readme.md +++ b/docs/readme.md @@ -167,7 +167,19 @@ After the setup, the examples provided using `vitest` can be converted to work w #### Contract Definition ```typescript -import { arc4, assert, Bytes, GlobalState, gtxn, LocalState, op, Txn, uint64, Uint64 } from '@algorandfoundation/algorand-typescript' +import { + arc4, + assert, + Bytes, + GlobalState, + gtxn, + LocalState, + op, + readonly, + Txn, + uint64, + Uint64, +} from '@algorandfoundation/algorand-typescript' export default class VotingContract extends arc4.Contract { topic = GlobalState({ initialValue: 'default_topic', key: Bytes('topic') }) @@ -193,7 +205,7 @@ export default class VotingContract extends arc4.Contract { return true } - @arc4.abimethod({ readonly: true }) + @readonly public getVotes(): uint64 { return this.votes.value } @@ -260,7 +272,7 @@ This example demonstrates key aspects of testing with `algorand-typescript-testi - Use of `arc4.Contract` as the base class for the contract. - ABI methods defined using the `@arc4.abimethod` decorator. - - Readonly method annotation with `@arc4.abimethod({readonly: true})`. + - Readonly method annotation with `@arc4.abimethod({readonly: true})` or `@readonly`. 2. Testing ARC4 Contracts: diff --git a/docs/tg-contract-testing.md b/docs/tg-contract-testing.md index 43c399d2..d1386d0b 100644 --- a/docs/tg-contract-testing.md +++ b/docs/tg-contract-testing.md @@ -47,7 +47,7 @@ class SimpleVotingContract extends arc4.Contract { return this.votes.value } - @arc4.abimethod({ readonly: true }) + @readonly getVotes(): uint64 { return this.votes.value } diff --git a/examples/arc4-simple-voting/contract.algo.ts b/examples/arc4-simple-voting/contract.algo.ts index aada9956..f1948e52 100644 --- a/examples/arc4-simple-voting/contract.algo.ts +++ b/examples/arc4-simple-voting/contract.algo.ts @@ -1,5 +1,5 @@ import type { gtxn, uint64 } from '@algorandfoundation/algorand-typescript' -import { arc4, assert, Bytes, GlobalState, LocalState, op, Txn, Uint64 } from '@algorandfoundation/algorand-typescript' +import { arc4, assert, Bytes, GlobalState, LocalState, op, readonly, Txn, Uint64 } from '@algorandfoundation/algorand-typescript' export default class VotingContract extends arc4.Contract { topic = GlobalState({ initialValue: Bytes('default_topic'), key: Bytes('topic') }) @@ -25,7 +25,7 @@ export default class VotingContract extends arc4.Contract { return new arc4.Bool(true) } - @arc4.abimethod({ readonly: true }) + @readonly public getVotes(): arc4.Uint64 { return new arc4.Uint64(this.votes.value) } diff --git a/examples/local-storage/contract.algo.ts b/examples/local-storage/contract.algo.ts index 285c4b52..9b658fec 100644 --- a/examples/local-storage/contract.algo.ts +++ b/examples/local-storage/contract.algo.ts @@ -1,5 +1,5 @@ import type { Account, bytes, uint64 } from '@algorandfoundation/algorand-typescript' -import { Bytes, Global, LocalState, Txn, arc4, assert, contract } from '@algorandfoundation/algorand-typescript' +import { Bytes, Global, LocalState, Txn, arc4, assert, contract, readonly } from '@algorandfoundation/algorand-typescript' /** * A contract demonstrating local storage functionality. @@ -55,7 +55,7 @@ export default class LocalStorage extends arc4.Contract { * - [4] boolean: The value of localBool * - [5] Address: The value of localAccount converted to Address type */ - @arc4.abimethod({ readonly: true }) + @readonly public readLocalState(): [uint64, uint64, bytes, string, boolean, arc4.Address] { const sender = Txn.sender // Convert Account reference type to native Address type for return value diff --git a/examples/marketplace/contract.algo.ts b/examples/marketplace/contract.algo.ts index a998d12c..8d9eb9ef 100644 --- a/examples/marketplace/contract.algo.ts +++ b/examples/marketplace/contract.algo.ts @@ -1,5 +1,5 @@ import type { Asset, gtxn, uint64 } from '@algorandfoundation/algorand-typescript' -import { arc4, assert, BoxMap, clone, Global, itxn, op, Txn } from '@algorandfoundation/algorand-typescript' +import { arc4, assert, BoxMap, clone, Global, itxn, op, readonly, Txn } from '@algorandfoundation/algorand-typescript' export class ListingKey extends arc4.Struct<{ owner: arc4.Address @@ -52,7 +52,7 @@ export default class DigitalMarketplace extends arc4.Contract { return amountToBePaid } - @arc4.abimethod({ readonly: true }) + @readonly getListingsMbr(): uint64 { return this.listingsBoxMbr() } diff --git a/examples/precompiled/precompiled-typed.algo.ts b/examples/precompiled/precompiled-typed.algo.ts index c4815a57..cf962197 100644 --- a/examples/precompiled/precompiled-typed.algo.ts +++ b/examples/precompiled/precompiled-typed.algo.ts @@ -2,7 +2,7 @@ import { assert, Contract, Global, itxn, Txn } from '@algorandfoundation/algoran import { abiCall, compileArc4, methodSelector } from '@algorandfoundation/algorand-typescript/arc4' import { Hello, - HelloStubbed, + type HelloStubbed, HelloTemplate, HelloTemplateCustomPrefix, LargeProgram, @@ -24,14 +24,15 @@ export class HelloFactoryTyped extends Contract { }).returnValue assert(result === 'hello world') - const result2 = abiCall(Hello.prototype.greet, { + const result2 = abiCall({ + method: Hello.prototype.greet, appId: app, args: ['abi'], }).returnValue assert(result2 === 'hello abi') - const result3 = abiCall(HelloStubbed.prototype.greet, { + const result3 = abiCall({ appId: app, args: ['stubbed'], }).returnValue diff --git a/examples/proof-of-attendance/contract.algo.ts b/examples/proof-of-attendance/contract.algo.ts index 6fa7ddc9..3d9cd7bd 100644 --- a/examples/proof-of-attendance/contract.algo.ts +++ b/examples/proof-of-attendance/contract.algo.ts @@ -12,6 +12,7 @@ import { itxn, op, OpUpFeeSource, + readonly, Txn, Uint64, } from '@algorandfoundation/algorand-typescript' @@ -81,14 +82,14 @@ export default class ProofOfAttendance extends arc4.Contract { this.boxMap(Txn.sender.bytes).value = mintedAsset.id } - @arc4.abimethod({ readonly: true }) + @readonly getPoaId(): uint64 { const [poaId, exists] = op.Box.get(Txn.sender.bytes) assert(exists, 'POA not found') return op.btoi(poaId) } - @arc4.abimethod({ readonly: true }) + @readonly getPoaIdWithBox(): uint64 { const box = Box({ key: Txn.sender.bytes }) const [poaId, exists] = box.maybe() @@ -96,7 +97,7 @@ export default class ProofOfAttendance extends arc4.Contract { return poaId } - @arc4.abimethod({ readonly: true }) + @readonly getPoaIdWithBoxRef(): uint64 { const boxRef = BoxRef({ key: Txn.sender.bytes }) const [poaId, exists] = boxRef.maybe() @@ -104,7 +105,7 @@ export default class ProofOfAttendance extends arc4.Contract { return op.btoi(poaId) } - @arc4.abimethod({ readonly: true }) + @readonly getPoaIdWithBoxMap(): uint64 { const [poaId, exists] = this.boxMap(Txn.sender.bytes).maybe() assert(exists, 'POA not found') diff --git a/examples/voting/contract.algo.ts b/examples/voting/contract.algo.ts index 7d379334..ae73a125 100644 --- a/examples/voting/contract.algo.ts +++ b/examples/voting/contract.algo.ts @@ -15,6 +15,7 @@ import { log, op, OpUpFeeSource, + readonly, Txn, Uint64, urange, @@ -158,7 +159,7 @@ export class VotingRoundApp extends arc4.Contract { .submit().createdAsset } - @abimethod({ readonly: true }) + @readonly public getPreconditions(signature: bytes<64>): VotingPreconditions { return { is_allowed_to_vote: Uint64(this.allowedToVote(signature)), diff --git a/package-lock.json b/package-lock.json index 06f054d7..c9e8a80b 100644 --- a/package-lock.json +++ b/package-lock.json @@ -8,13 +8,14 @@ "name": "@algorandfoundation/algorand-typescript-testing", "version": "1.0.0", "dependencies": { - "@algorandfoundation/algorand-typescript": "1.0.0-alpha.74", - "@algorandfoundation/puya-ts": "1.0.0-alpha.74", + "@algorandfoundation/algorand-typescript": "1.0.0-alpha.79", + "@algorandfoundation/puya-ts": "1.0.0-alpha.79", "elliptic": "^6.6.1", "js-sha256": "^0.11.0", "js-sha3": "^0.9.3", "js-sha512": "^0.9.0", - "tweetnacl": "^1.0.3" + "tweetnacl": "^1.0.3", + "upath": "^2.0.1" }, "devDependencies": { "@algorandfoundation/algokit-utils": "^9.0.0", @@ -75,21 +76,21 @@ } }, "node_modules/@algorandfoundation/algorand-typescript": { - "version": "1.0.0-alpha.74", - "resolved": "https://registry.npmjs.org/@algorandfoundation/algorand-typescript/-/algorand-typescript-1.0.0-alpha.74.tgz", - "integrity": "sha512-rUJtmbJt8cfju2U/4ZoaCUlgN4AW9Nor3rOLozVg6IFoML0N9QzgRlqebdFUgJvJGXQ7zrJdcy19movZMc9fnw==", + "version": "1.0.0-alpha.79", + "resolved": "https://registry.npmjs.org/@algorandfoundation/algorand-typescript/-/algorand-typescript-1.0.0-alpha.79.tgz", + "integrity": "sha512-pR5XxWvWMC76uSHsEcojnnWlIelIFYE2Z7+l+F2WAkQsXOMkcSNW4R7vjKv+BjtTfUHVaksbRXKjn2f5/nWQ1w==", "peerDependencies": { "tslib": "^2.6.2" } }, "node_modules/@algorandfoundation/puya-ts": { - "version": "1.0.0-alpha.74", - "resolved": "https://registry.npmjs.org/@algorandfoundation/puya-ts/-/puya-ts-1.0.0-alpha.74.tgz", - "integrity": "sha512-pDpHBNpR+iWIZVDwdGxxnTZOTSAaA1JURsegO0bbCHpVwgfrgks9MLAtJiHiN7/7eE7QKF/zo3DSLxNb6PY35A==", + "version": "1.0.0-alpha.79", + "resolved": "https://registry.npmjs.org/@algorandfoundation/puya-ts/-/puya-ts-1.0.0-alpha.79.tgz", + "integrity": "sha512-PGNPoutPGVIVTohBTznRfiX8VPO2AaRMdaAE/K3xUXKwFai8li38WQ2eplxof3cAHy96X2GeiCDze4XK6mcNyg==", "hasInstallScript": true, "license": "MIT", "dependencies": { - "@algorandfoundation/algorand-typescript": "1.0.0-alpha.74", + "@algorandfoundation/algorand-typescript": "1.0.0-alpha.79", "arcsecond": "^5.0.0", "argparse": "^2.0.1", "chalk": "^5.4.1", diff --git a/package.json b/package.json index f3e4bd36..d26e5ace 100644 --- a/package.json +++ b/package.json @@ -68,13 +68,14 @@ "vitest": "3.2.4" }, "dependencies": { - "@algorandfoundation/algorand-typescript": "1.0.0-alpha.74", - "@algorandfoundation/puya-ts": "1.0.0-alpha.74", + "@algorandfoundation/algorand-typescript": "1.0.0-alpha.79", + "@algorandfoundation/puya-ts": "1.0.0-alpha.79", "elliptic": "^6.6.1", "js-sha256": "^0.11.0", "js-sha3": "^0.9.3", "js-sha512": "^0.9.0", - "tweetnacl": "^1.0.3" + "tweetnacl": "^1.0.3", + "upath": "^2.0.1" }, "overrides": { "cross-spawn": "7.0.6", diff --git a/src/abi-metadata.ts b/src/abi-metadata.ts index 1747668b..7ffbdca8 100644 --- a/src/abi-metadata.ts +++ b/src/abi-metadata.ts @@ -19,8 +19,10 @@ export interface AbiMetadata { } const metadataStore: WeakMap<{ new (): Contract }, Record> = new WeakMap() +const contractMap: Map = new Map() /** @internal */ -export const attachAbiMetadata = (contract: { new (): Contract }, methodName: string, metadata: AbiMetadata): void => { +export const attachAbiMetadata = (fileName: string, contract: { new (): Contract }, methodName: string, metadata: AbiMetadata): void => { + contractMap.set(`${fileName}::${contract.prototype.constructor.name}`, contract) if (!metadataStore.has(contract)) { metadataStore.set(contract, {}) } @@ -33,6 +35,10 @@ export const attachAbiMetadata = (contract: { new (): Contract }, methodName: st } } +export const getContractByName = (contractFullame: string): { new (): Contract } | undefined => { + return contractMap.get(contractFullame) +} + /** @internal */ export const getContractAbiMetadata = (contract: T | { new (): T }): Record => { // Initialize result object to store merged metadata diff --git a/src/impl/c2c.ts b/src/impl/c2c.ts index 293b48a7..67a1cd4d 100644 --- a/src/impl/c2c.ts +++ b/src/impl/c2c.ts @@ -4,8 +4,9 @@ import type { ContractProxy, TypedApplicationCallFields, } from '@algorandfoundation/algorand-typescript/arc4' -import { getContractMethodAbiMetadata } from '../abi-metadata' +import { getContractByName, getContractMethodAbiMetadata } from '../abi-metadata' import { lazyContext } from '../context-helpers/internal-context' +import { InternalError } from '../errors' import type { ConstructorFor, DeliberateAny, InstanceMethod } from '../typescript-helpers' import type { ApplicationCallInnerTxn } from './inner-transactions' import { ApplicationCallInnerTxnContext } from './inner-transactions' @@ -106,11 +107,15 @@ export function getApplicationCallInnerTxnContext( - method: InstanceMethod, + contractFullName: string, + method: string, methodArgs: TypedApplicationCallFields, - contract?: Contract | { new (): Contract }, ): { itxn: ApplicationCallInnerTxn; returnValue: TReturn | undefined } { - const itxnContext = getApplicationCallInnerTxnContext(method, methodArgs, contract) + const contract = getContractByName(contractFullName) + if (contract === undefined || typeof contract !== 'function') throw new InternalError(`Unknown contract: ${contractFullName}`) + if (!Object.hasOwn(contract.prototype, method)) throw new InternalError(`Unknown method: ${method} in contract: ${contractFullName}`) + + const itxnContext = getApplicationCallInnerTxnContext(contract.prototype[method], methodArgs, contract) invokeAbiCall(itxnContext) diff --git a/src/impl/contract.ts b/src/impl/contract.ts index 9f317838..ceb69811 100644 --- a/src/impl/contract.ts +++ b/src/impl/contract.ts @@ -27,6 +27,17 @@ export function abimethod(config?: arc4.AbiMethodCon } } +/** @internal */ +export function readonly( + target: { [Arc4MethodConfigSymbol]: arc4.AbiMethodConfig } & ((this: TContract, ...args: TArgs) => TReturn), +): (this: TContract, ...args: TArgs) => TReturn { + target[Arc4MethodConfigSymbol] = { + ...target[Arc4MethodConfigSymbol], + readonly: true, + } + return target +} + /** @internal */ export function baremethod(config?: arc4.BareMethodConfig) { return function ( diff --git a/src/impl/encoded-types/index.ts b/src/impl/encoded-types/index.ts index f25a93da..29097fcb 100644 --- a/src/impl/encoded-types/index.ts +++ b/src/impl/encoded-types/index.ts @@ -24,4 +24,4 @@ export { /** @internal */ export { TypeInfo } from './types' /** @internal */ -export { arc4EncodedLength, getArc4TypeName, getMaxLengthOfStaticContentType, minLengthForType } from './utils' +export { getArc4TypeName, getMaxLengthOfStaticContentType, minLengthForType, sizeOf } from './utils' diff --git a/src/impl/encoded-types/utils.ts b/src/impl/encoded-types/utils.ts index 2bc79fc5..0aa4c541 100644 --- a/src/impl/encoded-types/utils.ts +++ b/src/impl/encoded-types/utils.ts @@ -147,7 +147,7 @@ export const getArc4TypeName = ( } /** @internal */ -export const arc4EncodedLength = (typeInfoString: string): uint64 => { +export const sizeOf = (typeInfoString: string): uint64 => { const typeInfo = JSON.parse(typeInfoString) return getMaxLengthOfStaticContentType(typeInfo, true) } diff --git a/src/impl/primitives.ts b/src/impl/primitives.ts index 1a751f50..86a2ed5f 100644 --- a/src/impl/primitives.ts +++ b/src/impl/primitives.ts @@ -539,8 +539,8 @@ export class BytesCls extends AlgoTsPrimitiveCls { return encodingUtil.uint8ArrayToHex(this.#v) } - toFixed(options: { length: TNewLength; checked?: boolean }): bytes { - if (options.checked !== false) { + toFixed(options: { length: TNewLength; strategy?: 'assert-length' | 'unsafe-cast' }): bytes { + if (options.strategy === undefined || options.strategy === 'assert-length') { if (this.#v.length !== options.length) { throw new CodeError(`Invalid bytes constant length of ${this.#v.length}, expected ${options.length}`) } diff --git a/src/impl/pure.ts b/src/impl/pure.ts index 0089a640..9bbbb6f9 100644 --- a/src/impl/pure.ts +++ b/src/impl/pure.ts @@ -156,14 +156,14 @@ export const extractUint64 = (a: StubBytesCompat, b: StubUint64Compat): uint64 = } /** @internal */ -export const getBit = (a: StubUint64Compat | StubBytesCompat, b: StubUint64Compat): uint64 => { +export const getBit = (a: StubUint64Compat | StubBytesCompat, b: StubUint64Compat): boolean => { const binaryString = toBinaryString(isUint64(a) ? asUint64Cls(a).toBytes().asAlgoTs() : asBytes(a)) const index = Uint64Cls.fromCompat(b).asNumber() const adjustedIndex = asMaybeUint64Cls(a) ? binaryString.length - index - 1 : index if (adjustedIndex < 0 || adjustedIndex >= binaryString.length) { throw new CodeError(`getBit index ${index} is beyond length`) } - return binaryString[adjustedIndex] === '1' ? 1 : 0 + return binaryString[adjustedIndex] === '1' } /** @internal */ diff --git a/src/internal/arc4.ts b/src/internal/arc4.ts index 6336db70..f1a3c9df 100644 --- a/src/internal/arc4.ts +++ b/src/internal/arc4.ts @@ -3,11 +3,10 @@ export * from '@algorandfoundation/algorand-typescript/arc4' /** @internal */ export { abiCall, compileArc4 } from '../impl/c2c' /** @internal */ -export { abimethod, baremethod, Contract } from '../impl/contract' +export { abimethod, baremethod, Contract, readonly } from '../impl/contract' /** @internal */ export { Address, - arc4EncodedLength, Bool, Byte, decodeArc4, @@ -17,6 +16,7 @@ export { FixedArray, interpretAsArc4, ReferenceArray, + sizeOf, StaticArray, StaticBytes, Str, diff --git a/src/internal/index.ts b/src/internal/index.ts index 3ea1c662..f0ca2d97 100644 --- a/src/internal/index.ts +++ b/src/internal/index.ts @@ -7,7 +7,7 @@ export { clone } from '../impl/clone' /** @internal */ export { compile } from '../impl/compiled' /** @internal */ -export { abimethod, baremethod, Contract } from '../impl/contract' +export { abimethod, baremethod, Contract, readonly } from '../impl/contract' /** @internal */ export { emit } from '../impl/emit' /** @internal */ diff --git a/src/test-transformer/helpers.ts b/src/test-transformer/helpers.ts index ef70c03c..9873a288 100644 --- a/src/test-transformer/helpers.ts +++ b/src/test-transformer/helpers.ts @@ -1,4 +1,5 @@ import ts from 'typescript' +import upath from 'upath' import { TransformerError } from './errors' /** @internal */ @@ -14,3 +15,28 @@ export const getPropertyNameAsString = (name: ts.PropertyName): ts.Identifier | /** @internal */ export const trimGenericTypeName = (typeName: string) => typeName.replace(/<.*>/, '') + +/** + * @internal + * Normalise a file path to only include relevant segments. + * + * - Anything in /node_modules/ is truncated to /path.ext + * - Anything in workingDirectory is truncated relative to the workingDirectory + * - Forward slashes are used to segment paths + * @param filePath + * @param workingDirectory + */ +export function normalisePath(filePath: string, workingDirectory: string): string { + const localPackageName = /packages\/algo-ts\/dist\/(.*)$/.exec(filePath) + if (localPackageName) { + return `@algorandfoundation/algorand-typescript/${localPackageName[1]}` + } + const nodeModuleName = /.*\/node_modules\/(.*)$/.exec(filePath) + if (nodeModuleName) { + return nodeModuleName[1] + } + const cwd = upath.normalize(`${workingDirectory}/`) + const normalizedPath = upath.normalize(filePath) + const moduleName = normalizedPath.startsWith(cwd) ? normalizedPath.slice(cwd.length) : normalizedPath + return moduleName.replaceAll('\\', '/') +} diff --git a/src/test-transformer/node-factory.ts b/src/test-transformer/node-factory.ts index 7a3b9986..712c182b 100644 --- a/src/test-transformer/node-factory.ts +++ b/src/test-transformer/node-factory.ts @@ -1,4 +1,4 @@ -import type { ptypes } from '@algorandfoundation/puya-ts' +import { ptypes } from '@algorandfoundation/puya-ts' import ts from 'typescript' import type { TypeInfo } from '../impl/encoded-types' import type { DeliberateAny } from '../typescript-helpers' @@ -62,9 +62,9 @@ export const nodeFactory = { }, attachMetaData( + sourceFileName: string, classIdentifier: ts.Identifier, method: ts.MethodDeclaration, - functionType: ptypes.FunctionPType, argTypes: string[], returnType: string, ) { @@ -81,7 +81,7 @@ export const nodeFactory = { factory.createCallExpression( factory.createPropertyAccessExpression(factory.createIdentifier('runtimeHelpers'), factory.createIdentifier('attachAbiMetadata')), undefined, - [classIdentifier, methodName, metadata], + [factory.createStringLiteral(sourceFileName), classIdentifier, methodName, metadata], ), ) }, @@ -127,7 +127,18 @@ export const nodeFactory = { return node }, - callAbiCallFunction(node: ts.CallExpression) { + callAbiCallFunction(node: ts.CallExpression, typeParams: ptypes.PType[]) { + if (typeParams.length === 1 && typeParams[0] instanceof ptypes.FunctionPType && typeParams[0].declaredIn) { + return factory.updateCallExpression(node, node.expression, node.typeArguments, [ + factory.createStringLiteral(typeParams[0].declaredIn.fullName), + factory.createStringLiteral(typeParams[0].name), + ...node.arguments, + ]) + } + return node + }, + + callItxnComposeFunction(node: ts.CallExpression) { if ( node.arguments.length === 2 && ts.isPropertyAccessExpression(node.arguments[0]) && diff --git a/src/test-transformer/visitors.ts b/src/test-transformer/visitors.ts index 02aca3d9..236f6a99 100644 --- a/src/test-transformer/visitors.ts +++ b/src/test-transformer/visitors.ts @@ -4,6 +4,7 @@ import ts from 'typescript' import { CodeError } from '../errors' import type { TypeInfo } from '../impl/encoded-types' import { instanceOfAny } from '../typescript-helpers' +import { normalisePath } from './helpers' import { nodeFactory } from './node-factory' import type { TransformerConfig } from './program-factory' import { @@ -39,16 +40,20 @@ type VisitorHelper = { getConfig(): TransformerConfig } +type Context = ts.TransformationContext & { currentDirectory: string } + /** @internal */ export class SourceFileVisitor { private helper: VisitorHelper + private context: Context constructor( - private context: ts.TransformationContext, + context: ts.TransformationContext, private sourceFile: ts.SourceFile, program: ts.Program, config: TransformerConfig, ) { + this.context = { ...context, currentDirectory: program.getCurrentDirectory() } const typeChecker = program.getTypeChecker() const loggingContext = LoggingContext.create() const typeResolver = new TypeResolver(typeChecker, program.getCurrentDirectory()) @@ -153,7 +158,7 @@ class ImportDeclarationVisitor { class ExpressionVisitor { constructor( - private context: ts.TransformationContext, + private context: Context, private helper: VisitorHelper, private expressionNode: ts.Expression, private stubbedFunctionName?: string, @@ -167,10 +172,7 @@ class ExpressionVisitor { handleTypeInfoCapture: if (ts.isCallExpression(node) || ts.isNewExpression(node)) { if (!tryGetAlgoTsSymbolName(node.expression, this.helper)) break handleTypeInfoCapture - let type = this.helper.resolveType(node) - - // `voted = LocalState()` is resolved to FunctionPType with returnType LocalState - if (type instanceof ptypes.FunctionPType) type = type.returnType + const type = this.helper.resolveType(node) const isGeneric = isGenericType(type) const needsToCaptureTypeInfo = isGeneric && isStateOrBoxType(type) @@ -197,7 +199,7 @@ class ExpressionVisitor { if ( isCallingEmit(stubbedFunctionName) || isCallingEncodeArc4(stubbedFunctionName) || - isCallingArc4EncodedLength(stubbedFunctionName) || + isCallingSizeOf(stubbedFunctionName) || isCallingClone(stubbedFunctionName) ) { infoArg = this.helper.resolveTypeParameters(updatedNode).map((t) => getGenericTypeInfo(t, sourceLocation))[0] @@ -212,7 +214,10 @@ class ExpressionVisitor { if (isCallingMethodSelector(stubbedFunctionName)) { updatedNode = nodeFactory.callMethodSelectorFunction(updatedNode) } else if (isCallingAbiCall(stubbedFunctionName)) { - updatedNode = nodeFactory.callAbiCallFunction(updatedNode) + const typeParams = this.helper.resolveTypeParameters(updatedNode) + updatedNode = nodeFactory.callAbiCallFunction(updatedNode, typeParams) + } else if (isCallingItxnCompose(stubbedFunctionName)) { + updatedNode = nodeFactory.callItxnComposeFunction(updatedNode) } else if (isCallingBytes(stubbedFunctionName)) { if (type instanceof ptypes.BytesPType && type.length) updatedNode = nodeFactory.callFixedBytesFunction(stubbedFunctionName, updatedNode, Number(type.length)) @@ -230,7 +235,7 @@ class ExpressionVisitor { } class VariableInitializerVisitor { constructor( - private context: ts.TransformationContext, + private context: Context, private helper: VisitorHelper, private declarationNode: ts.VariableDeclaration, ) {} @@ -253,7 +258,7 @@ class VariableInitializerVisitor { class FunctionOrMethodVisitor { constructor( - protected context: ts.TransformationContext, + protected context: Context, protected helper: VisitorHelper, ) {} protected visit = (node: ts.Node): ts.Node => { @@ -325,7 +330,7 @@ class FunctionOrMethodVisitor { class FunctionLikeDecVisitor extends FunctionOrMethodVisitor { constructor( - context: ts.TransformationContext, + context: Context, helper: VisitorHelper, private funcNode: ts.SignatureDeclaration, ) { @@ -338,7 +343,7 @@ class FunctionLikeDecVisitor extends FunctionOrMethodVisitor { } class MethodDecVisitor extends FunctionOrMethodVisitor { constructor( - context: ts.TransformationContext, + context: Context, helper: VisitorHelper, private methodNode: ts.MethodDeclaration, ) { @@ -353,7 +358,7 @@ class MethodDecVisitor extends FunctionOrMethodVisitor { class ClassVisitor { private isArc4: boolean constructor( - private context: ts.TransformationContext, + private context: Context, private helper: VisitorHelper, private classDec: ts.ClassDeclaration, ) { @@ -372,7 +377,8 @@ class ClassVisitor { if (methodType instanceof ptypes.FunctionPType) { const argTypes = methodType.parameters.map((p) => JSON.stringify(getGenericTypeInfo(p[1]))) const returnType = JSON.stringify(getGenericTypeInfo(methodType.returnType)) - this.helper.additionalStatements.push(nodeFactory.attachMetaData(this.classDec.name, node, methodType, argTypes, returnType)) + const sourceFileName = normalisePath(this.classDec.parent.getSourceFile().fileName, this.context.currentDirectory) + this.helper.additionalStatements.push(nodeFactory.attachMetaData(sourceFileName, this.classDec.name, node, argTypes, returnType)) } } @@ -506,7 +512,7 @@ const tryGetStubbedFunctionName = (node: ts.CallExpression, helper: VisitorHelpe 'encodeArc4', 'emit', 'methodSelector', - 'arc4EncodedLength', + 'sizeOf', 'abiCall', 'clone', 'Bytes', @@ -548,10 +554,11 @@ const tryGetAlgoTsSymbolName = (node: ts.Node, helper: VisitorHelper): string | const isCallingDecodeArc4 = (functionName: string | undefined): boolean => 'decodeArc4' === (functionName ?? '') const isCallingEncodeArc4 = (functionName: string | undefined): boolean => 'encodeArc4' === (functionName ?? '') -const isCallingArc4EncodedLength = (functionName: string | undefined): boolean => 'arc4EncodedLength' === (functionName ?? '') +const isCallingSizeOf = (functionName: string | undefined): boolean => 'sizeOf' === (functionName ?? '') const isCallingEmit = (functionName: string | undefined): boolean => 'emit' === (functionName ?? '') const isCallingMethodSelector = (functionName: string | undefined): boolean => 'methodSelector' === (functionName ?? '') -const isCallingAbiCall = (functionName: string | undefined): boolean => ['abiCall', 'begin', 'next'].includes(functionName ?? '') +const isCallingAbiCall = (functionName: string | undefined): boolean => ['abiCall'].includes(functionName ?? '') +const isCallingItxnCompose = (functionName: string | undefined): boolean => ['begin', 'next'].includes(functionName ?? '') const isCallingClone = (functionName: string | undefined): boolean => 'clone' === (functionName ?? '') const isCallingBytes = (functionName: string | undefined): boolean => ['Bytes', 'fromHex', 'fromBase64', 'fromBase32'].includes(functionName ?? '') diff --git a/tests/arc4/encode-decode-arc4.algo.spec.ts b/tests/arc4/encode-decode-arc4.algo.spec.ts index c30d361d..c4971f97 100644 --- a/tests/arc4/encode-decode-arc4.algo.spec.ts +++ b/tests/arc4/encode-decode-arc4.algo.spec.ts @@ -3,12 +3,12 @@ import { arc4, assertMatch, Bytes } from '@algorandfoundation/algorand-typescrip import type { StaticBytes, UFixed, Uint64 } from '@algorandfoundation/algorand-typescript/arc4' import { Address, - arc4EncodedLength, Bool, decodeArc4, DynamicArray, DynamicBytes, encodeArc4, + sizeOf, StaticArray, Str, Struct, @@ -224,20 +224,20 @@ class StaticStruct extends Struct<{ e: Address f: StaticArray, 10> }> {} -describe('arc4EncodedLength', () => { +describe('sizeOf', () => { test('should return the correct length', () => { - expect(arc4EncodedLength()).toEqual(8) - expect(arc4EncodedLength()).toEqual(64) - expect(arc4EncodedLength()).toEqual(1) - expect(arc4EncodedLength()).toEqual(1) - expect(arc4EncodedLength>()).toEqual(64) - expect(arc4EncodedLength<[uint64, uint64, boolean]>()).toEqual(17) - expect(arc4EncodedLength<[uint64, uint64, boolean, boolean]>()).toEqual(17) - expect(arc4EncodedLength, Bool]>>()).toEqual(3) - expect(arc4EncodedLength()).toEqual(395) - expect(arc4EncodedLength<[StaticArray, boolean, boolean]>()).toEqual(3) + expect(sizeOf()).toEqual(8) + expect(sizeOf()).toEqual(64) + expect(sizeOf()).toEqual(1) + expect(sizeOf()).toEqual(1) + expect(sizeOf>()).toEqual(64) + expect(sizeOf<[uint64, uint64, boolean]>()).toEqual(17) + expect(sizeOf<[uint64, uint64, boolean, boolean]>()).toEqual(17) + expect(sizeOf, Bool]>>()).toEqual(3) + expect(sizeOf()).toEqual(395) + expect(sizeOf<[StaticArray, boolean, boolean]>()).toEqual(3) expect( - arc4EncodedLength<[[boolean, boolean, boolean, boolean, boolean, boolean, boolean, boolean, boolean, boolean], boolean, boolean]>(), + sizeOf<[[boolean, boolean, boolean, boolean, boolean, boolean, boolean, boolean, boolean, boolean], boolean, boolean]>(), ).toEqual(3) }) }) diff --git a/tests/artifacts/miscellaneous-ops/contract.algo.ts b/tests/artifacts/miscellaneous-ops/contract.algo.ts index 75c992e0..e20c2d55 100644 --- a/tests/artifacts/miscellaneous-ops/contract.algo.ts +++ b/tests/artifacts/miscellaneous-ops/contract.algo.ts @@ -121,13 +121,13 @@ export class MiscellaneousOpsContract extends arc4.Contract { } @arc4.abimethod() - public verify_getbit_bytes(a: bytes, b: uint64): uint64 { + public verify_getbit_bytes(a: bytes, b: uint64): boolean { const result = op.getBit(a, b) return result } @arc4.abimethod() - public verify_getbit_uint64(a: uint64, b: uint64): uint64 { + public verify_getbit_uint64(a: uint64, b: uint64): boolean { const result = op.getBit(a, b) return result } diff --git a/tests/artifacts/resource-encoding/contract.algo.ts b/tests/artifacts/resource-encoding/contract.algo.ts index 11613420..56c4cb51 100644 --- a/tests/artifacts/resource-encoding/contract.algo.ts +++ b/tests/artifacts/resource-encoding/contract.algo.ts @@ -13,7 +13,7 @@ import { } from '@algorandfoundation/algorand-typescript' import { abiCall, compileArc4 } from '@algorandfoundation/algorand-typescript/arc4' -class ByIndex extends Contract { +export class ByIndex extends Contract { @abimethod({ resourceEncoding: 'index' }) testExplicitIndex(account: Account) { return account.balance @@ -61,7 +61,7 @@ class EchoResource extends Contract { export class C2C extends Contract { testCallToIndex(account: Account, appId: Application) { - const { returnValue: res1 } = abiCall(ByIndex.prototype.testExplicitIndex, { + const { returnValue: res1 } = abiCall({ appId, args: [account], }) @@ -69,7 +69,8 @@ export class C2C extends Contract { assert(res1 === account.balance) } testCallToValue(account: Account, appId: Application) { - const { returnValue: res1 } = abiCall(ByValue.prototype.testExplicitValue, { + const { returnValue: res1 } = abiCall({ + method: ByValue.prototype.testExplicitValue, appId, args: [account], }) diff --git a/tests/primitives/bytes.algo.spec.ts b/tests/primitives/bytes.algo.spec.ts index 01aa6c3e..f6ed134b 100644 --- a/tests/primitives/bytes.algo.spec.ts +++ b/tests/primitives/bytes.algo.spec.ts @@ -5,7 +5,7 @@ import { beforeAll, describe, expect, it } from 'vitest' import { MAX_BYTES_SIZE } from '../../src/constants' import type { Byte, StaticArray } from '@algorandfoundation/algorand-typescript/arc4' -import { arc4EncodedLength, decodeArc4, interpretAsArc4 } from '@algorandfoundation/algorand-typescript/arc4' +import { decodeArc4, interpretAsArc4, sizeOf } from '@algorandfoundation/algorand-typescript/arc4' import { sha256 } from '../../src/impl' import { BytesCls } from '../../src/impl/primitives' import { asUint8Array } from '../../src/util' @@ -221,8 +221,8 @@ describe('Bytes', async () => { }) it('should be treated as statically sized', () => { - expect(arc4EncodedLength>()).toEqual(32) - expect(arc4EncodedLength, 2>>()).toEqual(64) + expect(sizeOf>()).toEqual(32) + expect(sizeOf, 2>>()).toEqual(64) const x1 = new FixedArray, 2>() expect(x1.length).toEqual(2) diff --git a/tests/state-op-codes.algo.spec.ts b/tests/state-op-codes.algo.spec.ts index dd6681b1..c83055f9 100644 --- a/tests/state-op-codes.algo.spec.ts +++ b/tests/state-op-codes.algo.spec.ts @@ -269,7 +269,7 @@ describe('State op codes', async () => { 'should return the correct field value of the asset', async ([methodName, expectedValue], { appClientStateAssetParamsContract: appClient, testAccount, assetFactory }) => { const creator = Account(Bytes.fromBase32(testAccount.addr.toString())) - const metadataHash = Bytes(`test${' '.repeat(28)}`).toFixed({ length: 32 }) + const metadataHash = Bytes(`test${' '.repeat(28)}`).toFixed({ length: 32, strategy: 'assert-length' }) const mockAsset = ctx.any.asset({ total: 100, decimals: 0, @@ -529,12 +529,12 @@ describe('State op codes', async () => { describe('Block', async () => { test('should return the correct field value of the block', async () => { const index = 42 - const seed = getRandomBytes(32).asAlgoTs().toFixed({ length: 32 }) + const seed = getRandomBytes(32).asAlgoTs().toFixed({ length: 32, strategy: 'unsafe-cast' }) const timestamp = 1234567890 const proposer = ctx.any.account() const feesCollected = 1000 const bonus = 12 - const branch = getRandomBytes(32).asAlgoTs().toFixed({ length: 32 }) + const branch = getRandomBytes(32).asAlgoTs().toFixed({ length: 32, strategy: 'unsafe-cast' }) const feeSink = ctx.any.account() const protocol = getRandomBytes(32).asAlgoTs() const txnCounter = 32 From cc8ded2fd11a011779ac9ca05d960f45fa189a46 Mon Sep 17 00:00:00 2001 From: Bobby Lat Date: Wed, 3 Sep 2025 11:18:32 +0700 Subject: [PATCH 42/68] refactor: use WeakMap to store contract name to class map --- src/abi-metadata.ts | 19 ++++++++++++++----- src/test-transformer/node-factory.ts | 2 +- 2 files changed, 15 insertions(+), 6 deletions(-) diff --git a/src/abi-metadata.ts b/src/abi-metadata.ts index 7ffbdca8..52826f89 100644 --- a/src/abi-metadata.ts +++ b/src/abi-metadata.ts @@ -19,10 +19,18 @@ export interface AbiMetadata { } const metadataStore: WeakMap<{ new (): Contract }, Record> = new WeakMap() -const contractMap: Map = new Map() +const contractSymbolMap: Map = new Map() +const contractMap: WeakMap = new WeakMap() /** @internal */ -export const attachAbiMetadata = (fileName: string, contract: { new (): Contract }, methodName: string, metadata: AbiMetadata): void => { - contractMap.set(`${fileName}::${contract.prototype.constructor.name}`, contract) +export const attachAbiMetadata = (contract: { new (): Contract }, methodName: string, metadata: AbiMetadata, fileName: string): void => { + const contractFullName = `${fileName}::${contract.prototype.constructor.name}` + if (!contractSymbolMap.has(contractFullName)) { + contractSymbolMap.set(contractFullName, Symbol(contractFullName)) + } + const contractSymbol = contractSymbolMap.get(contractFullName)! + if (!contractMap.has(contractSymbol)) { + contractMap.set(contractSymbol, contract) + } if (!metadataStore.has(contract)) { metadataStore.set(contract, {}) } @@ -35,8 +43,9 @@ export const attachAbiMetadata = (fileName: string, contract: { new (): Contract } } -export const getContractByName = (contractFullame: string): { new (): Contract } | undefined => { - return contractMap.get(contractFullame) +export const getContractByName = (contractFullname: string): { new (): Contract } | undefined => { + const contractSymbol = contractSymbolMap.get(contractFullname) + return !contractSymbol ? undefined : contractMap.get(contractSymbol) } /** @internal */ diff --git a/src/test-transformer/node-factory.ts b/src/test-transformer/node-factory.ts index 712c182b..8688dc0d 100644 --- a/src/test-transformer/node-factory.ts +++ b/src/test-transformer/node-factory.ts @@ -81,7 +81,7 @@ export const nodeFactory = { factory.createCallExpression( factory.createPropertyAccessExpression(factory.createIdentifier('runtimeHelpers'), factory.createIdentifier('attachAbiMetadata')), undefined, - [factory.createStringLiteral(sourceFileName), classIdentifier, methodName, metadata], + [classIdentifier, methodName, metadata, factory.createStringLiteral(sourceFileName)], ), ) }, From 387f0057c9ca717b6d57eef05473b941dbaaa212 Mon Sep 17 00:00:00 2001 From: Bobby Lat Date: Thu, 4 Sep 2025 12:45:39 +0700 Subject: [PATCH 43/68] chore: update puya-ts dependencies --- package-lock.json | 19 +++++++++---------- package.json | 4 ++-- 2 files changed, 11 insertions(+), 12 deletions(-) diff --git a/package-lock.json b/package-lock.json index c9e8a80b..832baa96 100644 --- a/package-lock.json +++ b/package-lock.json @@ -8,8 +8,8 @@ "name": "@algorandfoundation/algorand-typescript-testing", "version": "1.0.0", "dependencies": { - "@algorandfoundation/algorand-typescript": "1.0.0-alpha.79", - "@algorandfoundation/puya-ts": "1.0.0-alpha.79", + "@algorandfoundation/algorand-typescript": "1.0.0-alpha.80", + "@algorandfoundation/puya-ts": "1.0.0-alpha.80", "elliptic": "^6.6.1", "js-sha256": "^0.11.0", "js-sha3": "^0.9.3", @@ -76,21 +76,20 @@ } }, "node_modules/@algorandfoundation/algorand-typescript": { - "version": "1.0.0-alpha.79", - "resolved": "https://registry.npmjs.org/@algorandfoundation/algorand-typescript/-/algorand-typescript-1.0.0-alpha.79.tgz", - "integrity": "sha512-pR5XxWvWMC76uSHsEcojnnWlIelIFYE2Z7+l+F2WAkQsXOMkcSNW4R7vjKv+BjtTfUHVaksbRXKjn2f5/nWQ1w==", + "version": "1.0.0-alpha.80", + "resolved": "https://registry.npmjs.org/@algorandfoundation/algorand-typescript/-/algorand-typescript-1.0.0-alpha.80.tgz", + "integrity": "sha512-8w66Oam/eqPNatY2GN1LzCuuwXjHu1Tj2sAuC1/0p3tdtIB5VIueFHSTy6zXUNl2wSsQQGcBc1ukHWNig/3Sew==", "peerDependencies": { "tslib": "^2.6.2" } }, "node_modules/@algorandfoundation/puya-ts": { - "version": "1.0.0-alpha.79", - "resolved": "https://registry.npmjs.org/@algorandfoundation/puya-ts/-/puya-ts-1.0.0-alpha.79.tgz", - "integrity": "sha512-PGNPoutPGVIVTohBTznRfiX8VPO2AaRMdaAE/K3xUXKwFai8li38WQ2eplxof3cAHy96X2GeiCDze4XK6mcNyg==", + "version": "1.0.0-alpha.80", + "resolved": "https://registry.npmjs.org/@algorandfoundation/puya-ts/-/puya-ts-1.0.0-alpha.80.tgz", + "integrity": "sha512-jBfYGnQrsoMq87KB2JBtrbbPbYFCE9ApG41w3JCH0ppcoyPOgIKzkoESAA2lIOKQ+wKasnnW6qn1HXKennLIyA==", "hasInstallScript": true, - "license": "MIT", "dependencies": { - "@algorandfoundation/algorand-typescript": "1.0.0-alpha.79", + "@algorandfoundation/algorand-typescript": "1.0.0-alpha.80", "arcsecond": "^5.0.0", "argparse": "^2.0.1", "chalk": "^5.4.1", diff --git a/package.json b/package.json index d26e5ace..f14dea0c 100644 --- a/package.json +++ b/package.json @@ -68,8 +68,8 @@ "vitest": "3.2.4" }, "dependencies": { - "@algorandfoundation/algorand-typescript": "1.0.0-alpha.79", - "@algorandfoundation/puya-ts": "1.0.0-alpha.79", + "@algorandfoundation/algorand-typescript": "1.0.0-alpha.80", + "@algorandfoundation/puya-ts": "1.0.0-alpha.80", "elliptic": "^6.6.1", "js-sha256": "^0.11.0", "js-sha3": "^0.9.3", From 782af0edfd83b76cccb075602da63c8cd655920e Mon Sep 17 00:00:00 2001 From: Bobby Lat Date: Thu, 4 Sep 2025 12:55:21 +0700 Subject: [PATCH 44/68] docs: update docs for abiCall signature change --- docs/coverage.md | 1 + docs/tg-application-spy.md | 2 +- docs/tg-transactions.md | 2 +- 3 files changed, 3 insertions(+), 2 deletions(-) diff --git a/docs/coverage.md b/docs/coverage.md index 5a15567b..0ebdda04 100644 --- a/docs/coverage.md +++ b/docs/coverage.md @@ -72,6 +72,7 @@ See which `algorand-typescript` stubs are implemented by the `algorand-typescrip | arc4.abimethod | Emulated | | arc4.methodSelector | Native | | arc4.baremethod | Emulated | +| arc4.readonly | Emulated | | gtxn.ApplicationCallTxn | Emulated | | gtxn.AssetConfigTxn | Emulated | | gtxn.AssetFreezeTxn | Emulated | diff --git a/docs/tg-application-spy.md b/docs/tg-application-spy.md index 462ba221..a1b262df 100644 --- a/docs/tg-application-spy.md +++ b/docs/tg-application-spy.md @@ -170,7 +170,7 @@ spy.onAbiCall(methodSelector('greet(string)string'), (itxnContext) => { **3. Strongly typed ABI calls** ```ts -const result = abiCall(Hello.prototype.greet, { +const result = abiCall({ appId: app, args: ['abi'], }).returnValue diff --git a/docs/tg-transactions.md b/docs/tg-transactions.md index ce1a2720..38070e56 100644 --- a/docs/tg-transactions.md +++ b/docs/tg-transactions.md @@ -376,7 +376,7 @@ export class Hello extends Contract { export class HelloFactoryTyped extends Contract { test_compile_contract(app: Application) { - const result2 = abiCall(Hello.prototype.greet, { + const result2 = abiCall({ appId: app, args: ['abi'], }).returnValue From 4edce80c272e5b9de47a0f9cd9d3e6a09a0a26a2 Mon Sep 17 00:00:00 2001 From: Bobby Lat Date: Thu, 4 Sep 2025 13:51:12 +0700 Subject: [PATCH 45/68] add tests for circular reference --- src/subcontexts/contract-context.ts | 17 ++--- tests/arc4/circular-reference.algo.spec.ts | 68 +++++++++++++++++++ .../circular-reference-2.algo.ts | 16 +++++ .../circular-reference.algo.ts | 16 +++++ 4 files changed, 104 insertions(+), 13 deletions(-) create mode 100644 tests/arc4/circular-reference.algo.spec.ts create mode 100644 tests/artifacts/circurlar-reference/circular-reference-2.algo.ts create mode 100644 tests/artifacts/circurlar-reference/circular-reference.algo.ts diff --git a/src/subcontexts/contract-context.ts b/src/subcontexts/contract-context.ts index fb7783f4..97310215 100644 --- a/src/subcontexts/contract-context.ts +++ b/src/subcontexts/contract-context.ts @@ -14,8 +14,9 @@ import { BytesMap } from '../collections/custom-key-map' import { checkRoutingConditions } from '../context-helpers/context-util' import { lazyContext } from '../context-helpers/internal-context' import { CodeError } from '../errors' -import { BaseContract, ContractOptionsSymbol } from '../impl/base-contract' -import { Contract } from '../impl/contract' +import type { BaseContract } from '../impl/base-contract' +import { ContractOptionsSymbol } from '../impl/base-contract' +import type { Contract } from '../impl/contract' import { getArc4Encoded, Uint, type TypeInfo } from '../impl/encoded-types' import { Bytes } from '../impl/primitives' import { AccountCls, ApplicationCls, AssetCls } from '../impl/reference' @@ -222,18 +223,8 @@ export class ContractContext { if (result !== undefined && result !== null) { return result } - // TODO: uncomment the following line once version puya-ts 1.0.0 is released and delete the rest of the function - // throw new internal.errors.CodeError('Cannot create a contract for class as it does not extend Contract or BaseContract') - const proto = Object.getPrototypeOf(type) - if (proto === BaseContract) { - return false - } else if (proto === Contract) { - return true - } else if (proto === Object || proto === null) { - throw new CodeError('Cannot create a contract for class as it does not extend Contract or BaseContract') - } - return this.isArc4(proto) + throw new CodeError('Cannot create a contract for class as it does not extend Contract or BaseContract') } /** diff --git a/tests/arc4/circular-reference.algo.spec.ts b/tests/arc4/circular-reference.algo.spec.ts new file mode 100644 index 00000000..fa4b8474 --- /dev/null +++ b/tests/arc4/circular-reference.algo.spec.ts @@ -0,0 +1,68 @@ +import { algo } from '@algorandfoundation/algokit-utils' +import { methodSelector } from '@algorandfoundation/algorand-typescript/arc4' +import { afterEach, beforeAll, describe, expect } from 'vitest' +import { ApplicationSpy } from '../../src/application-spy' +import { TestExecutionContext } from '../../src/test-execution-context' +import { ContractTwo } from '../artifacts/circurlar-reference/circular-reference-2.algo' +import { ContractOne } from '../artifacts/circurlar-reference/circular-reference.algo' +import { createArc4TestFixture } from '../test-fixture' + +describe('circular reference', () => { + const ctx = new TestExecutionContext() + const [test, localnetFixture] = createArc4TestFixture('tests/artifacts/circurlar-reference', { + ContractOne: { funding: algo(1) }, + ContractTwo: { funding: algo(1) }, + }) + + beforeAll(async () => { + await localnetFixture.newScope() + }) + + afterEach(() => { + ctx.reset() + }) + + test('test call contract one', async ({ appClientContractOne, appClientContractTwo }) => { + const result = await appClientContractOne.send.call({ + method: 'test', + args: [appClientContractTwo.appId], + extraFee: algo(1), + }) + expect(result.return).toEqual(appClientContractTwo.appId) + + const contractOne = ctx.contract.create(ContractOne) + const contractTwo = ctx.contract.create(ContractTwo) + const contractTwoApp = ctx.ledger.getApplicationForContract(contractTwo) + + const spy = new ApplicationSpy() + spy.onAbiCall(methodSelector(ContractTwo.prototype.receiver), (itxnContext) => { + itxnContext.setReturnValue(contractTwoApp.id) + }) + ctx.addApplicationSpy(spy) + + const output = contractOne.test(contractTwoApp) + expect(output).toEqual(contractTwoApp.id) + }) + + test('test call contract two', async ({ appClientContractOne, appClientContractTwo }) => { + const result = await appClientContractTwo.send.call({ + method: 'test', + args: [appClientContractOne.appId], + extraFee: algo(1), + }) + expect(result.return).toEqual(appClientContractOne.appId) + + const contractOne = ctx.contract.create(ContractOne) + const contractTwo = ctx.contract.create(ContractTwo) + const contractOneApp = ctx.ledger.getApplicationForContract(contractOne) + + const spy = new ApplicationSpy() + spy.onAbiCall(methodSelector(ContractOne.prototype.receiver), (itxnContext) => { + itxnContext.setReturnValue(contractOneApp.id) + }) + ctx.addApplicationSpy(spy) + + const output = contractTwo.test(contractOneApp) + expect(output).toEqual(contractOneApp.id) + }) +}) diff --git a/tests/artifacts/circurlar-reference/circular-reference-2.algo.ts b/tests/artifacts/circurlar-reference/circular-reference-2.algo.ts new file mode 100644 index 00000000..cebb403f --- /dev/null +++ b/tests/artifacts/circurlar-reference/circular-reference-2.algo.ts @@ -0,0 +1,16 @@ +import type { Application } from '@algorandfoundation/algorand-typescript' +import { Contract, log } from '@algorandfoundation/algorand-typescript' +import { abiCall } from '@algorandfoundation/algorand-typescript/arc4' +import type { ContractOne } from './circular-reference.algo' + +export class ContractTwo extends Contract { + test(appId: Application) { + const result = abiCall({ appId, args: [appId] }) + return result.returnValue + } + + receiver(appId: Application) { + log(appId.id) + return appId.id + } +} diff --git a/tests/artifacts/circurlar-reference/circular-reference.algo.ts b/tests/artifacts/circurlar-reference/circular-reference.algo.ts new file mode 100644 index 00000000..e6b111f5 --- /dev/null +++ b/tests/artifacts/circurlar-reference/circular-reference.algo.ts @@ -0,0 +1,16 @@ +import type { Application } from '@algorandfoundation/algorand-typescript' +import { Contract, log } from '@algorandfoundation/algorand-typescript' +import { abiCall } from '@algorandfoundation/algorand-typescript/arc4' +import type { ContractTwo } from './circular-reference-2.algo' + +export class ContractOne extends Contract { + test(appId: Application) { + const result = abiCall({ appId, args: [appId] }) + return result.returnValue + } + + receiver(appId: Application) { + log(appId.id) + return appId.id + } +} From fb5891d7c608b02a7ad126433c6813ef8a501b6e Mon Sep 17 00:00:00 2001 From: Bobby Lat Date: Fri, 5 Sep 2025 16:51:50 +0700 Subject: [PATCH 46/68] feat: replace .native property with .as_uint64() and .as_biguint() methods in arc4.Uint --- .../arc4-simple-voting/contract.algo.spec.ts | 2 +- examples/marketplace/contract.algo.spec.ts | 30 ++++---- examples/marketplace/contract.algo.ts | 24 +++--- examples/voting/contract.algo.ts | 10 +-- src/impl/encoded-types/encoded-types.ts | 33 +++++++-- src/set-up.ts | 9 +++ tests/arc4/address.algo.spec.ts | 6 +- tests/arc4/byte.algo.spec.ts | 18 ++--- tests/arc4/dynamic-array.algo.spec.ts | 4 +- tests/arc4/dynamic-bytes.algo.spec.ts | 4 +- tests/arc4/emit.algo.spec.ts | 4 +- tests/arc4/static-array.algo.spec.ts | 4 +- tests/arc4/static-bytes.algo.spec.ts | 4 +- tests/arc4/ufixednxm.algo.spec.ts | 12 +-- tests/arc4/uintn.algo.spec.ts | 74 +++++++++++++++---- .../arc4-abi-method/contract.algo.ts | 12 +-- .../arc4-primitive-ops/contract.algo.ts | 62 ++++++++++------ .../created-app-asset/contract.algo.ts | 2 +- tests/fixed-array.algo.spec.ts | 12 +-- tests/global-state-arc4-values.algo.spec.ts | 6 +- tests/local-state-arc4-values.algo.spec.ts | 6 +- tests/log.algo.spec.ts | 4 +- tests/native-mutable-array.algo.spec.ts | 14 ++-- tests/native-mutable-object.algo.spec.ts | 24 +++--- tests/native-readonly-array.algo.spec.ts | 12 +-- tests/native-readonly-object.algo.spec.ts | 24 +++--- tests/references/box-map.algo.spec.ts | 24 +++--- tests/references/box.algo.spec.ts | 16 ++-- 28 files changed, 277 insertions(+), 179 deletions(-) diff --git a/examples/arc4-simple-voting/contract.algo.spec.ts b/examples/arc4-simple-voting/contract.algo.spec.ts index a7381fe3..ac8a1da3 100644 --- a/examples/arc4-simple-voting/contract.algo.spec.ts +++ b/examples/arc4-simple-voting/contract.algo.spec.ts @@ -40,6 +40,6 @@ describe('Voting contract', () => { contract.votes.value = Uint64(5) const votes = contract.getVotes() - expect(votes.native).toEqual(5) + expect(votes.asUint64()).toEqual(5) }) }) diff --git a/examples/marketplace/contract.algo.spec.ts b/examples/marketplace/contract.algo.spec.ts index 3ca80e03..5baef712 100644 --- a/examples/marketplace/contract.algo.spec.ts +++ b/examples/marketplace/contract.algo.spec.ts @@ -40,7 +40,7 @@ describe('DigitalMarketplace', () => { nonce: testNonce, }) const listingValue = interpretAsArc4(Bytes(ctx.ledger.getBox(contract, Bytes('listings').concat(listingKey.bytes)))) - expect(listingValue.deposited.native).toEqual(10) + expect(listingValue.deposited.asUint64()).toEqual(10) }) test('deposit', () => { @@ -101,7 +101,7 @@ describe('DigitalMarketplace', () => { // Assert const updatedListing = interpretAsArc4(Bytes(ctx.ledger.getBox(contract, Bytes('listings').concat(listingKey.bytes)))) - expect(updatedListing.unitaryPrice.native).toEqual(testUnitaryPrice.native) + expect(updatedListing.unitaryPrice.asUint64()).toEqual(testUnitaryPrice.asUint64()) }) test('buy', () => { @@ -130,14 +130,14 @@ describe('DigitalMarketplace', () => { testNonce, ctx.any.txn.payment({ receiver: ctx.defaultSender, - amount: contract.quantityPrice(testBuyQuantity.native, testUnitaryPrice.native, testAsset.decimals), + amount: contract.quantityPrice(testBuyQuantity.asUint64(), testUnitaryPrice.asUint64(), testAsset.decimals), }), - testBuyQuantity.native, + testBuyQuantity.asUint64(), ) // Assert const updatedListing = interpretAsArc4(Bytes(ctx.ledger.getBox(contract, Bytes('listings').concat(listingKey.bytes)))) - expect(updatedListing.deposited.native).toEqual(initialDeposit.native - testBuyQuantity.native) + expect(updatedListing.deposited.asUint64()).toEqual(initialDeposit.asUint64() - testBuyQuantity.asUint64()) expect(ctx.txn.lastGroup.getItxnGroup(0).getAssetTransferInnerTxn(0).assetReceiver).toEqual(ctx.defaultSender) }) @@ -176,7 +176,7 @@ describe('DigitalMarketplace', () => { const assetTransferTxn = ctx.txn.lastGroup.getItxnGroup(1).getAssetTransferInnerTxn(0) expect(assetTransferTxn.xferAsset).toEqual(testAsset) expect(assetTransferTxn.assetReceiver).toEqual(testOwner.native) - expect(assetTransferTxn.assetAmount).toEqual(initialDeposit.native) + expect(assetTransferTxn.assetAmount).toEqual(initialDeposit.asUint64()) }) test('bid', () => { @@ -200,9 +200,9 @@ describe('DigitalMarketplace', () => { }) const bidder = ctx.any.account() - const bidQuantity = ctx.any.arc4.uint64(0, BigInt(initialDeposit.native)) - const bidPrice = ctx.any.arc4.uint64(initialPrice.native + 1, 10000000n) - const bidAmount = contract.quantityPrice(bidQuantity.native, bidPrice.native, testAsset.decimals) + const bidQuantity = ctx.any.arc4.uint64(0, BigInt(initialDeposit.asUint64())) + const bidPrice = ctx.any.arc4.uint64(initialPrice.asUint64() + 1, 10000000n) + const bidAmount = contract.quantityPrice(bidQuantity.asUint64(), bidPrice.asUint64(), testAsset.decimals) // Act ctx.txn.createScope([ctx.any.txn.applicationCall({ appId: app, sender: bidder })]).execute(() => { @@ -223,8 +223,8 @@ describe('DigitalMarketplace', () => { // Assert const updatedListing = contract.listings(listingKey).value expect(updatedListing.bidder.native).toEqual(bidder) - expect(updatedListing.bid.native).toEqual(bidQuantity.native) - expect(updatedListing.bidUnitaryPrice.native).toEqual(bidPrice.native) + expect(updatedListing.bid.asUint64()).toEqual(bidQuantity.asUint64()) + expect(updatedListing.bidUnitaryPrice.asUint64()).toEqual(bidPrice.asUint64()) }) test('acceptBid', () => { @@ -234,7 +234,7 @@ describe('DigitalMarketplace', () => { // Arrange const owner = ctx.defaultSender const initialDeposit = ctx.any.arc4.uint64(1, 10000000n) - const bidQuantity = ctx.any.arc4.uint64(0, BigInt(initialDeposit.native)) + const bidQuantity = ctx.any.arc4.uint64(0, BigInt(initialDeposit.asUint64())) const bidPrice = ctx.any.arc4.uint64(0, 10000000n) const bidder = ctx.any.account() @@ -251,15 +251,15 @@ describe('DigitalMarketplace', () => { bidUnitaryPrice: bidPrice, }) - const minQuantity = initialDeposit.native < bidQuantity.native ? initialDeposit.native : bidQuantity.native - const expectedPayment = contract.quantityPrice(minQuantity, bidPrice.native, testAsset.decimals) + const minQuantity = initialDeposit.asUint64() < bidQuantity.asUint64() ? initialDeposit.asUint64() : bidQuantity.asUint64() + const expectedPayment = contract.quantityPrice(minQuantity, bidPrice.asUint64(), testAsset.decimals) // Act contract.acceptBid(testAsset, testNonce) // Assert const updatedListing = contract.listings(listingKey).value - expect(updatedListing.deposited.native).toEqual(initialDeposit.native - minQuantity) + expect(updatedListing.deposited.asUint64()).toEqual(initialDeposit.asUint64() - minQuantity) expect(ctx.txn.lastGroup.itxnGroups.length).toEqual(2) diff --git a/examples/marketplace/contract.algo.ts b/examples/marketplace/contract.algo.ts index 8d9eb9ef..a60c5c6a 100644 --- a/examples/marketplace/contract.algo.ts +++ b/examples/marketplace/contract.algo.ts @@ -117,7 +117,7 @@ export default class DigitalMarketplace extends arc4.Contract { bidUnitaryPrice: existing.bidUnitaryPrice, bidder: existing.bidder, unitaryPrice: existing.unitaryPrice, - deposited: new arc4.Uint64(existing.deposited.native + xfer.assetAmount), + deposited: new arc4.Uint64(existing.deposited.asUint64() + xfer.assetAmount), }) } @@ -149,7 +149,7 @@ export default class DigitalMarketplace extends arc4.Contract { const listing = clone(this.listings(key).value) - const amountToBePaid = this.quantityPrice(quantity, listing.unitaryPrice.native, asset.decimals) + const amountToBePaid = this.quantityPrice(quantity, listing.unitaryPrice.asUint64(), asset.decimals) assert(buyPay.sender === Txn.sender) assert(buyPay.receiver.bytes === owner.bytes) @@ -160,7 +160,7 @@ export default class DigitalMarketplace extends arc4.Contract { bidUnitaryPrice: listing.bidUnitaryPrice, bidder: listing.bidder, unitaryPrice: listing.unitaryPrice, - deposited: new arc4.Uint64(listing.deposited.native - quantity), + deposited: new arc4.Uint64(listing.deposited.asUint64() - quantity), }) itxn @@ -182,7 +182,7 @@ export default class DigitalMarketplace extends arc4.Contract { const listing = clone(this.listings(key).value) if (listing.bidder !== new arc4.Address()) { - const currentBidDeposit = this.quantityPrice(listing.bid.native, listing.bidUnitaryPrice.native, asset.decimals) + const currentBidDeposit = this.quantityPrice(listing.bid.asUint64(), listing.bidUnitaryPrice.asUint64(), asset.decimals) itxn.payment({ receiver: listing.bidder.native, amount: currentBidDeposit }).submit() } @@ -194,7 +194,7 @@ export default class DigitalMarketplace extends arc4.Contract { .assetTransfer({ xferAsset: asset, assetReceiver: Txn.sender, - assetAmount: listing.deposited.native, + assetAmount: listing.deposited.asUint64(), }) .submit() } @@ -205,14 +205,14 @@ export default class DigitalMarketplace extends arc4.Contract { const listing = clone(this.listings(key).value) if (listing.bidder !== new arc4.Address()) { - assert(unitaryPrice.native > listing.bidUnitaryPrice.native) + assert(unitaryPrice.asUint64() > listing.bidUnitaryPrice.asUint64()) - const currentBidAmount = this.quantityPrice(listing.bid.native, listing.bidUnitaryPrice.native, asset.decimals) + const currentBidAmount = this.quantityPrice(listing.bid.asUint64(), listing.bidUnitaryPrice.asUint64(), asset.decimals) itxn.payment({ receiver: listing.bidder.native, amount: currentBidAmount }).submit() } - const amountToBeBid = this.quantityPrice(quantity.native, unitaryPrice.native, asset.decimals) + const amountToBeBid = this.quantityPrice(quantity.asUint64(), unitaryPrice.asUint64(), asset.decimals) assert(bidPay.sender === Txn.sender) assert(bidPay.receiver === Global.currentApplicationAddress) @@ -234,9 +234,9 @@ export default class DigitalMarketplace extends arc4.Contract { const listing = clone(this.listings(key).value) assert(listing.bidder !== new arc4.Address()) - const minQuantity = listing.deposited.native < listing.bid.native ? listing.deposited.native : listing.bid.native + const minQuantity = listing.deposited.asUint64() < listing.bid.asUint64() ? listing.deposited.asUint64() : listing.bid.asUint64() - const bestBidAmount = this.quantityPrice(minQuantity, listing.bidUnitaryPrice.native, asset.decimals) + const bestBidAmount = this.quantityPrice(minQuantity, listing.bidUnitaryPrice.asUint64(), asset.decimals) itxn.payment({ receiver: Txn.sender, amount: bestBidAmount }).submit() @@ -252,8 +252,8 @@ export default class DigitalMarketplace extends arc4.Contract { bidder: listing.bidder, bidUnitaryPrice: listing.bidUnitaryPrice, unitaryPrice: listing.unitaryPrice, - deposited: new arc4.Uint64(listing.deposited.native - minQuantity), - bid: new arc4.Uint64(listing.bid.native - minQuantity), + deposited: new arc4.Uint64(listing.deposited.asUint64() - minQuantity), + bid: new arc4.Uint64(listing.bid.asUint64() - minQuantity), }) } } diff --git a/examples/voting/contract.algo.ts b/examples/voting/contract.algo.ts index ae73a125..fe0cd606 100644 --- a/examples/voting/contract.algo.ts +++ b/examples/voting/contract.algo.ts @@ -131,9 +131,9 @@ export class VotingRoundApp extends arc4.Contract { if (questionIndex > 0) { note += ',' } - if (questionOptions.native > 0) { + if (questionOptions.asUint64() > 0) { note += '[' - for (let optionIndex = Uint64(0); optionIndex <= questionOptions.native; optionIndex++) { + for (let optionIndex = Uint64(0); optionIndex <= questionOptions.asUint64(); optionIndex++) { if (optionIndex > 0) { note += ',' } @@ -204,8 +204,8 @@ export class VotingRoundApp extends arc4.Contract { ) let cumulativeOffset = Uint64(0) for (const questionIndex of urange(questionsCount)) { - const answerOptionIndex = answerIds.at(questionIndex).native - const optionsCount = this.optionCounts.value.at(questionIndex).native + const answerOptionIndex = answerIds.at(questionIndex).asUint64() + const optionsCount = this.optionCounts.value.at(questionIndex).asUint64() assert(answerOptionIndex < optionsCount, 'Answer option index invalid') this.incrementVoteInBox(cumulativeOffset + answerOptionIndex) cumulativeOffset += optionsCount @@ -228,7 +228,7 @@ export class VotingRoundApp extends arc4.Contract { let totalOptions = Uint64(0) for (const item of optionCounts) { - totalOptions += item.native + totalOptions += item.asUint64() } this.optionCounts.value = clone(optionCounts) this.totalOptions.value = totalOptions diff --git a/src/impl/encoded-types/encoded-types.ts b/src/impl/encoded-types/encoded-types.ts index 0e33392b..8c277998 100644 --- a/src/impl/encoded-types/encoded-types.ts +++ b/src/impl/encoded-types/encoded-types.ts @@ -18,7 +18,13 @@ import { } from '@algorandfoundation/algorand-typescript/arc4' import { encodingUtil } from '@algorandfoundation/puya-ts' import assert from 'assert' -import { ABI_RETURN_VALUE_LOG_PREFIX, ALGORAND_ADDRESS_BYTE_LENGTH, ALGORAND_CHECKSUM_BYTE_LENGTH, UINT64_SIZE } from '../../constants' +import { + ABI_RETURN_VALUE_LOG_PREFIX, + ALGORAND_ADDRESS_BYTE_LENGTH, + ALGORAND_CHECKSUM_BYTE_LENGTH, + MAX_UINT64, + UINT64_SIZE, +} from '../../constants' import { AvmError, avmInvariant, CodeError, InternalError } from '../../errors' import { nameOfType, type DeliberateAny } from '../../typescript-helpers' import { @@ -99,7 +105,20 @@ export class Uint extends _Uint { get native() { const bigIntValue = encodingUtil.uint8ArrayToBigInt(this.value) - return (this.bitSize <= UINT64_SIZE ? asUint64(bigIntValue) : asBigUint(bigIntValue)) as _Uint['native'] + return this.bitSize <= UINT64_SIZE ? asUint64(bigIntValue) : asBigUint(bigIntValue) + } + + asUint64() { + const bigIntValue = encodingUtil.uint8ArrayToBigInt(this.value) + if (bigIntValue > MAX_UINT64) { + throw new CodeError('value too large to fit in uint64') + } + return asUint64(bigIntValue) + } + + asBigUint() { + const bigIntValue = encodingUtil.uint8ArrayToBigInt(this.value) + return asBigUint(bigIntValue) } get bytes(): bytes { @@ -155,7 +174,7 @@ export class UFixed extends _UFixed { get native() { const bigIntValue = encodingUtil.uint8ArrayToBigInt(this.value) - return (this.bitSize <= UINT64_SIZE ? asUint64(bigIntValue) : asBigUint(bigIntValue)) as _UFixed['native'] + return this.bitSize <= UINT64_SIZE ? asUint64(bigIntValue) : asBigUint(bigIntValue) } get bytes(): bytes { @@ -201,8 +220,12 @@ export class Byte extends _Byte { this.value = new Uint<8>(typeInfo, v) } - get native() { - return this.value.native + asUint64() { + return this.value.asUint64() + } + + asBigUint() { + return this.value.asBigUint() } get bytes(): bytes { diff --git a/src/set-up.ts b/src/set-up.ts index f1dc26ac..a697e13c 100644 --- a/src/set-up.ts +++ b/src/set-up.ts @@ -33,6 +33,15 @@ function doAddEqualityTesters(expectObj: ExpectObj) { // Defer to other testers return undefined }, + function NumericLiteralIsNumericPrimitive(this: TesterContext, subject, test, customTesters): boolean | undefined { + if (typeof subject === 'bigint' || typeof subject === 'number') { + const testValue = test instanceof Uint64Cls || test instanceof BigUintCls ? test.valueOf() : undefined + if (testValue !== undefined) return this.equals(BigInt(subject), testValue, customTesters) + return undefined + } + // Defer to other testers + return undefined + }, function BytesPrimitiveIsUint8Array(this: TesterContext, subject, test, customTesters): boolean | undefined { if (subject instanceof BytesCls) { const testValue = test instanceof Uint8Array ? test : undefined diff --git a/tests/arc4/address.algo.spec.ts b/tests/arc4/address.algo.spec.ts index 6f632ece..b92cda39 100644 --- a/tests/arc4/address.algo.spec.ts +++ b/tests/arc4/address.algo.spec.ts @@ -44,7 +44,7 @@ describe('arc4.Address', () => { const result = new Address(value) let i = 0 for (const entry of result) { - expect(entry.native).toEqual(uint8ArrayValue[i++]) + expect(entry.asUint64()).toEqual(uint8ArrayValue[i++]) } expect(result.length).toEqual(uint8ArrayValue.length) }) @@ -54,7 +54,7 @@ describe('arc4.Address', () => { const result = new Address(stringValue) let i = 0 for (const entry of result) { - expect(entry.native).toEqual(uint8ArrayValue[i++]) + expect(entry.asUint64()).toEqual(uint8ArrayValue[i++]) } expect(result.length).toEqual(uint8ArrayValue.length) }) @@ -64,7 +64,7 @@ describe('arc4.Address', () => { const result = new Address(accountValue) let i = 0 for (const entry of result) { - expect(entry.native).toEqual(uint8ArrayValue[i++]) + expect(entry.asUint64()).toEqual(uint8ArrayValue[i++]) } expect(result.length).toEqual(uint8ArrayValue.length) }) diff --git a/tests/arc4/byte.algo.spec.ts b/tests/arc4/byte.algo.spec.ts index 4a1a2ccf..fcd8da71 100644 --- a/tests/arc4/byte.algo.spec.ts +++ b/tests/arc4/byte.algo.spec.ts @@ -44,7 +44,7 @@ describe('arc4.Byte', async () => { const result = new Byte(value) - expect(result.native).toEqual(expected) + expect(result.asUint64()).toEqual(expected) expect(avmResult).toEqual(expected) }) @@ -85,13 +85,13 @@ describe('arc4.Byte', async () => { case '!==': return a !== b case '<': - return a.native < b.native + return a.asUint64() < b.asUint64() case '<=': - return a.native <= b.native + return a.asUint64() <= b.asUint64() case '>': - return a.native > b.native + return a.asUint64() > b.asUint64() case '>=': - return a.native >= b.native + return a.asUint64() >= b.asUint64() default: throw new Error(`Unknown operator: ${op}`) } @@ -118,7 +118,7 @@ describe('arc4.Byte', async () => { const avmResult = await getAvmResult({ appClient }, 'verify_byte_from_bytes', value) const result = interpretAsArc4(Bytes(value)) - expect(result.native).toEqual(avmResult) + expect(result.asUint64()).toEqual(avmResult) }) test.for([encodingUtil.bigIntToUint8Array(0n, 2), encodingUtil.bigIntToUint8Array(255n, 8)])( @@ -127,7 +127,7 @@ describe('arc4.Byte', async () => { await expect(getAvmResult({ appClient }, 'verify_byte_from_bytes', value)).rejects.toThrowError(invalidBytesLengthError) const result = interpretAsArc4(Bytes(value)) - expect(result.native).toEqual(encodingUtil.uint8ArrayToBigInt(value)) + expect(result.asUint64()).toEqual(encodingUtil.uint8ArrayToBigInt(value)) }, ) @@ -141,7 +141,7 @@ describe('arc4.Byte', async () => { const result = interpretAsArc4(Bytes(logValue), 'log') expect(avmResult).toEqual(expected) - expect(result.native).toEqual(expected) + expect(result.asUint64()).toEqual(expected) }) test.for([ @@ -169,7 +169,7 @@ describe('arc4.Byte', async () => { await expect(() => getAvmResult({ appClient }, 'verify_byte_from_log', logValue)).rejects.toThrowError(invalidBytesLengthError) const result = interpretAsArc4(Bytes(logValue), 'log') - expect(result.native).toEqual(encodingUtil.uint8ArrayToBigInt(value)) + expect(result.asUint64()).toEqual(encodingUtil.uint8ArrayToBigInt(value)) }, ) }) diff --git a/tests/arc4/dynamic-array.algo.spec.ts b/tests/arc4/dynamic-array.algo.spec.ts index 4b139514..e6aaee59 100644 --- a/tests/arc4/dynamic-array.algo.spec.ts +++ b/tests/arc4/dynamic-array.algo.spec.ts @@ -18,7 +18,7 @@ import { afterEach, describe, expect, it, test } from 'vitest' import { Bytes, type StubBytesCompat } from '../../src/impl/primitives' import { AccountCls } from '../../src/impl/reference' import type { DeliberateAny } from '../../src/typescript-helpers' -import { asBytes, asUint8Array } from '../../src/util' +import { asBigUint, asBytes, asUint8Array } from '../../src/util' const addressDynamicArray = { abiTypeString: 'address[]', @@ -106,7 +106,7 @@ const uint256DynamicArray = { const ufixedDynamicArray = { abiTypeString: 'ufixed256x16[]', nativeValues() { - return this.abiValues().map((v) => v.native.valueOf()) + return this.abiValues().map((v) => asBigUint(v.bytes).valueOf()) }, abiValues() { return [ diff --git a/tests/arc4/dynamic-bytes.algo.spec.ts b/tests/arc4/dynamic-bytes.algo.spec.ts index 933158fc..7c67d99c 100644 --- a/tests/arc4/dynamic-bytes.algo.spec.ts +++ b/tests/arc4/dynamic-bytes.algo.spec.ts @@ -41,7 +41,7 @@ describe('arc4.DynamicBytes', async () => { const dynamicArray = data.dynamicBytes() const nativeValue = data.nativeValue() for (let i = 0; i < dynamicArray.length; i++) { - expect(dynamicArray[i].native).toEqual(nativeValue[i]) + expect(dynamicArray[i].asUint64()).toEqual(nativeValue[i]) } expect(dynamicArray.length).toEqual(nativeValue.length) }) @@ -51,7 +51,7 @@ describe('arc4.DynamicBytes', async () => { const sdkEncodedBytes = getABIEncodedValue(nativeValue, abiTypeString, {}) const result = interpretAsArc4(Bytes(sdkEncodedBytes)) for (let i = 0; i < result.length; i++) { - expect(result[i].native).toEqual(nativeValue[i]) + expect(result[i].asUint64()).toEqual(nativeValue[i]) } }) }) diff --git a/tests/arc4/emit.algo.spec.ts b/tests/arc4/emit.algo.spec.ts index 72a3711b..28574ce5 100644 --- a/tests/arc4/emit.algo.spec.ts +++ b/tests/arc4/emit.algo.spec.ts @@ -78,8 +78,8 @@ describe('arc4.emit', async () => { test_data.f, asUint8Array(test_data.g), test_data.h, - test_data_arc4.m.native.valueOf(), - test_data_arc4.n.native.valueOf(), + test_data_arc4.m.asUint64().valueOf(), + test_data_arc4.n.asBigUint().valueOf(), asBigUintCls(test_data_arc4.o.bytes).asBigInt(), asBigUintCls(test_data_arc4.p.bytes).asBigInt(), test_data_arc4.q.native, diff --git a/tests/arc4/static-array.algo.spec.ts b/tests/arc4/static-array.algo.spec.ts index 3cfddf28..4d71e5f7 100644 --- a/tests/arc4/static-array.algo.spec.ts +++ b/tests/arc4/static-array.algo.spec.ts @@ -18,7 +18,7 @@ import { afterEach, describe, expect, it, test } from 'vitest' import type { StubBytesCompat } from '../../src/impl/primitives' import { AccountCls } from '../../src/impl/reference' import type { DeliberateAny } from '../../src/typescript-helpers' -import { asBytes, asUint8Array } from '../../src/util' +import { asBigUint, asBytes, asUint8Array } from '../../src/util' const addressStaticArray = { abiTypeString: 'address[10]', @@ -109,7 +109,7 @@ const uint256StaticArray = { const ufixedStaticArray = { abiTypeString: 'ufixed256x16[10]', nativeValues() { - return this.abiValues().map((v) => v.native.valueOf()) + return this.abiValues().map((v) => asBigUint(v.bytes).valueOf()) }, abiValues() { return [ diff --git a/tests/arc4/static-bytes.algo.spec.ts b/tests/arc4/static-bytes.algo.spec.ts index 0d6565b1..9a44b9af 100644 --- a/tests/arc4/static-bytes.algo.spec.ts +++ b/tests/arc4/static-bytes.algo.spec.ts @@ -46,7 +46,7 @@ describe('arc4.StaticBytes', async () => { const staticArray = data.staticBytes() const nativeValue = data.nativeValue() for (let i = 0; i < staticArray.length; i++) { - expect(staticArray[i].native).toEqual(nativeValue[i]) + expect(staticArray[i].asUint64()).toEqual(nativeValue[i]) } expect(staticArray.length).toEqual(nativeValue.length) }) @@ -56,7 +56,7 @@ describe('arc4.StaticBytes', async () => { const sdkEncodedBytes = getABIEncodedValue(nativeValue, data.abiTypeString(), {}) const result = interpretAsArc4(Bytes(sdkEncodedBytes)) for (let i = 0; i < result.length; i++) { - expect(result[i].native).toEqual(nativeValue[i]) + expect(result[i].asUint64()).toEqual(nativeValue[i]) } }) }) diff --git a/tests/arc4/ufixednxm.algo.spec.ts b/tests/arc4/ufixednxm.algo.spec.ts index c37abe5f..67dd2247 100644 --- a/tests/arc4/ufixednxm.algo.spec.ts +++ b/tests/arc4/ufixednxm.algo.spec.ts @@ -69,7 +69,7 @@ describe('arc4.UFixed', async () => { const avmResult = await getAvmResult({ appClient }, 'verify_ufixed_from_bytes', value) const result = interpretAsArc4>(Bytes(value)) - expect(result.native).toEqual(avmResult) + expect(asBigUint(result.bytes)).toEqual(avmResult) }) test.for([ @@ -98,7 +98,7 @@ describe('arc4.UFixed', async () => { const result = interpretAsArc4>(Bytes(logValue), 'log') expect(avmResult).toEqual(expected) - expect(result.native).toEqual(expected) + expect(asBigUint(result.bytes)).toEqual(expected) }) test.for([ @@ -125,7 +125,7 @@ describe('arc4.UFixed', async () => { await expect(() => getAvmResult({ appClient }, 'verify_ufixed_from_log', logValue)).rejects.toThrowError(invalidBytesLengthError(32)) const result = interpretAsArc4>(Bytes(logValue), 'log') - expect(result.native).toEqual(encodingUtil.uint8ArrayToBigInt(value)) + expect(asBigUint(result.bytes)).toEqual(encodingUtil.uint8ArrayToBigInt(value)) }, ) @@ -138,7 +138,7 @@ describe('arc4.UFixed', async () => { const avmResult = await getAvmResult({ appClient }, 'verify_bigufixed_from_bytes', value) const result = interpretAsArc4>(Bytes(value)) - expect(result.native).toEqual(avmResult) + expect(asBigUint(result.bytes)).toEqual(avmResult) }) test.for([ @@ -167,7 +167,7 @@ describe('arc4.UFixed', async () => { const result = interpretAsArc4>(Bytes(logValue), 'log') expect(avmResult).toEqual(expected) - expect(result.native).toEqual(expected) + expect(asBigUint(result.bytes)).toEqual(expected) }) test.for([ @@ -196,7 +196,7 @@ describe('arc4.UFixed', async () => { ) const result = interpretAsArc4>(Bytes(logValue), 'log') - expect(result.native).toEqual(encodingUtil.uint8ArrayToBigInt(value)) + expect(asBigUint(result.bytes)).toEqual(encodingUtil.uint8ArrayToBigInt(value)) }, ) }) diff --git a/tests/arc4/uintn.algo.spec.ts b/tests/arc4/uintn.algo.spec.ts index a6f132b8..b8f3346a 100644 --- a/tests/arc4/uintn.algo.spec.ts +++ b/tests/arc4/uintn.algo.spec.ts @@ -54,7 +54,7 @@ describe('arc4.Uint', async () => { const result = new Uint32(value) - expect(result.native).toEqual(expected) + expect(result.asUint64()).toEqual(expected) expect(avmResult).toEqual(expected) }) @@ -70,7 +70,7 @@ describe('arc4.Uint', async () => { const result = new Uint256(value) - expect(result.native).toEqual(BigInt(expected)) + expect(result.asBigUint()).toEqual(BigInt(expected)) expect(avmResult).toEqual(BigInt(expected)) }) @@ -119,13 +119,13 @@ describe('arc4.Uint', async () => { case '!==': return a !== b case '<': - return a.native < b.native + return a.asBigUint() < b.asBigUint() case '<=': - return a.native <= b.native + return a.asBigUint() <= b.asBigUint() case '>': - return a.native > b.native + return a.asBigUint() > b.asBigUint() case '>=': - return a.native >= b.native + return a.asBigUint() >= b.asBigUint() default: throw new Error(`Unknown operator: ${op}`) } @@ -173,7 +173,7 @@ describe('arc4.Uint', async () => { const result = interpretAsArc4(Bytes(value)) - expect(result.native).toEqual(avmResult) + expect(result.asUint64()).toEqual(avmResult) }) test.for([ @@ -187,7 +187,7 @@ describe('arc4.Uint', async () => { await expect(getAvmResult({ appClient }, 'verify_uintn_from_bytes', value)).rejects.toThrowError(invalidBytesLengthError(32)) const result = interpretAsArc4(Bytes(value)) - expect(result.native).toEqual(encodingUtil.uint8ArrayToBigInt(value)) + expect(result.asUint64()).toEqual(encodingUtil.uint8ArrayToBigInt(value)) }, ) @@ -200,7 +200,7 @@ describe('arc4.Uint', async () => { const avmResult = await getAvmResult({ appClient }, 'verify_biguintn_from_bytes', value) const result = interpretAsArc4(Bytes(value)) - expect(result.native).toEqual(avmResult) + expect(result.asBigUint()).toEqual(avmResult) }) test.for([ @@ -214,7 +214,7 @@ describe('arc4.Uint', async () => { await expect(getAvmResult({ appClient }, 'verify_biguintn_from_bytes', value)).rejects.toThrowError(invalidBytesLengthError(256)) const result = interpretAsArc4(Bytes(value)) - expect(result.native).toEqual(encodingUtil.uint8ArrayToBigInt(value)) + expect(result.asBigUint()).toEqual(encodingUtil.uint8ArrayToBigInt(value)) }, ) @@ -229,7 +229,7 @@ describe('arc4.Uint', async () => { const result = interpretAsArc4>(Bytes(logValue), 'log') expect(BigInt(avmResult as number)).toEqual(expected) - expect(result.native).toEqual(expected) + expect(result.asUint64()).toEqual(expected) }) test.for([ @@ -256,7 +256,7 @@ describe('arc4.Uint', async () => { await expect(() => getAvmResult({ appClient }, 'verify_uintn_from_log', logValue)).rejects.toThrowError(invalidBytesLengthError(32)) const result = interpretAsArc4>(Bytes(logValue), 'log') - expect(result.native).toEqual(encodingUtil.uint8ArrayToBigInt(value)) + expect(result.asUint64()).toEqual(encodingUtil.uint8ArrayToBigInt(value)) }, ) @@ -271,7 +271,7 @@ describe('arc4.Uint', async () => { const result = interpretAsArc4>(Bytes(logValue), 'log') expect(avmResult).toEqual(expected) - expect(result.native).toEqual(expected) + expect(result.asBigUint()).toEqual(expected) }) test.for([ @@ -300,7 +300,53 @@ describe('arc4.Uint', async () => { ) const result = interpretAsArc4>(Bytes(logValue), 'log') - expect(result.native).toEqual(encodingUtil.uint8ArrayToBigInt(value)) + expect(result.asBigUint()).toEqual(encodingUtil.uint8ArrayToBigInt(value)) + }, + ) + + test.for([encodingUtil.bigIntToUint8Array(1n, 32), encodingUtil.bigIntToUint8Array(MAX_UINT64, 32)])( + 'get uint64 from arc4 big uintn', + async (value, { appClientArc4PrimitiveOpsContract: appClient }) => { + const avm_result = await getAvmResult({ appClient }, 'verify_biguintn_as_uint64', value) + const result = interpretAsArc4(Bytes(value)).asUint64() + expect(avm_result).toEqual(result) + }, + ) + + test.for([encodingUtil.bigIntToUint8Array(MAX_UINT64 + 1n, 32), encodingUtil.bigIntToUint8Array(2n ** 256n - 1n, 32)])( + 'should throw error when getting uint64 from arc4 big uintn that overflows uint64', + async (value, { appClientArc4PrimitiveOpsContract: appClient }) => { + await expect(() => getAvmResult({ appClient }, 'verify_biguintn_as_uint64', value)).rejects.toThrowError('overflow') + expect(() => interpretAsArc4(Bytes(value)).asUint64()).toThrowError('value too large to fit in uint64') + }, + ) + + test.for([ + encodingUtil.bigIntToUint8Array(1n, 32), + encodingUtil.bigIntToUint8Array(MAX_UINT64, 32), + encodingUtil.bigIntToUint8Array(MAX_UINT64 + 1n, 32), + encodingUtil.bigIntToUint8Array(2n ** 256n - 1n, 32), + ])('get biguint from arc4 big uintn', async (value, { appClientArc4PrimitiveOpsContract: appClient }) => { + const avm_result = await getAvmResult({ appClient }, 'verify_biguintn_as_biguint', value) + const result = interpretAsArc4(Bytes(value)).asBigUint() + expect(avm_result).toEqual(result) + }) + + test.for([encodingUtil.bigIntToUint8Array(1n, 8), encodingUtil.bigIntToUint8Array(MAX_UINT64, 8)])( + 'get uint64 from arc4 uintn', + async (value, { appClientArc4PrimitiveOpsContract: appClient }) => { + const avm_result = await getAvmResult({ appClient }, 'verify_uintn64_as_uint64', value) + const result = interpretAsArc4(Bytes(value)).asUint64() + expect(avm_result).toEqual(result) + }, + ) + + test.for([encodingUtil.bigIntToUint8Array(1n, 8), encodingUtil.bigIntToUint8Array(MAX_UINT64, 8)])( + 'get biguint from arc4 uintn64', + async (value, { appClientArc4PrimitiveOpsContract: appClient }) => { + const avm_result = await getAvmResult({ appClient }, 'verify_uintn64_as_biguint', value) + const result = interpretAsArc4(Bytes(value)).asBigUint() + expect(avm_result).toEqual(result) }, ) }) diff --git a/tests/artifacts/arc4-abi-method/contract.algo.ts b/tests/artifacts/arc4-abi-method/contract.algo.ts index 19419e1d..7d4f1b0c 100644 --- a/tests/artifacts/arc4-abi-method/contract.algo.ts +++ b/tests/artifacts/arc4-abi-method/contract.algo.ts @@ -60,7 +60,7 @@ export class SignaturesContract extends arc4.Contract { withApp(value: arc4.Str, app: Application, appId: arc4.Uint64, arr: UInt8Array) { assert(value.native) assert(arr.length) - assert(app.id === appId.native, 'expected app id to match provided app id') + assert(app.id === appId.asUint64(), 'expected app id to match provided app id') assert(app.creator === op.Global.creatorAddress, 'expected other app to have same creator') const appTxn = gtxn.ApplicationCallTxn(0) assert(appTxn.apps(0) === op.Global.currentApplicationId) @@ -83,12 +83,12 @@ export class SignaturesContract extends arc4.Contract { assert(Txn.numAppArgs === 4) // struct - assert(struct1.anotherStruct.one.native === 1) + assert(struct1.anotherStruct.one.asUint64() === 1) assert(struct1.anotherStruct.two.native === '2') - assert(struct1.anotherStructAlias.one.native === 1) + assert(struct1.anotherStructAlias.one.asUint64() === 1) assert(struct1.anotherStructAlias.two.native === '2') - assert(struct1.three.native === 3n) - assert(struct1.four.native === 4n) + assert(struct1.three.asBigUint() === 3n) + assert(struct1.four.asBigUint() === 4n) // txn assert(txn.groupIndex === Txn.groupIndex - 1) @@ -96,7 +96,7 @@ export class SignaturesContract extends arc4.Contract { // acc assert(Txn.applicationArgs(2) === new arc4.Uint8(1).bytes) // acc array ref assert(acc.balance === acc.minBalance + 1234) - assert(five[0].native === 5) + assert(five[0].asUint64() === 5) return [clone(struct1.anotherStruct), clone(struct1)] } diff --git a/tests/artifacts/arc4-primitive-ops/contract.algo.ts b/tests/artifacts/arc4-primitive-ops/contract.algo.ts index 3a711acf..e6859009 100644 --- a/tests/artifacts/arc4-primitive-ops/contract.algo.ts +++ b/tests/artifacts/arc4-primitive-ops/contract.algo.ts @@ -1,4 +1,4 @@ -import type { bytes } from '@algorandfoundation/algorand-typescript' +import type { biguint, bytes, uint64 } from '@algorandfoundation/algorand-typescript' import { arc4, BigUint, clone, emit } from '@algorandfoundation/algorand-typescript' import type { Bool, UFixed } from '@algorandfoundation/algorand-typescript/arc4' import { Byte, Contract, interpretAsArc4, Str, Uint } from '@algorandfoundation/algorand-typescript/arc4' @@ -90,7 +90,7 @@ export class Arc4PrimitiveOpsContract extends Contract { const bBiguint = BigUint(b) const aUintN = new Uint<64>(aBiguint) const bUintN = new Uint<64>(bBiguint) - return aUintN.native < bUintN.native + return aUintN.asUint64() < bUintN.asUint64() } @arc4.abimethod() public verify_biguintn_uintn_lt(a: bytes, b: bytes): boolean { @@ -98,7 +98,7 @@ export class Arc4PrimitiveOpsContract extends Contract { const bBiguint = BigUint(b) const aUintN = new Uint<512>(aBiguint) const bUintN = new Uint<64>(bBiguint) - return aUintN.native < BigUint(bUintN.native) + return aUintN.asBigUint() < BigUint(bUintN.asUint64()) } @arc4.abimethod() public verify_uintn_biguintn_lt(a: bytes, b: bytes): boolean { @@ -106,7 +106,7 @@ export class Arc4PrimitiveOpsContract extends Contract { const bBiguint = BigUint(b) const aUintN = new Uint<64>(aBiguint) const bUintN = new Uint<512>(bBiguint) - return BigUint(aUintN.native) < bUintN.native + return BigUint(aUintN.asUint64()) < bUintN.asBigUint() } @arc4.abimethod() public verify_biguintn_biguintn_lt(a: bytes, b: bytes): boolean { @@ -114,7 +114,7 @@ export class Arc4PrimitiveOpsContract extends Contract { const bBiguint = BigUint(b) const aUintN = new Uint<512>(aBiguint) const bUintN = new Uint<512>(bBiguint) - return aUintN.native < bUintN.native + return aUintN.asBigUint() < bUintN.asBigUint() } @arc4.abimethod() public verify_byte_byte_lt(a: bytes, b: bytes): boolean { @@ -122,7 +122,7 @@ export class Arc4PrimitiveOpsContract extends Contract { const bBiguint = BigUint(b) const aByte = new Byte(aBiguint) const bByte = new Byte(bBiguint) - return aByte.native < bByte.native + return aByte.asUint64() < bByte.asUint64() } @arc4.abimethod() public verify_uintn_uintn_le(a: bytes, b: bytes): boolean { @@ -130,7 +130,7 @@ export class Arc4PrimitiveOpsContract extends Contract { const bBiguint = BigUint(b) const aUintN = new Uint<64>(aBiguint) const bUintN = new Uint<64>(bBiguint) - return aUintN.native <= bUintN.native + return aUintN.asUint64() <= bUintN.asUint64() } @arc4.abimethod() public verify_biguintn_uintn_le(a: bytes, b: bytes): boolean { @@ -138,7 +138,7 @@ export class Arc4PrimitiveOpsContract extends Contract { const bBiguint = BigUint(b) const aUintN = new Uint<512>(aBiguint) const bUintN = new Uint<64>(bBiguint) - return aUintN.native <= BigUint(bUintN.native) + return aUintN.asBigUint() <= BigUint(bUintN.asUint64()) } @arc4.abimethod() public verify_uintn_biguintn_le(a: bytes, b: bytes): boolean { @@ -146,7 +146,7 @@ export class Arc4PrimitiveOpsContract extends Contract { const bBiguint = BigUint(b) const aUintN = new Uint<64>(aBiguint) const bUintN = new Uint<512>(bBiguint) - return BigUint(aUintN.native) <= bUintN.native + return BigUint(aUintN.asUint64()) <= bUintN.asBigUint() } @arc4.abimethod() public verify_biguintn_biguintn_le(a: bytes, b: bytes): boolean { @@ -154,7 +154,7 @@ export class Arc4PrimitiveOpsContract extends Contract { const bBiguint = BigUint(b) const aUintN = new Uint<512>(aBiguint) const bUintN = new Uint<512>(bBiguint) - return aUintN.native <= bUintN.native + return aUintN.asBigUint() <= bUintN.asBigUint() } @arc4.abimethod() public verify_byte_byte_le(a: bytes, b: bytes): boolean { @@ -162,7 +162,7 @@ export class Arc4PrimitiveOpsContract extends Contract { const bBiguint = BigUint(b) const aByte = new Byte(aBiguint) const bByte = new Byte(bBiguint) - return aByte.native <= bByte.native + return aByte.asUint64() <= bByte.asUint64() } @arc4.abimethod() public verify_uintn_uintn_gt(a: bytes, b: bytes): boolean { @@ -170,7 +170,7 @@ export class Arc4PrimitiveOpsContract extends Contract { const bBiguint = BigUint(b) const aUintN = new Uint<64>(aBiguint) const bUintN = new Uint<64>(bBiguint) - return aUintN.native > bUintN.native + return aUintN.asUint64() > bUintN.asUint64() } @arc4.abimethod() public verify_biguintn_uintn_gt(a: bytes, b: bytes): boolean { @@ -178,7 +178,7 @@ export class Arc4PrimitiveOpsContract extends Contract { const bBiguint = BigUint(b) const aUintN = new Uint<512>(aBiguint) const bUintN = new Uint<64>(bBiguint) - return aUintN.native > BigUint(bUintN.native) + return aUintN.asBigUint() > BigUint(bUintN.asUint64()) } @arc4.abimethod() public verify_uintn_biguintn_gt(a: bytes, b: bytes): boolean { @@ -186,7 +186,7 @@ export class Arc4PrimitiveOpsContract extends Contract { const bBiguint = BigUint(b) const aUintN = new Uint<64>(aBiguint) const bUintN = new Uint<512>(bBiguint) - return BigUint(aUintN.native) > bUintN.native + return BigUint(aUintN.asUint64()) > bUintN.asBigUint() } @arc4.abimethod() public verify_biguintn_biguintn_gt(a: bytes, b: bytes): boolean { @@ -194,7 +194,7 @@ export class Arc4PrimitiveOpsContract extends Contract { const bBiguint = BigUint(b) const aUintN = new Uint<512>(aBiguint) const bUintN = new Uint<512>(bBiguint) - return aUintN.native > bUintN.native + return aUintN.asBigUint() > bUintN.asBigUint() } @arc4.abimethod() public verify_byte_byte_gt(a: bytes, b: bytes): boolean { @@ -202,7 +202,7 @@ export class Arc4PrimitiveOpsContract extends Contract { const bBiguint = BigUint(b) const aByte = new Byte(aBiguint) const bByte = new Byte(bBiguint) - return aByte.native > bByte.native + return aByte.asUint64() > bByte.asUint64() } @arc4.abimethod() public verify_uintn_uintn_ge(a: bytes, b: bytes): boolean { @@ -210,7 +210,7 @@ export class Arc4PrimitiveOpsContract extends Contract { const bBiguint = BigUint(b) const aUintN = new Uint<64>(aBiguint) const bUintN = new Uint<64>(bBiguint) - return aUintN.native >= bUintN.native + return aUintN.asUint64() >= bUintN.asUint64() } @arc4.abimethod() public verify_biguintn_uintn_ge(a: bytes, b: bytes): boolean { @@ -218,7 +218,7 @@ export class Arc4PrimitiveOpsContract extends Contract { const bBiguint = BigUint(b) const aUintN = new Uint<512>(aBiguint) const bUintN = new Uint<64>(bBiguint) - return aUintN.native >= BigUint(bUintN.native) + return aUintN.asBigUint() >= BigUint(bUintN.asUint64()) } @arc4.abimethod() public verify_uintn_biguintn_ge(a: bytes, b: bytes): boolean { @@ -226,7 +226,7 @@ export class Arc4PrimitiveOpsContract extends Contract { const bBiguint = BigUint(b) const aUintN = new Uint<64>(aBiguint) const bUintN = new Uint<512>(bBiguint) - return BigUint(aUintN.native) >= bUintN.native + return BigUint(aUintN.asUint64()) >= bUintN.asBigUint() } @arc4.abimethod() public verify_biguintn_biguintn_ge(a: bytes, b: bytes): boolean { @@ -234,7 +234,7 @@ export class Arc4PrimitiveOpsContract extends Contract { const bBiguint = BigUint(b) const aUintN = new Uint<512>(aBiguint) const bUintN = new Uint<512>(bBiguint) - return aUintN.native >= bUintN.native + return aUintN.asBigUint() >= bUintN.asBigUint() } @arc4.abimethod() public verify_byte_byte_ge(a: bytes, b: bytes): boolean { @@ -242,7 +242,7 @@ export class Arc4PrimitiveOpsContract extends Contract { const bBiguint = BigUint(b) const aByte = new Byte(aBiguint) const bByte = new Byte(bBiguint) - return aByte.native >= bByte.native + return aByte.asUint64() >= bByte.asUint64() } @arc4.abimethod() public verify_uintn_init(a: bytes): Uint<32> { @@ -279,6 +279,26 @@ export class Arc4PrimitiveOpsContract extends Contract { public verify_biguintn_from_log(a: bytes): Uint<256> { return interpretAsArc4>(a, 'log') } + @arc4.abimethod() + public verify_biguintn_as_uint64(a: bytes): uint64 { + return interpretAsArc4>(a).asUint64() + } + + @arc4.abimethod() + public verify_biguintn_as_biguint(a: bytes): biguint { + return interpretAsArc4>(a).asBigUint() + } + + @arc4.abimethod() + public verify_uintn64_as_uint64(a: bytes): uint64 { + return interpretAsArc4(a).asUint64() + } + + @arc4.abimethod() + public verify_uintn64_as_biguint(a: bytes): biguint { + return interpretAsArc4(a).asBigUint() + } + @arc4.abimethod() public verify_byte_from_log(a: bytes): Byte { return interpretAsArc4(a, 'log') diff --git a/tests/artifacts/created-app-asset/contract.algo.ts b/tests/artifacts/created-app-asset/contract.algo.ts index 8420b064..e6879259 100644 --- a/tests/artifacts/created-app-asset/contract.algo.ts +++ b/tests/artifacts/created-app-asset/contract.algo.ts @@ -18,6 +18,6 @@ export class AppExpectingEffects extends arc4.Contract { public log_group(appCall: gtxn.ApplicationCallTxn): void { assert(appCall.appArgs(0) === methodSelector('some_value()uint64'), 'expected correct method called') assert(appCall.numLogs === 1, 'expected logs') - assert(interpretAsArc4(appCall.lastLog, 'log').native === (appCall.groupIndex + 1) * Global.groupSize) + assert(interpretAsArc4(appCall.lastLog, 'log').asUint64() === (appCall.groupIndex + 1) * Global.groupSize) } } diff --git a/tests/fixed-array.algo.spec.ts b/tests/fixed-array.algo.spec.ts index fd5fd39f..7eba26cc 100644 --- a/tests/fixed-array.algo.spec.ts +++ b/tests/fixed-array.algo.spec.ts @@ -439,7 +439,7 @@ describe('FixedArray', () => { assertMatch(interpreted.length, arr.length) for (let i = 0; i < arr.length; i++) { - assertMatch(interpreted[i].native, arr[i]) + assertMatch(interpreted[i].asUint64(), arr[i]) } assertMatch(decoded, arr) }) @@ -493,7 +493,7 @@ describe('FixedArray', () => { for (let i = 0; i < arr.length; i++) { assertMatch(interpreted[i].length, arr[i].length) for (let j = 0; j < arr[i].length; j++) { - assertMatch(interpreted[i][j].native, arr[i][j]) + assertMatch(interpreted[i][j].asUint64(), arr[i][j]) } } assertMatch(decoded, arr) @@ -509,7 +509,7 @@ describe('FixedArray', () => { for (let i = 0; i < arr.length; i++) { assertMatch(interpreted[i].length, arr[i].length) for (let j = 0; j < arr[i].length; j++) { - assertMatch(interpreted[i][j].native, arr[i][j]) + assertMatch(interpreted[i][j].asUint64(), arr[i][j]) } } assertMatch(decoded, arr) @@ -523,7 +523,7 @@ describe('FixedArray', () => { assertMatch(interpreted.length, arr.length) for (let i = 0; i < arr.length; i++) { - assertMatch(interpreted[i].native[0].native, arr[i][0]) + assertMatch(interpreted[i].native[0].asUint64(), arr[i][0]) assertMatch(interpreted[i].native[1].native, arr[i][1]) } assertMatch(decoded, arr) @@ -539,8 +539,8 @@ describe('FixedArray', () => { assertMatch(interpreted.length, arr.length) for (let i = 0; i < arr.length; i++) { - assertMatch(interpreted[i].x.native, arr[i].x) - assertMatch(interpreted[i].y.native, arr[i].y) + assertMatch(interpreted[i].x.asUint64(), arr[i].x) + assertMatch(interpreted[i].y.asUint64(), arr[i].y) } assertMatch(decoded, arr) }) diff --git a/tests/global-state-arc4-values.algo.spec.ts b/tests/global-state-arc4-values.algo.spec.ts index cd5175d7..5e9cccf2 100644 --- a/tests/global-state-arc4-values.algo.spec.ts +++ b/tests/global-state-arc4-values.algo.spec.ts @@ -32,7 +32,7 @@ describe('ARC4 AppGlobal values', async () => { const arc4Value = value as Uint<64> expect(arc4Value).toBeInstanceOf(Uint) expect(arc4Value.bytes.length).toEqual(8) - expect(arc4Value.native).toEqual(expectedValue) + expect(arc4Value.asUint64()).toEqual(expectedValue) }, }, { @@ -52,7 +52,7 @@ describe('ARC4 AppGlobal values', async () => { assert: (value: ARC4Encoded, expectedValue: DeliberateAny) => { const arc4Value = value as Byte expect(arc4Value).toBeInstanceOf(Byte) - expect(arc4Value.native).toEqual(expectedValue) + expect(arc4Value.asUint64()).toEqual(expectedValue) }, }, { @@ -83,7 +83,7 @@ describe('ARC4 AppGlobal values', async () => { const arc4Value = value as Uint<128> expect(arc4Value).toBeInstanceOf(Uint) expect(arc4Value.bytes.length).toEqual(16) - expect(arc4Value.native).toEqual(expectedValue) + expect(arc4Value.asBigUint()).toEqual(expectedValue) }, }, { diff --git a/tests/local-state-arc4-values.algo.spec.ts b/tests/local-state-arc4-values.algo.spec.ts index d0ed1f48..3aa893ac 100644 --- a/tests/local-state-arc4-values.algo.spec.ts +++ b/tests/local-state-arc4-values.algo.spec.ts @@ -34,7 +34,7 @@ describe('ARC4 AppLocal values', async () => { expect(arc4Value).toBeInstanceOf(Uint) expect(arc4Value.bytes.length).toEqual(8) - expect(arc4Value.native).toEqual(expectedValue) + expect(arc4Value.asUint64()).toEqual(expectedValue) }, }, { @@ -50,7 +50,7 @@ describe('ARC4 AppLocal values', async () => { assert: (value: ARC4Encoded, expectedValue: DeliberateAny) => { const arc4Value = value as Byte expect(arc4Value).toBeInstanceOf(Byte) - expect(arc4Value.native).toEqual(expectedValue) + expect(arc4Value.asUint64()).toEqual(expectedValue) }, }, { @@ -75,7 +75,7 @@ describe('ARC4 AppLocal values', async () => { const arc4Value = value as Uint<128> expect(arc4Value).toBeInstanceOf(Uint) expect(arc4Value.bytes.length).toEqual(16) - expect(arc4Value.native).toEqual(expectedValue) + expect(arc4Value.asBigUint()).toEqual(expectedValue) }, }, { diff --git a/tests/log.algo.spec.ts b/tests/log.algo.spec.ts index 936e9515..a5507711 100644 --- a/tests/log.algo.spec.ts +++ b/tests/log.algo.spec.ts @@ -57,8 +57,8 @@ describe('log', async () => { asUint8Array(asBigUintCls(d).toBytes()), e.native, f.native, - g.native.valueOf(), - h.native.valueOf(), + g.asUint64().valueOf(), + h.asBigUint().valueOf(), asBigUint(i.bytes).valueOf(), asBigUint(j.bytes).valueOf(), asUint8Array(k.bytes), diff --git a/tests/native-mutable-array.algo.spec.ts b/tests/native-mutable-array.algo.spec.ts index eece6603..b37ee86d 100644 --- a/tests/native-mutable-array.algo.spec.ts +++ b/tests/native-mutable-array.algo.spec.ts @@ -314,7 +314,7 @@ describe('native mutable array', () => { assertMatch(interpreted.length, arr.length) for (let i = 0; i < arr.length; i++) { - assertMatch(interpreted[i].native, arr[i]) + assertMatch(interpreted[i].asUint64(), arr[i]) } assertMatch(decoded, arr) }) @@ -368,7 +368,7 @@ describe('native mutable array', () => { for (let i = 0; i < arr.length; i++) { assertMatch(interpreted[i].length, arr[i].length) for (let j = 0; j < arr[i].length; j++) { - assertMatch(interpreted[i][j].native, arr[i][j]) + assertMatch(interpreted[i][j].asUint64(), arr[i][j]) } } assertMatch(decoded, arr) @@ -388,7 +388,7 @@ describe('native mutable array', () => { for (let i = 0; i < arr.length; i++) { assertMatch(interpreted[i].length, arr[i].length) for (let j = 0; j < arr[i].length; j++) { - assertMatch(interpreted[i][j].native, arr[i][j]) + assertMatch(interpreted[i][j].asUint64(), arr[i][j]) } } assertMatch(decoded, arr) @@ -406,7 +406,7 @@ describe('native mutable array', () => { assertMatch(interpreted.length, arr.length) for (let i = 0; i < arr.length; i++) { - assertMatch(interpreted[i].native[0].native, arr[i][0]) + assertMatch(interpreted[i].native[0].asUint64(), arr[i][0]) assertMatch(interpreted[i].native[1].native, arr[i][1]) } assertMatch(decoded, arr) @@ -426,8 +426,8 @@ describe('native mutable array', () => { assertMatch(interpreted.length, arr.length) for (let i = 0; i < arr.length; i++) { - assertMatch(interpreted[i].x.native, arr[i].x) - assertMatch(interpreted[i].y.native, arr[i].y) + assertMatch(interpreted[i].x.asUint64(), arr[i].x) + assertMatch(interpreted[i].y.asUint64(), arr[i].y) } assertMatch(decoded, arr) }) @@ -469,7 +469,7 @@ describe('native mutable array', () => { const expected = [10, 2, 3, 4, 5] assertMatch(interpreted.length, expected.length) for (let i = 0; i < expected.length; i++) { - assertMatch(interpreted[i].native, expected[i]) + assertMatch(interpreted[i].asUint64(), expected[i]) } assertMatch(decoded, expected) }) diff --git a/tests/native-mutable-object.algo.spec.ts b/tests/native-mutable-object.algo.spec.ts index 83cc6990..4eca52b9 100644 --- a/tests/native-mutable-object.algo.spec.ts +++ b/tests/native-mutable-object.algo.spec.ts @@ -594,7 +594,7 @@ describe('native mutable object', () => { const interpreted = interpretAsArc4(encoded) const decoded = decodeArc4(encoded) - assertMatch(interpreted.a.native, obj.a) + assertMatch(interpreted.a.asUint64(), obj.a) assertMatch(interpreted.b.native, obj.b) assertMatch(interpreted.c.native, obj.c) assertMatch(interpreted.d.native, obj.d) @@ -625,10 +625,10 @@ describe('native mutable object', () => { const interpreted = interpretAsArc4(encoded) const decoded = decodeArc4(encoded) - assertMatch(interpreted.a.native, obj.a) + assertMatch(interpreted.a.asUint64(), obj.a) assertMatch(interpreted.b.native, obj.b) assertMatch(interpreted.c.native, obj.c) - assertMatch(interpreted.d.x.native, obj.d.x) + assertMatch(interpreted.d.x.asUint64(), obj.d.x) assertMatch(interpreted.d.y.native, obj.d.y) assertMatch(interpreted.d.z.native, obj.d.z) assertMatch(decoded, obj) @@ -653,12 +653,12 @@ describe('native mutable object', () => { const interpreted = interpretAsArc4(encoded) const decoded = decodeArc4(encoded) - assertMatch(interpreted.a.native, obj.a) + assertMatch(interpreted.a.asUint64(), obj.a) assertMatch(interpreted.b.native, obj.b) assertMatch(interpreted.c.native, obj.c) assertMatch(interpreted.d.length, obj.d.length) for (let i = 0; i < obj.d.length; i++) { - assertMatch(interpreted.d[i].native, obj.d[i]) + assertMatch(interpreted.d[i].asUint64(), obj.d[i]) } assertMatch(decoded, obj) }) @@ -682,12 +682,12 @@ describe('native mutable object', () => { const interpreted = interpretAsArc4(encoded) const decoded = decodeArc4(encoded) - assertMatch(interpreted.a.native, obj.a) + assertMatch(interpreted.a.asUint64(), obj.a) assertMatch(interpreted.b.native, obj.b) assertMatch(interpreted.c.native, obj.c) assertMatch(interpreted.d.length, obj.d.length) for (let i = 0; i < obj.d.length; i++) { - assertMatch(interpreted.d[i].native, obj.d[i]) + assertMatch(interpreted.d[i].asUint64(), obj.d[i]) } assertMatch(decoded, obj) }) @@ -711,10 +711,10 @@ describe('native mutable object', () => { const interpreted = interpretAsArc4(encoded) const decoded = decodeArc4(encoded) - assertMatch(interpreted.a.native, obj.a) + assertMatch(interpreted.a.asUint64(), obj.a) assertMatch(interpreted.b.native, obj.b) assertMatch(interpreted.c.native, obj.c) - assertMatch(interpreted.d.native[0].native, obj.d[0]) + assertMatch(interpreted.d.native[0].asUint64(), obj.d[0]) assertMatch(interpreted.d.native[1].native, obj.d[1]) assertMatch(interpreted.d.native[2].native, obj.d[2]) assertMatch(decoded, obj) @@ -776,14 +776,14 @@ describe('native mutable object', () => { const interpreted = interpretAsArc4(encoded) const decoded = decodeArc4(encoded) - assertMatch(interpreted.a.native, obj.a) - assertMatch(interpreted.b.x.p.native, obj.b.x.p) + assertMatch(interpreted.a.asUint64(), obj.a) + assertMatch(interpreted.b.x.p.asUint64(), obj.b.x.p) assertMatch(interpreted.b.x.q.native, obj.b.x.q) assertMatch(interpreted.b.y.length, obj.b.y.length) for (let i = 0; i < obj.b.y.length; i++) { assertMatch(interpreted.b.y[i].native, obj.b.y[i]) } - assertMatch(interpreted.b.z.native[0].native, obj.b.z[0]) + assertMatch(interpreted.b.z.native[0].asUint64(), obj.b.z[0]) assertMatch(interpreted.b.z.native[1].native, obj.b.z[1]) assertMatch(interpreted.c.native, obj.c) assertMatch(decoded, obj) diff --git a/tests/native-readonly-array.algo.spec.ts b/tests/native-readonly-array.algo.spec.ts index 5826f602..9646785e 100644 --- a/tests/native-readonly-array.algo.spec.ts +++ b/tests/native-readonly-array.algo.spec.ts @@ -345,7 +345,7 @@ describe('native readonly array', () => { assertMatch(interpreted.length, arr.length) for (let i = 0; i < arr.length; i++) { - assertMatch(interpreted[i].native, arr[i]) + assertMatch(interpreted[i].asUint64(), arr[i]) } assertMatch(decoded, arr) }) @@ -399,7 +399,7 @@ describe('native readonly array', () => { for (let i = 0; i < arr.length; i++) { assertMatch(interpreted[i].length, arr[i].length) for (let j = 0; j < arr[i].length; j++) { - assertMatch(interpreted[i][j].native, arr[i][j]) + assertMatch(interpreted[i][j].asUint64(), arr[i][j]) } } assertMatch(decoded, arr) @@ -419,7 +419,7 @@ describe('native readonly array', () => { for (let i = 0; i < arr.length; i++) { assertMatch(interpreted[i].length, arr[i].length) for (let j = 0; j < arr[i].length; j++) { - assertMatch(interpreted[i][j].native, arr[i][j]) + assertMatch(interpreted[i][j].asUint64(), arr[i][j]) } } assertMatch(decoded, arr) @@ -437,7 +437,7 @@ describe('native readonly array', () => { assertMatch(interpreted.length, arr.length) for (let i = 0; i < arr.length; i++) { - assertMatch(interpreted[i].native[0].native, arr[i][0]) + assertMatch(interpreted[i].native[0].asUint64(), arr[i][0]) assertMatch(interpreted[i].native[1].native, arr[i][1]) } assertMatch(decoded, arr) @@ -457,8 +457,8 @@ describe('native readonly array', () => { assertMatch(interpreted.length, arr.length) for (let i = 0; i < arr.length; i++) { - assertMatch(interpreted[i].x.native, arr[i].x) - assertMatch(interpreted[i].y.native, arr[i].y) + assertMatch(interpreted[i].x.asUint64(), arr[i].x) + assertMatch(interpreted[i].y.asUint64(), arr[i].y) } assertMatch(decoded, arr) }) diff --git a/tests/native-readonly-object.algo.spec.ts b/tests/native-readonly-object.algo.spec.ts index 81af00ef..9818e920 100644 --- a/tests/native-readonly-object.algo.spec.ts +++ b/tests/native-readonly-object.algo.spec.ts @@ -605,7 +605,7 @@ describe('native readonly object', () => { const interpreted = interpretAsArc4(encoded) const decoded = decodeArc4(encoded) - assertMatch(interpreted.a.native, obj.a) + assertMatch(interpreted.a.asUint64(), obj.a) assertMatch(interpreted.b.native, obj.b) assertMatch(interpreted.c.native, obj.c) assertMatch(interpreted.d.native, obj.d) @@ -636,10 +636,10 @@ describe('native readonly object', () => { const interpreted = interpretAsArc4(encoded) const decoded = decodeArc4(encoded) - assertMatch(interpreted.a.native, obj.a) + assertMatch(interpreted.a.asUint64(), obj.a) assertMatch(interpreted.b.native, obj.b) assertMatch(interpreted.c.native, obj.c) - assertMatch(interpreted.d.x.native, obj.d.x) + assertMatch(interpreted.d.x.asUint64(), obj.d.x) assertMatch(interpreted.d.y.native, obj.d.y) assertMatch(interpreted.d.z.native, obj.d.z) assertMatch(decoded, obj) @@ -664,12 +664,12 @@ describe('native readonly object', () => { const interpreted = interpretAsArc4(encoded) const decoded = decodeArc4(encoded) - assertMatch(interpreted.a.native, obj.a) + assertMatch(interpreted.a.asUint64(), obj.a) assertMatch(interpreted.b.native, obj.b) assertMatch(interpreted.c.native, obj.c) assertMatch(interpreted.d.length, obj.d.length) for (let i = 0; i < obj.d.length; i++) { - assertMatch(interpreted.d[i].native, obj.d[i]) + assertMatch(interpreted.d[i].asUint64(), obj.d[i]) } assertMatch(decoded, obj) }) @@ -693,12 +693,12 @@ describe('native readonly object', () => { const interpreted = interpretAsArc4(encoded) const decoded = decodeArc4(encoded) - assertMatch(interpreted.a.native, obj.a) + assertMatch(interpreted.a.asUint64(), obj.a) assertMatch(interpreted.b.native, obj.b) assertMatch(interpreted.c.native, obj.c) assertMatch(interpreted.d.length, obj.d.length) for (let i = 0; i < obj.d.length; i++) { - assertMatch(interpreted.d[i].native, obj.d[i]) + assertMatch(interpreted.d[i].asUint64(), obj.d[i]) } assertMatch(decoded, obj) }) @@ -722,10 +722,10 @@ describe('native readonly object', () => { const interpreted = interpretAsArc4(encoded) const decoded = decodeArc4(encoded) - assertMatch(interpreted.a.native, obj.a) + assertMatch(interpreted.a.asUint64(), obj.a) assertMatch(interpreted.b.native, obj.b) assertMatch(interpreted.c.native, obj.c) - assertMatch(interpreted.d.native[0].native, obj.d[0]) + assertMatch(interpreted.d.native[0].asUint64(), obj.d[0]) assertMatch(interpreted.d.native[1].native, obj.d[1]) assertMatch(interpreted.d.native[2].native, obj.d[2]) assertMatch(decoded, obj) @@ -787,14 +787,14 @@ describe('native readonly object', () => { const interpreted = interpretAsArc4(encoded) const decoded = decodeArc4(encoded) - assertMatch(interpreted.a.native, obj.a) - assertMatch(interpreted.b.x.p.native, obj.b.x.p) + assertMatch(interpreted.a.asUint64(), obj.a) + assertMatch(interpreted.b.x.p.asUint64(), obj.b.x.p) assertMatch(interpreted.b.x.q.native, obj.b.x.q) assertMatch(interpreted.b.y.length, obj.b.y.length) for (let i = 0; i < obj.b.y.length; i++) { assertMatch(interpreted.b.y[i].native, obj.b.y[i]) } - assertMatch(interpreted.b.z.native[0].native, obj.b.z[0]) + assertMatch(interpreted.b.z.native[0].asUint64(), obj.b.z[0]) assertMatch(interpreted.b.z.native[1].native, obj.b.z[1]) assertMatch(interpreted.c.native, obj.c) assertMatch(decoded, obj) diff --git a/tests/references/box-map.algo.spec.ts b/tests/references/box-map.algo.spec.ts index 6a5e0c8b..d9ac481f 100644 --- a/tests/references/box-map.algo.spec.ts +++ b/tests/references/box-map.algo.spec.ts @@ -312,22 +312,22 @@ describe('BoxMap', () => { const value = new DynamicArray(new arc4.Uint64(100), new arc4.Uint64(200)) boxMap(key).value = value expect(boxMap(key).value.length).toEqual(2) - expect(boxMap(key).value.at(-1).native).toEqual(200) + expect(boxMap(key).value.at(-1).asUint64()).toEqual(200) // newly pushed value should be retained boxMap(key).value.push(new arc4.Uint64(300)) expect(boxMap(key).value.length).toEqual(3) - expect(boxMap(key).value.at(-1).native).toEqual(300) + expect(boxMap(key).value.at(-1).asUint64()).toEqual(300) // setting bytes value through op should be reflected in the box value. const copy = clone(boxMap(key).value) copy[2] = new arc4.Uint64(400) - expect(boxMap(key).value.at(-1).native).toEqual(300) + expect(boxMap(key).value.at(-1).asUint64()).toEqual(300) const fullKey = keyPrefix.concat(toBytes(key)) op.Box.put(fullKey, toBytes(copy)) expect(boxMap(key).value.length).toEqual(3) - expect(boxMap(key).value.at(-1).native).toEqual(400) + expect(boxMap(key).value.at(-1).asUint64()).toEqual(400) }) }) @@ -340,26 +340,26 @@ describe('BoxMap', () => { const boxRefA = box1.ref boxRefA.replace(1, new Uint8(123).bytes) - expect(box1.value[0].native).toEqual(123) - expect(boxMap(1).value[0].native).toEqual(123) + expect(box1.value[0].asUint64()).toEqual(123) + expect(boxMap(1).value[0].asUint64()).toEqual(123) const boxRefB = box1.ref boxRefB.replace(2, new Uint8(255).bytes) - expect(box1.value[1].native).toEqual(65280) - expect(boxMap(1).value[1].native).toEqual(65280) + expect(box1.value[1].asUint64()).toEqual(65280) + expect(boxMap(1).value[1].asUint64()).toEqual(65280) const box2 = boxMap(2) box2.create() const boxRefC = box2.ref boxRefC.replace(1, new Uint8(223).bytes) - expect(box2.value[0].native).toEqual(223) - expect(boxMap(2).value[0].native).toEqual(223) + expect(box2.value[0].asUint64()).toEqual(223) + expect(boxMap(2).value[0].asUint64()).toEqual(223) const boxRefD = box2.ref boxRefD.replace(3, new Uint8(255).bytes) - expect(box2.value[1].native).toEqual(255) - expect(boxMap(2).value[1].native).toEqual(255) + expect(box2.value[1].asUint64()).toEqual(255) + expect(boxMap(2).value[1].asUint64()).toEqual(255) }) }) }) diff --git a/tests/references/box.algo.spec.ts b/tests/references/box.algo.spec.ts index e2327f66..8dc5f42a 100644 --- a/tests/references/box.algo.spec.ts +++ b/tests/references/box.algo.spec.ts @@ -260,8 +260,8 @@ describe('Box', () => { const [oca, txn] = deferredReadCall.submit().native const app = ctx.ledger.getApplicationForContract(contract) - expect(toBytes(ctx.ledger.getBox(app, 'oca'))).toEqual(itob(oca.native)) - expect(toBytes(ctx.ledger.getBox(app, 'txn'))).toEqual(itob(txn.native)) + expect(toBytes(ctx.ledger.getBox(app, 'oca'))).toEqual(itob(oca.asUint64())) + expect(toBytes(ctx.ledger.getBox(app, 'txn'))).toEqual(itob(txn.asUint64())) }) }) @@ -295,21 +295,21 @@ describe('Box', () => { const value = new DynamicArray(new arc4.Uint64(100), new arc4.Uint64(200)) box.value = value expect(box.value.length).toEqual(2) - expect(box.value.at(-1).native).toEqual(200) + expect(box.value.at(-1).asUint64()).toEqual(200) // newly pushed value should be retained box.value.push(new arc4.Uint64(300)) expect(box.value.length).toEqual(3) - expect(box.value.at(-1).native).toEqual(300) + expect(box.value.at(-1).asUint64()).toEqual(300) // setting bytes value through op should be reflected in the box value. const copy = clone(box.value) copy[2] = new arc4.Uint64(400) - expect(box.value.at(-1).native).toEqual(300) + expect(box.value.at(-1).asUint64()).toEqual(300) op.Box.put(key, toBytes(copy)) expect(box.value.length).toEqual(3) - expect(box.value.at(-1).native).toEqual(400) + expect(box.value.at(-1).asUint64()).toEqual(400) }) }) @@ -320,11 +320,11 @@ describe('Box', () => { const boxRef1 = box.ref boxRef1.replace(1, new Uint8(123).bytes) - expect(box.value[0].native).toEqual(123) + expect(box.value[0].asUint64()).toEqual(123) const boxRef2 = box.ref boxRef2.replace(2, new Uint8(255).bytes) - expect(box.value[1].native).toEqual(65280) + expect(box.value[1].asUint64()).toEqual(65280) }) }) From f4c737d59634e5717d3d755ed23711b6c098cc6f Mon Sep 17 00:00:00 2001 From: Bobby Lat Date: Fri, 12 Sep 2025 08:26:23 +0700 Subject: [PATCH 47/68] chore: update dependencies and fix audit vulnerabilities --- package-lock.json | 58 +++++++++++++++++++++++++---------------------- package.json | 4 ++-- 2 files changed, 33 insertions(+), 29 deletions(-) diff --git a/package-lock.json b/package-lock.json index 832baa96..fd9b8092 100644 --- a/package-lock.json +++ b/package-lock.json @@ -8,8 +8,8 @@ "name": "@algorandfoundation/algorand-typescript-testing", "version": "1.0.0", "dependencies": { - "@algorandfoundation/algorand-typescript": "1.0.0-alpha.80", - "@algorandfoundation/puya-ts": "1.0.0-alpha.80", + "@algorandfoundation/algorand-typescript": "1.0.0-alpha.81", + "@algorandfoundation/puya-ts": "1.0.0-alpha.81", "elliptic": "^6.6.1", "js-sha256": "^0.11.0", "js-sha3": "^0.9.3", @@ -76,20 +76,21 @@ } }, "node_modules/@algorandfoundation/algorand-typescript": { - "version": "1.0.0-alpha.80", - "resolved": "https://registry.npmjs.org/@algorandfoundation/algorand-typescript/-/algorand-typescript-1.0.0-alpha.80.tgz", - "integrity": "sha512-8w66Oam/eqPNatY2GN1LzCuuwXjHu1Tj2sAuC1/0p3tdtIB5VIueFHSTy6zXUNl2wSsQQGcBc1ukHWNig/3Sew==", + "version": "1.0.0-alpha.81", + "resolved": "https://registry.npmjs.org/@algorandfoundation/algorand-typescript/-/algorand-typescript-1.0.0-alpha.81.tgz", + "integrity": "sha512-T3/90TfUDXgpAq68WFS/1OYGCMjcjAlnh2P8OQtozDzOiqtXpNKCUVy1QmnqFlV5O/epaSItXXssByAE9ilR6g==", "peerDependencies": { "tslib": "^2.6.2" } }, "node_modules/@algorandfoundation/puya-ts": { - "version": "1.0.0-alpha.80", - "resolved": "https://registry.npmjs.org/@algorandfoundation/puya-ts/-/puya-ts-1.0.0-alpha.80.tgz", - "integrity": "sha512-jBfYGnQrsoMq87KB2JBtrbbPbYFCE9ApG41w3JCH0ppcoyPOgIKzkoESAA2lIOKQ+wKasnnW6qn1HXKennLIyA==", + "version": "1.0.0-alpha.81", + "resolved": "https://registry.npmjs.org/@algorandfoundation/puya-ts/-/puya-ts-1.0.0-alpha.81.tgz", + "integrity": "sha512-Cy9ofmHuM/KR31ILS1L8cqMW2Cb6XkY5oue/MnS2fVXDfjSRsKF0mRtrPJeZb8I+Am+oYmcQiwhXOYBJX5A0TA==", "hasInstallScript": true, + "license": "MIT", "dependencies": { - "@algorandfoundation/algorand-typescript": "1.0.0-alpha.80", + "@algorandfoundation/algorand-typescript": "1.0.0-alpha.81", "arcsecond": "^5.0.0", "argparse": "^2.0.1", "chalk": "^5.4.1", @@ -5860,11 +5861,14 @@ } }, "node_modules/fdir": { - "version": "6.4.6", - "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.4.6.tgz", - "integrity": "sha512-hiFoqpyZcfNm1yc4u8oWCf9A2c4D3QjCrks3zmoVKVxpQRzmPNar1hUJcBG2RQHvEVGDN+Jm81ZheVLAQMK6+w==", + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", + "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", "dev": true, "license": "MIT", + "engines": { + "node": ">=12.0.0" + }, "peerDependencies": { "picomatch": "^3 || ^4" }, @@ -11514,9 +11518,9 @@ "license": "ISC" }, "node_modules/picomatch": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.2.tgz", - "integrity": "sha512-M7BAV6Rlcy5u+m6oPhAPFgJTzAioX/6B0DxyvDlo9l8+T3nLKbrczg2WLUyzd45L8RqfUMyGPzekbMvX2Ldkwg==", + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", + "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", "dev": true, "license": "MIT", "engines": { @@ -13478,14 +13482,14 @@ "license": "MIT" }, "node_modules/tinyglobby": { - "version": "0.2.14", - "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.14.tgz", - "integrity": "sha512-tX5e7OM1HnYr2+a2C/4V0htOcSQcoSTH9KgJnVvNm5zm/cyEWKJ7j7YutsH9CxMdtOkkLFy2AHrMci9IM8IPZQ==", + "version": "0.2.15", + "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.15.tgz", + "integrity": "sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==", "dev": true, "license": "MIT", "dependencies": { - "fdir": "^6.4.4", - "picomatch": "^4.0.2" + "fdir": "^6.5.0", + "picomatch": "^4.0.3" }, "engines": { "node": ">=12.0.0" @@ -13937,18 +13941,18 @@ } }, "node_modules/vite": { - "version": "7.0.5", - "resolved": "https://registry.npmjs.org/vite/-/vite-7.0.5.tgz", - "integrity": "sha512-1mncVwJxy2C9ThLwz0+2GKZyEXuC3MyWtAAlNftlZZXZDP3AJt5FmwcMit/IGGaNZ8ZOB2BNO/HFUB+CpN0NQw==", + "version": "7.1.5", + "resolved": "https://registry.npmjs.org/vite/-/vite-7.1.5.tgz", + "integrity": "sha512-4cKBO9wR75r0BeIWWWId9XK9Lj6La5X846Zw9dFfzMRw38IlTk2iCcUt6hsyiDRcPidc55ZParFYDXi0nXOeLQ==", "dev": true, "license": "MIT", "dependencies": { "esbuild": "^0.25.0", - "fdir": "^6.4.6", - "picomatch": "^4.0.2", + "fdir": "^6.5.0", + "picomatch": "^4.0.3", "postcss": "^8.5.6", - "rollup": "^4.40.0", - "tinyglobby": "^0.2.14" + "rollup": "^4.43.0", + "tinyglobby": "^0.2.15" }, "bin": { "vite": "bin/vite.js" diff --git a/package.json b/package.json index f14dea0c..67ae0450 100644 --- a/package.json +++ b/package.json @@ -68,8 +68,8 @@ "vitest": "3.2.4" }, "dependencies": { - "@algorandfoundation/algorand-typescript": "1.0.0-alpha.80", - "@algorandfoundation/puya-ts": "1.0.0-alpha.80", + "@algorandfoundation/algorand-typescript": "1.0.0-alpha.81", + "@algorandfoundation/puya-ts": "1.0.0-alpha.81", "elliptic": "^6.6.1", "js-sha256": "^0.11.0", "js-sha3": "^0.9.3", From dcb3dd056e2eb3a0ac28beaf6ad60f249da711f2 Mon Sep 17 00:00:00 2001 From: Bobby Lat Date: Mon, 15 Sep 2025 14:13:53 +0700 Subject: [PATCH 48/68] feat: make BoxRef methods directly accessible on Box class --- docs/coverage.md | 1 - examples/proof-of-attendance/contract.algo.ts | 9 +- examples/simple-voting/contract.algo.spec.ts | 2 +- examples/voting/contract.algo.ts | 4 +- package-lock.json | 18 +- package.json | 4 +- src/impl/box.ts | 10 +- src/impl/crypto.ts | 4 +- src/impl/encoded-types/encoded-types.ts | 340 ++++++++++-------- src/impl/encoded-types/helpers.ts | 10 +- src/impl/encoded-types/utils.ts | 2 +- src/impl/primitives.ts | 4 +- src/impl/pure.ts | 2 +- src/impl/reference.ts | 6 +- src/impl/state.ts | 199 +++------- src/internal/index.ts | 2 +- src/subcontexts/contract-context.ts | 5 +- src/subcontexts/ledger-context.ts | 6 +- src/util.ts | 2 +- tests/arc4/resource-encoding.algo.spec.ts | 2 +- tests/crypto-op-codes.algo.spec.ts | 8 +- tests/pure-op-codes.algo.spec.ts | 2 +- tests/references/box-map.algo.spec.ts | 12 +- tests/references/box-ref.algo.spec.ts | 61 ++-- tests/references/box.algo.spec.ts | 53 ++- vitest.setup.ts | 2 +- 26 files changed, 389 insertions(+), 381 deletions(-) diff --git a/docs/coverage.md b/docs/coverage.md index 0ebdda04..6bdda146 100644 --- a/docs/coverage.md +++ b/docs/coverage.md @@ -15,7 +15,6 @@ See which `algorand-typescript` stubs are implemented by the `algorand-typescrip | BigUint | Native | | Box | Emulated | | BoxMap | Emulated | -| BoxRef | Emulated | | Bytes | Native | | CompiledContract | Mockable | | CompiledLogicSig | Mockable | diff --git a/examples/proof-of-attendance/contract.algo.ts b/examples/proof-of-attendance/contract.algo.ts index 3d9cd7bd..963b91aa 100644 --- a/examples/proof-of-attendance/contract.algo.ts +++ b/examples/proof-of-attendance/contract.algo.ts @@ -4,7 +4,6 @@ import { assert, Box, BoxMap, - BoxRef, Bytes, ensureBudget, Global, @@ -63,11 +62,11 @@ export default class ProofOfAttendance extends arc4.Contract { const mintedAsset = this.mintPoa(Txn.sender) this.totalAttendees.value += 1 - const boxRef = BoxRef({ key: Txn.sender.bytes }) + const boxRef = Box({ key: Txn.sender.bytes }) const hasClaimed = boxRef.exists assert(!hasClaimed, 'Already claimed POA') - boxRef.put(op.itob(mintedAsset.id)) + boxRef.value = op.itob(mintedAsset.id) } @arc4.abimethod() @@ -99,7 +98,7 @@ export default class ProofOfAttendance extends arc4.Contract { @readonly getPoaIdWithBoxRef(): uint64 { - const boxRef = BoxRef({ key: Txn.sender.bytes }) + const boxRef = Box({ key: Txn.sender.bytes }) const [poaId, exists] = boxRef.maybe() assert(exists, 'POA not found') return op.btoi(poaId) @@ -153,7 +152,7 @@ export default class ProofOfAttendance extends arc4.Contract { @arc4.abimethod() claimPoaWithBoxRef(optInTxn: gtxn.AssetTransferTxn) { - const boxRef = BoxRef({ key: Txn.sender.bytes }) + const boxRef = Box({ key: Txn.sender.bytes }) const [poaId, exists] = boxRef.maybe() assert(exists, 'POA not found, attendance validation failed!') assert(optInTxn.xferAsset.id === op.btoi(poaId), 'POA ID mismatch') diff --git a/examples/simple-voting/contract.algo.spec.ts b/examples/simple-voting/contract.algo.spec.ts index 861db966..9effdfcc 100644 --- a/examples/simple-voting/contract.algo.spec.ts +++ b/examples/simple-voting/contract.algo.spec.ts @@ -31,7 +31,7 @@ describe('Simple voting contract', () => { .execute(contract.approvalProgram) expect(result).toEqual(1) - expect(contract.topic.value).toBe(topic) + expect(contract.topic.value).toEqual(topic) }) }) }) diff --git a/examples/voting/contract.algo.ts b/examples/voting/contract.algo.ts index fe0cd606..d7258a46 100644 --- a/examples/voting/contract.algo.ts +++ b/examples/voting/contract.algo.ts @@ -4,8 +4,8 @@ import { arc4, assert, assertMatch, + Box, BoxMap, - BoxRef, Bytes, clone, ensureBudget, @@ -46,7 +46,7 @@ export class VotingRoundApp extends arc4.Contract { isBootstrapped = GlobalState({ initialValue: false }) voterCount = GlobalState({ initialValue: Uint64(0) }) closeTime = GlobalState() - tallyBox = BoxRef({ key: Bytes`V` }) + tallyBox = Box({ key: Bytes`V` }) votesByAccount = BoxMap({ keyPrefix: Bytes() }) voteId = GlobalState() snapshotPublicKey = GlobalState>() diff --git a/package-lock.json b/package-lock.json index fd9b8092..218d50df 100644 --- a/package-lock.json +++ b/package-lock.json @@ -8,8 +8,8 @@ "name": "@algorandfoundation/algorand-typescript-testing", "version": "1.0.0", "dependencies": { - "@algorandfoundation/algorand-typescript": "1.0.0-alpha.81", - "@algorandfoundation/puya-ts": "1.0.0-alpha.81", + "@algorandfoundation/algorand-typescript": "1.0.0-alpha.82", + "@algorandfoundation/puya-ts": "1.0.0-alpha.82", "elliptic": "^6.6.1", "js-sha256": "^0.11.0", "js-sha3": "^0.9.3", @@ -76,21 +76,21 @@ } }, "node_modules/@algorandfoundation/algorand-typescript": { - "version": "1.0.0-alpha.81", - "resolved": "https://registry.npmjs.org/@algorandfoundation/algorand-typescript/-/algorand-typescript-1.0.0-alpha.81.tgz", - "integrity": "sha512-T3/90TfUDXgpAq68WFS/1OYGCMjcjAlnh2P8OQtozDzOiqtXpNKCUVy1QmnqFlV5O/epaSItXXssByAE9ilR6g==", + "version": "1.0.0-alpha.82", + "resolved": "https://registry.npmjs.org/@algorandfoundation/algorand-typescript/-/algorand-typescript-1.0.0-alpha.82.tgz", + "integrity": "sha512-TG/IqbzJ7Kml6qS5eGfxCGWYG9vWsjoWzYwsg7c2inpIHUzUxgRuypxfig3WVNtZy6SQL1uQ0c3noz3ZXxicqg==", "peerDependencies": { "tslib": "^2.6.2" } }, "node_modules/@algorandfoundation/puya-ts": { - "version": "1.0.0-alpha.81", - "resolved": "https://registry.npmjs.org/@algorandfoundation/puya-ts/-/puya-ts-1.0.0-alpha.81.tgz", - "integrity": "sha512-Cy9ofmHuM/KR31ILS1L8cqMW2Cb6XkY5oue/MnS2fVXDfjSRsKF0mRtrPJeZb8I+Am+oYmcQiwhXOYBJX5A0TA==", + "version": "1.0.0-alpha.82", + "resolved": "https://registry.npmjs.org/@algorandfoundation/puya-ts/-/puya-ts-1.0.0-alpha.82.tgz", + "integrity": "sha512-uxjRljHI7+hVutCtxY7nA6ZCEzZyZUeh0DoQSH5P/nqJ4rvLAtjXnYCs/SRDwG+WllwSjpCort2AUgTkIAwPbQ==", "hasInstallScript": true, "license": "MIT", "dependencies": { - "@algorandfoundation/algorand-typescript": "1.0.0-alpha.81", + "@algorandfoundation/algorand-typescript": "1.0.0-alpha.82", "arcsecond": "^5.0.0", "argparse": "^2.0.1", "chalk": "^5.4.1", diff --git a/package.json b/package.json index 67ae0450..fbca85e9 100644 --- a/package.json +++ b/package.json @@ -68,8 +68,8 @@ "vitest": "3.2.4" }, "dependencies": { - "@algorandfoundation/algorand-typescript": "1.0.0-alpha.81", - "@algorandfoundation/puya-ts": "1.0.0-alpha.81", + "@algorandfoundation/algorand-typescript": "1.0.0-alpha.82", + "@algorandfoundation/puya-ts": "1.0.0-alpha.82", "elliptic": "^6.6.1", "js-sha256": "^0.11.0", "js-sha3": "^0.9.3", diff --git a/src/impl/box.ts b/src/impl/box.ts index 0120feea..b3f039b8 100644 --- a/src/impl/box.ts +++ b/src/impl/box.ts @@ -2,7 +2,7 @@ import type { bytes, op, uint64 } from '@algorandfoundation/algorand-typescript' import { MAX_BOX_SIZE } from '../constants' import { lazyContext } from '../context-helpers/internal-context' import { AvmError, InternalError } from '../errors' -import { asBytes, asBytesCls, asNumber, asUint8Array, conactUint8Arrays } from '../util' +import { asBytes, asBytesCls, asNumber, asUint8Array, concatUint8Arrays } from '../util' import { toBytes } from './encoded-types' import type { StubBytesCompat, StubUint64Compat } from './primitives' @@ -79,7 +79,7 @@ export const Box: typeof op.Box = { if (start + newContent.length > boxContent.length) { throw new InternalError('Replacement content exceeds box size') } - const updatedContent = conactUint8Arrays(boxContent.slice(0, start), newContent, boxContent.slice(start + newContent.length)) + const updatedContent = concatUint8Arrays(boxContent.slice(0, start), newContent, boxContent.slice(start + newContent.length)) lazyContext.ledger.setBox(app, name, updatedContent) }, resize(a: StubBytesCompat, b: StubUint64Compat): void { @@ -93,7 +93,7 @@ export const Box: typeof op.Box = { const size = boxContent.length let updatedContent if (newSize > size) { - updatedContent = conactUint8Arrays(boxContent, new Uint8Array(newSize - size)) + updatedContent = concatUint8Arrays(boxContent, new Uint8Array(newSize - size)) } else { updatedContent = boxContent.slice(0, newSize) } @@ -114,12 +114,12 @@ export const Box: typeof op.Box = { throw new InternalError('Start index exceeds box size') } const end = Math.min(start + length, size) - let updatedContent = conactUint8Arrays(boxContent.slice(0, start), newContent, boxContent.slice(end)) + let updatedContent = concatUint8Arrays(boxContent.slice(0, start), newContent, boxContent.slice(end)) // Adjust the size if necessary if (updatedContent.length > size) { updatedContent = updatedContent.slice(0, size) } else if (updatedContent.length < size) { - updatedContent = conactUint8Arrays(updatedContent, new Uint8Array(size - asNumber(updatedContent.length))) + updatedContent = concatUint8Arrays(updatedContent, new Uint8Array(size - asNumber(updatedContent.length))) } lazyContext.ledger.setBox(app, name, updatedContent) }, diff --git a/src/impl/crypto.ts b/src/impl/crypto.ts index eb037ced..3ccfbc2e 100644 --- a/src/impl/crypto.ts +++ b/src/impl/crypto.ts @@ -8,7 +8,7 @@ import nacl from 'tweetnacl' import { LOGIC_DATA_PREFIX, PROGRAM_TAG } from '../constants' import { lazyContext } from '../context-helpers/internal-context' import { InternalError, NotImplementedError } from '../errors' -import { asBytes, asBytesCls, asUint8Array, conactUint8Arrays } from '../util' +import { asBytes, asBytesCls, asUint8Array, concatUint8Arrays } from '../util' import type { StubBytesCompat, StubUint64Compat } from './primitives' import { Bytes, BytesCls, FixedBytes, Uint64Cls } from './primitives' @@ -57,7 +57,7 @@ export const ed25519verify = (a: StubBytesCompat, b: StubBytesCompat, c: StubByt const txn = lazyContext.activeGroup.activeTransaction as gtxn.ApplicationCallTxn const programBytes = asBytesCls(txn.onCompletion == OnCompleteAction.ClearState ? txn.clearStateProgram : txn.approvalProgram) - const logicSig = conactUint8Arrays(asUint8Array(PROGRAM_TAG), programBytes.asUint8Array()) + const logicSig = concatUint8Arrays(asUint8Array(PROGRAM_TAG), programBytes.asUint8Array()) const logicSigAddress = js_sha512.sha512_256.array(logicSig) const addressBytes = Bytes(logicSigAddress) diff --git a/src/impl/encoded-types/encoded-types.ts b/src/impl/encoded-types/encoded-types.ts index 8c277998..654c6c88 100644 --- a/src/impl/encoded-types/encoded-types.ts +++ b/src/impl/encoded-types/encoded-types.ts @@ -40,7 +40,7 @@ import { asUint64, asUint64Cls, asUint8Array, - conactUint8Arrays, + concatUint8Arrays, uint8ArrayToNumber, } from '../../util' import { BytesBackedCls, Uint64BackedCls } from '../base' @@ -83,9 +83,12 @@ import type { } from './types' import { getMaxLengthOfStaticContentType } from './utils' +interface _ARC4Encodedint8Array extends _ARC4Encoded { + get uint8ArrayValue(): Uint8Array +} /** @internal */ -export class Uint extends _Uint { - private value: Uint8Array +export class Uint extends _Uint implements _ARC4Encodedint8Array { + private _value: Uint8Array private bitSize: N typeInfo: TypeInfo @@ -100,16 +103,16 @@ export class Uint extends _Uint { const maxValue = maxBigIntValue(this.bitSize) assert(bigIntValue <= maxValue, `expected value <= ${maxValue}, got: ${bigIntValue}`) - this.value = encodingUtil.bigIntToUint8Array(bigIntValue, maxBytesLength(this.bitSize)) + this._value = encodingUtil.bigIntToUint8Array(bigIntValue, maxBytesLength(this.bitSize)) } get native() { - const bigIntValue = encodingUtil.uint8ArrayToBigInt(this.value) + const bigIntValue = encodingUtil.uint8ArrayToBigInt(this._value) return this.bitSize <= UINT64_SIZE ? asUint64(bigIntValue) : asBigUint(bigIntValue) } asUint64() { - const bigIntValue = encodingUtil.uint8ArrayToBigInt(this.value) + const bigIntValue = encodingUtil.uint8ArrayToBigInt(this._value) if (bigIntValue > MAX_UINT64) { throw new CodeError('value too large to fit in uint64') } @@ -117,12 +120,16 @@ export class Uint extends _Uint { } asBigUint() { - const bigIntValue = encodingUtil.uint8ArrayToBigInt(this.value) + const bigIntValue = encodingUtil.uint8ArrayToBigInt(this._value) return asBigUint(bigIntValue) } + get uint8ArrayValue(): Uint8Array { + return this._value + } + get bytes(): bytes { - return Bytes(this.value) + return Bytes(this._value) } equals(other: this): boolean { @@ -139,7 +146,7 @@ export class Uint extends _Uint { bytesValue = bytesValue.slice(4) } const result = new Uint(typeInfo) - result.value = asUint8Array(bytesValue) + result._value = asUint8Array(bytesValue) return result } @@ -149,8 +156,8 @@ export class Uint extends _Uint { } /** @internal */ -export class UFixed extends _UFixed { - private value: Uint8Array +export class UFixed extends _UFixed implements _ARC4Encodedint8Array { + private _value: Uint8Array private bitSize: N private precision: M typeInfo: TypeInfo @@ -169,16 +176,20 @@ export class UFixed extends _UFixed { const maxValue = maxBigIntValue(this.bitSize) assert(bigIntValue <= maxValue, `expected value <= ${maxValue}, got: ${bigIntValue}`) - this.value = encodingUtil.bigIntToUint8Array(bigIntValue, maxBytesLength(this.bitSize)) + this._value = encodingUtil.bigIntToUint8Array(bigIntValue, maxBytesLength(this.bitSize)) } get native() { - const bigIntValue = encodingUtil.uint8ArrayToBigInt(this.value) + const bigIntValue = encodingUtil.uint8ArrayToBigInt(this._value) return this.bitSize <= UINT64_SIZE ? asUint64(bigIntValue) : asBigUint(bigIntValue) } + get uint8ArrayValue(): Uint8Array { + return this._value + } + get bytes(): bytes { - return Bytes(this.value) + return Bytes(this._value) } equals(other: this): boolean { @@ -199,7 +210,7 @@ export class UFixed extends _UFixed { bytesValue = bytesValue.slice(4) } const result = new UFixed(typeInfo, '0.0') - result.value = asUint8Array(bytesValue) + result._value = asUint8Array(bytesValue) return result } @@ -210,31 +221,35 @@ export class UFixed extends _UFixed { } /** @internal */ -export class Byte extends _Byte { +export class Byte extends _Byte implements _ARC4Encodedint8Array { typeInfo: TypeInfo - private value: Uint<8> + private _value: Uint<8> constructor(typeInfo: TypeInfo | string, v?: CompatForArc4Int<8>) { super(v) this.typeInfo = typeof typeInfo === 'string' ? JSON.parse(typeInfo) : typeInfo - this.value = new Uint<8>(typeInfo, v) + this._value = new Uint<8>(typeInfo, v) } asUint64() { - return this.value.asUint64() + return this._value.asUint64() } asBigUint() { - return this.value.asBigUint() + return this._value.asBigUint() + } + + get uint8ArrayValue(): Uint8Array { + return this._value.uint8ArrayValue } get bytes(): bytes { - return this.value.bytes + return this._value.bytes } equals(other: this): boolean { - if (!(other instanceof Byte) || JSON.stringify(this.value.typeInfo) !== JSON.stringify(other.value.typeInfo)) { - throw new CodeError(`Expected expression of type ${this.value.typeInfo.name}, got ${other.value.typeInfo.name}`) + if (!(other instanceof Byte) || JSON.stringify(this._value.typeInfo) !== JSON.stringify(other._value.typeInfo)) { + throw new CodeError(`Expected expression of type ${this._value.typeInfo.name}, got ${other._value.typeInfo.name}`) } return this.bytes.equals(other.bytes) } @@ -242,7 +257,7 @@ export class Byte extends _Byte { static fromBytes(value: StubBytesCompat | Uint8Array, typeInfo: string | TypeInfo, prefix: 'none' | 'log' = 'none'): Byte { const uintNValue = Uint.fromBytes(value, typeInfo, prefix) as Uint<8> const result = new Byte(typeInfo) - result.value = uintNValue + result._value = uintNValue return result } @@ -252,23 +267,27 @@ export class Byte extends _Byte { } /** @internal */ -export class Str extends _Str { +export class Str extends _Str implements _ARC4Encodedint8Array { typeInfo: TypeInfo - private value: Uint8Array + private _value: Uint8Array constructor(typeInfo: TypeInfo | string, s?: StringCompat) { super() const bytesValue = asBytesCls(s ?? '') const bytesLength = encodeLength(bytesValue.length.asNumber()) this.typeInfo = typeof typeInfo === 'string' ? JSON.parse(typeInfo) : typeInfo - this.value = asUint8Array(bytesLength.concat(bytesValue)) + this._value = asUint8Array(bytesLength.concat(bytesValue)) } get native(): string { - return encodingUtil.uint8ArrayToUtf8(this.value.slice(ABI_LENGTH_SIZE)) + return encodingUtil.uint8ArrayToUtf8(this._value.slice(ABI_LENGTH_SIZE)) + } + + get uint8ArrayValue(): Uint8Array { + return this._value } get bytes(): bytes { - return Bytes(this.value) + return Bytes(this._value) } equals(other: this): boolean { @@ -285,24 +304,24 @@ export class Str extends _Str { bytesValue = bytesValue.slice(4) } const result = new Str(typeInfo) - result.value = asUint8Array(bytesValue) + result._value = asUint8Array(bytesValue) return result } } /** @internal */ -export class Bool extends _Bool { - private value: Uint8Array +export class Bool extends _Bool implements _ARC4Encodedint8Array { + private _value: Uint8Array typeInfo: TypeInfo constructor(typeInfo: TypeInfo | string, v?: boolean) { super(v) this.typeInfo = typeof typeInfo === 'string' ? JSON.parse(typeInfo) : typeInfo - this.value = encodingUtil.bigIntToUint8Array(v ? TRUE_BIGINT_VALUE : FALSE_BIGINT_VALUE, 1) + this._value = encodingUtil.bigIntToUint8Array(v ? TRUE_BIGINT_VALUE : FALSE_BIGINT_VALUE, 1) } get native(): boolean { - return encodingUtil.uint8ArrayToBigInt(this.value) === TRUE_BIGINT_VALUE + return encodingUtil.uint8ArrayToBigInt(this._value) === TRUE_BIGINT_VALUE } equals(other: this): boolean { @@ -312,8 +331,12 @@ export class Bool extends _Bool { return this.bytes.equals(other.bytes) } + get uint8ArrayValue(): Uint8Array { + return this._value + } + get bytes(): bytes { - return Bytes(this.value?.length ? this.value : new Uint8Array([0])) + return Bytes(this._value?.length ? this._value : new Uint8Array([0])) } static fromBytes(value: StubBytesCompat | Uint8Array, typeInfo: string | TypeInfo, prefix: 'none' | 'log' = 'none'): Bool { @@ -323,15 +346,18 @@ export class Bool extends _Bool { bytesValue = bytesValue.slice(4) } const result = new Bool(typeInfo) - result.value = asUint8Array(bytesValue) + result._value = asUint8Array(bytesValue) return result } } /** @internal */ -export class StaticArray extends _StaticArray { - private value?: NTuple - private uint8ArrayValue?: Uint8Array +export class StaticArray + extends _StaticArray + implements _ARC4Encodedint8Array +{ + private _value?: NTuple + private _uint8ArrayValue?: Uint8Array private size: number typeInfo: TypeInfo genericArgs: StaticArrayGenericArgs @@ -342,7 +368,7 @@ export class StaticArray ext // if first item is the symbol, we are initialising from bytes // so we don't need to pass the items to the super constructor const isInitialisingFromBytes = items.length === 1 && (items[0] as DeliberateAny) === IS_INITIALISING_FROM_BYTES_SYMBOL - super(...(isInitialisingFromBytes ? [] : (items as DeliberateAny))) + super() this.typeInfo = typeof typeInfo === 'string' ? JSON.parse(typeInfo) : typeInfo this.genericArgs = this.typeInfo.genericArgs as StaticArrayGenericArgs @@ -361,16 +387,20 @@ export class StaticArray ext }) if (items.length) { - this.value = items as NTuple + this._value = items as NTuple } else { - this.uint8ArrayValue = new Uint8Array(getMaxLengthOfStaticContentType(this.typeInfo)) + this._uint8ArrayValue = new Uint8Array(getMaxLengthOfStaticContentType(this.typeInfo)) } } return new Proxy(this, arrayProxyHandler()) as StaticArray } + get uint8ArrayValue(): Uint8Array { + return this._uint8ArrayValue ?? encode(this.items, true) + } + get bytes(): bytes { - return Bytes(this.uint8ArrayValue ?? encode(this.items)) + return Bytes(this._uint8ArrayValue ?? encode(this.items, true)) } equals(other: this): boolean { @@ -385,14 +415,14 @@ export class StaticArray ext } get items(): NTuple { - if (this.uint8ArrayValue) { + if (this._uint8ArrayValue) { const childTypes = Array(this.size).fill(this.genericArgs.elementType) - this.value = decode(this.uint8ArrayValue, childTypes) as NTuple - this.uint8ArrayValue = undefined - return this.value - } else if (this.value) { - this.uint8ArrayValue = undefined - return this.value + this._value = decode(this._uint8ArrayValue, childTypes, true) as NTuple + this._uint8ArrayValue = undefined + return this._value + } else if (this._value) { + this._uint8ArrayValue = undefined + return this._value } throw new CodeError('value is not set') } @@ -402,7 +432,7 @@ export class StaticArray ext } copy(): StaticArray { - return StaticArray.fromBytes(this.bytes, JSON.stringify(this.typeInfo)) as unknown as StaticArray + return StaticArray.fromBytes(this.uint8ArrayValue, JSON.stringify(this.typeInfo)) as unknown as StaticArray } concat(other: Parameters['concat']>[0]): DynamicArray { @@ -428,22 +458,22 @@ export class StaticArray ext typeInfo: string | TypeInfo, prefix: 'none' | 'log' = 'none', ): StaticArray<_ARC4Encoded, number> { - let bytesValue = asBytesCls(value) + let bytesValue = value instanceof Uint8Array ? value : asUint8Array(value) if (prefix === 'log') { - assert(bytesValue.slice(0, 4).equals(ABI_RETURN_VALUE_LOG_PREFIX), 'ABI return prefix not found') + assert(Bytes(bytesValue.slice(0, 4)).equals(ABI_RETURN_VALUE_LOG_PREFIX), 'ABI return prefix not found') bytesValue = bytesValue.slice(4) } // pass the symbol to the constructor to let it know we are initialising from bytes const result = new StaticArray<_ARC4Encoded, number>(typeInfo, IS_INITIALISING_FROM_BYTES_SYMBOL as DeliberateAny) - result.uint8ArrayValue = asUint8Array(bytesValue) + result._uint8ArrayValue = bytesValue return result } } /** @internal */ -export class Address extends _Address { +export class Address extends _Address implements _ARC4Encodedint8Array { typeInfo: TypeInfo - private value: StaticArray + private _value: StaticArray constructor(typeInfo: TypeInfo | string, value?: AccountType | string | bytes) { super(value) @@ -459,13 +489,17 @@ export class Address extends _Address { } avmInvariant(uint8ArrayValue.length === 32, 'Addresses should be 32 bytes') - this.value = StaticArray.fromBytes(uint8ArrayValue, typeInfo) as StaticArray + this._value = StaticArray.fromBytes(uint8ArrayValue, typeInfo) as StaticArray this.typeInfo = typeof typeInfo === 'string' ? JSON.parse(typeInfo) : typeInfo return new Proxy(this, arrayProxyHandler()) as Address } + get uint8ArrayValue(): Uint8Array { + return this._value.uint8ArrayValue + } + get bytes(): bytes { - return this.value.bytes + return this._value.bytes } equals(other: this): boolean { @@ -480,11 +514,11 @@ export class Address extends _Address { } get native(): AccountType { - return Account(this.value.bytes) + return Account(this._value.bytes) } get items(): readonly Byte[] { - return this.value.items + return this._value.items } setItem(_index: number, _value: Byte): void { @@ -494,20 +528,20 @@ export class Address extends _Address { static fromBytes(value: StubBytesCompat | Uint8Array, typeInfo: string | TypeInfo, prefix: 'none' | 'log' = 'none'): Address { const staticArrayValue = StaticArray.fromBytes(value, typeInfo, prefix) as StaticArray const result = new Address(typeInfo) - result.value = staticArrayValue + result._value = staticArrayValue return result } } /** @internal */ -export class DynamicArray extends _DynamicArray { - private value?: TItem[] - private uint8ArrayValue?: Uint8Array +export class DynamicArray extends _DynamicArray implements _ARC4Encodedint8Array { + private _value?: TItem[] + private _uint8ArrayValue?: Uint8Array typeInfo: TypeInfo genericArgs: DynamicArrayGenericArgs constructor(typeInfo: TypeInfo | string, ...items: TItem[]) { - super(...(items as TItem[])) + super() this.typeInfo = typeof typeInfo === 'string' ? JSON.parse(typeInfo) : typeInfo this.genericArgs = this.typeInfo.genericArgs as DynamicArrayGenericArgs @@ -516,13 +550,17 @@ export class DynamicArray extends _DynamicArray { checkItemTypeName(this.genericArgs.elementType, item) }) - this.value = items + this._value = items return new Proxy(this, arrayProxyHandler()) as DynamicArray } + get uint8ArrayValue(): Uint8Array { + return this._uint8ArrayValue ?? this.encodeWithLength(this.items) + } + get bytes(): bytes { - return Bytes(this.uint8ArrayValue ?? this.encodeWithLength(this.items)) + return Bytes(this._uint8ArrayValue ?? this.encodeWithLength(this.items)) } equals(other: this): boolean { @@ -537,15 +575,15 @@ export class DynamicArray extends _DynamicArray extends _DynamicArray { - return DynamicArray.fromBytes(this.bytes, JSON.stringify(this.typeInfo)) as DynamicArray + return DynamicArray.fromBytes(this.uint8ArrayValue, JSON.stringify(this.typeInfo)) as DynamicArray } get native(): TItem[] { @@ -596,19 +634,19 @@ export class DynamicArray extends _DynamicArray extends _Tuple { - private value?: TTuple - private uint8ArrayValue?: Uint8Array +export class Tuple extends _Tuple implements _ARC4Encodedint8Array { + private _value?: TTuple + private _uint8ArrayValue?: Uint8Array typeInfo: TypeInfo genericArgs: TypeInfo[] @@ -628,15 +666,19 @@ export class Tuple extends _Tu checkItemTypeName(this.genericArgs[index], item) }) if (items.length) { - this.value = items + this._value = items } else { - this.uint8ArrayValue = new Uint8Array(getMaxLengthOfStaticContentType(this.typeInfo)) + this._uint8ArrayValue = new Uint8Array(getMaxLengthOfStaticContentType(this.typeInfo)) } } } + get uint8ArrayValue(): Uint8Array { + return this._uint8ArrayValue ?? encode(this.items) + } + get bytes(): bytes { - return Bytes(this.uint8ArrayValue ?? encode(this.items)) + return Bytes(this._uint8ArrayValue ?? encode(this.items)) } equals(other: this): boolean { @@ -659,13 +701,13 @@ export class Tuple extends _Tu } private get items(): TTuple { - if (this.uint8ArrayValue) { - this.value = decode(this.uint8ArrayValue, this.genericArgs) as TTuple - this.uint8ArrayValue = undefined - return this.value - } else if (this.value) { - this.uint8ArrayValue = undefined - return this.value + if (this._uint8ArrayValue) { + this._value = decode(this._uint8ArrayValue, this.genericArgs) as TTuple + this._uint8ArrayValue = undefined + return this._value + } else if (this._value) { + this._uint8ArrayValue = undefined + return this._value } throw new CodeError('value is not set') } @@ -682,14 +724,14 @@ export class Tuple extends _Tu } // pass the symbol to the constructor to let it know we are initialising from bytes const result = new Tuple(typeInfo, IS_INITIALISING_FROM_BYTES_SYMBOL as DeliberateAny) - result.uint8ArrayValue = asUint8Array(bytesValue) + result._uint8ArrayValue = asUint8Array(bytesValue) return result } } /** @internal */ -export class Struct extends (_Struct as DeliberateAny) { - private uint8ArrayValue?: Uint8Array +export class Struct extends (_Struct as DeliberateAny) implements _ARC4Encodedint8Array { + private _uint8ArrayValue?: Uint8Array genericArgs: Record constructor(typeInfo: TypeInfo | string, value: T = {} as T) { @@ -708,13 +750,13 @@ export class Struct extends (_Struct extends (_Struct extends (_Struct { - return Struct.fromBytes(this.bytes, JSON.stringify(this.typeInfo)) as Struct + return Struct.fromBytes(this.uint8ArrayValue, JSON.stringify(this.typeInfo)) as Struct } private decodeAsProperties() { - if (this.uint8ArrayValue) { - const values = decode(this.uint8ArrayValue, Object.values(this.genericArgs)) + if (this._uint8ArrayValue) { + const values = decode(this._uint8ArrayValue, Object.values(this.genericArgs)) Object.keys(this.genericArgs).forEach((key, index) => { ;(this as unknown as StructConstraint)[key] = values[index] }) - this.uint8ArrayValue = undefined + this._uint8ArrayValue = undefined } } @@ -764,26 +810,30 @@ export class Struct extends (_Struct + private _value: DynamicArray constructor(typeInfo: TypeInfo | string, value?: bytes | string) { super(value) - const uint8ArrayValue = conactUint8Arrays(encodeLength(value?.length ?? 0).asUint8Array(), asUint8Array(value ?? new Uint8Array())) - this.value = DynamicArray.fromBytes(uint8ArrayValue, typeInfo) as DynamicArray + const uint8ArrayValue = concatUint8Arrays(encodeLength(value?.length ?? 0).asUint8Array(), asUint8Array(value ?? new Uint8Array())) + this._value = DynamicArray.fromBytes(uint8ArrayValue, typeInfo) as DynamicArray this.typeInfo = typeof typeInfo === 'string' ? JSON.parse(typeInfo) : typeInfo return new Proxy(this, arrayProxyHandler()) as DynamicBytes } + get uint8ArrayValue(): Uint8Array { + return this._value.uint8ArrayValue + } + get bytes(): bytes { - return this.value.bytes + return this._value.bytes } equals(other: this): boolean { @@ -794,15 +844,15 @@ export class DynamicBytes extends _DynamicBytes { } get length(): uint64 { - return this.value.length + return this._value.length } get native(): bytes { - return this.value.bytes.slice(ABI_LENGTH_SIZE) + return this._value.bytes.slice(ABI_LENGTH_SIZE) } get items(): Byte[] { - return this.value.items + return this._value.items } setItem(_index: number, _value: Byte): void { @@ -818,34 +868,38 @@ export class DynamicBytes extends _DynamicBytes { next = otherEntries.next() } const concatenatedBytes = items - .map((item) => item.bytes) - .reduce((acc, curr) => conactUint8Arrays(acc, asUint8Array(curr)), new Uint8Array()) + .map((item) => item.uint8ArrayValue) + .reduce((acc, curr) => concatUint8Arrays(acc, curr), new Uint8Array()) return new DynamicBytes(this.typeInfo, asBytes(concatenatedBytes)) } static fromBytes(value: StubBytesCompat | Uint8Array, typeInfo: string | TypeInfo, prefix: 'none' | 'log' = 'none'): DynamicBytes { const dynamicArrayValue = DynamicArray.fromBytes(value, typeInfo, prefix) as DynamicArray const result = new DynamicBytes(typeInfo) - result.value = dynamicArrayValue + result._value = dynamicArrayValue return result } } /** @internal */ -export class StaticBytes extends _StaticBytes { - private value: StaticArray +export class StaticBytes extends _StaticBytes implements _ARC4Encodedint8Array { + private _value: StaticArray typeInfo: TypeInfo constructor(typeInfo: TypeInfo | string, value?: bytes) { super(value ?? (Bytes() as bytes)) this.typeInfo = typeof typeInfo === 'string' ? JSON.parse(typeInfo) : typeInfo const uint8ArrayValue = asUint8Array(value ?? new Uint8Array(getMaxLengthOfStaticContentType(this.typeInfo))) - this.value = StaticArray.fromBytes(uint8ArrayValue, typeInfo) as unknown as StaticArray + this._value = StaticArray.fromBytes(uint8ArrayValue, typeInfo) as unknown as StaticArray return new Proxy(this, arrayProxyHandler()) as StaticBytes } + get uint8ArrayValue(): Uint8Array { + return this._value.uint8ArrayValue + } + get bytes(): bytes { - return this.value.bytes + return this._value.bytes } equals(other: this): boolean { @@ -856,15 +910,15 @@ export class StaticBytes extends _StaticBytes { - return this.value.bytes as bytes + return this._value.bytes as bytes } get items(): Byte[] { - return this.value.items + return this._value.items } setItem(_index: number, _value: Byte): void { @@ -880,15 +934,15 @@ export class StaticBytes extends _StaticBytes item.bytes) - .reduce((acc, curr) => conactUint8Arrays(acc, asUint8Array(curr)), new Uint8Array()) + .map((item) => item.uint8ArrayValue) + .reduce((acc, curr) => concatUint8Arrays(acc, curr), new Uint8Array()) return new DynamicBytes(this.typeInfo, asBytes(concatenatedBytes)) } static fromBytes(value: StubBytesCompat | Uint8Array, typeInfo: string | TypeInfo, prefix: 'none' | 'log' = 'none'): StaticBytes { const staticArrayValue = StaticArray.fromBytes(value, typeInfo, prefix) as StaticArray const result = new StaticBytes(typeInfo) - result.value = staticArrayValue as StaticArray + result._value = staticArrayValue as StaticArray return result } } @@ -956,7 +1010,7 @@ export class FixedArray extends _FixedArray extends _FixedArray { +const decode = (value: Uint8Array, childTypes: TypeInfo[], isHomogenous?: boolean) => { let i = 0 let arrayIndex = 0 const valuePartitions: Uint8Array[] = [] @@ -1017,8 +1071,8 @@ const decode = (value: Uint8Array, childTypes: TypeInfo[]) => { valuePartitions.push(new Uint8Array()) arrayIndex += ABI_LENGTH_SIZE } else if (['Bool', 'boolean'].includes(childType.name)) { - const before = findBoolTypes(childTypes, i, -1) - let after = findBoolTypes(childTypes, i, 1) + const before = findBoolTypes(childTypes, i, -1, isHomogenous) + let after = findBoolTypes(childTypes, i, 1, isHomogenous) if (before % 8 != 0) { throw new CodeError('"expected before index should have number of bool mod 8 equal 0"') @@ -1077,7 +1131,7 @@ const decode = (value: Uint8Array, childTypes: TypeInfo[]) => { return values } -const encode = (values: _ARC4Encoded[]) => { +const encode = (values: (_ARC4Encoded & { uint8ArrayValue?: Uint8Array })[], isHomogenous?: boolean) => { const length = values.length const heads = [] const tails = [] @@ -1090,11 +1144,11 @@ const encode = (values: _ARC4Encoded[]) => { dynamicLengthTypeIndex.push(isDynamicLengthType(value)) if (dynamicLengthTypeIndex.at(-1)) { heads.push(asUint8Array(Bytes.fromHex('0000'))) - tails.push(asUint8Array(value.bytes)) + tails.push((value as _ARC4Encodedint8Array).uint8ArrayValue ?? asUint8Array(value.bytes)) } else { if (value instanceof _Bool) { - const before = findBool(values, i, -1) - let after = findBool(values, i, 1) + const before = findBool(values, i, -1, isHomogenous) + let after = findBool(values, i, 1, isHomogenous) if (before % 8 != 0) { throw new CodeError('"expected before index should have number of bool mod 8 equal 0"') } @@ -1104,7 +1158,7 @@ const encode = (values: _ARC4Encoded[]) => { heads.push(new Uint8Array([compressedNumber])) i += after } else { - heads.push(asUint8Array(value.bytes)) + heads.push((value as _ARC4Encodedint8Array).uint8ArrayValue ?? asUint8Array(value.bytes)) } tails.push(new Uint8Array()) } @@ -1128,7 +1182,7 @@ const encode = (values: _ARC4Encoded[]) => { tailCurrLength += tails[i].length } - return conactUint8Arrays(valuesLengthBytes, ...heads, ...tails) + return concatUint8Arrays(valuesLengthBytes, ...heads, ...tails) } const isDynamicLengthType = (value: _ARC4Encoded) => { @@ -1268,26 +1322,32 @@ export const getArc4Encoded = (value: DeliberateAny, sourceTypeInfoString?: stri /** @internal */ export const toBytes = (val: unknown, sourceTypeInfoString?: string): bytes => { + return asBytes(toUint8Array(val, sourceTypeInfoString)) +} + +/** @internal */ +export const toUint8Array = (val: unknown, sourceTypeInfoString?: string): Uint8Array => { const uint64Val = asMaybeUint64Cls(val, false) if (uint64Val !== undefined) { - return uint64Val.toBytes().asAlgoTs() + return uint64Val.toBytes().asUint8Array() } const bytesVal = asMaybeBytesCls(val) if (bytesVal !== undefined) { - return bytesVal.asAlgoTs() + return bytesVal.asUint8Array() } const bigUintVal = asMaybeBigUintCls(val) if (bigUintVal !== undefined) { - return bigUintVal.toBytes().asAlgoTs() + return bigUintVal.toBytes().asUint8Array() } if (val instanceof BytesBackedCls) { - return val.bytes + return asUint8Array(val.bytes) } if (val instanceof Uint64BackedCls) { - return asUint64Cls(val.uint64).toBytes().asAlgoTs() + return asUint64Cls(val.uint64).toBytes().asUint8Array() } if (Array.isArray(val) || typeof val === 'object') { - return encodeArc4(sourceTypeInfoString, val) + const arc4Encoded = getArc4Encoded(val, sourceTypeInfoString) + return (arc4Encoded as _ARC4Encodedint8Array).uint8ArrayValue ?? asUint8Array(arc4Encoded.bytes) } throw new InternalError(`Invalid type for bytes: ${nameOfType(val)}`) } @@ -1333,7 +1393,7 @@ export const getEncoder = (typeInfo: TypeInfo): fromBytes => { const staticArray = StaticArray.fromBytes(value, typeInfo, prefix) return new FixedArray( typeInfo, - ...(asNumber(staticArray.bytes.length) ? staticArray.native : ([] as unknown as typeof staticArray.native)), + ...(asNumber(staticArray.uint8ArrayValue.length) ? staticArray.native : ([] as unknown as typeof staticArray.native)), ) } const encoders: Record> = { diff --git a/src/impl/encoded-types/helpers.ts b/src/impl/encoded-types/helpers.ts index 1c1cc225..145935a6 100644 --- a/src/impl/encoded-types/helpers.ts +++ b/src/impl/encoded-types/helpers.ts @@ -39,10 +39,13 @@ export const checkItemTypeName = (type: TypeInfo, value: ARC4Encoded) => { } /** @internal */ -export const findBoolTypes = (values: TypeInfo[], index: number, delta: number): number => { +export const findBoolTypes = (values: TypeInfo[], index: number, delta: number, isHomogenous?: boolean): number => { // Helper function to find consecutive booleans from current index in a tuple. let until = 0 const length = values.length + if (isHomogenous) { + return delta < 0 ? 0 : length - index - 1 + } while (true) { const curr = index + delta * until if (['Bool', 'boolean'].includes(values[curr].name)) { @@ -102,9 +105,12 @@ export const encodeLength = (length: number): BytesCls => { } /** @internal */ -export const findBool = (values: ARC4Encoded[], index: number, delta: number) => { +export const findBool = (values: ARC4Encoded[], index: number, delta: number, isHomogenous?: boolean) => { let until = 0 const length = values.length + if (isHomogenous) { + return delta < 0 ? 0 : length - index - 1 + } while (true) { const curr = index + delta * until if (values[curr] instanceof Bool) { diff --git a/src/impl/encoded-types/utils.ts b/src/impl/encoded-types/utils.ts index 0aa4c541..eca57758 100644 --- a/src/impl/encoded-types/utils.ts +++ b/src/impl/encoded-types/utils.ts @@ -15,7 +15,7 @@ export const getMaxLengthOfStaticContentType = (type: TypeInfo, asArc4Encoded: b let size = 0 if (['Bool', 'boolean'].includes(genericArgs.elementType.name)) { while (i < childTypes.length) { - const after = findBoolTypes(childTypes, i, 1) + const after = findBoolTypes(childTypes, i, 1, true) const boolNum = after + 1 size += Math.floor(boolNum / BITS_IN_BYTE) size += boolNum % BITS_IN_BYTE ? 1 : 0 diff --git a/src/impl/primitives.ts b/src/impl/primitives.ts index 86a2ed5f..8c332738 100644 --- a/src/impl/primitives.ts +++ b/src/impl/primitives.ts @@ -318,7 +318,9 @@ export const checkBigUint = (v: bigint): bigint => { /** @internal */ export const checkBytes = (v: Uint8Array): Uint8Array => { - if (v.length > MAX_BYTES_SIZE) throw new AvmError(`Bytes length ${v.length} exceeds maximum length ${MAX_BYTES_SIZE}`) + if (v.length > MAX_BYTES_SIZE) { + throw new AvmError(`Bytes length ${v.length} exceeds maximum length ${MAX_BYTES_SIZE}`) + } return v } diff --git a/src/impl/pure.ts b/src/impl/pure.ts index 9bbbb6f9..54b41c12 100644 --- a/src/impl/pure.ts +++ b/src/impl/pure.ts @@ -50,7 +50,7 @@ export const bsqrt = (a: StubBigUintCompat): biguint => { export const btoi = (a: StubBytesCompat): uint64 => { const bytesValue = BytesCls.fromCompat(a) if (bytesValue.length.asAlgoTs() > BITS_IN_BYTE) { - throw new AvmError(`btoi arg too long, got [${bytesValue.length.valueOf()}]bytes`) + throw new AvmError(`btoi arg too long, got ${bytesValue.length.valueOf()} bytes`) } return bytesValue.toUint64().asAlgoTs() } diff --git a/src/impl/reference.ts b/src/impl/reference.ts index a15fe79b..2b1156f2 100644 --- a/src/impl/reference.ts +++ b/src/impl/reference.ts @@ -23,7 +23,7 @@ import { import { lazyContext } from '../context-helpers/internal-context' import { AvmError, InternalError } from '../errors' import type { DeliberateAny, Mutable } from '../typescript-helpers' -import { asBigInt, asBytes, asUint64, asUint64Cls, asUint8Array, conactUint8Arrays } from '../util' +import { asBigInt, asBytes, asUint64, asUint64Cls, asUint8Array, concatUint8Arrays } from '../util' import { BytesBackedCls, Uint64BackedCls } from './base' import type { StubUint64Compat } from './primitives' import { Bytes, BytesCls, Uint64Cls } from './primitives' @@ -321,7 +321,7 @@ export const checksumFromPublicKey = (pk: Uint8Array): Uint8Array => { /** @internal */ export const getApplicationAddress = (appId: StubUint64Compat): AccountType => { - const toBeSigned = conactUint8Arrays(asUint8Array(APP_ID_PREFIX), encodingUtil.bigIntToUint8Array(asBigInt(appId), 8)) + const toBeSigned = concatUint8Arrays(asUint8Array(APP_ID_PREFIX), encodingUtil.bigIntToUint8Array(asBigInt(appId), 8)) const appIdHash = js_sha512.sha512_256.array(toBeSigned) const publicKey = Uint8Array.from(appIdHash) const address = encodeAddress(publicKey) @@ -331,7 +331,7 @@ export const getApplicationAddress = (appId: StubUint64Compat): AccountType => { /** @internal */ export const encodeAddress = (address: Uint8Array): string => { const checksum = checksumFromPublicKey(address) - return encodingUtil.uint8ArrayToBase32(conactUint8Arrays(address, checksum)).slice(0, ALGORAND_ADDRESS_LENGTH) + return encodingUtil.uint8ArrayToBase32(concatUint8Arrays(address, checksum)).slice(0, ALGORAND_ADDRESS_LENGTH) } /** @internal */ diff --git a/src/impl/state.ts b/src/impl/state.ts index b734f7e6..33fbe3a2 100644 --- a/src/impl/state.ts +++ b/src/impl/state.ts @@ -2,7 +2,6 @@ import type { Account, Application, BoxMap as BoxMapType, - BoxRef as BoxRefType, Box as BoxType, bytes, GlobalStateOptions, @@ -18,7 +17,7 @@ import { AssertError, CodeError, InternalError } from '../errors' import type { TypeInfo } from '../impl/encoded-types' import { toBytes } from '../impl/encoded-types' import { getGenericTypeInfo } from '../runtime-helpers' -import { asBytes, asBytesCls, asNumber, asUint8Array, conactUint8Arrays } from '../util' +import { asBytes, asBytesCls, asNumber, asUint8Array, concatUint8Arrays } from '../util' import { getEncoder, minLengthForType } from './encoded-types' import type { StubBytesCompat, StubUint64Compat } from './primitives' import { Bytes, Uint64, Uint64Cls } from './primitives' @@ -189,7 +188,11 @@ export class BoxCls { ) } } - lazyContext.ledger.setBox(this.#app, this.key, new Uint8Array(Math.max(asNumber(options?.size ?? 0), valueTypeSize ?? 0))) + const size = asNumber(options?.size ?? 0) + if (size > MAX_BOX_SIZE) { + throw new InternalError(`Box size cannot exceed ${MAX_BOX_SIZE}`) + } + lazyContext.ledger.setBox(this.#app, this.key, new Uint8Array(Math.max(size, valueTypeSize ?? 0))) return true } @@ -245,10 +248,6 @@ export class BoxCls { return lazyContext.ledger.getBox(this.#app, this.key).length } - get ref(): BoxRefCls { - return new BoxRefCls(this.key, this.#app) - } - get(options: { default: TValue }): TValue { const [value, exists] = this.maybe() return exists ? value : options.default @@ -262,125 +261,6 @@ export class BoxCls { const value = this.fromBytes(lazyContext.ledger.getBox(this.#app, this.key)) return [value, lazyContext.ledger.boxExists(this.#app, this.key)] } -} - -/** @internal */ -export class BoxMapCls { - private _keyPrefix: bytes | undefined - #app: Application - - private readonly _type: string = BoxMapCls.name - - static [Symbol.hasInstance](x: unknown): x is BoxMapCls { - return x instanceof Object && '_type' in x && (x as { _type: string })['_type'] === BoxMapCls.name - } - - constructor() { - this.#app = lazyContext.activeApplication - } - - get hasKeyPrefix(): boolean { - return this._keyPrefix !== undefined && this._keyPrefix.length > 0 - } - - get keyPrefix(): bytes { - if (this._keyPrefix === undefined || this._keyPrefix.length === 0) { - throw new InternalError('Box key prefix is empty') - } - return this._keyPrefix - } - - set keyPrefix(keyPrefix: StubBytesCompat) { - this._keyPrefix = asBytes(keyPrefix) - } - - call(key: TKey, proxy: (key: TKey) => BoxType): BoxType { - const typeInfo = getGenericTypeInfo(proxy) - const valueType = (typeInfo!.genericArgs! as TypeInfo[])[1] - const box = new BoxCls(this.getFullKey(key), this.#app, valueType) - return box - } - - private getFullKey(key: TKey): bytes { - return this.keyPrefix.concat(toBytes(key)) - } -} - -/** @internal */ -export class BoxRefCls { - #key: bytes | undefined - #app: Application - - private readonly _type: string = BoxRefCls.name - - static [Symbol.hasInstance](x: unknown): x is BoxRefCls { - return x instanceof Object && '_type' in x && (x as { _type: string })['_type'] === BoxRefCls.name - } - - constructor(key?: StubBytesCompat, app?: Application) { - this.#key = key ? asBytes(key) : undefined - this.#app = app ?? lazyContext.activeApplication - } - - get hasKey(): boolean { - return this.#key !== undefined && this.#key.length > 0 - } - - get key(): bytes { - if (this.#key === undefined || this.#key.length === 0) { - throw new InternalError('Box key is empty') - } - return this.#key - } - - set key(key: StubBytesCompat) { - this.#key = asBytes(key) - } - - get value(): bytes { - if (!this.exists) { - throw new InternalError('Box has not been created') - } - return toBytes(this.backingValue) - } - - set value(v: StubBytesCompat) { - const bytesValue = asBytesCls(v) - const content = this.backingValue - if (this.exists && content.length !== bytesValue.length.asNumber()) { - throw new InternalError('Box already exists with a different size') - } - this.backingValue = bytesValue.asUint8Array() - } - - get exists(): boolean { - return lazyContext.ledger.boxExists(this.#app, this.key) - } - - create(options: { size: StubUint64Compat }): boolean { - const size = asNumber(options.size) - if (size > MAX_BOX_SIZE) { - throw new InternalError(`Box size cannot exceed ${MAX_BOX_SIZE}`) - } - const content = this.backingValue - if (this.exists && content.length !== size) { - throw new InternalError('Box already exists with a different size') - } - if (this.exists) { - return false - } - this.backingValue = new Uint8Array(size) - return true - } - - get(options: { default: StubBytesCompat }): bytes { - const [value, exists] = this.maybe() - return exists ? value : asBytes(options.default) - } - - put(value: StubBytesCompat): void { - this.value = value - } splice(start: StubUint64Compat, length: StubUint64Compat, value: StubBytesCompat): void { const content = this.backingValue @@ -394,12 +274,12 @@ export class BoxRefCls { throw new InternalError('Start index exceeds box size') } const end = Math.min(startNumber + lengthNumber, content.length) - let updatedContent = conactUint8Arrays(content.slice(0, startNumber), valueBytes.asUint8Array(), content.slice(end)) + let updatedContent = concatUint8Arrays(content.slice(0, startNumber), valueBytes.asUint8Array(), content.slice(end)) if (updatedContent.length > content.length) { updatedContent = updatedContent.slice(0, content.length) } else if (updatedContent.length < content.length) { - updatedContent = conactUint8Arrays(updatedContent, new Uint8Array(content.length - updatedContent.length)) + updatedContent = concatUint8Arrays(updatedContent, new Uint8Array(content.length - updatedContent.length)) } this.backingValue = updatedContent } @@ -414,7 +294,7 @@ export class BoxRefCls { if (startNumber + asNumber(valueBytes.length) > content.length) { throw new InternalError('Replacement content exceeds box size') } - const updatedContent = conactUint8Arrays( + const updatedContent = concatUint8Arrays( content.slice(0, startNumber), valueBytes.asUint8Array(), content.slice(startNumber + valueBytes.length.asNumber()), @@ -434,9 +314,6 @@ export class BoxRefCls { } return toBytes(content.slice(startNumber, startNumber + lengthNumber)) } - delete(): boolean { - return lazyContext.ledger.deleteBox(this.#app, this.key) - } resize(newSize: uint64): void { const newSizeNumber = asNumber(newSize) @@ -449,30 +326,61 @@ export class BoxRefCls { } let updatedContent if (newSizeNumber > content.length) { - updatedContent = conactUint8Arrays(content, new Uint8Array(newSizeNumber - content.length)) + updatedContent = concatUint8Arrays(content, new Uint8Array(newSizeNumber - content.length)) } else { updatedContent = content.slice(0, newSize) } this.backingValue = updatedContent } - maybe(): readonly [bytes, boolean] { - return [Bytes(lazyContext.ledger.getBox(this.#app, this.key)), lazyContext.ledger.boxExists(this.#app, this.key)] + private get backingValue(): Uint8Array { + return lazyContext.ledger.getBox(this.#app, this.key) } - get length(): uint64 { - if (!this.exists) { - throw new InternalError('Box has not been created') + private set backingValue(value: Uint8Array) { + lazyContext.ledger.setBox(this.#app, this.key, value) + } +} + +/** @internal */ +export class BoxMapCls { + private _keyPrefix: bytes | undefined + #app: Application + + private readonly _type: string = BoxMapCls.name + + static [Symbol.hasInstance](x: unknown): x is BoxMapCls { + return x instanceof Object && '_type' in x && (x as { _type: string })['_type'] === BoxMapCls.name + } + + constructor() { + this.#app = lazyContext.activeApplication + } + + get hasKeyPrefix(): boolean { + return this._keyPrefix !== undefined && this._keyPrefix.length > 0 + } + + get keyPrefix(): bytes { + if (this._keyPrefix === undefined || this._keyPrefix.length === 0) { + throw new InternalError('Box key prefix is empty') } - return this.backingValue.length + return this._keyPrefix } - private get backingValue(): Uint8Array { - return lazyContext.ledger.getBox(this.#app, this.key) + set keyPrefix(keyPrefix: StubBytesCompat) { + this._keyPrefix = asBytes(keyPrefix) } - private set backingValue(value: Uint8Array) { - lazyContext.ledger.setBox(this.#app, this.key, value) + call(key: TKey, proxy: (key: TKey) => BoxType): BoxType { + const typeInfo = getGenericTypeInfo(proxy) + const valueType = (typeInfo!.genericArgs! as TypeInfo[])[1] + const box = new BoxCls(this.getFullKey(key), this.#app, valueType) + return box + } + + private getFullKey(key: TKey): bytes { + return this.keyPrefix.concat(toBytes(key)) } } @@ -491,8 +399,3 @@ export function BoxMap(options?: { keyPrefix: bytes | string }): B const x = (key: TKey): BoxType => boxMap.call(key, x) return Object.setPrototypeOf(x, boxMap) } - -/** @internal */ -export function BoxRef(options?: { key: bytes | string }): BoxRefType { - return new BoxRefCls(options?.key) -} diff --git a/src/internal/index.ts b/src/internal/index.ts index f0ca2d97..ecfc4d34 100644 --- a/src/internal/index.ts +++ b/src/internal/index.ts @@ -23,7 +23,7 @@ export { BigUint, Bytes, Uint64 } from '../impl/primitives' /** @internal */ export { Account, Application, Asset } from '../impl/reference' /** @internal */ -export { Box, BoxMap, BoxRef, GlobalState, LocalState } from '../impl/state' +export { Box, BoxMap, GlobalState, LocalState } from '../impl/state' /** @internal */ export { TemplateVar } from '../impl/template-var' /** @internal */ diff --git a/src/subcontexts/contract-context.ts b/src/subcontexts/contract-context.ts index 97310215..f59eb8cd 100644 --- a/src/subcontexts/contract-context.ts +++ b/src/subcontexts/contract-context.ts @@ -20,7 +20,7 @@ import type { Contract } from '../impl/contract' import { getArc4Encoded, Uint, type TypeInfo } from '../impl/encoded-types' import { Bytes } from '../impl/primitives' import { AccountCls, ApplicationCls, AssetCls } from '../impl/reference' -import { BoxCls, BoxMapCls, BoxRefCls, GlobalStateCls } from '../impl/state' +import { BoxCls, BoxMapCls, GlobalStateCls } from '../impl/state' import type { Transaction } from '../impl/transactions' import { ApplicationCallTransaction, @@ -58,9 +58,8 @@ const extractStates = (contract: BaseContract, contractOptions: ContractOptionsP const isLocalState = value instanceof Function && value.name === 'localStateInternal' const isGlobalState = value instanceof GlobalStateCls const isBox = value instanceof BoxCls - const isBoxRef = value instanceof BoxRefCls const isBoxMap = value instanceof BoxMapCls - if (isLocalState || isGlobalState || isBox || isBoxRef) { + if (isLocalState || isGlobalState || isBox) { // set key using property name if not already set if (!value.hasKey) value.key = Bytes(key) } else if (isBoxMap) { diff --git a/src/subcontexts/ledger-context.ts b/src/subcontexts/ledger-context.ts index ede9f378..e638af6a 100644 --- a/src/subcontexts/ledger-context.ts +++ b/src/subcontexts/ledger-context.ts @@ -13,7 +13,7 @@ import { AccountMap, Uint64Map } from '../collections/custom-key-map' import { MAX_UINT64 } from '../constants' import { InternalError } from '../errors' import { BlockData } from '../impl/block' -import { toBytes } from '../impl/encoded-types' +import { toUint8Array } from '../impl/encoded-types/encoded-types' import { GlobalData } from '../impl/global' import { Uint64Cls } from '../impl/primitives' import type { AssetData } from '../impl/reference' @@ -354,7 +354,9 @@ export class LedgerContext { const appData = this.applicationDataMap.getOrFail(appId) const materialised = appData.application.materialisedBoxes.get(key) if (materialised !== undefined) { - return asUint8Array(toBytes(materialised)) + const uint8ArrayValue = toUint8Array(materialised) + appData.application.boxes.set(key, uint8ArrayValue) + appData.application.materialisedBoxes.set(key, undefined) } return appData.application.boxes.get(key) ?? new Uint8Array() } diff --git a/src/util.ts b/src/util.ts index 41c006e3..d91f8489 100644 --- a/src/util.ts +++ b/src/util.ts @@ -202,7 +202,7 @@ export const combineIntoMaxBytePages = (pages: bytes[]): bytes[] => { } /** @internal */ -export const conactUint8Arrays = (...values: Uint8Array[]): Uint8Array => { +export const concatUint8Arrays = (...values: Uint8Array[]): Uint8Array => { const result = new Uint8Array(values.reduce((acc, value) => acc + value.length, 0)) let index = 0 for (const value of values) { diff --git a/tests/arc4/resource-encoding.algo.spec.ts b/tests/arc4/resource-encoding.algo.spec.ts index 052c5c28..b8868a18 100644 --- a/tests/arc4/resource-encoding.algo.spec.ts +++ b/tests/arc4/resource-encoding.algo.spec.ts @@ -32,7 +32,7 @@ describe('resource encoding', () => { accountReferences: [], populateAppCallResources: false, }), - ).rejects.toThrow('invalid Account reference') + ).rejects.toThrow('unavailable Account') const res3 = await appClientByIndex.send.call({ method: 'testImplicitValue', diff --git a/tests/crypto-op-codes.algo.spec.ts b/tests/crypto-op-codes.algo.spec.ts index 04105faa..797086a8 100644 --- a/tests/crypto-op-codes.algo.spec.ts +++ b/tests/crypto-op-codes.algo.spec.ts @@ -12,7 +12,7 @@ import { TestExecutionContext } from '../src' import { LOGIC_DATA_PREFIX, MAX_BYTES_SIZE, PROGRAM_TAG } from '../src/constants' import { BytesCls, Uint64Cls } from '../src/impl/primitives' import { decodePublicKey } from '../src/impl/reference' -import { asBytes, asUint64, asUint8Array, conactUint8Arrays } from '../src/util' +import { asBytes, asUint64, asUint8Array, concatUint8Arrays } from '../src/util' import { getAvmResult, INITIAL_BALANCE_MICRO_ALGOS } from './avm-invoker' import { createArc4TestFixture } from './test-fixture' import { getPaddedBytes } from './util' @@ -137,10 +137,10 @@ describe('crypto op codes', async () => { }) const publicKey = decodePublicKey(account.addr.toString()) - const logicSig = conactUint8Arrays(asUint8Array(PROGRAM_TAG), approval.compiledBase64ToBytes) + const logicSig = concatUint8Arrays(asUint8Array(PROGRAM_TAG), approval.compiledBase64ToBytes) const logicSigAddress = js_sha512.sha512_256.array(logicSig) - const parts = conactUint8Arrays(new Uint8Array(logicSigAddress), asUint8Array(message)) - const toBeSigned = conactUint8Arrays(asUint8Array(LOGIC_DATA_PREFIX), parts) + const parts = concatUint8Arrays(new Uint8Array(logicSigAddress), asUint8Array(message)) + const toBeSigned = concatUint8Arrays(asUint8Array(LOGIC_DATA_PREFIX), parts) const signature = nacl.sign.detached(toBeSigned, account.sk) const avmResult = await getAvmResult( diff --git a/tests/pure-op-codes.algo.spec.ts b/tests/pure-op-codes.algo.spec.ts index f0693a45..6d5482bd 100644 --- a/tests/pure-op-codes.algo.spec.ts +++ b/tests/pure-op-codes.algo.spec.ts @@ -185,7 +185,7 @@ describe('Pure op codes', async () => { Bytes(encodingUtil.bigIntToUint8Array(MAX_UINT512 * MAX_UINT512, 128)), Bytes(Array(5).fill(0x00).concat(Array(4).fill(0x0f))), ])('should throw error when input overflows', async (a, { appClientMiscellaneousOpsContract: appClient }) => { - const errorRegex = new RegExp(`btoi arg too long, got \\[${a.length.valueOf()}\\]bytes`) + const errorRegex = new RegExp(`btoi arg too long, got ${a.length.valueOf()} bytes`) await expect(getAvmResultRaw({ appClient }, 'verify_btoi', asUint8Array(a))).rejects.toThrow(errorRegex) expect(() => op.btoi(a)).toThrow(errorRegex) }) diff --git a/tests/references/box-map.algo.spec.ts b/tests/references/box-map.algo.spec.ts index d9ac481f..5752df68 100644 --- a/tests/references/box-map.algo.spec.ts +++ b/tests/references/box-map.algo.spec.ts @@ -338,26 +338,22 @@ describe('BoxMap', () => { const box1 = boxMap(1) box1.create() - const boxRefA = box1.ref - boxRefA.replace(1, new Uint8(123).bytes) + box1.replace(1, new Uint8(123).bytes) expect(box1.value[0].asUint64()).toEqual(123) expect(boxMap(1).value[0].asUint64()).toEqual(123) - const boxRefB = box1.ref - boxRefB.replace(2, new Uint8(255).bytes) + box1.replace(2, new Uint8(255).bytes) expect(box1.value[1].asUint64()).toEqual(65280) expect(boxMap(1).value[1].asUint64()).toEqual(65280) const box2 = boxMap(2) box2.create() - const boxRefC = box2.ref - boxRefC.replace(1, new Uint8(223).bytes) + box2.replace(1, new Uint8(223).bytes) expect(box2.value[0].asUint64()).toEqual(223) expect(boxMap(2).value[0].asUint64()).toEqual(223) - const boxRefD = box2.ref - boxRefD.replace(3, new Uint8(255).bytes) + box2.replace(3, new Uint8(255).bytes) expect(box2.value[1].asUint64()).toEqual(255) expect(boxMap(2).value[1].asUint64()).toEqual(255) }) diff --git a/tests/references/box-ref.algo.spec.ts b/tests/references/box-ref.algo.spec.ts index cdd2b7d0..683386cb 100644 --- a/tests/references/box-ref.algo.spec.ts +++ b/tests/references/box-ref.algo.spec.ts @@ -1,4 +1,5 @@ -import { BoxRef, Bytes, op } from '@algorandfoundation/algorand-typescript' +import type { bytes } from '@algorandfoundation/algorand-typescript' +import { Box, Bytes, op } from '@algorandfoundation/algorand-typescript' import { TestExecutionContext } from '@algorandfoundation/algorand-typescript-testing' import { afterEach, describe, expect, it, test } from 'vitest' import { MAX_BOX_SIZE, MAX_BYTES_SIZE } from '../../src/constants' @@ -16,7 +17,7 @@ describe('BoxRef', () => { test.each(['key', Bytes('key')])('can be initialised with key %s', (key) => { ctx.txn.createScope([ctx.any.txn.applicationCall()]).execute(() => { - const box = BoxRef({ key }) + const box = Box({ key }) expect(box.exists).toBe(false) expect(box.key).toEqual(asBytes(key)) expect(() => box.value).toThrow(BOX_NOT_CREATED_ERROR) @@ -26,7 +27,7 @@ describe('BoxRef', () => { test.each([0, 1, 10, MAX_BOX_SIZE])('can create a box of size %s', (size) => { ctx.txn.createScope([ctx.any.txn.applicationCall()]).execute(() => { - const box = BoxRef({ key: TEST_BOX_KEY }) + const box = Box({ key: TEST_BOX_KEY }) box.create({ size }) expect(box.length).toBe(size) @@ -37,14 +38,14 @@ describe('BoxRef', () => { it(`throws error when creating a box with size greater than ${MAX_BOX_SIZE}`, () => { ctx.txn.createScope([ctx.any.txn.applicationCall()]).execute(() => { - const box = BoxRef({ key: TEST_BOX_KEY }) + const box = Box({ key: TEST_BOX_KEY }) expect(() => box.create({ size: MAX_BOX_SIZE + 1 })).toThrow(`Box size cannot exceed ${MAX_BOX_SIZE}`) }) }) it('can delete value', () => { ctx.txn.createScope([ctx.any.txn.applicationCall()]).execute(() => { - const box = BoxRef({ key: TEST_BOX_KEY }) + const box = Box({ key: TEST_BOX_KEY }) box.create({ size: MAX_BOX_SIZE }) expect(box.length).toBe(MAX_BOX_SIZE) @@ -69,7 +70,7 @@ describe('BoxRef', () => { [MAX_BOX_SIZE, MAX_BYTES_SIZE], ])('can resize to smaller size from %s to %s', (size, newSize) => { ctx.txn.createScope([ctx.any.txn.applicationCall()]).execute(() => { - const box = BoxRef({ key: TEST_BOX_KEY }) + const box = Box({ key: TEST_BOX_KEY }) box.create({ size }) initialiseBoxValue(box, new Uint8Array(Array(size).fill(0x11))) @@ -86,7 +87,7 @@ describe('BoxRef', () => { [MAX_BYTES_SIZE, MAX_BOX_SIZE], ])('can resize to larger size from %s to %s', (size, newSize) => { ctx.txn.createScope([ctx.any.txn.applicationCall()]).execute(() => { - const box = BoxRef({ key: TEST_BOX_KEY }) + const box = Box({ key: TEST_BOX_KEY }) box.create({ size }) initialiseBoxValue(box, new Uint8Array(Array(size).fill(0x11))) @@ -105,7 +106,7 @@ describe('BoxRef', () => { it(`throws error when resizing to size greater than ${MAX_BOX_SIZE}`, () => { ctx.txn.createScope([ctx.any.txn.applicationCall()]).execute(() => { - const box = BoxRef({ key: TEST_BOX_KEY }) + const box = Box({ key: TEST_BOX_KEY }) box.create({ size: 10 }) expect(() => box.resize(MAX_BOX_SIZE + 1)).toThrow(`Box size cannot exceed ${MAX_BOX_SIZE}`) @@ -114,7 +115,7 @@ describe('BoxRef', () => { it('can replace value', () => { ctx.txn.createScope([ctx.any.txn.applicationCall()]).execute(() => { - const box = BoxRef({ key: TEST_BOX_KEY }) + const box = Box({ key: TEST_BOX_KEY }) box.create({ size: MAX_BOX_SIZE }) const boxValue = new Uint8Array( @@ -131,7 +132,7 @@ describe('BoxRef', () => { it('throws error when replacing value of a non-existing box', () => { ctx.txn.createScope([ctx.any.txn.applicationCall()]).execute(() => { - const box = BoxRef({ key: TEST_BOX_KEY }) + const box = Box({ key: TEST_BOX_KEY }) expect(() => box.replace(0, Bytes(0x11))).toThrow(BOX_NOT_CREATED_ERROR) }) @@ -143,7 +144,7 @@ describe('BoxRef', () => { [9, Bytes(new Uint8Array(Array(2).fill(0x11)))], ])('throws error when replacing with a value that exceeds box size', (start, replacement) => { ctx.txn.createScope([ctx.any.txn.applicationCall()]).execute(() => { - const box = BoxRef({ key: TEST_BOX_KEY }) + const box = Box({ key: TEST_BOX_KEY }) box.create({ size: 10 }) expect(() => box.replace(asUint64(start), replacement)).toThrow('Replacement content exceeds box size') @@ -152,11 +153,11 @@ describe('BoxRef', () => { it('can retrieve value using maybe', () => { ctx.txn.createScope([ctx.any.txn.applicationCall()]).execute(() => { - const box = BoxRef({ key: TEST_BOX_KEY }) + const box = Box({ key: TEST_BOX_KEY }) box.create({ size: 10 }) const boxValue = new Uint8Array(Array(5).fill([0x01, 0x02]).flat()) - box.put(Bytes(boxValue)) + box.value = Bytes(boxValue) const [value, exists] = box.maybe() const [opValue, opExists] = op.Box.get(box.key) @@ -169,11 +170,11 @@ describe('BoxRef', () => { it('can retrieve non-existing value using maybe', () => { ctx.txn.createScope([ctx.any.txn.applicationCall()]).execute(() => { - const box = BoxRef({ key: TEST_BOX_KEY }) + const box = Box({ key: TEST_BOX_KEY }) box.create({ size: 10 }) const boxValue = new Uint8Array(Array(5).fill([0x01, 0x02]).flat()) - box.put(Bytes(boxValue)) + box.value = Bytes(boxValue) box.delete() const [value, exists] = box.maybe() @@ -191,10 +192,10 @@ describe('BoxRef', () => { [MAX_BYTES_SIZE, Bytes(new Uint8Array(Array(MAX_BYTES_SIZE).fill(0x11)))], ])('can put and get value', (size, value) => { ctx.txn.createScope([ctx.any.txn.applicationCall()]).execute(() => { - const box = BoxRef({ key: TEST_BOX_KEY }) + const box = Box({ key: TEST_BOX_KEY }) box.create({ size }) - box.put(value) + box.value = value const content = box.get({ default: Bytes(new Uint8Array(size)) }) expect(content).toEqual(value) @@ -207,10 +208,10 @@ describe('BoxRef', () => { it('can put value when box is not created', () => { ctx.txn.createScope([ctx.any.txn.applicationCall()]).execute(() => { - const box = BoxRef({ key: TEST_BOX_KEY }) + const box = Box({ key: TEST_BOX_KEY }) const boxValue = new Uint8Array(Array(5).fill([0x01, 0x02]).flat()) - box.put(Bytes(boxValue)) + box.value = Bytes(boxValue) expect(box.exists).toBe(true) expect(box.length).toBe(10) @@ -222,7 +223,7 @@ describe('BoxRef', () => { it('can get value with default value when box is not created', () => { ctx.txn.createScope([ctx.any.txn.applicationCall()]).execute(() => { - const box = BoxRef({ key: TEST_BOX_KEY }) + const box = Box({ key: TEST_BOX_KEY }) const defaultValue = Bytes(new Uint8Array(10)) const content = box.get({ default: defaultValue }) @@ -232,10 +233,10 @@ describe('BoxRef', () => { it('throws error when put and get value exceeding max bytes size', () => { ctx.txn.createScope([ctx.any.txn.applicationCall()]).execute(() => { - const box = BoxRef({ key: TEST_BOX_KEY }) + const box = Box({ key: TEST_BOX_KEY }) box.create({ size: MAX_BOX_SIZE }) - expect(() => box.put(Bytes(new Uint8Array(Array(MAX_BOX_SIZE).fill(0x11))))).toThrow('exceeds maximum length') + expect(() => (box.value = Bytes(new Uint8Array(Array(MAX_BOX_SIZE).fill(0x11))))).toThrow('exceeds maximum length') expect(() => box.get({ default: Bytes(new Uint8Array(Array(MAX_BOX_SIZE).fill(0x11))) })).toThrow('exceeds maximum length') }) }) @@ -243,11 +244,11 @@ describe('BoxRef', () => { it('can splice with longer value', () => { ctx.txn.createScope([ctx.any.txn.applicationCall()]).execute(() => { const size = 10 - const box = BoxRef({ key: TEST_BOX_KEY }) + const box = Box({ key: TEST_BOX_KEY }) box.create({ size: size }) const boxValue = new Uint8Array(Array(5).fill([0x01, 0x02]).flat()) - box.put(Bytes(boxValue)) + box.value = Bytes(boxValue) const replacement = new Uint8Array(Array(2).fill(0x11)) box.splice(1, 1, Bytes(replacement)) @@ -271,11 +272,11 @@ describe('BoxRef', () => { it('can splice with shorter value', () => { ctx.txn.createScope([ctx.any.txn.applicationCall()]).execute(() => { const size = 10 - const box = BoxRef({ key: TEST_BOX_KEY }) + const box = Box({ key: TEST_BOX_KEY }) box.create({ size: size }) const boxValue = new Uint8Array(Array(5).fill([0x01, 0x02]).flat()) - box.put(Bytes(boxValue)) + box.value = Bytes(boxValue) const replacement = new Uint8Array(Array(2).fill(0x11)) box.splice(1, 5, Bytes(replacement)) @@ -298,7 +299,7 @@ describe('BoxRef', () => { it('can splice when box does not exist', () => { ctx.txn.createScope([ctx.any.txn.applicationCall()]).execute(() => { - const box = BoxRef({ key: TEST_BOX_KEY }) + const box = Box({ key: TEST_BOX_KEY }) expect(() => box.splice(1, 1, Bytes(new Uint8Array([0x11])))).toThrow(BOX_NOT_CREATED_ERROR) }) @@ -306,7 +307,7 @@ describe('BoxRef', () => { it('throws error when splicing with index out of bounds', () => { ctx.txn.createScope([ctx.any.txn.applicationCall()]).execute(() => { - const box = BoxRef({ key: TEST_BOX_KEY }) + const box = Box({ key: TEST_BOX_KEY }) box.create({ size: 10 }) expect(() => box.splice(11, 1, Bytes(new Uint8Array([0x11])))).toThrow('Start index exceeds box size') @@ -314,7 +315,7 @@ describe('BoxRef', () => { }) }) -const initialiseBoxValue = (box: BoxRef, value: Uint8Array): void => { +const initialiseBoxValue = (box: Box, value: Uint8Array): void => { let index = 0 const size = asNumber(value.length) while (index < size) { @@ -324,7 +325,7 @@ const initialiseBoxValue = (box: BoxRef, value: Uint8Array): void => { } } -const assertBoxValue = (box: BoxRef, expectedValue: Uint8Array, start: number = 0): void => { +const assertBoxValue = (box: Box, expectedValue: Uint8Array, start: number = 0): void => { let index = start const size = expectedValue.length while (index < size) { diff --git a/tests/references/box.algo.spec.ts b/tests/references/box.algo.spec.ts index 8dc5f42a..1618baad 100644 --- a/tests/references/box.algo.spec.ts +++ b/tests/references/box.algo.spec.ts @@ -1,4 +1,4 @@ -import type { biguint, bytes, uint64 } from '@algorandfoundation/algorand-typescript' +import type { biguint, bytes, FixedArray, uint64 } from '@algorandfoundation/algorand-typescript' import { arc4, BigUint, Box, Bytes, clone, op, Uint64 } from '@algorandfoundation/algorand-typescript' import { TestExecutionContext } from '@algorandfoundation/algorand-typescript-testing' import type { Uint16 } from '@algorandfoundation/algorand-typescript/arc4' @@ -16,9 +16,10 @@ import { } from '@algorandfoundation/algorand-typescript/arc4' import { itob } from '@algorandfoundation/algorand-typescript/op' import { afterEach, describe, expect, it, test } from 'vitest' +import { MAX_BYTES_SIZE } from '../../src/constants' import { toBytes } from '../../src/impl/encoded-types' import type { DeliberateAny } from '../../src/typescript-helpers' -import { asBytes } from '../../src/util' +import { asBytes, asUint8Array, concatUint8Arrays } from '../../src/util' import { BoxContract } from '../artifacts/box-contract/contract.algo' const BOX_NOT_CREATED_ERROR = 'Box has not been created' @@ -318,16 +319,56 @@ describe('Box', () => { const box = Box>({ key: 'a' }) box.create() - const boxRef1 = box.ref - boxRef1.replace(1, new Uint8(123).bytes) + box.replace(1, new Uint8(123).bytes) expect(box.value[0].asUint64()).toEqual(123) - const boxRef2 = box.ref - boxRef2.replace(2, new Uint8(255).bytes) + box.replace(2, new Uint8(255).bytes) expect(box.value[1].asUint64()).toEqual(65280) }) }) + it('should be able to store large boolean array', () => { + ctx.txn.createScope([ctx.any.txn.applicationCall()]).execute(() => { + const boxBool = Box>({ key: 'tooManyBools' }) + boxBool.create() + expect(boxBool.length).toEqual(4125) + + boxBool.value[0] = true + boxBool.value[42] = true + boxBool.value[500] = true + boxBool.value[32_999] = true + + expect(boxBool.value[0]).toBe(true) + expect(boxBool.value[42]).toBe(true) + expect(boxBool.value[500]).toBe(true) + expect(boxBool.value[32_999]).toBe(true) + + const box_length = op.Box.length(Bytes('tooManyBools'))[0] + expect(box_length).toBe(4125) + const bytes1 = op.Box.extract(Bytes('tooManyBools'), 0, MAX_BYTES_SIZE) + const bytes2 = op.Box.extract(Bytes('tooManyBools'), MAX_BYTES_SIZE, box_length - MAX_BYTES_SIZE) + const allBytes = concatUint8Arrays(asUint8Array(bytes1), asUint8Array(bytes2)) + expect(allBytes.length).toBe(4125) + + const tooManyBools = Array(33_000).fill(false) + tooManyBools[0] = true + tooManyBools[42] = true + tooManyBools[500] = true + tooManyBools[32_999] = true + + const expectedBytes = new Uint8Array( + Array.from({ length: Math.ceil(tooManyBools.length / 8) }, (_, index) => + tooManyBools + .slice(index * 8, index * 8 + 8) + .reverse() + .reduce((sum, val, bit) => sum + (val ? 1 << bit : 0), 0), + ), + ) + + expect(allBytes).toEqual(expectedBytes) + }) + }) + describe('Box.create', () => { it('throw errors if size is not provided for dynamic value type', () => { ctx.txn.createScope([ctx.any.txn.applicationCall()]).execute(() => { diff --git a/vitest.setup.ts b/vitest.setup.ts index ed602a0a..75b49451 100644 --- a/vitest.setup.ts +++ b/vitest.setup.ts @@ -4,7 +4,7 @@ import path from 'path' import { beforeAll, expect } from 'vitest' import { addEqualityTesters } from './src/set-up' -dotenv.config({ path: path.join(__dirname, '.env') }) +dotenv.config({ path: path.join(__dirname, '.env'), ignore: ['MISSING_ENV_FILE'], }) beforeAll(() => { addEqualityTesters({ expect }) From e80d8479b85e64432b4e4867ec3fdeaa63f752b3 Mon Sep 17 00:00:00 2001 From: Bobby Lat Date: Tue, 16 Sep 2025 14:18:24 +0700 Subject: [PATCH 49/68] refactor: rename `interpretAsArc4` function as `convertBytes` with `strategy` option --- docs/coverage.md | 2 +- examples/marketplace/contract.algo.spec.ts | 14 ++++-- examples/zk-whitelist/contract.algo.ts | 6 ++- package-lock.json | 18 ++++---- package.json | 4 +- src/impl/encoded-types/encoded-types.ts | 6 +-- src/impl/encoded-types/index.ts | 2 +- src/internal/arc4.ts | 2 +- src/test-transformer/visitors.ts | 12 +---- tests/arc4/address.algo.spec.ts | 6 +-- tests/arc4/bool.algo.spec.ts | 10 +++-- tests/arc4/byte.algo.spec.ts | 14 +++--- tests/arc4/dynamic-array.algo.spec.ts | 30 ++++++------- tests/arc4/dynamic-bytes.algo.spec.ts | 4 +- tests/arc4/static-array.algo.spec.ts | 30 ++++++------- tests/arc4/static-bytes.algo.spec.ts | 4 +- tests/arc4/str.algo.spec.ts | 10 +++-- tests/arc4/struct.algo.spec.ts | 14 +++--- tests/arc4/tuple.algo.spec.ts | 25 +++++++---- tests/arc4/ufixednxm.algo.spec.ts | 26 ++++++----- tests/arc4/uintn.algo.spec.ts | 38 +++++++++------- .../arc4-primitive-ops/contract.algo.ts | 44 +++++++++---------- .../created-app-asset/contract.algo.ts | 7 ++- .../artifacts/primitive-ops/contract.algo.ts | 8 ++-- tests/fixed-array.algo.spec.ts | 20 ++++----- tests/native-mutable-array.algo.spec.ts | 24 +++++----- tests/native-mutable-object.algo.spec.ts | 16 +++---- tests/native-readonly-array.algo.spec.ts | 24 +++++----- tests/native-readonly-object.algo.spec.ts | 16 +++---- tests/primitives/bytes.algo.spec.ts | 4 +- tests/references/box-map.algo.spec.ts | 8 ++-- tests/references/box.algo.spec.ts | 6 +-- 32 files changed, 240 insertions(+), 214 deletions(-) diff --git a/docs/coverage.md b/docs/coverage.md index 6bdda146..d43a8324 100644 --- a/docs/coverage.md +++ b/docs/coverage.md @@ -42,7 +42,7 @@ See which `algorand-typescript` stubs are implemented by the `algorand-typescrip | encodeArc4 | Native | | ensureBudget | Emulated | | err | Native | -| interpretAsArc4 | Native | +| convertBytes | Native | | itxnCompose | Emulated | | log | Emulated | | logicSig | Emulated | diff --git a/examples/marketplace/contract.algo.spec.ts b/examples/marketplace/contract.algo.spec.ts index 5baef712..7af1fe98 100644 --- a/examples/marketplace/contract.algo.spec.ts +++ b/examples/marketplace/contract.algo.spec.ts @@ -1,6 +1,6 @@ import { arc4, Bytes } from '@algorandfoundation/algorand-typescript' import { TestExecutionContext } from '@algorandfoundation/algorand-typescript-testing' -import { interpretAsArc4 } from '@algorandfoundation/algorand-typescript/arc4' +import { convertBytes } from '@algorandfoundation/algorand-typescript/arc4' import { afterEach, describe, expect, test } from 'vitest' import DigitalMarketplace, { ListingKey, ListingValue } from './contract.algo' @@ -39,7 +39,9 @@ describe('DigitalMarketplace', () => { asset: new arc4.Uint64(testAsset.id), nonce: testNonce, }) - const listingValue = interpretAsArc4(Bytes(ctx.ledger.getBox(contract, Bytes('listings').concat(listingKey.bytes)))) + const listingValue = convertBytes(Bytes(ctx.ledger.getBox(contract, Bytes('listings').concat(listingKey.bytes))), { + strategy: 'unsafe-cast', + }) expect(listingValue.deposited.asUint64()).toEqual(10) }) @@ -100,7 +102,9 @@ describe('DigitalMarketplace', () => { contract.setPrice(testAsset, testNonce, testUnitaryPrice) // Assert - const updatedListing = interpretAsArc4(Bytes(ctx.ledger.getBox(contract, Bytes('listings').concat(listingKey.bytes)))) + const updatedListing = convertBytes(Bytes(ctx.ledger.getBox(contract, Bytes('listings').concat(listingKey.bytes))), { + strategy: 'unsafe-cast', + }) expect(updatedListing.unitaryPrice.asUint64()).toEqual(testUnitaryPrice.asUint64()) }) @@ -136,7 +140,9 @@ describe('DigitalMarketplace', () => { ) // Assert - const updatedListing = interpretAsArc4(Bytes(ctx.ledger.getBox(contract, Bytes('listings').concat(listingKey.bytes)))) + const updatedListing = convertBytes(Bytes(ctx.ledger.getBox(contract, Bytes('listings').concat(listingKey.bytes))), { + strategy: 'unsafe-cast', + }) expect(updatedListing.deposited.asUint64()).toEqual(initialDeposit.asUint64() - testBuyQuantity.asUint64()) expect(ctx.txn.lastGroup.getItxnGroup(0).getAssetTransferInnerTxn(0).assetReceiver).toEqual(ctx.defaultSender) }) diff --git a/examples/zk-whitelist/contract.algo.ts b/examples/zk-whitelist/contract.algo.ts index c152c205..5aee35c1 100644 --- a/examples/zk-whitelist/contract.algo.ts +++ b/examples/zk-whitelist/contract.algo.ts @@ -56,7 +56,9 @@ export default class ZkWhitelistContract extends arc4.Contract { // The verifier expects public inputs to be in the curve field, but an // Algorand address might represent a number larger than the field // modulus, so to be safe we take the address modulo the field modulus - const addressMod = arc4.interpretAsArc4(op.bzero(32).bitwiseOr(Bytes(BigUint(address.bytes) % curveMod))) + const addressMod = arc4.convertBytes(op.bzero(32).bitwiseOr(Bytes(BigUint(address.bytes) % curveMod)), { + strategy: 'unsafe-cast', + }) // Verify the proof by calling the deposit verifier app const verified = this.verifyProof(TemplateVar('VERIFIER_APP_ID'), proof, new arc4.DynamicArray(addressMod)) if (!verified.native) { @@ -93,6 +95,6 @@ export default class ZkWhitelistContract extends arc4.Contract { onCompletion: OnCompleteAction.NoOp, }) .submit().lastLog - return arc4.interpretAsArc4(verified, 'log') + return arc4.convertBytes(verified, { prefix: 'log', strategy: 'unsafe-cast' }) } } diff --git a/package-lock.json b/package-lock.json index 218d50df..66c22aba 100644 --- a/package-lock.json +++ b/package-lock.json @@ -8,8 +8,8 @@ "name": "@algorandfoundation/algorand-typescript-testing", "version": "1.0.0", "dependencies": { - "@algorandfoundation/algorand-typescript": "1.0.0-alpha.82", - "@algorandfoundation/puya-ts": "1.0.0-alpha.82", + "@algorandfoundation/algorand-typescript": "1.0.0-alpha.83", + "@algorandfoundation/puya-ts": "1.0.0-alpha.83", "elliptic": "^6.6.1", "js-sha256": "^0.11.0", "js-sha3": "^0.9.3", @@ -76,21 +76,21 @@ } }, "node_modules/@algorandfoundation/algorand-typescript": { - "version": "1.0.0-alpha.82", - "resolved": "https://registry.npmjs.org/@algorandfoundation/algorand-typescript/-/algorand-typescript-1.0.0-alpha.82.tgz", - "integrity": "sha512-TG/IqbzJ7Kml6qS5eGfxCGWYG9vWsjoWzYwsg7c2inpIHUzUxgRuypxfig3WVNtZy6SQL1uQ0c3noz3ZXxicqg==", + "version": "1.0.0-alpha.83", + "resolved": "https://registry.npmjs.org/@algorandfoundation/algorand-typescript/-/algorand-typescript-1.0.0-alpha.83.tgz", + "integrity": "sha512-TTdc5fGSRssQSj0eGb2GTBZHthqlfIMEhk6qSbaJbTvk5XBLG6W6ktRNsXe+ncxZn2qQQ4fuoZ+uJJARs1FpBQ==", "peerDependencies": { "tslib": "^2.6.2" } }, "node_modules/@algorandfoundation/puya-ts": { - "version": "1.0.0-alpha.82", - "resolved": "https://registry.npmjs.org/@algorandfoundation/puya-ts/-/puya-ts-1.0.0-alpha.82.tgz", - "integrity": "sha512-uxjRljHI7+hVutCtxY7nA6ZCEzZyZUeh0DoQSH5P/nqJ4rvLAtjXnYCs/SRDwG+WllwSjpCort2AUgTkIAwPbQ==", + "version": "1.0.0-alpha.83", + "resolved": "https://registry.npmjs.org/@algorandfoundation/puya-ts/-/puya-ts-1.0.0-alpha.83.tgz", + "integrity": "sha512-yB6kXwDMfK2pjBODmSTf/xC5+olrdU8Co8My5Z/kEVRDjoZsVXCbCHGMI+ixdq02XFrvgM4DgjdXLvaM1YeoTQ==", "hasInstallScript": true, "license": "MIT", "dependencies": { - "@algorandfoundation/algorand-typescript": "1.0.0-alpha.82", + "@algorandfoundation/algorand-typescript": "1.0.0-alpha.83", "arcsecond": "^5.0.0", "argparse": "^2.0.1", "chalk": "^5.4.1", diff --git a/package.json b/package.json index fbca85e9..33184dc2 100644 --- a/package.json +++ b/package.json @@ -68,8 +68,8 @@ "vitest": "3.2.4" }, "dependencies": { - "@algorandfoundation/algorand-typescript": "1.0.0-alpha.82", - "@algorandfoundation/puya-ts": "1.0.0-alpha.82", + "@algorandfoundation/algorand-typescript": "1.0.0-alpha.83", + "@algorandfoundation/puya-ts": "1.0.0-alpha.83", "elliptic": "^6.6.1", "js-sha256": "^0.11.0", "js-sha3": "^0.9.3", diff --git a/src/impl/encoded-types/encoded-types.ts b/src/impl/encoded-types/encoded-types.ts index 654c6c88..66fbd312 100644 --- a/src/impl/encoded-types/encoded-types.ts +++ b/src/impl/encoded-types/encoded-types.ts @@ -1217,13 +1217,13 @@ export function decodeArc4( } /** @internal */ -export function interpretAsArc4( +export function convertBytes( typeInfoString: string, bytes: StubBytesCompat, - prefix: 'none' | 'log' = 'none', + options: { prefix?: 'none' | 'log'; strategy: 'unsafe-cast' }, ): T { const typeInfo = JSON.parse(typeInfoString) - return getEncoder(typeInfo)(bytes, typeInfo, prefix) + return getEncoder(typeInfo)(bytes, typeInfo, options.prefix) } /** @internal */ diff --git a/src/impl/encoded-types/index.ts b/src/impl/encoded-types/index.ts index 29097fcb..502f2d24 100644 --- a/src/impl/encoded-types/index.ts +++ b/src/impl/encoded-types/index.ts @@ -3,6 +3,7 @@ export { Address, Bool, Byte, + convertBytes, decodeArc4, DynamicArray, DynamicBytes, @@ -10,7 +11,6 @@ export { FixedArray, getArc4Encoded, getEncoder, - interpretAsArc4, ReferenceArray, StaticArray, StaticBytes, diff --git a/src/internal/arc4.ts b/src/internal/arc4.ts index f1a3c9df..d87fa8f2 100644 --- a/src/internal/arc4.ts +++ b/src/internal/arc4.ts @@ -9,12 +9,12 @@ export { Address, Bool, Byte, + convertBytes, decodeArc4, DynamicArray, DynamicBytes, encodeArc4, FixedArray, - interpretAsArc4, ReferenceArray, sizeOf, StaticArray, diff --git a/src/test-transformer/visitors.ts b/src/test-transformer/visitors.ts index 236f6a99..9116654c 100644 --- a/src/test-transformer/visitors.ts +++ b/src/test-transformer/visitors.ts @@ -506,17 +506,7 @@ const tryGetStubbedFunctionName = (node: ts.CallExpression, helper: VisitorHelpe : (node.expression as ts.Identifier) const functionName = tryGetAlgoTsSymbolName(identityExpression, helper) if (functionName === undefined) return undefined - const stubbedFunctionNames = [ - 'interpretAsArc4', - 'decodeArc4', - 'encodeArc4', - 'emit', - 'methodSelector', - 'sizeOf', - 'abiCall', - 'clone', - 'Bytes', - ] + const stubbedFunctionNames = ['convertBytes', 'decodeArc4', 'encodeArc4', 'emit', 'methodSelector', 'sizeOf', 'abiCall', 'clone', 'Bytes'] if (stubbedFunctionNames.includes(functionName)) { if (ts.isPropertyAccessExpression(node.expression)) { diff --git a/tests/arc4/address.algo.spec.ts b/tests/arc4/address.algo.spec.ts index b92cda39..a7d5ed5e 100644 --- a/tests/arc4/address.algo.spec.ts +++ b/tests/arc4/address.algo.spec.ts @@ -1,7 +1,7 @@ import { getABIEncodedValue } from '@algorandfoundation/algokit-utils/types/app-arc56' import { Account, Bytes } from '@algorandfoundation/algorand-typescript' import { TestExecutionContext } from '@algorandfoundation/algorand-typescript-testing' -import { Address, interpretAsArc4 } from '@algorandfoundation/algorand-typescript/arc4' +import { Address, convertBytes } from '@algorandfoundation/algorand-typescript/arc4' import { afterEach, describe, expect, test } from 'vitest' import { ABI_RETURN_VALUE_LOG_PREFIX } from '../../src/constants' import { encodeAddress } from '../../src/impl/reference' @@ -71,14 +71,14 @@ describe('arc4.Address', () => { test.each(testData)('fromBytes method', (value) => { const sdkResult = getABIEncodedValue(asUint8Array(value), abiTypeString, {}) - const result = interpretAsArc4
(value) + const result = convertBytes
(value, { strategy: 'unsafe-cast' }) expect(result.bytes).toEqual(sdkResult) }) test.each(testData)('fromLog method', (value) => { const sdkResult = getABIEncodedValue(asUint8Array(value), abiTypeString, {}) const paddedValue = Bytes([...asUint8Array(ABI_RETURN_VALUE_LOG_PREFIX), ...asUint8Array(value)]) - const result = interpretAsArc4
(paddedValue, 'log') + const result = convertBytes
(paddedValue, { prefix: 'log', strategy: 'unsafe-cast' }) expect(result.bytes).toEqual(sdkResult) }) }) diff --git a/tests/arc4/bool.algo.spec.ts b/tests/arc4/bool.algo.spec.ts index 8cb3e4ea..f805c1ba 100644 --- a/tests/arc4/bool.algo.spec.ts +++ b/tests/arc4/bool.algo.spec.ts @@ -1,6 +1,6 @@ import { Bytes } from '@algorandfoundation/algorand-typescript' import { TestExecutionContext } from '@algorandfoundation/algorand-typescript-testing' -import { Bool, interpretAsArc4 } from '@algorandfoundation/algorand-typescript/arc4' +import { Bool, convertBytes } from '@algorandfoundation/algorand-typescript/arc4' import { afterEach, beforeAll, describe, expect } from 'vitest' import { ABI_RETURN_VALUE_LOG_PREFIX } from '../../src/constants' import { asUint8Array } from '../../src/util' @@ -34,7 +34,7 @@ describe('arc4.Bool', async () => { 'create Bool from bytes', async (value, { appClientArc4PrimitiveOpsContract: appClient }) => { const avmResult = await getAvmResult({ appClient }, 'verify_bool_from_bytes', value) - const result = interpretAsArc4(Bytes(value)) + const result = convertBytes(Bytes(value), { strategy: 'unsafe-cast' }) expect(result.native).toEqual(avmResult) }, ) @@ -44,7 +44,7 @@ describe('arc4.Bool', async () => { async (value, { appClientArc4PrimitiveOpsContract: appClient }) => { const paddedValue = new Uint8Array([...asUint8Array(ABI_RETURN_VALUE_LOG_PREFIX), ...value]) const avmResult = await getAvmResult({ appClient }, 'verify_bool_from_log', paddedValue) - const result = interpretAsArc4(Bytes(paddedValue), 'log') + const result = convertBytes(Bytes(paddedValue), { prefix: 'log', strategy: 'unsafe-cast' }) expect(result.native).toEqual(avmResult) }, ) @@ -60,7 +60,9 @@ describe('arc4.Bool', async () => { await expect(() => getAvmResult({ appClient }, 'verify_bool_from_log', paddedValue)).rejects.toThrowError( new RegExp('(has valid prefix)|(extraction start \\d+ is beyond length)'), ) - expect(() => interpretAsArc4(Bytes(paddedValue), 'log')).toThrowError('ABI return prefix not found') + expect(() => convertBytes(Bytes(paddedValue), { prefix: 'log', strategy: 'unsafe-cast' })).toThrowError( + 'ABI return prefix not found', + ) }, ) }) diff --git a/tests/arc4/byte.algo.spec.ts b/tests/arc4/byte.algo.spec.ts index fcd8da71..2b95eab5 100644 --- a/tests/arc4/byte.algo.spec.ts +++ b/tests/arc4/byte.algo.spec.ts @@ -1,7 +1,7 @@ import type { bytes } from '@algorandfoundation/algorand-typescript' import { Bytes } from '@algorandfoundation/algorand-typescript' import { TestExecutionContext } from '@algorandfoundation/algorand-typescript-testing' -import { Byte, interpretAsArc4 } from '@algorandfoundation/algorand-typescript/arc4' +import { Byte, convertBytes } from '@algorandfoundation/algorand-typescript/arc4' import { encodingUtil } from '@algorandfoundation/puya-ts' import { afterEach, beforeAll, describe, expect } from 'vitest' import { ABI_RETURN_VALUE_LOG_PREFIX, MAX_UINT64 } from '../../src/constants' @@ -116,7 +116,7 @@ describe('arc4.Byte', async () => { encodingUtil.bigIntToUint8Array(2n ** 8n - 1n, 1), ])('create Byte from bytes', async (value, { appClientArc4PrimitiveOpsContract: appClient }) => { const avmResult = await getAvmResult({ appClient }, 'verify_byte_from_bytes', value) - const result = interpretAsArc4(Bytes(value)) + const result = convertBytes(Bytes(value), { strategy: 'unsafe-cast' }) expect(result.asUint64()).toEqual(avmResult) }) @@ -126,7 +126,7 @@ describe('arc4.Byte', async () => { async (value, { appClientArc4PrimitiveOpsContract: appClient }) => { await expect(getAvmResult({ appClient }, 'verify_byte_from_bytes', value)).rejects.toThrowError(invalidBytesLengthError) - const result = interpretAsArc4(Bytes(value)) + const result = convertBytes(Bytes(value), { strategy: 'unsafe-cast' }) expect(result.asUint64()).toEqual(encodingUtil.uint8ArrayToBigInt(value)) }, ) @@ -139,7 +139,7 @@ describe('arc4.Byte', async () => { const logValue = asUint8Array(ABI_RETURN_VALUE_LOG_PREFIX.concat(Bytes(value as Uint8Array))) const avmResult = await getAvmResult({ appClient }, 'verify_byte_from_log', logValue) - const result = interpretAsArc4(Bytes(logValue), 'log') + const result = convertBytes(Bytes(logValue), { prefix: 'log', strategy: 'unsafe-cast' }) expect(avmResult).toEqual(expected) expect(result.asUint64()).toEqual(expected) }) @@ -154,7 +154,9 @@ describe('arc4.Byte', async () => { await expect(() => getAvmResult({ appClient }, 'verify_byte_from_log', logValue)).rejects.toThrowError( new RegExp('(has valid prefix)|(extraction start \\d+ is beyond length)'), ) - expect(() => interpretAsArc4(Bytes(logValue), 'log')).toThrowError('ABI return prefix not found') + expect(() => convertBytes(Bytes(logValue), { prefix: 'log', strategy: 'unsafe-cast' })).toThrowError( + 'ABI return prefix not found', + ) }, ) @@ -168,7 +170,7 @@ describe('arc4.Byte', async () => { const logValue = asUint8Array(ABI_RETURN_VALUE_LOG_PREFIX.concat(Bytes(value))) await expect(() => getAvmResult({ appClient }, 'verify_byte_from_log', logValue)).rejects.toThrowError(invalidBytesLengthError) - const result = interpretAsArc4(Bytes(logValue), 'log') + const result = convertBytes(Bytes(logValue), { prefix: 'log', strategy: 'unsafe-cast' }) expect(result.asUint64()).toEqual(encodingUtil.uint8ArrayToBigInt(value)) }, ) diff --git a/tests/arc4/dynamic-array.algo.spec.ts b/tests/arc4/dynamic-array.algo.spec.ts index e6aaee59..2ac65183 100644 --- a/tests/arc4/dynamic-array.algo.spec.ts +++ b/tests/arc4/dynamic-array.algo.spec.ts @@ -4,8 +4,8 @@ import { TestExecutionContext } from '@algorandfoundation/algorand-typescript-te import { Address, Bool, + convertBytes, DynamicArray, - interpretAsArc4, StaticArray, Str, Struct, @@ -43,7 +43,7 @@ const addressDynamicArray = { return isEmpty ? new DynamicArray
() : new DynamicArray
(...this.abiValues()) }, create(value: StubBytesCompat) { - return interpretAsArc4>(asBytes(value)) + return convertBytes>(asBytes(value), { strategy: 'unsafe-cast' }) }, concat() { return this.array().concat(this.array()) @@ -67,7 +67,7 @@ const boolDynamicArray = { return isEmpty ? new DynamicArray() : new DynamicArray(...this.abiValues()) }, create(value: StubBytesCompat) { - return interpretAsArc4>(asBytes(value)) + return convertBytes>(asBytes(value), { strategy: 'unsafe-cast' }) }, concat() { return this.array().concat(this.array()) @@ -91,7 +91,7 @@ const uint256DynamicArray = { return isEmpty ? new DynamicArray>() : new DynamicArray>(...this.abiValues()) }, create(value: StubBytesCompat) { - return interpretAsArc4>>(asBytes(value)) + return convertBytes>>(asBytes(value), { strategy: 'unsafe-cast' }) }, concat() { return this.array().concat(this.array()) @@ -126,7 +126,7 @@ const ufixedDynamicArray = { return isEmpty ? new DynamicArray>() : new DynamicArray>(...this.abiValues()) }, create(value: StubBytesCompat) { - return interpretAsArc4>>(asBytes(value)) + return convertBytes>>(asBytes(value), { strategy: 'unsafe-cast' }) }, concat() { return this.array().concat(this.array()) @@ -161,7 +161,7 @@ const stringDynamicArray = { return isEmpty ? new DynamicArray() : new DynamicArray(...this.abiValues()) }, create(value: StubBytesCompat) { - return interpretAsArc4>(asBytes(value)) + return convertBytes>(asBytes(value), { strategy: 'unsafe-cast' }) }, concat() { return this.array().concat(this.array()) @@ -187,7 +187,7 @@ const boolDynamicArrayOfArray = { : new DynamicArray>(...this.abiValues().map((a) => new DynamicArray(...a))) }, create(value: StubBytesCompat) { - return interpretAsArc4>>(asBytes(value)) + return convertBytes>>(asBytes(value), { strategy: 'unsafe-cast' }) }, concat() { return this.array().concat(this.array()) @@ -213,7 +213,7 @@ const addressDynamicArrayOfArray = { : new DynamicArray>(...this.abiValues().map((a) => new DynamicArray
(...a))) }, create(value: StubBytesCompat) { - return interpretAsArc4>>(asBytes(value)) + return convertBytes>>(asBytes(value), { strategy: 'unsafe-cast' }) }, concat() { return this.array().concat(this.array()) @@ -239,7 +239,7 @@ const uint256DynamicArrayOfArray = { : new DynamicArray>>(...this.abiValues().map((a) => new DynamicArray>(...a))) }, create(value: StubBytesCompat) { - return interpretAsArc4>>>(asBytes(value)) + return convertBytes>>>(asBytes(value), { strategy: 'unsafe-cast' }) }, concat() { return this.array().concat(this.array()) @@ -267,7 +267,7 @@ const uint256DynamicArrayOfStaticArray = { ) }, create(value: StubBytesCompat) { - return interpretAsArc4, 10>>>(asBytes(value)) + return convertBytes, 10>>>(asBytes(value), { strategy: 'unsafe-cast' }) }, concat() { return this.array().concat(this.array()) @@ -293,7 +293,7 @@ const stringDynamicArrayOfArray = { : new DynamicArray>(...this.abiValues().map((a) => new DynamicArray(...a))) }, create(value: StubBytesCompat) { - return interpretAsArc4>>(asBytes(value)) + return convertBytes>>(asBytes(value), { strategy: 'unsafe-cast' }) }, concat() { return this.array().concat(this.array()) @@ -324,7 +324,7 @@ const stringDynamicArrayOfArrayOfArray = { : new DynamicArray>>(...this.abiValues().map((x) => new DynamicArray>(...x))) }, create(value: StubBytesCompat) { - return interpretAsArc4>>>(asBytes(value)) + return convertBytes>>>(asBytes(value), { strategy: 'unsafe-cast' }) }, concat() { return this.array().concat(this.array()) @@ -376,9 +376,9 @@ const tupleDynamicArray = { ) }, create(value: StubBytesCompat) { - return interpretAsArc4< + return convertBytes< DynamicArray, Tuple<[DynamicArray, Str, Uint<256>, Address]>, Bool, StaticArray, 3>]>> - >(asBytes(value)) + >(asBytes(value), { strategy: 'unsafe-cast' }) }, concat() { return this.array().concat(this.array()) @@ -440,7 +440,7 @@ const structDynamicArray = { return isEmpty ? new DynamicArray() : new DynamicArray(...this.abiValues()) }, create(value: StubBytesCompat) { - return interpretAsArc4>(asBytes(value)) + return convertBytes>(asBytes(value), { strategy: 'unsafe-cast' }) }, concat() { return this.array().concat(this.array()) diff --git a/tests/arc4/dynamic-bytes.algo.spec.ts b/tests/arc4/dynamic-bytes.algo.spec.ts index 7c67d99c..812e51fc 100644 --- a/tests/arc4/dynamic-bytes.algo.spec.ts +++ b/tests/arc4/dynamic-bytes.algo.spec.ts @@ -1,6 +1,6 @@ import { getABIEncodedValue } from '@algorandfoundation/algokit-utils/types/app-arc56' import { Bytes } from '@algorandfoundation/algorand-typescript' -import { DynamicBytes, interpretAsArc4 } from '@algorandfoundation/algorand-typescript/arc4' +import { DynamicBytes, convertBytes } from '@algorandfoundation/algorand-typescript/arc4' import { encodingUtil } from '@algorandfoundation/puya-ts' import { describe, expect, test } from 'vitest' @@ -49,7 +49,7 @@ describe('arc4.DynamicBytes', async () => { test.each(testData)('create dynamic bytes from bytes', async (data) => { const nativeValue = data.nativeValue() const sdkEncodedBytes = getABIEncodedValue(nativeValue, abiTypeString, {}) - const result = interpretAsArc4(Bytes(sdkEncodedBytes)) + const result = convertBytes(Bytes(sdkEncodedBytes), { strategy: 'unsafe-cast' }) for (let i = 0; i < result.length; i++) { expect(result[i].asUint64()).toEqual(nativeValue[i]) } diff --git a/tests/arc4/static-array.algo.spec.ts b/tests/arc4/static-array.algo.spec.ts index 4d71e5f7..abada513 100644 --- a/tests/arc4/static-array.algo.spec.ts +++ b/tests/arc4/static-array.algo.spec.ts @@ -4,8 +4,8 @@ import { TestExecutionContext } from '@algorandfoundation/algorand-typescript-te import { Address, Bool, + convertBytes, DynamicArray, - interpretAsArc4, StaticArray, Str, Struct, @@ -43,7 +43,7 @@ const addressStaticArray = { return new StaticArray(...(this.abiValues() as [])) }, create(value: StubBytesCompat) { - return interpretAsArc4>(asBytes(value)) + return convertBytes>(asBytes(value), { strategy: 'unsafe-cast' }) }, concat() { return this.array().concat(this.array()) @@ -68,7 +68,7 @@ const boolStaticArray = { return new StaticArray(...(this.abiValues() as [])) }, create(value: StubBytesCompat) { - return interpretAsArc4>(asBytes(value)) + return convertBytes>(asBytes(value), { strategy: 'unsafe-cast' }) }, concat() { return this.array().concat(this.array()) @@ -93,7 +93,7 @@ const uint256StaticArray = { return new StaticArray, 10>(...(this.abiValues() as [])) }, create(value: StubBytesCompat) { - return interpretAsArc4, 10>>(asBytes(value)) + return convertBytes, 10>>(asBytes(value), { strategy: 'unsafe-cast' }) }, concat() { return this.array().concat(this.array()) @@ -129,7 +129,7 @@ const ufixedStaticArray = { return new StaticArray, 10>(...(this.abiValues() as [])) }, create(value: StubBytesCompat) { - return interpretAsArc4, 10>>(asBytes(value)) + return convertBytes, 10>>(asBytes(value), { strategy: 'unsafe-cast' }) }, concat() { return this.array().concat(this.array()) @@ -165,7 +165,7 @@ const stringStaticArray = { return new StaticArray(...(this.abiValues() as [])) }, create(value: StubBytesCompat) { - return interpretAsArc4>(asBytes(value)) + return convertBytes>(asBytes(value), { strategy: 'unsafe-cast' }) }, concat() { return this.array().concat(this.array()) @@ -192,7 +192,7 @@ const addressStaticArrayOfArray = { ) }, create(value: StubBytesCompat) { - return interpretAsArc4, 2>>(asBytes(value)) + return convertBytes, 2>>(asBytes(value), { strategy: 'unsafe-cast' }) }, concat() { return this.array().concat(this.array()) @@ -219,7 +219,7 @@ const boolStaticArrayOfArray = { ) }, create(value: StubBytesCompat) { - return interpretAsArc4, 2>>(asBytes(value)) + return convertBytes, 2>>(asBytes(value), { strategy: 'unsafe-cast' }) }, concat() { return this.array().concat(this.array()) @@ -246,7 +246,7 @@ const uint256StaticArrayOfArray = { ) }, create(value: StubBytesCompat) { - return interpretAsArc4, 10>, 2>>(asBytes(value)) + return convertBytes, 10>, 2>>(asBytes(value), { strategy: 'unsafe-cast' }) }, concat() { return this.array().concat(this.array()) @@ -271,7 +271,7 @@ const uint256StaticArrayOfDynamicArray = { return new StaticArray>, 2>(...(this.abiValues().map((a) => new DynamicArray>(...a)) as [])) }, create(value: StubBytesCompat) { - return interpretAsArc4>, 2>>(asBytes(value)) + return convertBytes>, 2>>(asBytes(value), { strategy: 'unsafe-cast' }) }, concat() { return this.array().concat(this.array()) @@ -298,7 +298,7 @@ const stringStaticArrayOfArray = { ) }, create(value: StubBytesCompat) { - return interpretAsArc4, 2>>(asBytes(value)) + return convertBytes, 2>>(asBytes(value), { strategy: 'unsafe-cast' }) }, concat() { return this.array().concat(this.array()) @@ -331,7 +331,7 @@ const stringStaticArrayOfArrayOfArray = { ) }, create(value: StubBytesCompat) { - return interpretAsArc4, 3>, 2>>(asBytes(value)) + return convertBytes, 3>, 2>>(asBytes(value), { strategy: 'unsafe-cast' }) }, concat() { return this.array().concat(this.array()) @@ -383,9 +383,9 @@ const tupleStaticArray = { >(...(this.abiValues() as [])) }, create(value: StubBytesCompat) { - return interpretAsArc4< + return convertBytes< StaticArray, Tuple<[DynamicArray, Str, Uint<256>, Address]>, Bool, StaticArray, 3>]>, 2> - >(asBytes(value)) + >(asBytes(value), { strategy: 'unsafe-cast' }) }, concat() { return this.array().concat(this.array()) @@ -455,7 +455,7 @@ const structStaticArray = { return new StaticArray(...(this.abiValues() as [])) }, create(value: StubBytesCompat) { - return interpretAsArc4>(asBytes(value)) + return convertBytes>(asBytes(value), { strategy: 'unsafe-cast' }) }, concat() { return this.array().concat(this.array()) diff --git a/tests/arc4/static-bytes.algo.spec.ts b/tests/arc4/static-bytes.algo.spec.ts index 9a44b9af..8378e892 100644 --- a/tests/arc4/static-bytes.algo.spec.ts +++ b/tests/arc4/static-bytes.algo.spec.ts @@ -1,6 +1,6 @@ import { getABIEncodedValue } from '@algorandfoundation/algokit-utils/types/app-arc56' import { Bytes } from '@algorandfoundation/algorand-typescript' -import { interpretAsArc4, StaticBytes } from '@algorandfoundation/algorand-typescript/arc4' +import { convertBytes, StaticBytes } from '@algorandfoundation/algorand-typescript/arc4' import { encodingUtil } from '@algorandfoundation/puya-ts' import { describe, expect, test } from 'vitest' @@ -54,7 +54,7 @@ describe('arc4.StaticBytes', async () => { test.each(testData)('create static bytes from bytes', async (data) => { const nativeValue = data.nativeValue() const sdkEncodedBytes = getABIEncodedValue(nativeValue, data.abiTypeString(), {}) - const result = interpretAsArc4(Bytes(sdkEncodedBytes)) + const result = convertBytes(Bytes(sdkEncodedBytes), { strategy: 'unsafe-cast' }) for (let i = 0; i < result.length; i++) { expect(result[i].asUint64()).toEqual(nativeValue[i]) } diff --git a/tests/arc4/str.algo.spec.ts b/tests/arc4/str.algo.spec.ts index 6076da01..26ef037a 100644 --- a/tests/arc4/str.algo.spec.ts +++ b/tests/arc4/str.algo.spec.ts @@ -1,6 +1,6 @@ import { Bytes } from '@algorandfoundation/algorand-typescript' import { TestExecutionContext } from '@algorandfoundation/algorand-typescript-testing' -import { interpretAsArc4, Str } from '@algorandfoundation/algorand-typescript/arc4' +import { convertBytes, Str } from '@algorandfoundation/algorand-typescript/arc4' import { encodingUtil } from '@algorandfoundation/puya-ts' import { afterEach, beforeAll, describe, expect } from 'vitest' import { ABI_RETURN_VALUE_LOG_PREFIX, MAX_LOG_SIZE } from '../../src/constants' @@ -73,7 +73,7 @@ describe('arc4.Str', async () => { ])('create Str from bytes', async (value, { appClientArc4PrimitiveOpsContract: appClient }) => { const paddedValue = new Uint8Array([...encodingUtil.bigIntToUint8Array(BigInt(value.length), 2), ...value]) const avmResult = await getAvmResult({ appClient }, 'verify_string_from_bytes', paddedValue) - const result = interpretAsArc4(Bytes(paddedValue)) + const result = convertBytes(Bytes(paddedValue), { strategy: 'unsafe-cast' }) expect(result.native).toEqual(avmResult) }) @@ -88,7 +88,7 @@ describe('arc4.Str', async () => { ...value, ]) const avmResult = await getAvmResult({ appClient }, 'verify_string_from_log', paddedValue) - const result = interpretAsArc4(Bytes(paddedValue), 'log') + const result = convertBytes(Bytes(paddedValue), { prefix: 'log', strategy: 'unsafe-cast' }) expect(result.native).toEqual(avmResult) }) @@ -101,6 +101,8 @@ describe('arc4.Str', async () => { await expect(() => getAvmResult({ appClient }, 'verify_string_from_log', paddedValue)).rejects.toThrowError( new RegExp('(has valid prefix)|(extraction start \\d+ is beyond length)'), ) - expect(() => interpretAsArc4(Bytes(paddedValue), 'log')).toThrowError('ABI return prefix not found') + expect(() => convertBytes(Bytes(paddedValue), { prefix: 'log', strategy: 'unsafe-cast' })).toThrowError( + 'ABI return prefix not found', + ) }) }) diff --git a/tests/arc4/struct.algo.spec.ts b/tests/arc4/struct.algo.spec.ts index f673115f..83ea6450 100644 --- a/tests/arc4/struct.algo.spec.ts +++ b/tests/arc4/struct.algo.spec.ts @@ -1,6 +1,6 @@ import { getABIEncodedValue } from '@algorandfoundation/algokit-utils/types/app-arc56' import { Bytes } from '@algorandfoundation/algorand-typescript' -import { Bool, DynamicArray, interpretAsArc4, StaticArray, Str, Struct, Tuple, Uint } from '@algorandfoundation/algorand-typescript/arc4' +import { Bool, convertBytes, DynamicArray, StaticArray, Str, Struct, Tuple, Uint } from '@algorandfoundation/algorand-typescript/arc4' import { encodingUtil } from '@algorandfoundation/puya-ts' import { describe, expect, it, test } from 'vitest' import type { StubBytesCompat } from '../../src/impl/primitives' @@ -71,7 +71,7 @@ const testData = [ return new Swapped1(this.abiValues()) }, create(value: StubBytesCompat) { - return interpretAsArc4(asBytes(value)) + return convertBytes(asBytes(value), { strategy: 'unsafe-cast' }) }, }, { @@ -99,7 +99,7 @@ const testData = [ return new Swapped2(this.abiValues()) }, create(value: StubBytesCompat) { - return interpretAsArc4(asBytes(value)) + return convertBytes(asBytes(value), { strategy: 'unsafe-cast' }) }, }, { @@ -138,7 +138,7 @@ const testData = [ return new Swapped3(this.abiValues()) }, create(value: StubBytesCompat) { - return interpretAsArc4(asBytes(value)) + return convertBytes(asBytes(value), { strategy: 'unsafe-cast' }) }, }, { @@ -167,7 +167,7 @@ const testData = [ return new Swapped4(this.abiValues()) }, create(value: StubBytesCompat) { - return interpretAsArc4(asBytes(value)) + return convertBytes(asBytes(value), { strategy: 'unsafe-cast' }) }, }, { @@ -198,7 +198,7 @@ const testData = [ return new Swapped5(this.abiValues()) }, create(value: StubBytesCompat) { - return interpretAsArc4(asBytes(value)) + return convertBytes(asBytes(value), { strategy: 'unsafe-cast' }) }, }, { @@ -229,7 +229,7 @@ const testData = [ return new Swapped6(this.abiValues()) }, create(value: StubBytesCompat) { - return interpretAsArc4(asBytes(value)) + return convertBytes(asBytes(value), { strategy: 'unsafe-cast' }) }, }, ] diff --git a/tests/arc4/tuple.algo.spec.ts b/tests/arc4/tuple.algo.spec.ts index e386eb6d..f31acdf9 100644 --- a/tests/arc4/tuple.algo.spec.ts +++ b/tests/arc4/tuple.algo.spec.ts @@ -1,7 +1,7 @@ import { getABIEncodedValue } from '@algorandfoundation/algokit-utils/types/app-arc56' import { Bytes } from '@algorandfoundation/algorand-typescript' import { TestExecutionContext } from '@algorandfoundation/algorand-typescript-testing' -import { Address, Bool, DynamicArray, interpretAsArc4, StaticArray, Str, Tuple, Uint } from '@algorandfoundation/algorand-typescript/arc4' +import { Address, Bool, convertBytes, DynamicArray, StaticArray, Str, Tuple, Uint } from '@algorandfoundation/algorand-typescript/arc4' import { encodingUtil } from '@algorandfoundation/puya-ts' import { afterEach, describe, expect, test } from 'vitest' import type { StubBytesCompat } from '../../src/impl/primitives' @@ -46,7 +46,7 @@ const testData = [ return new Tuple<[StaticArray, Bool, Bool]>(...this.abiValues()) }, create(value: StubBytesCompat) { - return interpretAsArc4, Bool, Bool]>>(asBytes(value)) + return convertBytes, Bool, Bool]>>(asBytes(value), { strategy: 'unsafe-cast' }) }, }, { @@ -61,7 +61,7 @@ const testData = [ return new Tuple<[Uint<8>, Bool, Bool, Address]>(...this.abiValues()) }, create(value: StubBytesCompat) { - return interpretAsArc4, Bool, Bool, Address]>>(asBytes(value)) + return convertBytes, Bool, Bool, Address]>>(asBytes(value), { strategy: 'unsafe-cast' }) }, }, { @@ -76,7 +76,7 @@ const testData = [ return new Tuple<[Str, Uint<8>, Bool]>(...this.abiValues()) }, create(value: StubBytesCompat) { - return interpretAsArc4, Bool]>>(asBytes(value)) + return convertBytes, Bool]>>(asBytes(value), { strategy: 'unsafe-cast' }) }, }, { @@ -97,7 +97,7 @@ const testData = [ return new Tuple<[Tuple<[Uint<8>, Bool, Bool]>, Tuple<[Uint<8>, Bool, Bool]>]>(...this.abiValues()) }, create(value: StubBytesCompat) { - return interpretAsArc4, Bool, Bool]>, Tuple<[Uint<8>, Bool, Bool]>]>>(asBytes(value)) + return convertBytes, Bool, Bool]>, Tuple<[Uint<8>, Bool, Bool]>]>>(asBytes(value), { strategy: 'unsafe-cast' }) }, }, { @@ -126,7 +126,9 @@ const testData = [ return new Tuple<[DynamicArray, DynamicArray, Str, Uint<8>, Bool, StaticArray, 3>]>(...this.abiValues()) }, create(value: StubBytesCompat) { - return interpretAsArc4, DynamicArray, Str, Uint<8>, Bool, StaticArray, 3>]>>(asBytes(value)) + return convertBytes, DynamicArray, Str, Uint<8>, Bool, StaticArray, 3>]>>(asBytes(value), { + strategy: 'unsafe-cast', + }) }, }, { @@ -145,7 +147,9 @@ const testData = [ return new Tuple<[Tuple<[Bool, DynamicArray, Str]>, Uint<8>, StaticArray, 3>]>(...this.abiValues()) }, create(value: StubBytesCompat) { - return interpretAsArc4, Str]>, Uint<8>, StaticArray, 3>]>>(asBytes(value)) + return convertBytes, Str]>, Uint<8>, StaticArray, 3>]>>(asBytes(value), { + strategy: 'unsafe-cast', + }) }, }, { @@ -166,7 +170,9 @@ const testData = [ return new Tuple<[Tuple<[Bool, DynamicArray, Str]>, Tuple<[Uint<8>, StaticArray, 3>]>]>(...this.abiValues()) }, create(value: StubBytesCompat) { - return interpretAsArc4, Str]>, Tuple<[Uint<8>, StaticArray, 3>]>]>>(asBytes(value)) + return convertBytes, Str]>, Tuple<[Uint<8>, StaticArray, 3>]>]>>(asBytes(value), { + strategy: 'unsafe-cast', + }) }, }, { @@ -192,8 +198,9 @@ const testData = [ ) }, create(value: StubBytesCompat) { - return interpretAsArc4, Str, Address]>]>, Tuple<[Uint<8>, StaticArray, 3>]>]>>( + return convertBytes, Str, Address]>]>, Tuple<[Uint<8>, StaticArray, 3>]>]>>( asBytes(value), + { strategy: 'unsafe-cast' }, ) }, }, diff --git a/tests/arc4/ufixednxm.algo.spec.ts b/tests/arc4/ufixednxm.algo.spec.ts index 67dd2247..fc39d9b3 100644 --- a/tests/arc4/ufixednxm.algo.spec.ts +++ b/tests/arc4/ufixednxm.algo.spec.ts @@ -1,7 +1,7 @@ import type { bytes } from '@algorandfoundation/algorand-typescript' import { Bytes } from '@algorandfoundation/algorand-typescript' import { TestExecutionContext } from '@algorandfoundation/algorand-typescript-testing' -import { interpretAsArc4, UFixed } from '@algorandfoundation/algorand-typescript/arc4' +import { convertBytes, UFixed } from '@algorandfoundation/algorand-typescript/arc4' import { encodingUtil } from '@algorandfoundation/puya-ts' import { afterEach, beforeAll, describe, expect } from 'vitest' import { ABI_RETURN_VALUE_LOG_PREFIX } from '../../src/constants' @@ -67,7 +67,7 @@ describe('arc4.UFixed', async () => { encodingUtil.bigIntToUint8Array(2n ** 32n - 1n), ])('create UFixed<32,8> from bytes', async (value, { appClientArc4PrimitiveOpsContract: appClient }) => { const avmResult = await getAvmResult({ appClient }, 'verify_ufixed_from_bytes', value) - const result = interpretAsArc4>(Bytes(value)) + const result = convertBytes>(Bytes(value), { strategy: 'unsafe-cast' }) expect(asBigUint(result.bytes)).toEqual(avmResult) }) @@ -82,7 +82,7 @@ describe('arc4.UFixed', async () => { async (value, { appClientArc4PrimitiveOpsContract: appClient }) => { await expect(getAvmResult({ appClient }, 'verify_ufixed_from_bytes', value)).rejects.toThrowError(invalidBytesLengthError(32)) - const result = interpretAsArc4>(Bytes(value)) + const result = convertBytes>(Bytes(value), { strategy: 'unsafe-cast' }) expect(result.bytes).toEqual(value) }, ) @@ -96,7 +96,7 @@ describe('arc4.UFixed', async () => { const logValue = asUint8Array(ABI_RETURN_VALUE_LOG_PREFIX.concat(Bytes(value as Uint8Array))) const avmResult = await getAvmResult({ appClient }, 'verify_ufixed_from_log', logValue) - const result = interpretAsArc4>(Bytes(logValue), 'log') + const result = convertBytes>(Bytes(logValue), { prefix: 'log', strategy: 'unsafe-cast' }) expect(avmResult).toEqual(expected) expect(asBigUint(result.bytes)).toEqual(expected) }) @@ -109,7 +109,9 @@ describe('arc4.UFixed', async () => { async ([value, prefix], { appClientArc4PrimitiveOpsContract: appClient }) => { const logValue = asUint8Array(Bytes(prefix as Uint8Array).concat(Bytes(value as bytes))) await expect(() => getAvmResult({ appClient }, 'verify_ufixed_from_log', logValue)).rejects.toThrowError('has valid prefix') - expect(() => interpretAsArc4>(Bytes(logValue), 'log')).toThrowError('ABI return prefix not found') + expect(() => convertBytes>(Bytes(logValue), { prefix: 'log', strategy: 'unsafe-cast' })).toThrowError( + 'ABI return prefix not found', + ) }, ) @@ -124,7 +126,7 @@ describe('arc4.UFixed', async () => { const logValue = asUint8Array(ABI_RETURN_VALUE_LOG_PREFIX.concat(Bytes(value))) await expect(() => getAvmResult({ appClient }, 'verify_ufixed_from_log', logValue)).rejects.toThrowError(invalidBytesLengthError(32)) - const result = interpretAsArc4>(Bytes(logValue), 'log') + const result = convertBytes>(Bytes(logValue), { prefix: 'log', strategy: 'unsafe-cast' }) expect(asBigUint(result.bytes)).toEqual(encodingUtil.uint8ArrayToBigInt(value)) }, ) @@ -136,7 +138,7 @@ describe('arc4.UFixed', async () => { encodingUtil.bigIntToUint8Array(2n ** 256n - 1n), ])('create UFixed<256,16> from bytes', async (value, { appClientArc4PrimitiveOpsContract: appClient }) => { const avmResult = await getAvmResult({ appClient }, 'verify_bigufixed_from_bytes', value) - const result = interpretAsArc4>(Bytes(value)) + const result = convertBytes>(Bytes(value), { strategy: 'unsafe-cast' }) expect(asBigUint(result.bytes)).toEqual(avmResult) }) @@ -151,7 +153,7 @@ describe('arc4.UFixed', async () => { async (value, { appClientArc4PrimitiveOpsContract: appClient }) => { await expect(getAvmResult({ appClient }, 'verify_bigufixed_from_bytes', value)).rejects.toThrowError(invalidBytesLengthError(256)) - const result = interpretAsArc4>(Bytes(value)) + const result = convertBytes>(Bytes(value), { strategy: 'unsafe-cast' }) expect(result.bytes).toEqual(value) }, ) @@ -165,7 +167,7 @@ describe('arc4.UFixed', async () => { const logValue = asUint8Array(ABI_RETURN_VALUE_LOG_PREFIX.concat(Bytes(value as Uint8Array))) const avmResult = await getAvmResult({ appClient }, 'verify_bigufixed_from_log', logValue) - const result = interpretAsArc4>(Bytes(logValue), 'log') + const result = convertBytes>(Bytes(logValue), { prefix: 'log', strategy: 'unsafe-cast' }) expect(avmResult).toEqual(expected) expect(asBigUint(result.bytes)).toEqual(expected) }) @@ -178,7 +180,9 @@ describe('arc4.UFixed', async () => { async ([value, prefix], { appClientArc4PrimitiveOpsContract: appClient }) => { const logValue = asUint8Array(Bytes(prefix as Uint8Array).concat(Bytes(value as bytes))) await expect(() => getAvmResult({ appClient }, 'verify_bigufixed_from_log', logValue)).rejects.toThrowError('has valid prefix') - expect(() => interpretAsArc4>(Bytes(logValue), 'log')).toThrowError('ABI return prefix not found') + expect(() => convertBytes>(Bytes(logValue), { prefix: 'log', strategy: 'unsafe-cast' })).toThrowError( + 'ABI return prefix not found', + ) }, ) @@ -195,7 +199,7 @@ describe('arc4.UFixed', async () => { invalidBytesLengthError(256), ) - const result = interpretAsArc4>(Bytes(logValue), 'log') + const result = convertBytes>(Bytes(logValue), { prefix: 'log', strategy: 'unsafe-cast' }) expect(asBigUint(result.bytes)).toEqual(encodingUtil.uint8ArrayToBigInt(value)) }, ) diff --git a/tests/arc4/uintn.algo.spec.ts b/tests/arc4/uintn.algo.spec.ts index b8f3346a..1a76cda4 100644 --- a/tests/arc4/uintn.algo.spec.ts +++ b/tests/arc4/uintn.algo.spec.ts @@ -2,7 +2,7 @@ import type { bytes } from '@algorandfoundation/algorand-typescript' import { arc4, Bytes } from '@algorandfoundation/algorand-typescript' import { TestExecutionContext } from '@algorandfoundation/algorand-typescript-testing' import type { BitSize } from '@algorandfoundation/algorand-typescript/arc4' -import { interpretAsArc4, Uint, Uint16, Uint256, Uint32, Uint8 } from '@algorandfoundation/algorand-typescript/arc4' +import { convertBytes, Uint, Uint16, Uint256, Uint32, Uint8 } from '@algorandfoundation/algorand-typescript/arc4' import { encodingUtil } from '@algorandfoundation/puya-ts' import { afterEach, beforeAll, describe, expect } from 'vitest' import { ABI_RETURN_VALUE_LOG_PREFIX, MAX_UINT512, MAX_UINT64 } from '../../src/constants' @@ -171,7 +171,7 @@ describe('arc4.Uint', async () => { ])('create Uint<32> from bytes', async (value, { appClientArc4PrimitiveOpsContract: appClient }) => { const avmResult = await getAvmResult({ appClient }, 'verify_uintn_from_bytes', value) - const result = interpretAsArc4(Bytes(value)) + const result = convertBytes(Bytes(value), { strategy: 'unsafe-cast' }) expect(result.asUint64()).toEqual(avmResult) }) @@ -186,7 +186,7 @@ describe('arc4.Uint', async () => { async (value, { appClientArc4PrimitiveOpsContract: appClient }) => { await expect(getAvmResult({ appClient }, 'verify_uintn_from_bytes', value)).rejects.toThrowError(invalidBytesLengthError(32)) - const result = interpretAsArc4(Bytes(value)) + const result = convertBytes(Bytes(value), { strategy: 'unsafe-cast' }) expect(result.asUint64()).toEqual(encodingUtil.uint8ArrayToBigInt(value)) }, ) @@ -198,7 +198,7 @@ describe('arc4.Uint', async () => { encodingUtil.bigIntToUint8Array(2n ** 256n - 1n), ])('create Uint<256> from bytes', async (value, { appClientArc4PrimitiveOpsContract: appClient }) => { const avmResult = await getAvmResult({ appClient }, 'verify_biguintn_from_bytes', value) - const result = interpretAsArc4(Bytes(value)) + const result = convertBytes(Bytes(value), { strategy: 'unsafe-cast' }) expect(result.asBigUint()).toEqual(avmResult) }) @@ -213,7 +213,7 @@ describe('arc4.Uint', async () => { async (value, { appClientArc4PrimitiveOpsContract: appClient }) => { await expect(getAvmResult({ appClient }, 'verify_biguintn_from_bytes', value)).rejects.toThrowError(invalidBytesLengthError(256)) - const result = interpretAsArc4(Bytes(value)) + const result = convertBytes(Bytes(value), { strategy: 'unsafe-cast' }) expect(result.asBigUint()).toEqual(encodingUtil.uint8ArrayToBigInt(value)) }, ) @@ -227,7 +227,7 @@ describe('arc4.Uint', async () => { const logValue = asUint8Array(ABI_RETURN_VALUE_LOG_PREFIX.concat(Bytes(value as Uint8Array))) const avmResult = await getAvmResult({ appClient }, 'verify_uintn_from_log', logValue) - const result = interpretAsArc4>(Bytes(logValue), 'log') + const result = convertBytes>(Bytes(logValue), { prefix: 'log', strategy: 'unsafe-cast' }) expect(BigInt(avmResult as number)).toEqual(expected) expect(result.asUint64()).toEqual(expected) }) @@ -240,7 +240,9 @@ describe('arc4.Uint', async () => { async ([value, prefix], { appClientArc4PrimitiveOpsContract: appClient }) => { const logValue = asUint8Array(Bytes(prefix as Uint8Array).concat(Bytes(value as bytes))) await expect(() => getAvmResult({ appClient }, 'verify_uintn_from_log', logValue)).rejects.toThrowError('has valid prefix') - expect(() => interpretAsArc4>(Bytes(logValue), 'log')).toThrowError('ABI return prefix not found') + expect(() => convertBytes>(Bytes(logValue), { prefix: 'log', strategy: 'unsafe-cast' })).toThrowError( + 'ABI return prefix not found', + ) }, ) @@ -255,7 +257,7 @@ describe('arc4.Uint', async () => { const logValue = asUint8Array(ABI_RETURN_VALUE_LOG_PREFIX.concat(Bytes(value))) await expect(() => getAvmResult({ appClient }, 'verify_uintn_from_log', logValue)).rejects.toThrowError(invalidBytesLengthError(32)) - const result = interpretAsArc4>(Bytes(logValue), 'log') + const result = convertBytes>(Bytes(logValue), { prefix: 'log', strategy: 'unsafe-cast' }) expect(result.asUint64()).toEqual(encodingUtil.uint8ArrayToBigInt(value)) }, ) @@ -269,7 +271,7 @@ describe('arc4.Uint', async () => { const logValue = asUint8Array(ABI_RETURN_VALUE_LOG_PREFIX.concat(Bytes(value as Uint8Array))) const avmResult = await getAvmResult({ appClient }, 'verify_biguintn_from_log', logValue) - const result = interpretAsArc4>(Bytes(logValue), 'log') + const result = convertBytes>(Bytes(logValue), { prefix: 'log', strategy: 'unsafe-cast' }) expect(avmResult).toEqual(expected) expect(result.asBigUint()).toEqual(expected) }) @@ -282,7 +284,9 @@ describe('arc4.Uint', async () => { async ([value, prefix], { appClientArc4PrimitiveOpsContract: appClient }) => { const logValue = asUint8Array(Bytes(prefix as Uint8Array).concat(Bytes(value as bytes))) await expect(() => getAvmResult({ appClient }, 'verify_biguintn_from_log', logValue)).rejects.toThrowError('has valid prefix') - expect(() => interpretAsArc4>(Bytes(logValue), 'log')).toThrowError('ABI return prefix not found') + expect(() => convertBytes>(Bytes(logValue), { prefix: 'log', strategy: 'unsafe-cast' })).toThrowError( + 'ABI return prefix not found', + ) }, ) @@ -299,7 +303,7 @@ describe('arc4.Uint', async () => { invalidBytesLengthError(256), ) - const result = interpretAsArc4>(Bytes(logValue), 'log') + const result = convertBytes>(Bytes(logValue), { prefix: 'log', strategy: 'unsafe-cast' }) expect(result.asBigUint()).toEqual(encodingUtil.uint8ArrayToBigInt(value)) }, ) @@ -308,7 +312,7 @@ describe('arc4.Uint', async () => { 'get uint64 from arc4 big uintn', async (value, { appClientArc4PrimitiveOpsContract: appClient }) => { const avm_result = await getAvmResult({ appClient }, 'verify_biguintn_as_uint64', value) - const result = interpretAsArc4(Bytes(value)).asUint64() + const result = convertBytes(Bytes(value), { strategy: 'unsafe-cast' }).asUint64() expect(avm_result).toEqual(result) }, ) @@ -317,7 +321,9 @@ describe('arc4.Uint', async () => { 'should throw error when getting uint64 from arc4 big uintn that overflows uint64', async (value, { appClientArc4PrimitiveOpsContract: appClient }) => { await expect(() => getAvmResult({ appClient }, 'verify_biguintn_as_uint64', value)).rejects.toThrowError('overflow') - expect(() => interpretAsArc4(Bytes(value)).asUint64()).toThrowError('value too large to fit in uint64') + expect(() => convertBytes(Bytes(value), { strategy: 'unsafe-cast' }).asUint64()).toThrowError( + 'value too large to fit in uint64', + ) }, ) @@ -328,7 +334,7 @@ describe('arc4.Uint', async () => { encodingUtil.bigIntToUint8Array(2n ** 256n - 1n, 32), ])('get biguint from arc4 big uintn', async (value, { appClientArc4PrimitiveOpsContract: appClient }) => { const avm_result = await getAvmResult({ appClient }, 'verify_biguintn_as_biguint', value) - const result = interpretAsArc4(Bytes(value)).asBigUint() + const result = convertBytes(Bytes(value), { strategy: 'unsafe-cast' }).asBigUint() expect(avm_result).toEqual(result) }) @@ -336,7 +342,7 @@ describe('arc4.Uint', async () => { 'get uint64 from arc4 uintn', async (value, { appClientArc4PrimitiveOpsContract: appClient }) => { const avm_result = await getAvmResult({ appClient }, 'verify_uintn64_as_uint64', value) - const result = interpretAsArc4(Bytes(value)).asUint64() + const result = convertBytes(Bytes(value), { strategy: 'unsafe-cast' }).asUint64() expect(avm_result).toEqual(result) }, ) @@ -345,7 +351,7 @@ describe('arc4.Uint', async () => { 'get biguint from arc4 uintn64', async (value, { appClientArc4PrimitiveOpsContract: appClient }) => { const avm_result = await getAvmResult({ appClient }, 'verify_uintn64_as_biguint', value) - const result = interpretAsArc4(Bytes(value)).asBigUint() + const result = convertBytes(Bytes(value), { strategy: 'unsafe-cast' }).asBigUint() expect(avm_result).toEqual(result) }, ) diff --git a/tests/artifacts/arc4-primitive-ops/contract.algo.ts b/tests/artifacts/arc4-primitive-ops/contract.algo.ts index e6859009..8c28f085 100644 --- a/tests/artifacts/arc4-primitive-ops/contract.algo.ts +++ b/tests/artifacts/arc4-primitive-ops/contract.algo.ts @@ -1,7 +1,7 @@ import type { biguint, bytes, uint64 } from '@algorandfoundation/algorand-typescript' import { arc4, BigUint, clone, emit } from '@algorandfoundation/algorand-typescript' import type { Bool, UFixed } from '@algorandfoundation/algorand-typescript/arc4' -import { Byte, Contract, interpretAsArc4, Str, Uint } from '@algorandfoundation/algorand-typescript/arc4' +import { Byte, Contract, convertBytes, Str, Uint } from '@algorandfoundation/algorand-typescript/arc4' export class Arc4PrimitiveOpsContract extends Contract { @arc4.abimethod() @@ -261,47 +261,47 @@ export class Arc4PrimitiveOpsContract extends Contract { } @arc4.abimethod() public verify_uintn_from_bytes(a: bytes): Uint<32> { - return interpretAsArc4>(a) + return convertBytes>(a, { strategy: 'unsafe-cast' }) } @arc4.abimethod() public verify_biguintn_from_bytes(a: bytes): Uint<256> { - return interpretAsArc4>(a) + return convertBytes>(a, { strategy: 'unsafe-cast' }) } @arc4.abimethod() public verify_byte_from_bytes(a: bytes): Byte { - return interpretAsArc4(a) + return convertBytes(a, { strategy: 'unsafe-cast' }) } @arc4.abimethod() public verify_uintn_from_log(a: bytes): Uint<32> { - return interpretAsArc4>(a, 'log') + return convertBytes>(a, { prefix: 'log', strategy: 'unsafe-cast' }) } @arc4.abimethod() public verify_biguintn_from_log(a: bytes): Uint<256> { - return interpretAsArc4>(a, 'log') + return convertBytes>(a, { prefix: 'log', strategy: 'unsafe-cast' }) } @arc4.abimethod() public verify_biguintn_as_uint64(a: bytes): uint64 { - return interpretAsArc4>(a).asUint64() + return convertBytes>(a, { strategy: 'unsafe-cast' }).asUint64() } @arc4.abimethod() public verify_biguintn_as_biguint(a: bytes): biguint { - return interpretAsArc4>(a).asBigUint() + return convertBytes>(a, { strategy: 'unsafe-cast' }).asBigUint() } @arc4.abimethod() public verify_uintn64_as_uint64(a: bytes): uint64 { - return interpretAsArc4(a).asUint64() + return convertBytes(a, { strategy: 'unsafe-cast' }).asUint64() } @arc4.abimethod() public verify_uintn64_as_biguint(a: bytes): biguint { - return interpretAsArc4(a).asBigUint() + return convertBytes(a, { strategy: 'unsafe-cast' }).asBigUint() } @arc4.abimethod() public verify_byte_from_log(a: bytes): Byte { - return interpretAsArc4(a, 'log') + return convertBytes(a, { prefix: 'log', strategy: 'unsafe-cast' }) } @arc4.abimethod() public verify_ufixed_bytes(a: UFixed<32, 8>): bytes { @@ -313,19 +313,19 @@ export class Arc4PrimitiveOpsContract extends Contract { } @arc4.abimethod() public verify_ufixed_from_bytes(a: bytes): UFixed<32, 8> { - return interpretAsArc4>(a) + return convertBytes>(a, { strategy: 'unsafe-cast' }) } @arc4.abimethod() public verify_bigufixed_from_bytes(a: bytes): UFixed<256, 16> { - return interpretAsArc4>(a) + return convertBytes>(a, { strategy: 'unsafe-cast' }) } @arc4.abimethod() public verify_ufixed_from_log(a: bytes): UFixed<32, 8> { - return interpretAsArc4>(a, 'log') + return convertBytes>(a, { prefix: 'log', strategy: 'unsafe-cast' }) } @arc4.abimethod() public verify_bigufixed_from_log(a: bytes): UFixed<256, 16> { - return interpretAsArc4>(a, 'log') + return convertBytes>(a, { prefix: 'log', strategy: 'unsafe-cast' }) } @arc4.abimethod() public verify_string_init(a: string): Str { @@ -348,11 +348,11 @@ export class Arc4PrimitiveOpsContract extends Contract { } @arc4.abimethod() public verify_string_from_bytes(a: bytes): Str { - return interpretAsArc4(a) + return convertBytes(a, { strategy: 'unsafe-cast' }) } @arc4.abimethod() public verify_string_from_log(a: bytes): Str { - return interpretAsArc4(a, 'log') + return convertBytes(a, { prefix: 'log', strategy: 'unsafe-cast' }) } @arc4.abimethod() public verify_bool_bytes(a: Bool): bytes { @@ -360,11 +360,11 @@ export class Arc4PrimitiveOpsContract extends Contract { } @arc4.abimethod() public verify_bool_from_bytes(a: bytes): Bool { - return interpretAsArc4(a) + return convertBytes(a, { strategy: 'unsafe-cast' }) } @arc4.abimethod() public verify_bool_from_log(a: bytes): Bool { - return interpretAsArc4(a, 'log') + return convertBytes(a, { prefix: 'log', strategy: 'unsafe-cast' }) } // TODO: recompile when puya-ts is updated @@ -387,9 +387,9 @@ export class Arc4PrimitiveOpsContract extends Contract { s: bytes, t: bytes, ): void { - const arc4_r = interpretAsArc4>(r) - const arc4_s = interpretAsArc4>(s) - const arc4_t = interpretAsArc4>(t) + const arc4_r = convertBytes>(r, { strategy: 'unsafe-cast' }) + const arc4_s = convertBytes>(s, { strategy: 'unsafe-cast' }) + const arc4_t = convertBytes>(t, { strategy: 'unsafe-cast' }) emit(new SwappedArc4({ m, n, o, p, q, r: clone(arc4_r), s: clone(arc4_s), t: clone(arc4_t) })) emit('Swapped', a, b, c, d, e, f, g, h, m, n, o, p, q, arc4_r, arc4_s, arc4_t) diff --git a/tests/artifacts/created-app-asset/contract.algo.ts b/tests/artifacts/created-app-asset/contract.algo.ts index e6879259..338d58f5 100644 --- a/tests/artifacts/created-app-asset/contract.algo.ts +++ b/tests/artifacts/created-app-asset/contract.algo.ts @@ -1,7 +1,7 @@ import type { gtxn, uint64 } from '@algorandfoundation/algorand-typescript' import { arc4, assert, Global, op } from '@algorandfoundation/algorand-typescript' import type { Uint64 } from '@algorandfoundation/algorand-typescript/arc4' -import { interpretAsArc4, methodSelector } from '@algorandfoundation/algorand-typescript/arc4' +import { convertBytes, methodSelector } from '@algorandfoundation/algorand-typescript/arc4' export class AppExpectingEffects extends arc4.Contract { @arc4.abimethod() @@ -18,6 +18,9 @@ export class AppExpectingEffects extends arc4.Contract { public log_group(appCall: gtxn.ApplicationCallTxn): void { assert(appCall.appArgs(0) === methodSelector('some_value()uint64'), 'expected correct method called') assert(appCall.numLogs === 1, 'expected logs') - assert(interpretAsArc4(appCall.lastLog, 'log').asUint64() === (appCall.groupIndex + 1) * Global.groupSize) + assert( + convertBytes(appCall.lastLog, { prefix: 'log', strategy: 'unsafe-cast' }).asUint64() === + (appCall.groupIndex + 1) * Global.groupSize, + ) } } diff --git a/tests/artifacts/primitive-ops/contract.algo.ts b/tests/artifacts/primitive-ops/contract.algo.ts index bc068e09..189a7ca4 100644 --- a/tests/artifacts/primitive-ops/contract.algo.ts +++ b/tests/artifacts/primitive-ops/contract.algo.ts @@ -13,7 +13,7 @@ import type { Uint64, Uint8, } from '@algorandfoundation/algorand-typescript/arc4' -import { interpretAsArc4 } from '@algorandfoundation/algorand-typescript/arc4' +import { convertBytes } from '@algorandfoundation/algorand-typescript/arc4' export class PrimitiveOpsContract extends arc4.Contract { @arc4.abimethod() @@ -422,9 +422,9 @@ export class PrimitiveOpsContract extends arc4.Contract { n: bytes, ) { const d_biguint = BigUint(d) - const arc4_k = interpretAsArc4>(k) - const arc4_m = interpretAsArc4>(m) - const arc4_n = interpretAsArc4>(n) + const arc4_k = convertBytes>(k, { strategy: 'unsafe-cast' }) + const arc4_m = convertBytes>(m, { strategy: 'unsafe-cast' }) + const arc4_n = convertBytes>(n, { strategy: 'unsafe-cast' }) log(a, b, c, d_biguint, e, f, g, h, i, j, arc4_k, arc4_m, arc4_n) } } diff --git a/tests/fixed-array.algo.spec.ts b/tests/fixed-array.algo.spec.ts index 7eba26cc..2e9e2bb8 100644 --- a/tests/fixed-array.algo.spec.ts +++ b/tests/fixed-array.algo.spec.ts @@ -14,7 +14,7 @@ import { Uint64, } from '@algorandfoundation/algorand-typescript' import { TestExecutionContext } from '@algorandfoundation/algorand-typescript-testing' -import { decodeArc4, encodeArc4, interpretAsArc4, methodSelector } from '@algorandfoundation/algorand-typescript/arc4' +import { convertBytes, decodeArc4, encodeArc4, methodSelector } from '@algorandfoundation/algorand-typescript/arc4' import { describe, expect, it } from 'vitest' class TestContract extends Contract { @@ -434,7 +434,7 @@ describe('FixedArray', () => { it('should decode and encode native uint64 fixed array', () => { const arr = new FixedArray(10, 20, 30) const encoded = encodeArc4(arr) - const interpreted = interpretAsArc4>(encoded) + const interpreted = convertBytes>(encoded, { strategy: 'unsafe-cast' }) const decoded = decodeArc4>(encoded) assertMatch(interpreted.length, arr.length) @@ -447,7 +447,7 @@ describe('FixedArray', () => { it('should decode and encode string fixed array', () => { const arr = new FixedArray('hello', 'world') const encoded = encodeArc4(arr) - const interpreted = interpretAsArc4>(encoded) + const interpreted = convertBytes>(encoded, { strategy: 'unsafe-cast' }) const decoded = decodeArc4>(encoded) assertMatch(interpreted.length, arr.length) @@ -460,7 +460,7 @@ describe('FixedArray', () => { it('should decode and encode boolean fixed array', () => { const arr = new FixedArray(true, false, true, false, true, false, true, false, true, false) const encoded = encodeArc4(arr) - const interpreted = interpretAsArc4>(encoded) + const interpreted = convertBytes>(encoded, { strategy: 'unsafe-cast' }) const decoded = decodeArc4>(encoded) assertMatch(interpreted.length, arr.length) @@ -473,7 +473,7 @@ describe('FixedArray', () => { it('should decode and encode bytes fixed array', () => { const arr = new FixedArray(Bytes('hello'), Bytes('world')) const encoded = encodeArc4(arr) - const interpreted = interpretAsArc4>(encoded) + const interpreted = convertBytes>(encoded, { strategy: 'unsafe-cast' }) const decoded = decodeArc4>(encoded) assertMatch(interpreted.length, arr.length) @@ -486,7 +486,7 @@ describe('FixedArray', () => { it('should decode and encode nested fixed array', () => { const arr = new FixedArray, 2>(new FixedArray(1, 2), new FixedArray(3, 4)) const encoded = encodeArc4(arr) - const interpreted = interpretAsArc4, 2>>(encoded) + const interpreted = convertBytes, 2>>(encoded, { strategy: 'unsafe-cast' }) const decoded = decodeArc4, 2>>(encoded) assertMatch(interpreted.length, arr.length) @@ -502,7 +502,7 @@ describe('FixedArray', () => { it('should decode and encode fixed array with native arrays', () => { const arr = new FixedArray([1, 2, 3], [4, 5]) const encoded = encodeArc4(arr) - const interpreted = interpretAsArc4, 2>>(encoded) + const interpreted = convertBytes, 2>>(encoded, { strategy: 'unsafe-cast' }) const decoded = decodeArc4>(encoded) assertMatch(interpreted.length, arr.length) @@ -518,7 +518,7 @@ describe('FixedArray', () => { it('should decode and encode fixed array with tuples', () => { const arr = new FixedArray<[uint64, string], 2>([10, 'first'], [20, 'second']) const encoded = encodeArc4(arr) - const interpreted = interpretAsArc4, 2>>(encoded) + const interpreted = convertBytes, 2>>(encoded, { strategy: 'unsafe-cast' }) const decoded = decodeArc4>(encoded) assertMatch(interpreted.length, arr.length) @@ -534,7 +534,7 @@ describe('FixedArray', () => { const arr = new FixedArray({ x: 1, y: 2 }, { x: 3, y: 4 }) class ObjStruct extends arc4.Struct<{ x: arc4.Uint64; y: arc4.Uint64 }> {} const encoded = encodeArc4(arr) - const interpreted = interpretAsArc4>(encoded) + const interpreted = convertBytes>(encoded, { strategy: 'unsafe-cast' }) const decoded = decodeArc4>(encoded) assertMatch(interpreted.length, arr.length) @@ -548,7 +548,7 @@ describe('FixedArray', () => { it('should decode and encode arc4 fixed array', () => { const arr = new FixedArray(new arc4.Uint64(100), new arc4.Uint64(200), new arc4.Uint64(300)) const encoded = encodeArc4(arr) - const interpreted = interpretAsArc4>(encoded) + const interpreted = convertBytes>(encoded, { strategy: 'unsafe-cast' }) const decoded = decodeArc4>(encoded) assertMatch(interpreted.length, arr.length) diff --git a/tests/native-mutable-array.algo.spec.ts b/tests/native-mutable-array.algo.spec.ts index b37ee86d..35bc9f88 100644 --- a/tests/native-mutable-array.algo.spec.ts +++ b/tests/native-mutable-array.algo.spec.ts @@ -14,7 +14,7 @@ import { Uint64, } from '@algorandfoundation/algorand-typescript' import { TestExecutionContext } from '@algorandfoundation/algorand-typescript-testing' -import { decodeArc4, encodeArc4, interpretAsArc4, methodSelector } from '@algorandfoundation/algorand-typescript/arc4' +import { convertBytes, decodeArc4, encodeArc4, methodSelector } from '@algorandfoundation/algorand-typescript/arc4' import { describe, expect, it } from 'vitest' class TestContract extends Contract { @@ -309,7 +309,7 @@ describe('native mutable array', () => { it('should decode and encode mutable uint64 array', () => { const arr: uint64[] = [10, 20, 30, 40] const encoded = encodeArc4(arr) - const interpreted = interpretAsArc4>(encoded) + const interpreted = convertBytes>(encoded, { strategy: 'unsafe-cast' }) const decoded = decodeArc4(encoded) assertMatch(interpreted.length, arr.length) @@ -322,7 +322,7 @@ describe('native mutable array', () => { it('should decode and encode mutable string array', () => { const arr: string[] = ['hello', 'world', 'test', 'mutable'] const encoded = encodeArc4(arr) - const interpreted = interpretAsArc4>(encoded) + const interpreted = convertBytes>(encoded, { strategy: 'unsafe-cast' }) const decoded = decodeArc4(encoded) assertMatch(interpreted.length, arr.length) @@ -335,7 +335,7 @@ describe('native mutable array', () => { it('should decode and encode mutable boolean array', () => { const arr: boolean[] = [true, false, true, false, true, true, false, true, false, true] const encoded = encodeArc4(arr) - const interpreted = interpretAsArc4>(encoded) + const interpreted = convertBytes>(encoded, { strategy: 'unsafe-cast' }) const decoded = decodeArc4(encoded) assertMatch(interpreted.length, arr.length) @@ -348,7 +348,7 @@ describe('native mutable array', () => { it('should decode and encode mutable bytes array', () => { const arr: bytes[] = [Bytes('hello'), Bytes('world'), Bytes('test'), Bytes('data')] const encoded = encodeArc4(arr) - const interpreted = interpretAsArc4>(encoded) + const interpreted = convertBytes>(encoded, { strategy: 'unsafe-cast' }) const decoded = decodeArc4(encoded) assertMatch(interpreted.length, arr.length) @@ -361,7 +361,7 @@ describe('native mutable array', () => { it('should decode and encode mutable nested array', () => { const arr: uint64[][] = [[1, 2], [3, 4, 5], [6], [7, 8, 9, 10]] const encoded = encodeArc4(arr) - const interpreted = interpretAsArc4>>(encoded) + const interpreted = convertBytes>>(encoded, { strategy: 'unsafe-cast' }) const decoded = decodeArc4(encoded) assertMatch(interpreted.length, arr.length) @@ -381,7 +381,7 @@ describe('native mutable array', () => { new FixedArray(5, 6), ] const encoded = encodeArc4(arr) - const interpreted = interpretAsArc4>>(encoded) + const interpreted = convertBytes>>(encoded, { strategy: 'unsafe-cast' }) const decoded = decodeArc4[]>(encoded) assertMatch(interpreted.length, arr.length) @@ -401,7 +401,7 @@ describe('native mutable array', () => { [30, 'third'], ] const encoded = encodeArc4(arr) - const interpreted = interpretAsArc4>>(encoded) + const interpreted = convertBytes>>(encoded, { strategy: 'unsafe-cast' }) const decoded = decodeArc4<[uint64, string][]>(encoded) assertMatch(interpreted.length, arr.length) @@ -421,7 +421,7 @@ describe('native mutable array', () => { ] class PointStruct extends arc4.Struct<{ x: arc4.Uint64; y: arc4.Uint64 }> {} const encoded = encodeArc4(arr) - const interpreted = interpretAsArc4>(encoded) + const interpreted = convertBytes>(encoded, { strategy: 'unsafe-cast' }) const decoded = decodeArc4(encoded) assertMatch(interpreted.length, arr.length) @@ -435,7 +435,7 @@ describe('native mutable array', () => { it('should decode and encode mutable arc4 array', () => { const arr: arc4.Uint64[] = [new arc4.Uint64(100), new arc4.Uint64(200), new arc4.Uint64(300), new arc4.Uint64(400)] const encoded = encodeArc4(arr) - const interpreted = interpretAsArc4>(encoded) + const interpreted = convertBytes>(encoded, { strategy: 'unsafe-cast' }) const decoded = decodeArc4(encoded) assertMatch(interpreted.length, arr.length) @@ -448,7 +448,7 @@ describe('native mutable array', () => { it('should decode and encode empty mutable array', () => { const arr: uint64[] = [] const encoded = encodeArc4(arr) - const interpreted = interpretAsArc4>(encoded) + const interpreted = convertBytes>(encoded, { strategy: 'unsafe-cast' }) const decoded = decodeArc4(encoded) assertMatch(interpreted.length, 0) @@ -463,7 +463,7 @@ describe('native mutable array', () => { arr[0] = 10 const encoded = encodeArc4(arr) - const interpreted = interpretAsArc4>(encoded) + const interpreted = convertBytes>(encoded, { strategy: 'unsafe-cast' }) const decoded = decodeArc4(encoded) const expected = [10, 2, 3, 4, 5] diff --git a/tests/native-mutable-object.algo.spec.ts b/tests/native-mutable-object.algo.spec.ts index 4eca52b9..3b0228ad 100644 --- a/tests/native-mutable-object.algo.spec.ts +++ b/tests/native-mutable-object.algo.spec.ts @@ -13,7 +13,7 @@ import { LocalState, } from '@algorandfoundation/algorand-typescript' import { TestExecutionContext } from '@algorandfoundation/algorand-typescript-testing' -import { decodeArc4, encodeArc4, interpretAsArc4, methodSelector } from '@algorandfoundation/algorand-typescript/arc4' +import { convertBytes, decodeArc4, encodeArc4, methodSelector } from '@algorandfoundation/algorand-typescript/arc4' import { describe, expect, it } from 'vitest' type SimpleObj = { @@ -591,7 +591,7 @@ describe('native mutable object', () => { }> {} const obj: SimpleObj = { a: 1, b: true, c: 'hello', d: Bytes('world') } const encoded = encodeArc4(obj) - const interpreted = interpretAsArc4(encoded) + const interpreted = convertBytes(encoded, { strategy: 'unsafe-cast' }) const decoded = decodeArc4(encoded) assertMatch(interpreted.a.asUint64(), obj.a) @@ -622,7 +622,7 @@ describe('native mutable object', () => { } const encoded = encodeArc4(obj) - const interpreted = interpretAsArc4(encoded) + const interpreted = convertBytes(encoded, { strategy: 'unsafe-cast' }) const decoded = decodeArc4(encoded) assertMatch(interpreted.a.asUint64(), obj.a) @@ -650,7 +650,7 @@ describe('native mutable object', () => { } const encoded = encodeArc4(obj) - const interpreted = interpretAsArc4(encoded) + const interpreted = convertBytes(encoded, { strategy: 'unsafe-cast' }) const decoded = decodeArc4(encoded) assertMatch(interpreted.a.asUint64(), obj.a) @@ -679,7 +679,7 @@ describe('native mutable object', () => { } const encoded = encodeArc4(obj) - const interpreted = interpretAsArc4(encoded) + const interpreted = convertBytes(encoded, { strategy: 'unsafe-cast' }) const decoded = decodeArc4(encoded) assertMatch(interpreted.a.asUint64(), obj.a) @@ -708,7 +708,7 @@ describe('native mutable object', () => { } const encoded = encodeArc4(obj) - const interpreted = interpretAsArc4(encoded) + const interpreted = convertBytes(encoded, { strategy: 'unsafe-cast' }) const decoded = decodeArc4(encoded) assertMatch(interpreted.a.asUint64(), obj.a) @@ -736,7 +736,7 @@ describe('native mutable object', () => { } const encoded = encodeArc4(obj) - const interpreted = interpretAsArc4(encoded) + const interpreted = convertBytes(encoded, { strategy: 'unsafe-cast' }) const decoded = decodeArc4(encoded) assertMatch(interpreted.a, obj.a) @@ -773,7 +773,7 @@ describe('native mutable object', () => { } const encoded = encodeArc4(obj) - const interpreted = interpretAsArc4(encoded) + const interpreted = convertBytes(encoded, { strategy: 'unsafe-cast' }) const decoded = decodeArc4(encoded) assertMatch(interpreted.a.asUint64(), obj.a) diff --git a/tests/native-readonly-array.algo.spec.ts b/tests/native-readonly-array.algo.spec.ts index 9646785e..c0405d93 100644 --- a/tests/native-readonly-array.algo.spec.ts +++ b/tests/native-readonly-array.algo.spec.ts @@ -14,7 +14,7 @@ import { Uint64, } from '@algorandfoundation/algorand-typescript' import { TestExecutionContext } from '@algorandfoundation/algorand-typescript-testing' -import { decodeArc4, encodeArc4, interpretAsArc4, methodSelector } from '@algorandfoundation/algorand-typescript/arc4' +import { convertBytes, decodeArc4, encodeArc4, methodSelector } from '@algorandfoundation/algorand-typescript/arc4' import { describe, expect, it } from 'vitest' class TestContract extends Contract { @@ -340,7 +340,7 @@ describe('native readonly array', () => { it('should decode and encode readonly uint64 array', () => { const arr: readonly uint64[] = [10, 20, 30, 40] const encoded = encodeArc4(arr) - const interpreted = interpretAsArc4>(encoded) + const interpreted = convertBytes>(encoded, { strategy: 'unsafe-cast' }) const decoded = decodeArc4(encoded) assertMatch(interpreted.length, arr.length) @@ -353,7 +353,7 @@ describe('native readonly array', () => { it('should decode and encode readonly string array', () => { const arr: readonly string[] = ['hello', 'world', 'test'] const encoded = encodeArc4(arr) - const interpreted = interpretAsArc4>(encoded) + const interpreted = convertBytes>(encoded, { strategy: 'unsafe-cast' }) const decoded = decodeArc4(encoded) assertMatch(interpreted.length, arr.length) @@ -366,7 +366,7 @@ describe('native readonly array', () => { it('should decode and encode readonly boolean array', () => { const arr: readonly boolean[] = [true, false, true, false, true, true, false, true, false, true] const encoded = encodeArc4(arr) - const interpreted = interpretAsArc4>(encoded) + const interpreted = convertBytes>(encoded, { strategy: 'unsafe-cast' }) const decoded = decodeArc4(encoded) assertMatch(interpreted.length, arr.length) @@ -379,7 +379,7 @@ describe('native readonly array', () => { it('should decode and encode readonly bytes array', () => { const arr: readonly bytes[] = [Bytes('hello'), Bytes('world'), Bytes('test')] const encoded = encodeArc4(arr) - const interpreted = interpretAsArc4>(encoded) + const interpreted = convertBytes>(encoded, { strategy: 'unsafe-cast' }) const decoded = decodeArc4(encoded) assertMatch(interpreted.length, arr.length) @@ -392,7 +392,7 @@ describe('native readonly array', () => { it('should decode and encode readonly nested array', () => { const arr: readonly (readonly uint64[])[] = [[1, 2], [3, 4, 5], [6]] const encoded = encodeArc4(arr) - const interpreted = interpretAsArc4>>(encoded) + const interpreted = convertBytes>>(encoded, { strategy: 'unsafe-cast' }) const decoded = decodeArc4(encoded) assertMatch(interpreted.length, arr.length) @@ -412,7 +412,7 @@ describe('native readonly array', () => { new FixedArray(5, 6), ] const encoded = encodeArc4(arr) - const interpreted = interpretAsArc4>>(encoded) + const interpreted = convertBytes>>(encoded, { strategy: 'unsafe-cast' }) const decoded = decodeArc4[]>(encoded) assertMatch(interpreted.length, arr.length) @@ -432,7 +432,9 @@ describe('native readonly array', () => { [30, 'third'], ] const encoded = encodeArc4(arr) - const interpreted = interpretAsArc4>>(encoded) + const interpreted = convertBytes>>(encoded, { + strategy: 'unsafe-cast', + }) const decoded = decodeArc4(encoded) assertMatch(interpreted.length, arr.length) @@ -452,7 +454,7 @@ describe('native readonly array', () => { ] class PointStruct extends arc4.Struct<{ x: arc4.Uint64; y: arc4.Uint64 }> {} const encoded = encodeArc4(arr) - const interpreted = interpretAsArc4>(encoded) + const interpreted = convertBytes>(encoded, { strategy: 'unsafe-cast' }) const decoded = decodeArc4(encoded) assertMatch(interpreted.length, arr.length) @@ -466,7 +468,7 @@ describe('native readonly array', () => { it('should decode and encode readonly arc4 array', () => { const arr: readonly arc4.Uint64[] = [new arc4.Uint64(100), new arc4.Uint64(200), new arc4.Uint64(300)] const encoded = encodeArc4(arr) - const interpreted = interpretAsArc4>(encoded) + const interpreted = convertBytes>(encoded, { strategy: 'unsafe-cast' }) const decoded = decodeArc4(encoded) assertMatch(interpreted.length, arr.length) @@ -479,7 +481,7 @@ describe('native readonly array', () => { it('should decode and encode empty readonly array', () => { const arr: readonly uint64[] = [] const encoded = encodeArc4(arr) - const interpreted = interpretAsArc4>(encoded) + const interpreted = convertBytes>(encoded, { strategy: 'unsafe-cast' }) const decoded = decodeArc4(encoded) assertMatch(interpreted.length, 0) diff --git a/tests/native-readonly-object.algo.spec.ts b/tests/native-readonly-object.algo.spec.ts index 9818e920..12085c67 100644 --- a/tests/native-readonly-object.algo.spec.ts +++ b/tests/native-readonly-object.algo.spec.ts @@ -13,7 +13,7 @@ import { LocalState, } from '@algorandfoundation/algorand-typescript' import { TestExecutionContext } from '@algorandfoundation/algorand-typescript-testing' -import { decodeArc4, encodeArc4, interpretAsArc4, methodSelector } from '@algorandfoundation/algorand-typescript/arc4' +import { convertBytes, decodeArc4, encodeArc4, methodSelector } from '@algorandfoundation/algorand-typescript/arc4' import { describe, expect, it } from 'vitest' type SimpleObj = Readonly<{ @@ -602,7 +602,7 @@ describe('native readonly object', () => { }> {} const obj: SimpleObj = { a: 1, b: true, c: 'hello', d: Bytes('world') } const encoded = encodeArc4(obj) - const interpreted = interpretAsArc4(encoded) + const interpreted = convertBytes(encoded, { strategy: 'unsafe-cast' }) const decoded = decodeArc4(encoded) assertMatch(interpreted.a.asUint64(), obj.a) @@ -633,7 +633,7 @@ describe('native readonly object', () => { } const encoded = encodeArc4(obj) - const interpreted = interpretAsArc4(encoded) + const interpreted = convertBytes(encoded, { strategy: 'unsafe-cast' }) const decoded = decodeArc4(encoded) assertMatch(interpreted.a.asUint64(), obj.a) @@ -661,7 +661,7 @@ describe('native readonly object', () => { } const encoded = encodeArc4(obj) - const interpreted = interpretAsArc4(encoded) + const interpreted = convertBytes(encoded, { strategy: 'unsafe-cast' }) const decoded = decodeArc4(encoded) assertMatch(interpreted.a.asUint64(), obj.a) @@ -690,7 +690,7 @@ describe('native readonly object', () => { } const encoded = encodeArc4(obj) - const interpreted = interpretAsArc4(encoded) + const interpreted = convertBytes(encoded, { strategy: 'unsafe-cast' }) const decoded = decodeArc4(encoded) assertMatch(interpreted.a.asUint64(), obj.a) @@ -719,7 +719,7 @@ describe('native readonly object', () => { } const encoded = encodeArc4(obj) - const interpreted = interpretAsArc4(encoded) + const interpreted = convertBytes(encoded, { strategy: 'unsafe-cast' }) const decoded = decodeArc4(encoded) assertMatch(interpreted.a.asUint64(), obj.a) @@ -747,7 +747,7 @@ describe('native readonly object', () => { } const encoded = encodeArc4(obj) - const interpreted = interpretAsArc4(encoded) + const interpreted = convertBytes(encoded, { strategy: 'unsafe-cast' }) const decoded = decodeArc4(encoded) assertMatch(interpreted.a, obj.a) @@ -784,7 +784,7 @@ describe('native readonly object', () => { } const encoded = encodeArc4(obj) - const interpreted = interpretAsArc4(encoded) + const interpreted = convertBytes(encoded, { strategy: 'unsafe-cast' }) const decoded = decodeArc4(encoded) assertMatch(interpreted.a.asUint64(), obj.a) diff --git a/tests/primitives/bytes.algo.spec.ts b/tests/primitives/bytes.algo.spec.ts index f6ed134b..a3546e5b 100644 --- a/tests/primitives/bytes.algo.spec.ts +++ b/tests/primitives/bytes.algo.spec.ts @@ -5,7 +5,7 @@ import { beforeAll, describe, expect, it } from 'vitest' import { MAX_BYTES_SIZE } from '../../src/constants' import type { Byte, StaticArray } from '@algorandfoundation/algorand-typescript/arc4' -import { decodeArc4, interpretAsArc4, sizeOf } from '@algorandfoundation/algorand-typescript/arc4' +import { convertBytes, decodeArc4, sizeOf } from '@algorandfoundation/algorand-typescript/arc4' import { sha256 } from '../../src/impl' import { BytesCls } from '../../src/impl/primitives' import { asUint8Array } from '../../src/util' @@ -236,7 +236,7 @@ describe('Bytes', async () => { expect(x2[0].length).toEqual(32) expect(x2[1].length).toEqual(32) - const x3 = interpretAsArc4, 2>>(Bytes<64>()) + const x3 = convertBytes, 2>>(Bytes<64>(), { strategy: 'unsafe-cast' }) expect(x3.length).toEqual(2) expect(x3[0].bytes).toEqual(Bytes.fromHex<32>('0000000000000000000000000000000000000000000000000000000000000000')) expect(x3[1].bytes).toEqual(Bytes.fromHex<32>('0000000000000000000000000000000000000000000000000000000000000000')) diff --git a/tests/references/box-map.algo.spec.ts b/tests/references/box-map.algo.spec.ts index 5752df68..b0b1d404 100644 --- a/tests/references/box-map.algo.spec.ts +++ b/tests/references/box-map.algo.spec.ts @@ -5,9 +5,9 @@ import type { StaticArray, Uint16 } from '@algorandfoundation/algorand-typescrip import { ARC4Encoded, Bool, + convertBytes, DynamicArray, DynamicBytes, - interpretAsArc4, Str, Struct, Uint8, @@ -91,7 +91,7 @@ describe('BoxMap', () => { key: Uint64(456), value: new Str('Test1'), newValue: new Str('hello'), - emptyValue: interpretAsArc4(Bytes('')), + emptyValue: convertBytes(Bytes(''), { strategy: 'unsafe-cast' }), withBoxContext: (test: (boxMap: BoxMap) => void) => { ctx.txn.createScope([ctx.any.txn.applicationCall()]).execute(() => { const boxMap = BoxMap({ keyPrefix }) @@ -103,7 +103,7 @@ describe('BoxMap', () => { key: new Str('jkl'), value: new DynamicArray(new arc4.Uint64(100), new arc4.Uint64(200)), newValue: new DynamicArray(new arc4.Uint64(200), new arc4.Uint64(300)), - emptyValue: interpretAsArc4>(Bytes('')), + emptyValue: convertBytes>(Bytes(''), { strategy: 'unsafe-cast' }), withBoxContext: (test: (boxMap: BoxMap>) => void) => { ctx.txn.createScope([ctx.any.txn.applicationCall()]).execute(() => { const boxMap = BoxMap>({ keyPrefix }) @@ -139,7 +139,7 @@ describe('BoxMap', () => { key: new Str('OTest'), value: { a: 'hello', b: Bytes('world'), c: true } as unknown as MyStruct, newValue: { a: 'world', b: Bytes('hello'), c: false } as unknown as MyStruct, - emptyValue: interpretAsArc4(Bytes('')), + emptyValue: convertBytes(Bytes(''), { strategy: 'unsafe-cast' }), withBoxContext: (test: (boxMap: BoxMap) => void) => { ctx.txn.createScope([ctx.any.txn.applicationCall()]).execute(() => { const boxMap = BoxMap({ keyPrefix }) diff --git a/tests/references/box.algo.spec.ts b/tests/references/box.algo.spec.ts index 1618baad..b79e480d 100644 --- a/tests/references/box.algo.spec.ts +++ b/tests/references/box.algo.spec.ts @@ -5,9 +5,9 @@ import type { Uint16 } from '@algorandfoundation/algorand-typescript/arc4' import { ARC4Encoded, Bool, + convertBytes, DynamicArray, DynamicBytes, - interpretAsArc4, StaticArray, Str, Tuple, @@ -89,7 +89,7 @@ describe('Box', () => { { value: new Str('Test1'), newValue: new Str('hello'), - emptyValue: interpretAsArc4(Bytes('')), + emptyValue: convertBytes(Bytes(''), { strategy: 'unsafe-cast' }), withBoxContext: (test: (boxMap: Box) => void) => { ctx.txn.createScope([ctx.any.txn.applicationCall()]).execute(() => { const boxMap = Box({ key }) @@ -100,7 +100,7 @@ describe('Box', () => { { value: new DynamicArray(new arc4.Uint64(100), new arc4.Uint64(200)), newValue: new DynamicArray(new arc4.Uint64(200), new arc4.Uint64(300)), - emptyValue: interpretAsArc4>(Bytes('')), + emptyValue: convertBytes>(Bytes(''), { strategy: 'unsafe-cast' }), withBoxContext: (test: (boxMap: Box>) => void) => { ctx.txn.createScope([ctx.any.txn.applicationCall()]).execute(() => { const boxMap = Box>({ key }) From ab53bd311fd98eafe80ced9e1dc3ca8ab8b5d9cd Mon Sep 17 00:00:00 2001 From: Bobby Lat Date: Thu, 18 Sep 2025 14:19:08 +0700 Subject: [PATCH 50/68] feat: update langspec to v4.3.0 --- package-lock.json | 18 ++++---- package.json | 4 +- src/impl/app-params.ts | 4 ++ src/impl/gtxn.ts | 5 +- src/impl/itxn.ts | 13 +++++- src/impl/reference.ts | 4 ++ src/impl/transactions.ts | 5 ++ src/impl/txn.ts | 5 +- tests/artifacts/avm12/contract.algo.ts | 63 ++++++++++++++++++++++++++ tests/avm12.algo.spec.ts | 49 ++++++++++++++++++++ 10 files changed, 155 insertions(+), 15 deletions(-) create mode 100644 tests/artifacts/avm12/contract.algo.ts create mode 100644 tests/avm12.algo.spec.ts diff --git a/package-lock.json b/package-lock.json index 66c22aba..3c179dae 100644 --- a/package-lock.json +++ b/package-lock.json @@ -8,8 +8,8 @@ "name": "@algorandfoundation/algorand-typescript-testing", "version": "1.0.0", "dependencies": { - "@algorandfoundation/algorand-typescript": "1.0.0-alpha.83", - "@algorandfoundation/puya-ts": "1.0.0-alpha.83", + "@algorandfoundation/algorand-typescript": "1.0.0-alpha.84", + "@algorandfoundation/puya-ts": "1.0.0-alpha.84", "elliptic": "^6.6.1", "js-sha256": "^0.11.0", "js-sha3": "^0.9.3", @@ -76,21 +76,21 @@ } }, "node_modules/@algorandfoundation/algorand-typescript": { - "version": "1.0.0-alpha.83", - "resolved": "https://registry.npmjs.org/@algorandfoundation/algorand-typescript/-/algorand-typescript-1.0.0-alpha.83.tgz", - "integrity": "sha512-TTdc5fGSRssQSj0eGb2GTBZHthqlfIMEhk6qSbaJbTvk5XBLG6W6ktRNsXe+ncxZn2qQQ4fuoZ+uJJARs1FpBQ==", + "version": "1.0.0-alpha.84", + "resolved": "https://registry.npmjs.org/@algorandfoundation/algorand-typescript/-/algorand-typescript-1.0.0-alpha.84.tgz", + "integrity": "sha512-JhAPv2DAUEdVTKifQ7RNtynH2A0uJqifL9HTTLkJpJiiYEJlUN/Td3nwwntpVW5uIHunPPzdKjiTbh4HW2nluA==", "peerDependencies": { "tslib": "^2.6.2" } }, "node_modules/@algorandfoundation/puya-ts": { - "version": "1.0.0-alpha.83", - "resolved": "https://registry.npmjs.org/@algorandfoundation/puya-ts/-/puya-ts-1.0.0-alpha.83.tgz", - "integrity": "sha512-yB6kXwDMfK2pjBODmSTf/xC5+olrdU8Co8My5Z/kEVRDjoZsVXCbCHGMI+ixdq02XFrvgM4DgjdXLvaM1YeoTQ==", + "version": "1.0.0-alpha.84", + "resolved": "https://registry.npmjs.org/@algorandfoundation/puya-ts/-/puya-ts-1.0.0-alpha.84.tgz", + "integrity": "sha512-veHuRk9RVrpEXuXBa8U5VeM5gqL6z+B/yNzSXvgEqub1H3ERxfYI7iWGcG0LDC/zVtMFLhCo42P/fwL/81tzpg==", "hasInstallScript": true, "license": "MIT", "dependencies": { - "@algorandfoundation/algorand-typescript": "1.0.0-alpha.83", + "@algorandfoundation/algorand-typescript": "1.0.0-alpha.84", "arcsecond": "^5.0.0", "argparse": "^2.0.1", "chalk": "^5.4.1", diff --git a/package.json b/package.json index 33184dc2..069636cc 100644 --- a/package.json +++ b/package.json @@ -68,8 +68,8 @@ "vitest": "3.2.4" }, "dependencies": { - "@algorandfoundation/algorand-typescript": "1.0.0-alpha.83", - "@algorandfoundation/puya-ts": "1.0.0-alpha.83", + "@algorandfoundation/algorand-typescript": "1.0.0-alpha.84", + "@algorandfoundation/puya-ts": "1.0.0-alpha.84", "elliptic": "^6.6.1", "js-sha256": "^0.11.0", "js-sha3": "^0.9.3", diff --git a/src/impl/app-params.ts b/src/impl/app-params.ts index 03d23508..15ae51ea 100644 --- a/src/impl/app-params.ts +++ b/src/impl/app-params.ts @@ -71,4 +71,8 @@ export const AppParams: typeof op.AppParams = { const app = getApp(a) return app === undefined ? [Account(), false] : [app.address, true] }, + appVersion: function (a: ApplicationType | uint64): readonly [uint64, boolean] { + const app = getApp(a) + return app === undefined ? [Uint64(0), false] : [app.version, true] + }, } diff --git a/src/impl/gtxn.ts b/src/impl/gtxn.ts index 871e0385..d08de4d7 100644 --- a/src/impl/gtxn.ts +++ b/src/impl/gtxn.ts @@ -197,7 +197,7 @@ export const GTxn: typeof op.GTxn = { lastLog(t: StubUint64Compat): bytes { return lazyContext.activeGroup.getApplicationCallTransaction(asUint64(t)).lastLog }, - stateProofPk(t: StubUint64Compat): bytes { + stateProofPk(t: StubUint64Compat): bytes<64> { return lazyContext.activeGroup.getKeyRegistrationTransaction(asUint64(t)).stateProofKey }, approvalProgramPages(a: StubUint64Compat, b: StubUint64Compat): bytes { @@ -212,6 +212,9 @@ export const GTxn: typeof op.GTxn = { numClearStateProgramPages(t: StubUint64Compat): uint64 { return lazyContext.activeGroup.getApplicationCallTransaction(asUint64(t)).numClearStateProgramPages }, + rejectVersion: function (a: uint64): uint64 { + return lazyContext.activeGroup.getApplicationCallTransaction(asUint64(a)).rejectVersion + }, } /** @internal */ diff --git a/src/impl/itxn.ts b/src/impl/itxn.ts index 281e1107..3225cf2e 100644 --- a/src/impl/itxn.ts +++ b/src/impl/itxn.ts @@ -243,7 +243,7 @@ export const GITxn: typeof op.GITxn = { lastLog: function (t: StubUint64Compat): bytes { return getApplicationCallInnerTxn(t).lastLog }, - stateProofPk: function (t: StubUint64Compat): bytes { + stateProofPk: function (t: StubUint64Compat): bytes<64> { return getKeyRegistrationInnerTxn(t).stateProofKey }, approvalProgramPages: function (t: StubUint64Compat, a: StubUint64Compat): bytes { @@ -258,6 +258,9 @@ export const GITxn: typeof op.GITxn = { numClearStateProgramPages: function (t: StubUint64Compat): uint64 { return getApplicationCallInnerTxn(t).numClearStateProgramPages }, + rejectVersion: function (t: StubUint64Compat): uint64 { + return getApplicationCallInnerTxn(t).rejectVersion + }, } /** @internal */ export const ITxn: typeof op.ITxn = { @@ -643,7 +646,7 @@ export const ITxn: typeof op.ITxn = { /** * 64 byte state proof public key */ - get stateProofPk(): bytes { + get stateProofPk(): bytes<64> { return lazyContext.activeGroup.getItxnGroup().getKeyRegistrationInnerTxn().stateProofKey }, /** @@ -670,6 +673,9 @@ export const ITxn: typeof op.ITxn = { get numClearStateProgramPages(): uint64 { return lazyContext.activeGroup.getItxnGroup().getApplicationCallInnerTxn().numClearStateProgramPages }, + get rejectVersion(): uint64 { + return lazyContext.activeGroup.getItxnGroup().getApplicationCallInnerTxn().rejectVersion + }, } const setConstructingItxnField = (fields: Partial): void => { @@ -862,6 +868,9 @@ export const ITxnCreate: typeof op.ITxnCreate = { pages.push(asBytes(a)) setConstructingItxnField({ clearStateProgram: pages }) }, + setRejectVersion: function (a: StubUint64Compat): void { + setConstructingItxnField({ rejectVersion: asUint64(a) }) + }, next: function (): void { lazyContext.activeGroup.appendInnerTransactionGroup() }, diff --git a/src/impl/reference.ts b/src/impl/reference.ts index 2b1156f2..41a6a28c 100644 --- a/src/impl/reference.ts +++ b/src/impl/reference.ts @@ -168,6 +168,7 @@ export class ApplicationData { localStateMaps: new BytesMap(), boxes: new BytesMap(), materialisedBoxes: new BytesMap(), + version: 0, } } } @@ -221,6 +222,9 @@ export class ApplicationCls extends Uint64BackedCls implements ApplicationType { } return result } + get version(): uint64 { + return this.data.application.version + } } export type AssetData = Mutable> diff --git a/src/impl/transactions.ts b/src/impl/transactions.ts index 84c59d59..9c2891ed 100644 --- a/src/impl/transactions.ts +++ b/src/impl/transactions.ts @@ -253,6 +253,7 @@ export class ApplicationCallTransaction extends TransactionBase implements gtxn. #clearStateProgramPages: Array #appLogs: Array #appId: ApplicationType + #rejectVersion: uint64 /** @internal */ protected constructor(fields: ApplicationCallTransactionFields) { @@ -272,6 +273,7 @@ export class ApplicationCallTransaction extends TransactionBase implements gtxn. this.#apps = fields.apps ?? [] this.#approvalProgramPages = fields.approvalProgramPages ?? (fields.approvalProgram ? [fields.approvalProgram] : []) this.#clearStateProgramPages = fields.clearStateProgramPages ?? (fields.clearStateProgram ? [fields.clearStateProgram] : []) + this.#rejectVersion = fields.rejectVersion ?? Uint64(0) Object.entries(fields.scratchSpace ?? {}).forEach(([k, v]) => this.setScratchSlot(Number(k), v)) } @@ -335,6 +337,9 @@ export class ApplicationCallTransaction extends TransactionBase implements gtxn. get apfa() { return this.#apps } + get rejectVersion() { + return this.#rejectVersion + } appArgs(index: Uint64Compat): bytes { return toBytes(this.args[asNumber(index)]) } diff --git a/src/impl/txn.ts b/src/impl/txn.ts index cae045c1..28a147d0 100644 --- a/src/impl/txn.ts +++ b/src/impl/txn.ts @@ -462,7 +462,7 @@ export const Txn: typeof op.Txn = { /** * 64 byte state proof public key */ - get stateProofPk(): bytes { + get stateProofPk(): bytes<64> { return lazyContext.activeGroup.getKeyRegistrationTransaction().stateProofKey }, @@ -493,4 +493,7 @@ export const Txn: typeof op.Txn = { get numClearStateProgramPages(): uint64 { return lazyContext.activeGroup.getApplicationCallTransaction().numClearStateProgramPages }, + get rejectVersion(): uint64 { + return lazyContext.activeGroup.getApplicationCallTransaction().rejectVersion + }, } diff --git a/tests/artifacts/avm12/contract.algo.ts b/tests/artifacts/avm12/contract.algo.ts new file mode 100644 index 00000000..a04763b4 --- /dev/null +++ b/tests/artifacts/avm12/contract.algo.ts @@ -0,0 +1,63 @@ +import { + abimethod, + arc4, + assert, + Bytes, + contract, + Contract, + itxn, + OnCompleteAction, + op, + Txn, +} from '@algorandfoundation/algorand-typescript' + +@contract({ avmVersion: 12 }) +export class Avm12Contract extends Contract { + testFalconVerify() { + assert(!op.falconVerify(Bytes(), Bytes(), op.bzero(1793).toFixed({ length: 1793 }))) + } + + testRejectVersion() { + const compiledV0 = arc4.compileArc4(ContractV0) + const v0Txn = compiledV0.bareCreate() + const app = v0Txn.createdApp + assert(app.version === 0, 'should be version 0') + + const compiledV1 = arc4.compileArc4(ContractV1) + + const v1Txn = compiledV0.call.update({ + rejectVersion: 1, + appId: app, + onCompletion: OnCompleteAction.UpdateApplication, + approvalProgram: compiledV1.approvalProgram, + clearStateProgram: compiledV1.clearStateProgram, + extraProgramPages: compiledV1.extraProgramPages, + }) + assert(v1Txn.itxn.appId.version === 1, 'should be version 1') + + itxn + .applicationCall({ + appArgs: [arc4.methodSelector(ContractV1.prototype.delete)], + onCompletion: OnCompleteAction.DeleteApplication, + appId: app, + rejectVersion: 2, + }) + .submit() + } +} + +@contract({ avmVersion: 12 }) +export class ContractV0 extends Contract { + @abimethod({ allowActions: 'UpdateApplication' }) + update() { + assert(Txn.rejectVersion === 1, 'can only update if caller expects this to be currently be v0') + } +} + +@contract({ avmVersion: 12 }) +export class ContractV1 extends Contract { + @abimethod({ allowActions: 'DeleteApplication' }) + delete() { + assert(Txn.rejectVersion === 2, 'can only update if caller expects this to be currently be v1') + } +} diff --git a/tests/avm12.algo.spec.ts b/tests/avm12.algo.spec.ts new file mode 100644 index 00000000..943fcfc5 --- /dev/null +++ b/tests/avm12.algo.spec.ts @@ -0,0 +1,49 @@ +import { algos } from '@algorandfoundation/algokit-utils' +import { ApplicationSpy, TestExecutionContext } from '@algorandfoundation/algorand-typescript-testing' +import { afterEach, beforeAll, describe, expect } from 'vitest' +import { Avm12Contract, ContractV0, ContractV1 } from './artifacts/avm12/contract.algo' +import { createArc4TestFixture } from './test-fixture' + +describe('avm12', () => { + const [test, localnetFixture] = createArc4TestFixture('tests/artifacts/avm12/contract.algo.ts', { + Avm12Contract: { funding: algos(1) }, + }) + + const ctx = new TestExecutionContext() + beforeAll(async () => { + await localnetFixture.newScope() + }) + afterEach(() => { + ctx.reset() + }) + + test('reject wrong app version', async ({ appClientAvm12Contract }) => { + await appClientAvm12Contract.send.call({ method: 'testRejectVersion', args: [], extraFee: algos(1) }) + const itxnComposeAlgoContract = ctx.contract.create(Avm12Contract) + + const contractV0App = ctx.any.application({ + approvalProgram: ctx.any.bytes(), + }) + ctx.setCompiledApp(ContractV0, contractV0App.id) + + const spyV0 = new ApplicationSpy(ContractV0) + spyV0.onBareCall((itxnContext) => { + if (itxnContext.approvalProgram === contractV0App.approvalProgram) { + itxnContext.createdApp = contractV0App + } + }) + spyV0.on.update((itxnContext) => { + if (itxnContext.appId === contractV0App) { + ctx.ledger.patchApplicationData(itxnContext.appId, { application: { version: 1 } }) + } + }) + + const spyV1 = new ApplicationSpy(ContractV1) + spyV1.on.delete((itxnContext) => { + expect(itxnContext.rejectVersion).toEqual(2) + }) + ctx.addApplicationSpy(spyV0) + ctx.addApplicationSpy(spyV1) + itxnComposeAlgoContract.testRejectVersion() + }) +}) From 38e0c38d3358bc46b2f72cd222b03b0753f89985 Mon Sep 17 00:00:00 2001 From: Bobby Lat Date: Tue, 23 Sep 2025 13:20:28 +0700 Subject: [PATCH 51/68] test: add mock test for falconVerify --- docs/coverage.md | 1 + src/impl/crypto.ts | 5 +++++ src/internal/op.ts | 1 + tests/crypto-op-codes.algo.spec.ts | 22 ++++++++++++++++++++++ 4 files changed, 29 insertions(+) diff --git a/docs/coverage.md b/docs/coverage.md index d43a8324..37113f38 100644 --- a/docs/coverage.md +++ b/docs/coverage.md @@ -151,3 +151,4 @@ See which `algorand-typescript` stubs are implemented by the `algorand-typescrip | op.sqrt | Native | | op.substring | Native | | op.vrfVerify | Mockable | +| op.falconVerify | Mockable | diff --git a/src/impl/crypto.ts b/src/impl/crypto.ts index 3ccfbc2e..00ce5b58 100644 --- a/src/impl/crypto.ts +++ b/src/impl/crypto.ts @@ -147,6 +147,11 @@ export const mimc = (_c: MimcConfigurations, _a: StubBytesCompat): bytes => { throw new NotImplementedError('mimc') } +/** @internal */ +export const falconVerify = (_a: StubBytesCompat, _b: StubBytesCompat, _c: StubBytesCompat): boolean => { + throw new NotImplementedError('falconVerify') +} + const curveMap = { [Ecdsa.Secp256k1]: 'secp256k1', [Ecdsa.Secp256r1]: 'p256', diff --git a/src/internal/op.ts b/src/internal/op.ts index 67dec9cc..3f4ae94c 100644 --- a/src/internal/op.ts +++ b/src/internal/op.ts @@ -24,6 +24,7 @@ export { ed25519verify, ed25519verifyBare, EllipticCurve, + falconVerify, keccak256, mimc, sha256, diff --git a/tests/crypto-op-codes.algo.spec.ts b/tests/crypto-op-codes.algo.spec.ts index 797086a8..f8acd131 100644 --- a/tests/crypto-op-codes.algo.spec.ts +++ b/tests/crypto-op-codes.algo.spec.ts @@ -30,6 +30,7 @@ vi.mock('../src/impl/crypto', async (importOriginal) => { ...mod, vrfVerify: vi.fn(mod.vrfVerify), mimc: vi.fn(mod.mimc), + falconVerify: vi.fn(mod.falconVerify), } }) @@ -362,6 +363,27 @@ describe('crypto op codes', async () => { ) }) }) + + describe('falconVerify', async () => { + const a = Bytes.fromHex('528b9e23d93d0e020a119d7ba213f6beb1c1f3495a217166ecd20f5a70e7c2d7') + const b = op.bzero(1232).toFixed({ length: 1232 }) + + const c = op.bzero(1793).toFixed({ length: 1793 }) + + test('should throw not available error', async () => { + const mockedFalconVerify = op.falconVerify as Mock + // restore to original stub implemention which should throw not available error + mockedFalconVerify.mockRestore() + expect(() => op.falconVerify(a, b, c)).toThrow('falconVerify is not available in test context') + }) + + test('should return mocked result', () => { + const mockedFalconVerify = op.falconVerify as Mock + mockedFalconVerify.mockReturnValue(true) + const result = op.falconVerify(a, b, c) + expect(result).toBe(true) + }) + }) }) const generateEcdsaTestData = (v: Ecdsa) => { From e59da620d38ed557c4a2a2edc9f74f9812f01fb8 Mon Sep 17 00:00:00 2001 From: Bobby Lat Date: Tue, 23 Sep 2025 14:51:28 +0700 Subject: [PATCH 52/68] refactor: simplify test transformer as Bytes factor method now always return dynamic bytes --- examples/voting/contract.algo.spec.ts | 10 ++- package-lock.json | 18 +++--- package.json | 4 +- src/constants.ts | 7 +- src/impl/crypto.ts | 14 ++-- src/impl/primitives.ts | 92 --------------------------- src/runtime-helpers.ts | 2 - src/test-transformer/node-factory.ts | 13 ---- src/test-transformer/visitors.ts | 11 +--- tests/crypto-op-codes.algo.spec.ts | 38 +++++------ tests/primitives/bytes.algo.spec.ts | 28 ++++---- tests/references/asset.algo.spec.ts | 4 +- 12 files changed, 66 insertions(+), 175 deletions(-) diff --git a/examples/voting/contract.algo.spec.ts b/examples/voting/contract.algo.spec.ts index 8d52cb38..fab56189 100644 --- a/examples/voting/contract.algo.spec.ts +++ b/examples/voting/contract.algo.spec.ts @@ -16,7 +16,7 @@ describe('VotingRoundApp', () => { const createContract = () => { const contract = ctx.contract.create(VotingRoundApp) - const snapshotPublicKey = Bytes<32>(keyPair.publicKey) + const snapshotPublicKey = Bytes(keyPair.publicKey).toFixed({ length: 32 }) const metadataIpfsCid = ctx.any.string(16) const startTime = ctx.any.uint64(Date.now() - 10_000, Date.now()) const endTime = ctx.any.uint64(Date.now() + 10_000, Date.now() + 100_000) @@ -52,7 +52,7 @@ describe('VotingRoundApp', () => { const account = ctx.any.account() const signature = nacl.sign.detached(toExternalValue(account.bytes), keyPair.secretKey) ctx.txn.createScope([ctx.any.txn.applicationCall({ sender: account })]).execute(() => { - const preconditions = contract.getPreconditions(Bytes(signature)) + const preconditions = contract.getPreconditions(Bytes(signature).toFixed({ length: 64 })) expect(preconditions.is_allowed_to_vote).toEqual(1) expect(preconditions.is_voting_open).toEqual(1) @@ -75,7 +75,11 @@ describe('VotingRoundApp', () => { ) ctx.txn.createScope([ctx.any.txn.applicationCall({ appId: app, sender: account })]).execute(() => { - contract.vote(ctx.any.txn.payment({ receiver: app.address, amount: voteMinBalanceReq }), Bytes(signature), answerIds) + contract.vote( + ctx.any.txn.payment({ receiver: app.address, amount: voteMinBalanceReq }), + Bytes(signature).toFixed({ length: 64 }), + answerIds, + ) expect(contract.votesByAccount(account).value.bytes).toEqual(answerIds.bytes) expect(contract.voterCount.value).toEqual(13) diff --git a/package-lock.json b/package-lock.json index 3c179dae..e0d25145 100644 --- a/package-lock.json +++ b/package-lock.json @@ -8,8 +8,8 @@ "name": "@algorandfoundation/algorand-typescript-testing", "version": "1.0.0", "dependencies": { - "@algorandfoundation/algorand-typescript": "1.0.0-alpha.84", - "@algorandfoundation/puya-ts": "1.0.0-alpha.84", + "@algorandfoundation/algorand-typescript": "1.0.0-alpha.85", + "@algorandfoundation/puya-ts": "1.0.0-alpha.85", "elliptic": "^6.6.1", "js-sha256": "^0.11.0", "js-sha3": "^0.9.3", @@ -76,21 +76,21 @@ } }, "node_modules/@algorandfoundation/algorand-typescript": { - "version": "1.0.0-alpha.84", - "resolved": "https://registry.npmjs.org/@algorandfoundation/algorand-typescript/-/algorand-typescript-1.0.0-alpha.84.tgz", - "integrity": "sha512-JhAPv2DAUEdVTKifQ7RNtynH2A0uJqifL9HTTLkJpJiiYEJlUN/Td3nwwntpVW5uIHunPPzdKjiTbh4HW2nluA==", + "version": "1.0.0-alpha.85", + "resolved": "https://registry.npmjs.org/@algorandfoundation/algorand-typescript/-/algorand-typescript-1.0.0-alpha.85.tgz", + "integrity": "sha512-lTOeDCRVNUhhDn03vILJIOY1++ZLJfo3tps4mRaENtD1+fqSMaUfH6msJaKsblY4XqBhrOWtBTPKpx1yyj7OkQ==", "peerDependencies": { "tslib": "^2.6.2" } }, "node_modules/@algorandfoundation/puya-ts": { - "version": "1.0.0-alpha.84", - "resolved": "https://registry.npmjs.org/@algorandfoundation/puya-ts/-/puya-ts-1.0.0-alpha.84.tgz", - "integrity": "sha512-veHuRk9RVrpEXuXBa8U5VeM5gqL6z+B/yNzSXvgEqub1H3ERxfYI7iWGcG0LDC/zVtMFLhCo42P/fwL/81tzpg==", + "version": "1.0.0-alpha.85", + "resolved": "https://registry.npmjs.org/@algorandfoundation/puya-ts/-/puya-ts-1.0.0-alpha.85.tgz", + "integrity": "sha512-Y21eXPgHo81yDsOy9a79qsdCGaLvQpz4BO1Bv9xct/Kjioq2InnWFmcV2LuKzCZC7a/kaBlvJkE8ChR6BU6M8g==", "hasInstallScript": true, "license": "MIT", "dependencies": { - "@algorandfoundation/algorand-typescript": "1.0.0-alpha.84", + "@algorandfoundation/algorand-typescript": "1.0.0-alpha.85", "arcsecond": "^5.0.0", "argparse": "^2.0.1", "chalk": "^5.4.1", diff --git a/package.json b/package.json index 069636cc..cbffb169 100644 --- a/package.json +++ b/package.json @@ -68,8 +68,8 @@ "vitest": "3.2.4" }, "dependencies": { - "@algorandfoundation/algorand-typescript": "1.0.0-alpha.84", - "@algorandfoundation/puya-ts": "1.0.0-alpha.84", + "@algorandfoundation/algorand-typescript": "1.0.0-alpha.85", + "@algorandfoundation/puya-ts": "1.0.0-alpha.85", "elliptic": "^6.6.1", "js-sha256": "^0.11.0", "js-sha3": "^0.9.3", diff --git a/src/constants.ts b/src/constants.ts index 1d8d9ec9..c2176509 100644 --- a/src/constants.ts +++ b/src/constants.ts @@ -1,4 +1,4 @@ -import { Bytes, FixedBytes } from './impl/primitives' +import { Bytes } from './impl/primitives' /** @internal */ export const UINT64_SIZE = 64 @@ -40,13 +40,12 @@ export const DEFAULT_ASSET_OPT_IN_MIN_BALANCE = 10_000 /** @internal * from python code: list(b"\x85Y\xb5\x14x\xfd\x89\xc1vC\xd0]\x15\xa8\xaek\x10\xabG\xbbm\x8a1\x88\x11V\xe6\xbd;\xae\x95\xd1") */ -export const DEFAULT_GLOBAL_GENESIS_HASH = FixedBytes( - 32, +export const DEFAULT_GLOBAL_GENESIS_HASH = Bytes( new Uint8Array([ 133, 89, 181, 20, 120, 253, 137, 193, 118, 67, 208, 93, 21, 168, 174, 107, 16, 171, 71, 187, 109, 138, 49, 136, 17, 86, 230, 189, 59, 174, 149, 209, ]), -) +).toFixed({ length: 32 }) /** @internal * algorand encoded address of 32 zero bytes diff --git a/src/impl/crypto.ts b/src/impl/crypto.ts index 00ce5b58..2f3302d4 100644 --- a/src/impl/crypto.ts +++ b/src/impl/crypto.ts @@ -10,13 +10,13 @@ import { lazyContext } from '../context-helpers/internal-context' import { InternalError, NotImplementedError } from '../errors' import { asBytes, asBytesCls, asUint8Array, concatUint8Arrays } from '../util' import type { StubBytesCompat, StubUint64Compat } from './primitives' -import { Bytes, BytesCls, FixedBytes, Uint64Cls } from './primitives' +import { Bytes, BytesCls, Uint64Cls } from './primitives' /** @internal */ export const sha256 = (a: StubBytesCompat): bytes<32> => { const bytesA = BytesCls.fromCompat(a) const hashArray = js_sha256.sha256.create().update(bytesA.asUint8Array()).digest() - const hashBytes = FixedBytes(32, new Uint8Array(hashArray)) + const hashBytes = Bytes(new Uint8Array(hashArray)).toFixed({ length: 32 }) return hashBytes } @@ -24,7 +24,7 @@ export const sha256 = (a: StubBytesCompat): bytes<32> => { export const sha3_256 = (a: StubBytesCompat): bytes<32> => { const bytesA = BytesCls.fromCompat(a) const hashArray = js_sha3.sha3_256.create().update(bytesA.asUint8Array()).digest() - const hashBytes = FixedBytes(32, new Uint8Array(hashArray)) + const hashBytes = Bytes(new Uint8Array(hashArray)).toFixed({ length: 32 }) return hashBytes } @@ -32,7 +32,7 @@ export const sha3_256 = (a: StubBytesCompat): bytes<32> => { export const keccak256 = (a: StubBytesCompat): bytes<32> => { const bytesA = BytesCls.fromCompat(a) const hashArray = js_sha3.keccak256.create().update(bytesA.asUint8Array()).digest() - const hashBytes = FixedBytes(32, new Uint8Array(hashArray)) + const hashBytes = Bytes(new Uint8Array(hashArray)).toFixed({ length: 32 }) return hashBytes } @@ -40,7 +40,7 @@ export const keccak256 = (a: StubBytesCompat): bytes<32> => { export const sha512_256 = (a: StubBytesCompat): bytes<32> => { const bytesA = BytesCls.fromCompat(a) const hashArray = js_sha512.sha512_256.create().update(bytesA.asUint8Array()).digest() - const hashBytes = FixedBytes(32, new Uint8Array(hashArray)) + const hashBytes = Bytes(new Uint8Array(hashArray)).toFixed({ length: 32 }) return hashBytes } @@ -114,7 +114,7 @@ export const ecdsaPkRecover = ( const x = pubKey.getX().toArray('be') const y = pubKey.getY().toArray('be') - return [FixedBytes(32, x), FixedBytes(32, y)] + return [Bytes(x).toFixed({ length: 32 }), Bytes(y).toFixed({ length: 32 })] } /** @internal */ @@ -127,7 +127,7 @@ export const ecdsaPkDecompress = (v: Ecdsa, a: StubBytesCompat): readonly [bytes const x = pubKey.getX().toArray('be') const y = pubKey.getY().toArray('be') - return [FixedBytes(32, new Uint8Array(x)), FixedBytes(32, new Uint8Array(y))] + return [Bytes(x).toFixed({ length: 32 }), Bytes(y).toFixed({ length: 32 })] } /** @internal */ diff --git a/src/impl/primitives.ts b/src/impl/primitives.ts index 8c332738..9394b030 100644 --- a/src/impl/primitives.ts +++ b/src/impl/primitives.ts @@ -1,7 +1,6 @@ import type { biguint, BigUintCompat, bytes, BytesCompat, uint64, Uint64Compat } from '@algorandfoundation/algorand-typescript' import { encodingUtil } from '@algorandfoundation/puya-ts' import { avmError, AvmError, avmInvariant, CodeError, InternalError } from '../errors' -import type { DeliberateAny } from '../typescript-helpers' import { nameOfType } from '../typescript-helpers' import { base32ToUint8Array } from './base-32' @@ -88,97 +87,6 @@ export function BigUint(v?: BigUintCompat | string): biguint { return BigUintCls.fromCompat(v).asAlgoTs() } -/** - * @internal - * Create a byte array from a string interpolation template and compatible replacements - * @param value * - * @param replacements * - */ -export function FixedBytes( - length: TLength, - value: TemplateStringsArray, - ...replacements: BytesCompat[] -): bytes -/** - * @internal - * Create a byte array from a utf8 string - */ -export function FixedBytes(length: TLength, value: string): bytes -/** - * @internal - * No op, returns the provided byte array. - */ -export function FixedBytes(length: TLength, value: bytes): bytes -/** - * @internal - * Create a byte array from a biguint value encoded as a variable length big-endian number * - */ -export function FixedBytes(length: TLength, value: biguint): bytes -/** - * @internal - * Create a byte array from a uint64 value encoded as a fixed length 64-bit number - */ -export function FixedBytes(length: TLength, value: uint64): bytes -/** - * @internal - * Create a byte array from an Iterable where each item is interpreted as a single byte and must be between 0 and 255 inclusively - */ -export function FixedBytes(length: TLength, value: Iterable): bytes -/** - * @internal - * Create an empty byte array - */ -export function FixedBytes(length: TLength): bytes -export function FixedBytes( - length: TLength, - value?: BytesCompat | TemplateStringsArray | biguint | uint64 | Iterable, - ...replacements: BytesCompat[] -): bytes { - const result = Bytes((value ?? new Uint8Array(length)) as DeliberateAny, ...replacements) - if (length && length !== getNumber(result.length)) { - throw new CodeError(`Invalid bytes constant length of ${result.length}, expected ${length}`) - } - return result.toFixed({ length }) -} - -/** - * @internal - * Create a new bytes value from a hexadecimal encoded string - * @param hex - */ -FixedBytes.fromHex = (length: TLength, hex: string): bytes => { - const result = BytesCls.fromHex(hex).asAlgoTs() - if (length && length !== getNumber(result.length)) { - throw new CodeError(`Expected decoded bytes value of length ${length}, received ${result.length}`) - } - return result.toFixed({ length }) -} -/** - * @internal - * Create a new bytes value from a base 64 encoded string - * @param b64 - */ -FixedBytes.fromBase64 = (length: TLength, b64: string): bytes => { - const result = BytesCls.fromBase64(b64).asAlgoTs() - if (length && length !== getNumber(result.length)) { - throw new CodeError(`Expected decoded bytes value of length ${length}, received ${result.length}`) - } - return result.toFixed({ length }) -} - -/** - * @internal - * Create a new bytes value from a base 32 encoded string - * @param b32 - */ -FixedBytes.fromBase32 = (length: TLength, b32: string): bytes => { - const result = BytesCls.fromBase32(b32).asAlgoTs() - if (length && length !== getNumber(result.length)) { - throw new CodeError(`Expected decoded bytes value of length ${length}, received ${result.length}`) - } - return result.toFixed({ length }) -} - /** * @internal * Create a byte array from a string interpolation template and compatible replacements diff --git a/src/runtime-helpers.ts b/src/runtime-helpers.ts index b3f32ad1..bf66e61d 100644 --- a/src/runtime-helpers.ts +++ b/src/runtime-helpers.ts @@ -11,8 +11,6 @@ import { flattenAsBytes } from './util' /** @internal */ export { attachAbiMetadata } from './abi-metadata' -/** @internal */ -export { FixedBytes } from './impl/primitives' /** @internal */ export function switchableValue(x: unknown): bigint | string | boolean { diff --git a/src/test-transformer/node-factory.ts b/src/test-transformer/node-factory.ts index 8688dc0d..484de3f9 100644 --- a/src/test-transformer/node-factory.ts +++ b/src/test-transformer/node-factory.ts @@ -149,17 +149,4 @@ export const nodeFactory = { } return node }, - - callFixedBytesFunction(functionName: string, node: ts.CallExpression, length: number) { - const updatedPropertyAccessExpression = factory.createPropertyAccessExpression( - factory.createIdentifier('runtimeHelpers'), - `FixedBytes${functionName === 'Bytes' ? '' : `.${functionName}`}`, - ) - - return factory.createCallExpression( - updatedPropertyAccessExpression, - node.typeArguments, - [factory.createNumericLiteral(length), ...(node.arguments ?? [])].filter((arg) => !!arg), - ) - }, } satisfies Record ts.Node | ts.Node[]> diff --git a/src/test-transformer/visitors.ts b/src/test-transformer/visitors.ts index 9116654c..37bde05e 100644 --- a/src/test-transformer/visitors.ts +++ b/src/test-transformer/visitors.ts @@ -218,9 +218,6 @@ class ExpressionVisitor { updatedNode = nodeFactory.callAbiCallFunction(updatedNode, typeParams) } else if (isCallingItxnCompose(stubbedFunctionName)) { updatedNode = nodeFactory.callItxnComposeFunction(updatedNode) - } else if (isCallingBytes(stubbedFunctionName)) { - if (type instanceof ptypes.BytesPType && type.length) - updatedNode = nodeFactory.callFixedBytesFunction(stubbedFunctionName, updatedNode, Number(type.length)) } else { updatedNode = nodeFactory.callStubbedFunction(updatedNode, infoArg) } @@ -506,7 +503,7 @@ const tryGetStubbedFunctionName = (node: ts.CallExpression, helper: VisitorHelpe : (node.expression as ts.Identifier) const functionName = tryGetAlgoTsSymbolName(identityExpression, helper) if (functionName === undefined) return undefined - const stubbedFunctionNames = ['convertBytes', 'decodeArc4', 'encodeArc4', 'emit', 'methodSelector', 'sizeOf', 'abiCall', 'clone', 'Bytes'] + const stubbedFunctionNames = ['convertBytes', 'decodeArc4', 'encodeArc4', 'emit', 'methodSelector', 'sizeOf', 'abiCall', 'clone'] if (stubbedFunctionNames.includes(functionName)) { if (ts.isPropertyAccessExpression(node.expression)) { @@ -516,10 +513,10 @@ const tryGetStubbedFunctionName = (node: ts.CallExpression, helper: VisitorHelpe return functionName } - if (['begin', 'next', 'fromHex', 'fromBase64', 'fromBase32'].includes(functionName) && ts.isPropertyAccessExpression(node.expression)) { + if (['begin', 'next'].includes(functionName) && ts.isPropertyAccessExpression(node.expression)) { const objectExpression = node.expression.expression const objectName = tryGetAlgoTsSymbolName(objectExpression, helper) - if (['itxnCompose', 'Bytes'].includes(objectName || '')) return functionName + if (['itxnCompose'].includes(objectName || '')) return functionName } return undefined @@ -550,5 +547,3 @@ const isCallingMethodSelector = (functionName: string | undefined): boolean => ' const isCallingAbiCall = (functionName: string | undefined): boolean => ['abiCall'].includes(functionName ?? '') const isCallingItxnCompose = (functionName: string | undefined): boolean => ['begin', 'next'].includes(functionName ?? '') const isCallingClone = (functionName: string | undefined): boolean => 'clone' === (functionName ?? '') -const isCallingBytes = (functionName: string | undefined): boolean => - ['Bytes', 'fromHex', 'fromBase64', 'fromBase32'].includes(functionName ?? '') diff --git a/tests/crypto-op-codes.algo.spec.ts b/tests/crypto-op-codes.algo.spec.ts index f8acd131..9200e133 100644 --- a/tests/crypto-op-codes.algo.spec.ts +++ b/tests/crypto-op-codes.algo.spec.ts @@ -163,17 +163,17 @@ describe('crypto op codes', async () => { }) }) test('should throw error when no active txn group', async () => { - expect(() => op.ed25519verify(Bytes(''), Bytes<64>(), Bytes<32>())).toThrow('no active txn group') + expect(() => op.ed25519verify(Bytes(''), op.bzero(64), op.bzero(32))).toThrow('no active txn group') }) }) describe('ecdsaVerify', async () => { test('should be able to verify k1 signature', async ({ appClientCryptoOpsContract: appClient }) => { - const messageHash = Bytes.fromHex<32>('f809fd0aa0bb0f20b354c6b2f86ea751957a4e262a546bd716f34f69b9516ae1') - const sigR = Bytes.fromHex<32>('f7f913754e5c933f3825d3aef22e8bf75cfe35a18bede13e15a6e4adcfe816d2') - const sigS = Bytes.fromHex<32>('0b5599159aa859d79677f33280848ae4c09c2061e8b5881af8507f8112966754') - const pubkeyX = Bytes.fromHex<32>('a710244d62747aa8db022ddd70617240adaf881b439e5f69993800e614214076') - const pubkeyY = Bytes.fromHex<32>('48d0d337704fe2c675909d2c93f7995e199156f302f63c74a8b96827b28d777b') + const messageHash = Bytes.fromHex('f809fd0aa0bb0f20b354c6b2f86ea751957a4e262a546bd716f34f69b9516ae1').toFixed({ length: 32 }) + const sigR = Bytes.fromHex('f7f913754e5c933f3825d3aef22e8bf75cfe35a18bede13e15a6e4adcfe816d2').toFixed({ length: 32 }) + const sigS = Bytes.fromHex('0b5599159aa859d79677f33280848ae4c09c2061e8b5881af8507f8112966754').toFixed({ length: 32 }) + const pubkeyX = Bytes.fromHex('a710244d62747aa8db022ddd70617240adaf881b439e5f69993800e614214076').toFixed({ length: 32 }) + const pubkeyY = Bytes.fromHex('48d0d337704fe2c675909d2c93f7995e199156f302f63c74a8b96827b28d777b').toFixed({ length: 32 }) const avmResult = await getAvmResult( { @@ -194,11 +194,11 @@ describe('crypto op codes', async () => { expect(result).toEqual(avmResult) }) test('should be able to verify r1 signature', async ({ appClientCryptoOpsContract: appClient }) => { - const messageHash = Bytes.fromHex<32>('f809fd0aa0bb0f20b354c6b2f86ea751957a4e262a546bd716f34f69b9516ae1') - const sigR = Bytes.fromHex<32>('18d96c7cda4bc14d06277534681ded8a94828eb731d8b842e0da8105408c83cf') - const sigS = Bytes.fromHex<32>('7d33c61acf39cbb7a1d51c7126f1718116179adebd31618c4604a1f03b5c274a') - const pubkeyX = Bytes.fromHex<32>('f8140e3b2b92f7cbdc8196bc6baa9ce86cf15c18e8ad0145d50824e6fa890264') - const pubkeyY = Bytes.fromHex<32>('bd437b75d6f1db67155a95a0da4b41f2b6b3dc5d42f7db56238449e404a6c0a3') + const messageHash = Bytes.fromHex('f809fd0aa0bb0f20b354c6b2f86ea751957a4e262a546bd716f34f69b9516ae1').toFixed({ length: 32 }) + const sigR = Bytes.fromHex('18d96c7cda4bc14d06277534681ded8a94828eb731d8b842e0da8105408c83cf').toFixed({ length: 32 }) + const sigS = Bytes.fromHex('7d33c61acf39cbb7a1d51c7126f1718116179adebd31618c4604a1f03b5c274a').toFixed({ length: 32 }) + const pubkeyX = Bytes.fromHex('f8140e3b2b92f7cbdc8196bc6baa9ce86cf15c18e8ad0145d50824e6fa890264').toFixed({ length: 32 }) + const pubkeyY = Bytes.fromHex('bd437b75d6f1db67155a95a0da4b41f2b6b3dc5d42f7db56238449e404a6c0a3').toFixed({ length: 32 }) const avmResult = await getAvmResult( { @@ -291,11 +291,11 @@ describe('crypto op codes', async () => { describe('vrfVerify', async () => { const a = Bytes.fromHex('528b9e23d93d0e020a119d7ba213f6beb1c1f3495a217166ecd20f5a70e7c2d7') - const b = Bytes.fromHex<80>( + const b = Bytes.fromHex( '372a3afb42f55449c94aaa5f274f26543e77e8d8af4babee1a6fbc1c0391aa9e6e0b8d8d7f4ed045d5b517fea8ad3566025ae90d2f29f632e38384b4c4f5b9eb741c6e446b0f540c1b3761d814438b04', - ) + ).toFixed({ length: 80 }) - const c = Bytes.fromHex<32>('3a2740da7a0788ebb12a52154acbcca1813c128ca0b249e93f8eb6563fee418d') + const c = Bytes.fromHex('3a2740da7a0788ebb12a52154acbcca1813c128ca0b249e93f8eb6563fee418d').toFixed({ length: 32 }) test('should throw not available error', async () => { const mockedVrfVerify = op.vrfVerify as Mock @@ -313,7 +313,7 @@ describe('crypto op codes', async () => { asUint8Array(c), ) const mockedVrfVerify = op.vrfVerify as Mock - mockedVrfVerify.mockReturnValue([Bytes<64>(new Uint8Array(avmResult[0])), avmResult[1]]) + mockedVrfVerify.mockReturnValue([Bytes(new Uint8Array(avmResult[0])).toFixed({ length: 64 }), avmResult[1]]) const result = op.vrfVerify(VrfVerify.VrfAlgorand, asBytes(a), b, c) expect(asUint8Array(result[0])).toEqual(new Uint8Array(avmResult[0])) @@ -338,7 +338,7 @@ describe('crypto op codes', async () => { asUint8Array(a), ) const mockedMimc = op.mimc as Mock - mockedMimc.mockReturnValue(Bytes(avmResult)) + mockedMimc.mockReturnValue(Bytes(avmResult).toFixed({ length: 32 })) const result = op.mimc(MimcConfigurations.BN254Mp110, Bytes(a)) expect(result).toEqual(avmResult) @@ -396,9 +396,9 @@ const generateEcdsaTestData = (v: Ecdsa) => { const recoveryId = 0 // Recovery ID is typically 0 or 1 return { - data: Bytes<32>(new Uint8Array(messageHash)), - r: Bytes<32>(new Uint8Array(signature.r.toArray('be', 32))), - s: Bytes<32>(new Uint8Array(signature.s.toArray('be', 32))), + data: Bytes(new Uint8Array(messageHash)).toFixed({ length: 32 }), + r: Bytes(new Uint8Array(signature.r.toArray('be', 32))).toFixed({ length: 32 }), + s: Bytes(new Uint8Array(signature.s.toArray('be', 32))).toFixed({ length: 32 }), recoveryId: Uint64Cls.fromCompat(recoveryId), pubkeyX: Bytes(new Uint8Array(pk.slice(0, 32))), pubkeyY: Bytes(new Uint8Array(pk.slice(32))), diff --git a/tests/primitives/bytes.algo.spec.ts b/tests/primitives/bytes.algo.spec.ts index a3546e5b..46b733c4 100644 --- a/tests/primitives/bytes.algo.spec.ts +++ b/tests/primitives/bytes.algo.spec.ts @@ -1,5 +1,5 @@ import type { bytes } from '@algorandfoundation/algorand-typescript' -import { Bytes, FixedArray } from '@algorandfoundation/algorand-typescript' +import { Bytes, FixedArray, op } from '@algorandfoundation/algorand-typescript' import { encodingUtil } from '@algorandfoundation/puya-ts' import { beforeAll, describe, expect, it } from 'vitest' import { MAX_BYTES_SIZE } from '../../src/constants' @@ -200,19 +200,19 @@ describe('Bytes', async () => { describe('fixed size', () => { it('should be able to create fixed size bytes with no parameter', () => { - const x = Bytes<32>() + const x = op.bzero(32) expect(x.length).toEqual(32) - expect(x).toEqual(Bytes.fromHex<32>('0000000000000000000000000000000000000000000000000000000000000000')) - expect(x).toEqual(Bytes.fromBase64<32>('AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=')) - expect(x).toEqual(Bytes.fromBase32<32>('AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA==')) + expect(x).toEqual(Bytes.fromHex('0000000000000000000000000000000000000000000000000000000000000000').toFixed({ length: 32 })) + expect(x).toEqual(Bytes.fromBase64('AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=').toFixed({ length: 32 })) + expect(x).toEqual(Bytes.fromBase32('AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA==').toFixed({ length: 32 })) }) it('should be able to create fixed size bytes with parameter', () => { - const x1 = Bytes<32>(new Uint8Array(32)) + const x1 = Bytes(new Uint8Array(32)).toFixed({ length: 32 }) expect(x1.length).toEqual(32) - expect(x1).toEqual(Bytes<32>()) + expect(x1).toEqual(op.bzero(32)) - const x2 = Bytes<32>('abcdefghijklmnopqrstuvwxyz123456') + const x2 = Bytes('abcdefghijklmnopqrstuvwxyz123456').toFixed({ length: 32 }) expect(x2.length).toEqual(32) expect(x2).toEqual(Bytes('abcdefghijklmnopqrstuvwxyz123456')) expect(x2).toEqual(Bytes.fromHex('6162636465666768696a6b6c6d6e6f707172737475767778797a313233343536')) @@ -228,18 +228,18 @@ describe('Bytes', async () => { expect(x1.length).toEqual(2) expect(x1[0].length).toEqual(32) expect(x1[1].length).toEqual(32) - expect(x1[0]).toEqual(Bytes<32>(new Uint8Array(32))) - expect(x1[1]).toEqual(Bytes<32>(new Uint8Array(32))) + expect(x1[0]).toEqual(op.bzero(32)) + expect(x1[1]).toEqual(op.bzero(32)) - const x2 = decodeArc4, 2>>(Bytes<64>()) + const x2 = decodeArc4, 2>>(op.bzero(64)) expect(x2.length).toEqual(2) expect(x2[0].length).toEqual(32) expect(x2[1].length).toEqual(32) - const x3 = convertBytes, 2>>(Bytes<64>(), { strategy: 'unsafe-cast' }) + const x3 = convertBytes, 2>>(op.bzero(64), { strategy: 'unsafe-cast' }) expect(x3.length).toEqual(2) - expect(x3[0].bytes).toEqual(Bytes.fromHex<32>('0000000000000000000000000000000000000000000000000000000000000000')) - expect(x3[1].bytes).toEqual(Bytes.fromHex<32>('0000000000000000000000000000000000000000000000000000000000000000')) + expect(x3[0].bytes).toEqual(Bytes.fromHex('0000000000000000000000000000000000000000000000000000000000000000').toFixed({ length: 32 })) + expect(x3[1].bytes).toEqual(Bytes.fromHex('0000000000000000000000000000000000000000000000000000000000000000').toFixed({ length: 32 })) }) }) }) diff --git a/tests/references/asset.algo.spec.ts b/tests/references/asset.algo.spec.ts index 5ee954c3..23fc8628 100644 --- a/tests/references/asset.algo.spec.ts +++ b/tests/references/asset.algo.spec.ts @@ -1,4 +1,4 @@ -import { Account, Bytes, Uint64 } from '@algorandfoundation/algorand-typescript' +import { Account, op, Uint64 } from '@algorandfoundation/algorand-typescript' import { afterEach, describe, expect, it, test } from 'vitest' import { TestExecutionContext } from '../../src' import { AssetCls } from '../../src/impl/reference' @@ -55,7 +55,7 @@ describe('Asset', () => { unitName: asBytes('TEST'), name: asBytes('Test Asset'), url: asBytes('https://test.com'), - metadataHash: Bytes<32>(new Uint8Array(32)), + metadataHash: op.bzero(32), manager: Account(), freeze: Account(), clawback: Account(), From 780aa793e7aa10d984d0b9be023ad97b54a2af12 Mon Sep 17 00:00:00 2001 From: Bobby Lat Date: Tue, 30 Sep 2025 14:54:06 +0700 Subject: [PATCH 53/68] docs: fix typos and broken links --- README.md | 6 ++--- docs/algots.md | 8 +++--- docs/api.md | 4 +-- docs/coverage.md | 2 +- docs/examples.md | 2 +- docs/faq.md | 14 +++++------ docs/readme.md | 22 ++++++++-------- docs/testing-guide.md | 8 +++--- docs/tg-application-spy.md | 36 +++++++++++++-------------- docs/tg-arc4-types.md | 8 +++--- docs/tg-avm-types.md | 6 ++--- docs/tg-concepts.md | 10 ++++---- docs/tg-contract-testing.md | 12 ++++----- docs/tg-opcodes.md | 4 +-- docs/tg-signature-testing.md | 2 +- docs/tg-state-management.md | 2 +- docs/tg-transactions.md | 4 +-- examples/voting/contract.algo.spec.ts | 2 +- tests/state-op-codes.algo.spec.ts | 2 +- 19 files changed, 75 insertions(+), 79 deletions(-) diff --git a/README.md b/README.md index 7f28d88c..348747d5 100644 --- a/README.md +++ b/README.md @@ -5,7 +5,7 @@ [![github-stars](https://img.shields.io/github/stars/algorandfoundation/algorand-typescript-testing?color=74dfdc&logo=star&style=flat)](https://github.com/algorandfoundation/algorand-typescript-testing) [![visitor-badge](https://api.visitorbadge.io/api/visitors?path=https%3A%2F%2Fgithub.com%2Falgorandfoundation%2Falgorand-typescript-testing&countColor=%2374dfdc&style=flat)](https://github.com/algorandfoundation/algorand-typescript-testing/) -`algorand-typescript-testing` is a companion package to [Algorand Typescript](https://github.com/algorandfoundation/puya-ts/tree/main/packages/algo-ts) that enables efficient unit testing of Algorand TypeScript smart contracts in an offline environment. This package emulates key AVM behaviors without requiring a network connection, offering fast and reliable testing capabilities with a familiar TypeScript interface. +`algorand-typescript-testing` is a companion package to [Algorand Typescript](https://github.com/algorandfoundation/puya-ts/tree/main/packages/algo-ts) that enables efficient unit testing of Algorand TypeScript smart contracts in an offline environment. This package emulates key AVM behaviours without requiring a network connection, offering fast and reliable testing capabilities with a familiar TypeScript interface. The `algorand-typescript-testing` package provides: @@ -46,7 +46,7 @@ Let's write a simple contract and test it using the `algorand-typescript-testing `algorand-typescript-testing` includes a TypeScript transformer (`puyaTsTransformer`) that ensures contracts (with `.algo.ts` extension) and tests (with `.algo.spec.ts` or `.algo.test.ts` extensions) behave consistently between Node.js and AVM environments. -The transformer replicates AVM behavior, such as integer-only arithmetic where `3 / 2` produces `1`. For code requiring standard Node.js behaviour (e.g., `3 / 2` produces `1.5`), place it in separate `.ts` files and reference them from test files. +The transformer replicates AVM behaviour, such as integer-only arithmetic where `3 / 2` produces `1`. For code requiring standard Node.js behaviour (e.g., `3 / 2` produces `1.5`), place it in separate `.ts` files and reference them from test files. The transformer also redirects `@algorandfoundation/algorand-typescript` imports to `@algorandfoundation/algorand-typescript-testing/internal` to provide executable implementations of Algorand TypeScript constructs like `Global`, `Box`, `Uint64`, and `clone`. @@ -302,6 +302,6 @@ To dig deeper into the capabilities of `algorand-typescript-testing`, continue w - [Testing Guide](./docs/testing-guide.md) - [Examples](./docs/examples.md) - [Coverage](./docs/coverage.md) -- [FQA](./docs/faq.md) +- [FAQ](./docs/faq.md) - [API Reference](./docs/api.md) - [Algorand TypeScript](./docs/algots.md) diff --git a/docs/algots.md b/docs/algots.md index 440ec236..cb0962fc 100644 --- a/docs/algots.md +++ b/docs/algots.md @@ -4,12 +4,10 @@ title: Algorand TypeScript # Algorand TypeScript -Algorand TypeScript is a partial implementation of the TypeScript programming language that runs on the Algorand Virtual Machine (AVM). It includes a statically typed framework for development of Algorand smart contracts and logic signatures, with TypeScript interfaces to underlying AVM functionality that works with standard TypeScript tooling. +Algorand TypeScript is a partial implementation of the TypeScript programming language that runs on the Algorand Virtual Machine (AVM). It includes a statically typed framework for developing Algorand smart contracts and logic signatures, and provides TypeScript interfaces to underlying AVM functionality that work with standard TypeScript tooling. -It maintains the syntax and semantics of TypeScript such that a developer who knows TypeScript can make safe assumptions -about the behaviour of the compiled code when running on the AVM. Algorand TypeScript is also executable TypeScript that can be run -and debugged on a Node.js virtual machine with transpilation to EcmaScript and run from automated tests. +It preserves the syntax and semantics of TypeScript so that a developer who knows TypeScript can make safe assumptions about the behaviour of the compiled code when running on the AVM. Algorand TypeScript is also executable TypeScript that can be run and debugged on Node.js after transpilation to ECMAScript and run from automated tests. -Algorand TypeScript is compiled for execution on the AVM by PuyaTs, a TypeScript frontend for the [Puya](https://github.com/algorandfoundation/puya) optimising compiler that ensures the resulting AVM bytecode execution semantics that match the given TypeScript code. PuyaTs produces output that is directly compatible with AlgoKit typed clients to make deployment and calling easy. +Algorand TypeScript is compiled for execution on the AVM by PuyaTs, a TypeScript frontend for the [Puya](https://github.com/algorandfoundation/puya) optimising compiler. PuyaTs ensures the resulting AVM bytecode has execution semantics that match the given TypeScript code, and it produces output that is directly compatible with AlgoKit typed clients to simplify deployment and invocation. [Documentation](https://algorandfoundation.github.io/puya-ts/index.html) diff --git a/docs/api.md b/docs/api.md index dda092bd..923baf22 100644 --- a/docs/api.md +++ b/docs/api.md @@ -6,9 +6,7 @@ title: API Reference An overview of the `algorand-typescript-testing` package - covering the main classes and functions. -``` -Spotted a typo in documentation? This project is open source, please submit an issue or a PR on [GitHub](https://github.com/algorandfoundation/algorand-typescript-testing). -``` +> Spotted a typo in the documentation? This project is open source; please submit an issue or a PR on [GitHub](https://github.com/algorandfoundation/algorand-typescript-testing). ## Contexts diff --git a/docs/coverage.md b/docs/coverage.md index 37113f38..89a5c72c 100644 --- a/docs/coverage.md +++ b/docs/coverage.md @@ -4,7 +4,7 @@ title: Coverage # Coverage -See which `algorand-typescript` stubs are implemented by the `algorand-typescript-testing` library. See the [Concepts](tg-concepts.md#types-of-algorand-typescript-stub-implementations) section for more details on the implementation categories. Refer to the [`algorand-typescript` stubs API](api.md) for the full list of the stubs for which the `algorand-typescript-testing` library provides implementations referenced in the table below. +See which `algorand-typescript` stubs are implemented in the `algorand-typescript-testing` library. See the [Concepts](tg-concepts.md#types-of-algorand-typescript-stub-implementations) section for more details on the implementation categories. Refer to the [`algorand-typescript-testing` API](api.md) for a full list of stubs implemented by the `algorand-typescript-testing` library, as referenced in the table below. | Name | Implementation type | | ---------------------------- | ------------------- | diff --git a/docs/examples.md b/docs/examples.md index 7cbb0b96..738f988c 100644 --- a/docs/examples.md +++ b/docs/examples.md @@ -4,7 +4,7 @@ title: Examples # Examples -Below is a showcase of various examples of unit testing real and sample Algorand Python smart contracts using `algorand-typescript-testing`. +Below is a showcase of various examples of unit testing real and sample Algorand TypeScript smart contracts using `algorand-typescript-testing`. | Contract Name | Test File | Key Features Demonstrated | Test versions of Algorand TypeScript Abstractions used | | ------------------- | --------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------- | diff --git a/docs/faq.md b/docs/faq.md index 5275425d..bda401d7 100644 --- a/docs/faq.md +++ b/docs/faq.md @@ -6,7 +6,7 @@ title: FAQ ## What is a Test Context? -A Test Context is a context manager that provides a simulated Algorand environment for testing TypeScript smart contracts. It allows developers to create and manipulate a virtual Algorand ecosystem for testing purposes. For more details, see the [Test Context section](tg-concepts.md#test-context) in our documentation. +A test context is a context manager that provides a simulated Algorand environment for testing TypeScript smart contracts. It allows developers to create and manipulate a virtual Algorand ecosystem for testing purposes. For more details, see the [Test Context section](tg-concepts.md#test-context) in our documentation. ## What is the Algorand Virtual Machine (AVM)? @@ -18,7 +18,7 @@ Operational Codes, or opcodes, are AVM instructions that are executed directly b ## What are Value Generators? -Value Generators are helper methods that generate randomized values for testing when the specific value of the tested type is not important. In the context of Algorand TypeScript testing, these are represented by property on the context manager, accessed via `any.*` (`any.txn.*`, or `any.arc4.*`. in the case of ARC 4 types). To understand how to use Value Generators effectively, check out our [Value Generators section](tg-concepts.md#value-generators) in the documentation. +Value Generators are helper methods that generate randomized values for testing when the specific value of the tested type is not important. In the context of Algorand TypeScript testing, these are represented by properties on the context manager, accessed via `any.*` (`any.txn.*` or `any.arc4.*` in the case of ARC 4 types). To understand how to use Value Generators effectively, check out our [Value Generators section](tg-concepts.md#value-generators) in the documentation. ## What are the limitations of the Algorand TypeScript Testing framework? @@ -30,7 +30,7 @@ The Algorand TypeScript Testing framework emulates the Algorand Virtual Machine 4. Certain cryptographic operations are mocked or simplified 5. No state proof generation or verification -For scenarios where these limitations are crucial, it's recommended to pair this framework with integration testing. If you have a solid reason to justify introducing new emulated behaviour, please open an issue or contribute to the project on [Github](https://github.com/algorandfoundation/algorand-typescript-testing). +For scenarios where these limitations are crucial, it's recommended to pair this framework with integration testing. If you have a good reason to suggest new emulated behaviour, please open an issue or contribute to the project on [GitHub](https://github.com/algorandfoundation/algorand-typescript-testing). ## How does balance tracking work in the testing framework? @@ -50,16 +50,16 @@ Some cryptographic operations are mocked or simplified in the framework. For a d ## Can I use this framework for security-critical validations? -While this framework is useful for unit testing and local development, it should not be the only tool used for security-critical validations or performance benchmarking. It's designed to approximate AVM behavior for common scenarios. Always complement your testing with additional integration testing options available in `algokit`, where you can test against real localnet or testnet environments. +While this framework is useful for unit testing and local development, it should not be the only tool used for security-critical validations or performance benchmarking. It's designed to approximate AVM behaviour for common scenarios. Always complement your testing with additional integration testing options available in `algokit`, where you can test against real localnet or testnet environments. ## Is there an example of how to use this framework alongside integration tests? -Yes, the `algokit-typescript-template`, accessible via `algokit init`, provides a working example of how to structure `algorand-typecript-testing` along with regular integration tests against localnet. +Yes, the `algokit-typescript-template`, accessible via `algokit init`, provides a working example of how to structure `algorand-typescript-testing` along with regular integration tests against localnet. ``` -An `algokit-typescript-template` accessible via `algokit init -t typescript`, provides a comprehensive and customizable working example of how to structure `algorand-typescript-testing` along with regular integration tests against localnet. +The `algokit-typescript-template` accessible via `algokit init -t typescript` provides a comprehensive and customizable working example of how to structure `algorand-typescript-testing` along with regular integration tests against localnet. ``` ## Is it compatible with `vitest`? -Yes, it is compatible with `vitest` and _any_ other TypeScript testing framework as its agnostic of the testing framework as long as it's TypeScript. If you spot incompatibility with a certain tool, please open an issue or contribute to the project on [Github](https://github.com/algorandfoundation/algorand-typescript-testing). +Yes, it is compatible with `vitest` and _any_ other TypeScript testing framework as its agnostic of the testing framework as long as it's TypeScript. If you spot incompatibility with a certain tool, please open an issue or contribute to the project on [GitHub](https://github.com/algorandfoundation/algorand-typescript-testing). diff --git a/docs/readme.md b/docs/readme.md index 86aa7e89..58e83e42 100644 --- a/docs/readme.md +++ b/docs/readme.md @@ -3,21 +3,21 @@ [![github-stars](https://img.shields.io/github/stars/algorandfoundation/algorand-typescript-testing?color=74dfdc&logo=star&style=flat)](https://github.com/algorandfoundation/algorand-typescript-testing) [![visitor-badge](https://api.visitorbadge.io/api/visitors?path=https%3A%2F%2Fgithub.com%2Falgorandfoundation%2Falgorand-typescript-testing&countColor=%2374dfdc&style=flat)](https://github.com/algorandfoundation/algorand-typescript-testing/) -`algorand-typescript-testing` is a companion package to [Algorand Typescript](https://github.com/algorandfoundation/puya-ts/tree/main/packages/algo-ts) that enables efficient unit testing of Algorand TypeScript smart contracts in an offline environment. This package emulates key AVM behaviors without requiring a network connection, offering fast and reliable testing capabilities with a familiar TypeScript interface. +`algorand-typescript-testing` is a companion package to [Algorand TypeScript](https://github.com/algorandfoundation/puya-ts/tree/main/packages/algo-ts) that enables efficient unit testing of Algorand TypeScript smart contracts in an offline environment. This package emulates key AVM behaviours without requiring a network connection, offering fast and reliable testing through a familiar TypeScript interface. The `algorand-typescript-testing` package provides: - A simple interface for fast and reliable unit testing - An offline testing environment that simulates core AVM functionality -- A familiar TypeScript experience, compatible with testing frameworks like [vitest](https://vitest.dev/), and [jest](https://jestjs.io/) +- A familiar TypeScript experience, compatible with testing frameworks like [vitest](https://vitest.dev/) and [jest](https://jestjs.io/) ## Quick Start -`algorand-typescript` is a prerequisite for `algorand-typescript-testing`, providing stubs and type annotations for Algorand TypeScript syntax. It enhances code completion and type checking when writing smart contracts. Note that this code isn't directly executable in standard Node.js environment; it's compiled by `puya-ts` into TEAL for Algorand Network deployment. +`algorand-typescript` is a prerequisite for `algorand-typescript-testing`, providing stubs and type annotations for Algorand TypeScript syntax. It improves code completion and type checking when writing smart contracts. Note that this code isn't directly executable in a standard Node.js environment; it's compiled by `puya-ts` into TEAL for Algorand Network deployment. -Traditionally, testing Algorand smart contracts involved deployment on sandboxed networks and interacting with live instances. While robust, this approach can be inefficient and lacks versatility for testing Algorand TypeScript code. +Traditionally, testing Algorand smart contracts involved deployment on sandboxed networks and interacting with live instances. While robust, this approach can be inefficient and lacks versatility for unit testing Algorand TypeScript code. -Enter `algorand-typescript-testing`: it leverages TypeScript's rich testing ecosystem for unit testing without network deployment. This enables rapid iteration and granular logic testing. +`algorand-typescript-testing` leverages TypeScript's testing ecosystem for unit testing without network deployment, enabling rapid iteration and granular logic testing. > **NOTE**: While `algorand-typescript-testing` offers valuable unit testing capabilities, it's not a replacement for comprehensive testing. Use it alongside other test types, particularly those running against the actual Algorand Network, for thorough contract validation. @@ -44,11 +44,11 @@ Let's write a simple contract and test it using the `algorand-typescript-testing `algorand-typescript-testing` includes a TypeScript transformer (`puyaTsTransformer`) that ensures contracts (with `.algo.ts` extension) and tests (with `.algo.spec.ts` or `.algo.test.ts` extensions) behave consistently between Node.js and AVM environments. -The transformer replicates AVM behavior, such as integer-only arithmetic where `3 / 2` produces `1`. For code requiring standard Node.js behaviour (e.g., `3 / 2` produces `1.5`), place it in separate `.ts` files and reference them from test files. +The transformer replicates AVM behaviour, such as integer-only arithmetic where `3 / 2` produces `1`. For code requiring standard Node.js behaviour (e.g., `3 / 2` produces `1.5`), place it in separate `.ts` files and reference them from test files. The transformer also redirects `@algorandfoundation/algorand-typescript` imports to `@algorandfoundation/algorand-typescript-testing/internal` to provide executable implementations of Algorand TypeScript constructs like `Global`, `Box`, `Uint64`, and `clone`. -If there are tests which do not need to be executed in the AVM context such as end to end tests, simply use `.test.ts` or `.spec.ts` file extensions without `.algo` part and the transformer would skip them. +If there are tests that do not need to be executed in the AVM context, such as end-to-end tests, simply use `.test.ts` or `.spec.ts` file extensions without `.algo` part and the transformer will skip them. #### Configuring vitest @@ -74,7 +74,7 @@ export default defineConfig({ }) ``` -`algorand-typescript-testing` package also exposes additional equality testers which enables the smart contract developers to write terser test by avoiding type casting in assertions. It can setup in `beforeAll` hook point in the setup file, `vitest.setup.ts`. +`algorand-typescript-testing` package also exposes additional equality testers which enable smart contract developers to write terser tests by avoiding type casting in assertions. They can be set up in the `beforeAll` hook in the setup file, `vitest.setup.ts`. ```typescript import { beforeAll, expect } from 'vitest' @@ -113,7 +113,7 @@ const jestConfig: JestConfigWithTsJest = { export default jestConfig ``` -`algorand-typescript-testing` package also exposes additional equality testers which enables the smart contract developers to write terser test by avoiding type casting in assertions. It can setup in `beforeAll` hook point in the setup file, `jest.setup.ts`. +`algorand-typescript-testing` package also exposes additional equality testers which enable smart contract developers to write terser tests by avoiding type casting in assertions. They can be set up in the `beforeAll` hook in the setup file, `jest.setup.ts`. ```typescript import { beforeAll, expect } from '@jest/globals' @@ -138,7 +138,7 @@ You'll also need to run `jest` with the `--experimental-vm-modules` and `--exper } ``` -There is also a patch file `ts-jest+29.2.5.patch` that needs to be applied to `ts-jest` package to for the `puyaTsTransformer` to work with the test files. +There is also a patch file `ts-jest+29.2.5.patch` that needs to be applied to `ts-jest` package for the `puyaTsTransformer` to work with the test files. 1. Place the file in `\patches` folder. 1. Install [patch-package](https://www.npmjs.com/package/patch-package) package as a dev dependency. @@ -300,6 +300,6 @@ To dig deeper into the capabilities of `algorand-typescript-testing`, continue w - [Testing Guide](./testing-guide.md) - [Examples](./examples.md) - [Coverage](./coverage.md) -- [FQA](./faq.md) +- [FAQ](./faq.md) - [API Reference](./api.md) - [Algorand TypeScript](./algots.md) diff --git a/docs/testing-guide.md b/docs/testing-guide.md index a2e52bf7..c4afc19d 100644 --- a/docs/testing-guide.md +++ b/docs/testing-guide.md @@ -17,7 +17,7 @@ children: The Algorand TypeScript Testing framework provides powerful tools for testing Algorand TypeScript smart contracts within a Node.js environment. This guide covers the main features and concepts of the framework, helping you write effective tests for your Algorand applications. ``` -For all code examples in the _Testing Guide_ section, assume `context` is an instance of `TestExecutionContext` obtained using the initialising an instance of `TestExecutionContext` class. All subsequent code is executed within this context. +For all code examples in the _Testing Guide_ section, assume `context` is an instance of `TestExecutionContext` obtained by initialising the `TestExecutionContext` class. All subsequent code is executed within this context. ``` The Algorand TypeScript Testing framework streamlines unit testing of your Algorand TypeScript smart contracts by offering functionality to: @@ -28,7 +28,7 @@ The Algorand TypeScript Testing framework streamlines unit testing of your Algor 4. Verify logic signatures and subroutines 5. Manage global state, local state, scratch slots, and boxes in test contexts 6. Simulate transactions and transaction groups, including inner transactions -7. Verify opcode behavior +7. Verify opcode behaviour By using this framework, you can ensure your Algorand TypeScript smart contracts function correctly before deploying them to a live network. @@ -39,9 +39,9 @@ Key features of the framework include: - ARC4 Support: Tools for testing ARC4 contracts and methods, including struct definitions and ABI encoding/decoding - Transaction Simulation: Ability to create and execute various transaction types - State Management: Tools for managing and verifying global and local state changes -- Opcode Simulation: Implementations of AVM opcodes for accurate smart contract behavior testing +- Opcode Simulation: Implementations of AVM opcodes for accurate smart contract behaviour testing -The framework is designed to work seamlessly with Algorand TypeScript smart contracts, allowing developers to write comprehensive unit tests that closely mimic the behavior of contracts on the Algorand blockchain. +The framework is designed to work seamlessly with Algorand TypeScript smart contracts, allowing developers to write comprehensive unit tests that closely mimic the behaviour of contracts on the Algorand blockchain. ## Table of Contents diff --git a/docs/tg-application-spy.md b/docs/tg-application-spy.md index a1b262df..3cb933ad 100644 --- a/docs/tg-application-spy.md +++ b/docs/tg-application-spy.md @@ -4,11 +4,11 @@ title: Application Spy # ApplicationSpy -The `ApplicationSpy` class provides a way to mock making method calls for from within contracts. This is particularly useful when testing contracts that deploy and interact with other contracts in a type safe manner. It can be used with all the approaches for making method calls supported by `algorand-typescript`. +The `ApplicationSpy` class provides a way to mock making method calls from within contracts. This is particularly useful when testing contracts that deploy and interact with other contracts in a type-safe manner. It can be used with all the approaches for making method calls supported by `algorand-typescript`. ## Using ApplicationSpy -### Deploying other contracts with explict create method +### Deploying other contracts with explicit create method **1. `itxn.applicationCall`** @@ -33,7 +33,7 @@ const app = compiled.call.create({ }).itxn.createdApp ``` -Mock result can be setup for both of the snippets above as +Mock result can be set up for both snippets above as follows: ````ts // create an application and register it with test execution context. @@ -62,8 +62,8 @@ const spy = new ApplicationSpy(Hello) // ``` // `itxnContext` is provided as a parameter to the callback method and // it allows reading and setting of the properties of `itxn.ApplicationCallInnerTxn` interface. -// it also maps and encodes the arugments to `appArgs` collection as bytes values, -// and provides consistent access those arguments. +// it also maps and encodes the arguments to the `appArgs` collection as bytes values, +// and provides consistent access to those arguments. spy.on.create((itxnContext) => { itxnContext.createdApp = helloApp }) @@ -92,7 +92,7 @@ const compiled = compileArc4(Hello) const appId = compiled.bareCreate().createdApp ``` -Mock result can be setup for both of the snippets above as +Mock result can be set up for both of the snippets above as ```ts const helloApp = ctx.any.application() @@ -100,7 +100,7 @@ ctx.setCompiledApp(Hello, helloApp.id) const spy = new ApplicationSpy(Hello) -// the mock setup is the same as using explicit create method except +// The mock setup is the same as using the explicit create method except // `onBareCall` method is used instead of `on.{methodName}` or `onAbiCall` methods // to register the callback spy.onBareCall((itxnContext) => { @@ -132,16 +132,16 @@ const result = compiled.call.greet({ assert(result === 'hello world') ``` -Mock result can be setup for both snippets above as +Mock result can be set up for both snippets above as ````ts // `itxnContext.setReturnValue` is added as the last entry to the logs of the constructed `itxn.ApplicationCall` -// so that it can be access via `txn.lastLog` property. +// so that it can be accessed via the `txn.lastLog` property. // `setReturnValue` should only be called as the last statement of the callback and // especially no further manipulations of logs should take place afterwards. // `appArgs` collection holds method selector and method arguments encoded as `bytes` values. -// They need to be decoded if the orginal argument values are needed. -// you can check `itxnContext.appId` if there are multiple callback registered for the same method selector +// They need to be decoded if the original argument values are needed. +// You can check `itxnContext.appId` if there are multiple callbacks registered for the same method selector // e.g. // ``` // if (itxnContext.appId === helloApp) { @@ -153,14 +153,14 @@ spy.on.greet((itxnContext) => { }) ```` -You can also use the alternative approach below to setup the mock result. It is especially useful if you do not have `Contract` subclass available and only method signature and application id are availbe to make the method call. +You can also use the alternative approach below to set up the mock result. It is especially useful if you do not have a `Contract` subclass available and only the method signature and application id are available to make the method call. ```ts -// create a spy without the contract type provided +// Create a spy without the contract type provided const spy = new ApplicationSpy() spy.onAbiCall(methodSelector('greet(string)string'), (itxnContext) => { - // check for a well-known appId or the appId provided to the contract under test in some other manner + // Check for a well-known appId or the appId provided to the contract under test in some other manner if (itxnContext.appId === appId) { itxnContext.setReturnValue(`hey ${decodeArc4(itxnContext.appArgs(1))}`) } @@ -176,10 +176,10 @@ const result = abiCall({ }).returnValue ``` -Mock result can be setup for the snippet above as +Mock result can be set up for the snippet above as ```ts -// the setup is the same as the previous case +// The setup is the same as in the previous case. spy.on.greet((itxnContext) => { itxnContext.setReturnValue(`hello ${decodeArc4(itxnContext.appArgs(0))}`) }) @@ -209,8 +209,8 @@ spy.on.greet((itxnContext) => { 3. **Handle Method Arguments** ```ts spy.on.setValue((itxnContext) => { - // arguments provided to the method are encoded as bytes values - // and available via `itxnContext.appArgs` method + // Arguments provided to the method are encoded as bytes values + // and available via the `itxnContext.appArgs` method itxnContext.setReturnValue(`hello ${decodeArc4(itxnContext.appArgs(0))}`) }) ``` diff --git a/docs/tg-arc4-types.md b/docs/tg-arc4-types.md index b0429a54..4f053392 100644 --- a/docs/tg-arc4-types.md +++ b/docs/tg-arc4-types.md @@ -4,14 +4,14 @@ title: ARC4 Types # ARC4 Types -These types are available under the `arc4` namespace. Refer to the [ARC4 specification](https://arc.algorand.foundation/ARCs/arc-0004) for more details on the spec. +These types are available under the `arc4` namespace. Refer to the [ARC4 specification](https://dev.algorand.co/arc-standards/arc-0004/) for more details on the spec. ``` Test execution context provides _value generators_ for ARC4 types. To access their _value generators_, use `{context_instance}.any.arc4` property. See more examples below. ``` ``` -For all `arc4` types with and without respective _value generator_, instantiation can be performed directly. If you have a suggestion for a new _value generator_ implementation, please open an issue in the [`algorand-typescript-testing`](https://github.com/algorandfoundation/algorand-typescript-testing) repository or contribute by following the [contribution guide](https://github.com/algorandfoundation/algorand-typescript-testing/blob/main/CONTRIBUTING.md). +For all `arc4` types, with or without a respective _value generator_, instantiation can be performed directly. If you have a suggestion for a new _value generator_ implementation, please open an issue in the [`algorand-typescript-testing`](https://github.com/algorandfoundation/algorand-typescript-testing) repository or contribute by following the [contribution guide](https://github.com/algorandfoundation/algorand-typescript-testing/blob/main/CONTRIBUTING.md). ``` ```ts @@ -59,7 +59,7 @@ const addressValue = new arc4.Address('AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA // Generate a random address const randomAddress = ctx.any.arc4.address() -// Access native underlaying type +// Access native underlying type const native = randomAddress.native ``` @@ -84,6 +84,6 @@ const randomString = ctx.any.arc4.str(12) // n is the number of bits in the arc4 ``` ```ts -// test cleanup +// Test cleanup ctx.reset() ``` diff --git a/docs/tg-avm-types.md b/docs/tg-avm-types.md index b65cea6b..03e17ece 100644 --- a/docs/tg-avm-types.md +++ b/docs/tg-avm-types.md @@ -4,10 +4,10 @@ title: AVM Types # AVM Types -These types are available directly under the `algorand-typescript` namespace. They represent the basic AVM primitive types and can be instantiated directly or via _value generators_: +These types are available directly under the `algorand-typescript` namespace. They represent the basic AVM primitive types and can be instantiated directly or created via _value generators_: ``` -For 'primitive `algorand-typescript` types such as `Account`, `Application`, `Asset`, `uint64`, `biguint`, `bytes`, `string` with and without respective _value generator_, instantiation can be performed directly. If you have a suggestion for a new _value generator_ implementation, please open an issue in the [`algorand-typescript-testing`](https://github.com/algorandfoundation/algorand-typescript-testing) repository or contribute by following the [contribution guide](https://github.com/algorandfoundation/algorand-typescript-testing/blob/main/CONTRIBUTING.md). +For primitive `algorand-typescript` types such as `Account`, `Application`, `Asset`, `uint64`, `biguint`, `bytes`, and `string`, instantiation can be performed directly, with or without a respective _value generator_. If you have a suggestion for a new _value generator_ implementation, please open an issue in the [`algorand-typescript-testing`](https://github.com/algorandfoundation/algorand-typescript-testing) repository or contribute by following the [contribution guide](https://github.com/algorandfoundation/algorand-typescript-testing/blob/main/CONTRIBUTING.md). ``` ```ts @@ -200,7 +200,7 @@ ctx.ledger.patchApplicationData(randomApp, { }, }) -// Patch logs for an application. When accessing via transactions or inner transaction related opcodes, will return the patched logs unless new logs where added into the transaction during execution. +// Patch logs for an application. When accessing via transactions or inner transaction related opcodes, will return the patched logs unless new logs were added into the transaction during execution. const testApp = ctx.any.application({ appLogs: [algots.Bytes('log entry 1'), algots.Bytes('log entry 2')] }) // Get app associated with the active contract diff --git a/docs/tg-concepts.md b/docs/tg-concepts.md index fc322fa9..0bbc191d 100644 --- a/docs/tg-concepts.md +++ b/docs/tg-concepts.md @@ -8,7 +8,7 @@ The following sections provide an overview of key concepts and features in the A ## Test Context -The main abstraction for interacting with the testing framework is the [`TestExecutionContext`](../classes/index.TestExecutionContext.html). It creates an emulated Algorand environment that closely mimics AVM behavior relevant to unit testing the contracts and provides a TypeScript interface for interacting with the emulated environment. +The main abstraction for interacting with the testing framework is the [`TestExecutionContext`](../classes/index.TestExecutionContext.html). It creates an emulated Algorand environment that closely mimics AVM behaviour relevant to unit testing the contracts and provides a TypeScript interface for interacting with the emulated environment. ```typescript import { TestExecutionContext } from '@algorandfoundation/algorand-typescript-testing' @@ -18,7 +18,7 @@ describe('MyContract', () => { // Recommended way to instantiate the test context const ctx = new TestExecutionContext() afterEach(() => { - // ctx should be reset after each test is executed + // The context should be reset after each test is executed ctx.reset() }) @@ -30,7 +30,7 @@ describe('MyContract', () => { The context manager interface exposes four main properties: -1. `contract`: An instance of `ContractContext` for creating instances of Contract under test and register them with the test execution context. +1. `contract`: An instance of `ContractContext` for creating instances of Contract under test and registering them with the test execution context. 1. `ledger`: An instance of `LedgerContext` for interacting with and querying the emulated Algorand ledger state. 1. `txn`: An instance of `TransactionContext` for creating and managing transaction groups, submitting transactions, and accessing transaction results. 1. `any`: An instance of `ValueGenerator` for generating randomized test data. @@ -65,8 +65,8 @@ As explained in the [introduction](testing-guide.md), `algorand-typescript-testi 1. **Native**: Fully matches AVM computation in Python. For example, `op.sha256` and other cryptographic operations behave identically in AVM and unit tests. This implies that the majority of opcodes that are 'pure' functions in AVM also have a native TypeScript implementation provided by this package. These abstractions and opcodes can be used within and outside of the testing context. -2. **Emulated**: Uses `TestExecutionContext` to mimic AVM behavior. For example, `Box.put` on an `Box` within a test context stores data in the test manager, not the real Algorand network, but provides the same interface. +2. **Emulated**: Uses `TestExecutionContext` to mimic AVM behaviour. For example, `Box.put` on a `Box` within a test context stores data in the test manager, not the real Algorand network, but provides the same interface. -3. **Mockable**: Not implemented, but can be mocked or patched. For example, `op.onlineStake` can be mocked to return specific values or behaviors; otherwise, it raises a `NotImplementedError`. This category covers cases where native or emulated implementation in a unit test context is impractical or overly complex. +3. **Mockable**: Not implemented, but can be mocked or patched. For example, `op.onlineStake` can be mocked to return specific values or behaviours; otherwise, it raises a `NotImplementedError`. This category covers cases where native or emulated implementation in a unit test context is impractical or overly complex. For a full list of all public `algorand-typescript` types and their corresponding implementation category, refer to the [Coverage](./coverage.md) section. diff --git a/docs/tg-contract-testing.md b/docs/tg-contract-testing.md index d1386d0b..4eebc7fe 100644 --- a/docs/tg-contract-testing.md +++ b/docs/tg-contract-testing.md @@ -4,10 +4,10 @@ title: Smart Contract Testing # Smart Contract Testing -This guide provides an overview of how to test smart contracts using the [Algorand Typescript Testing package](https://www.npmjs.com/package/@algorandfoundation/algorand-typescript-testing). We will cover the basics of testing `arc4.Contract` and `BaseContract` classes, focusing on `abimethod` and `baremethod` decorators. +This guide provides an overview of how to test smart contracts using the [Algorand TypeScript Testing package](https://www.npmjs.com/package/@algorandfoundation/algorand-typescript-testing). We cover the basics of testing `arc4.Contract` and `BaseContract` classes, with a focus on the `abimethod` and `baremethod` decorators. ``` -The code snippets showcasing the contract testing capabilities are using [vitest](https://vitest.dev/) as the test framework. However, note that the `algorand-typescript-testing` package can be used with any other test framework that supports TypeScript. `vitest` is used for demonstration purposes in this documentation. +The code snippets demonstrating contract testing use [vitest](https://vitest.dev/) as the test framework. Note that `algorand-typescript-testing` works with any test framework that supports TypeScript; `vitest` is used here for demonstration. ``` ```ts @@ -20,9 +20,9 @@ const ctx = new TestExecutionContext() ## `arc4.Contract` -Subclasses of `arc4.Contract` are **required** to be instantiated with an active test context. As part of instantiation, the test context will automatically create a matching `Application` object instance. +Subclasses of `arc4.Contract` must be instantiated with an active test context. As part of instantiation, the test context automatically creates a matching `Application` object instance. -Within the class implementation, methods decorated with `arc4.abimethod` and `arc4.baremethod` will automatically assemble an `gtxn.ApplicationCallTxn` transaction to emulate the AVM application call. This behavior can be overriden by setting the transaction group manually as part of test setup, this is done via implicit invocation of `ctx.any.txn.applicationCall` _value generator_ (refer to [APIs](../modules/index.html) for more details). +Within the class implementation, methods decorated with `arc4.abimethod` and `arc4.baremethod` automatically assemble a `gtxn.ApplicationCallTxn` to emulate the AVM application call. This behaviour can be overridden by setting the transaction group manually as part of test setup; this is done via implicit invocation of the `ctx.any.txn.applicationCall` _value generator_ (see [APIs](../modules/index.html) for details). ```ts class SimpleVotingContract extends arc4.Contract { @@ -81,7 +81,7 @@ const result = contract.vote() // Assert - you can access the corresponding auto generated application call transaction via test context expect(ctx.txn.lastGroup.transactions.length).toEqual(1) -// Assert - Note how local and global state are accessed via regular python instance attributes +// Assert - Note how local and global state are accessed via regular TypeScript instance attributes expect(result).toEqual(1) expect(contract.votes.value).toEqual(1) expect(contract.voted(ctx.defaultSender).value).toEqual(1) @@ -106,7 +106,7 @@ For more examples of tests using `arc4.Contract`, see the [examples](./examples. ## `BaseContract`` -Subclasses of `BaseContract` are **required** to be instantiated with an active test context. As part of instantiation, the test context will automatically create a matching `Application` object instance. This behavior is identical to `arc4.Contract` class instances. +Subclasses of `BaseContract` are **required** to be instantiated with an active test context. As part of instantiation, the test context will automatically create a matching `Application` object instance. This behaviour is identical to `arc4.Contract` class instances. Unlike `arc4.Contract`, `BaseContract` requires manual setup of the transaction context and explicit method calls. diff --git a/docs/tg-opcodes.md b/docs/tg-opcodes.md index 8eec5fde..68c82e46 100644 --- a/docs/tg-opcodes.md +++ b/docs/tg-opcodes.md @@ -314,7 +314,7 @@ class MockContract extends arc4.Contract {} class ContractFactory extends arc4.Contract { @arc4.abimethod() compileAndGetBytes(): uint64 { - const contractResponse = compile(MockContract) + const compiled = compile(MockContract) return compiled.localBytes } } @@ -393,7 +393,7 @@ These examples demonstrate how to mock key mockable opcodes in `algorand-typescr Mocking these opcodes allows you to: -1. Control complex operations' behavior not covered by _implemented_ and _emulated_ types. +1. Control complex operations' behaviour not covered by _implemented_ and _emulated_ types. 2. Test edge cases and error conditions. 3. Isolate contract logic from external dependencies. diff --git a/docs/tg-signature-testing.md b/docs/tg-signature-testing.md index d53d32e5..280d6aff 100644 --- a/docs/tg-signature-testing.md +++ b/docs/tg-signature-testing.md @@ -75,6 +75,6 @@ expect(ctx.executeLogicSig(new HashedTimeLockedLogicSig(), secret)) For more details on available operations, see the [coverage](./coverage.md). ```ts -// test cleanup +// Test cleanup ctx.reset() ``` diff --git a/docs/tg-state-management.md b/docs/tg-state-management.md index 5d24cfa8..dc7ed13c 100644 --- a/docs/tg-state-management.md +++ b/docs/tg-state-management.md @@ -105,6 +105,6 @@ expect(scratchSpace[1]).toEqual(5) For more detailed information, explore the example contracts in the `examples/` directory, the [coverage](./coverage.md) page, and the [API documentation](../modules/index.html). ```ts -// test cleanup +// Test cleanup ctx.reset() ``` diff --git a/docs/tg-transactions.md b/docs/tg-transactions.md index 38070e56..f507cacc 100644 --- a/docs/tg-transactions.md +++ b/docs/tg-transactions.md @@ -274,7 +274,7 @@ describe('pre compiled app calls', () => { ## Strongly typed contract to contract -Assuming the contract you wish to compile extends the ARC4 `Contract` type, you can make use of `compileArc4` to produce a contract proxy object that makes it easy to invoke application methods with compile time type safety. You can use the same `ctx.setCompiledApp` method set up the mock result for `compile` call and `ApplicationSpy` for mocking subsequent calls to the compiled contract. +Assuming the contract you wish to compile extends the ARC4 `Contract` type, you can make use of `compileArc4` to produce a contract proxy object that makes it easy to invoke application methods with compile-time type safety. You can use the same `ctx.setCompiledApp` method to set up the mock result for `compile` calls and `ApplicationSpy` for mocking subsequent calls to the compiled contract. ```ts import { assert, Contract, GlobalState } from '@algorandfoundation/algorand-typescript' @@ -417,6 +417,6 @@ describe('pre compiled typed app calls', () => { - [ApplicationSpy](./tg-application-spy.md) for detailed explanation on the usage of it ```ts -// test cleanup +// Test cleanup ctx.reset() ``` diff --git a/examples/voting/contract.algo.spec.ts b/examples/voting/contract.algo.spec.ts index fab56189..992cb027 100644 --- a/examples/voting/contract.algo.spec.ts +++ b/examples/voting/contract.algo.spec.ts @@ -35,7 +35,7 @@ describe('VotingRoundApp', () => { ctx.reset() }) - it('shoulld be able to bootstrap', () => { + it('should be able to bootstrap', () => { const contract = createContract() const app = ctx.ledger.getApplicationForContract(contract) contract.bootstrap(ctx.any.txn.payment({ receiver: app.address, amount: boostrapMinBalanceReq })) diff --git a/tests/state-op-codes.algo.spec.ts b/tests/state-op-codes.algo.spec.ts index c83055f9..966079ce 100644 --- a/tests/state-op-codes.algo.spec.ts +++ b/tests/state-op-codes.algo.spec.ts @@ -424,7 +424,7 @@ describe('State op codes', async () => { expect(appItxn.onCompletion).toEqual(OnCompleteAction.DeleteApplication) expect(asNumber(appItxn.fee)).toEqual(MIN_TXN_FEE) expect(appItxn.sender).toEqual(ctx.ledger.getApplicationForContract(contract).address) - // NOTE: would implementing emulation for this behavior be useful + // NOTE: would implementing emulation for this behaviour be useful // in unit testing context (vs integration tests)? // considering we don't emulate balance (transfer, accounting for fees and etc) expect(asNumber(appItxn.appId.id)).toEqual(0) From 315d53cc92dfd92d4d5ea4624862a2f7629eb281 Mon Sep 17 00:00:00 2001 From: Bobby Lat Date: Fri, 26 Sep 2025 15:28:42 +0700 Subject: [PATCH 54/68] refactor: allow fixed sized bytes to be created by Bytes factory methods by passing same options parameter --- examples/voting/contract.algo.spec.ts | 10 +- package-lock.json | 18 +-- package.json | 4 +- src/constants.ts | 3 +- src/impl/crypto.ts | 12 +- src/impl/primitives.ts | 216 ++++++++++++++++++++----- tests/artifacts/avm12/contract.algo.ts | 2 +- tests/crypto-op-codes.algo.spec.ts | 40 ++--- tests/primitives/bytes.algo.spec.ts | 14 +- tests/state-op-codes.algo.spec.ts | 2 +- 10 files changed, 226 insertions(+), 95 deletions(-) diff --git a/examples/voting/contract.algo.spec.ts b/examples/voting/contract.algo.spec.ts index fab56189..977e993a 100644 --- a/examples/voting/contract.algo.spec.ts +++ b/examples/voting/contract.algo.spec.ts @@ -16,7 +16,7 @@ describe('VotingRoundApp', () => { const createContract = () => { const contract = ctx.contract.create(VotingRoundApp) - const snapshotPublicKey = Bytes(keyPair.publicKey).toFixed({ length: 32 }) + const snapshotPublicKey = Bytes(keyPair.publicKey, { length: 32 }) const metadataIpfsCid = ctx.any.string(16) const startTime = ctx.any.uint64(Date.now() - 10_000, Date.now()) const endTime = ctx.any.uint64(Date.now() + 10_000, Date.now() + 100_000) @@ -52,7 +52,7 @@ describe('VotingRoundApp', () => { const account = ctx.any.account() const signature = nacl.sign.detached(toExternalValue(account.bytes), keyPair.secretKey) ctx.txn.createScope([ctx.any.txn.applicationCall({ sender: account })]).execute(() => { - const preconditions = contract.getPreconditions(Bytes(signature).toFixed({ length: 64 })) + const preconditions = contract.getPreconditions(Bytes(signature, { length: 64 })) expect(preconditions.is_allowed_to_vote).toEqual(1) expect(preconditions.is_voting_open).toEqual(1) @@ -75,11 +75,7 @@ describe('VotingRoundApp', () => { ) ctx.txn.createScope([ctx.any.txn.applicationCall({ appId: app, sender: account })]).execute(() => { - contract.vote( - ctx.any.txn.payment({ receiver: app.address, amount: voteMinBalanceReq }), - Bytes(signature).toFixed({ length: 64 }), - answerIds, - ) + contract.vote(ctx.any.txn.payment({ receiver: app.address, amount: voteMinBalanceReq }), Bytes(signature, { length: 64 }), answerIds) expect(contract.votesByAccount(account).value.bytes).toEqual(answerIds.bytes) expect(contract.voterCount.value).toEqual(13) diff --git a/package-lock.json b/package-lock.json index e0d25145..fc6185c1 100644 --- a/package-lock.json +++ b/package-lock.json @@ -8,8 +8,8 @@ "name": "@algorandfoundation/algorand-typescript-testing", "version": "1.0.0", "dependencies": { - "@algorandfoundation/algorand-typescript": "1.0.0-alpha.85", - "@algorandfoundation/puya-ts": "1.0.0-alpha.85", + "@algorandfoundation/algorand-typescript": "1.0.0-alpha.87", + "@algorandfoundation/puya-ts": "1.0.0-alpha.87", "elliptic": "^6.6.1", "js-sha256": "^0.11.0", "js-sha3": "^0.9.3", @@ -76,21 +76,21 @@ } }, "node_modules/@algorandfoundation/algorand-typescript": { - "version": "1.0.0-alpha.85", - "resolved": "https://registry.npmjs.org/@algorandfoundation/algorand-typescript/-/algorand-typescript-1.0.0-alpha.85.tgz", - "integrity": "sha512-lTOeDCRVNUhhDn03vILJIOY1++ZLJfo3tps4mRaENtD1+fqSMaUfH6msJaKsblY4XqBhrOWtBTPKpx1yyj7OkQ==", + "version": "1.0.0-alpha.87", + "resolved": "https://registry.npmjs.org/@algorandfoundation/algorand-typescript/-/algorand-typescript-1.0.0-alpha.87.tgz", + "integrity": "sha512-f9NaQ9j3tPS0quM8ylfBdRD9v0HxEfbcX2KzO6EFOI4RygubX9jc+i5hXTyB8jYXnr7gQJfiKZaLzIRP9eRnyA==", "peerDependencies": { "tslib": "^2.6.2" } }, "node_modules/@algorandfoundation/puya-ts": { - "version": "1.0.0-alpha.85", - "resolved": "https://registry.npmjs.org/@algorandfoundation/puya-ts/-/puya-ts-1.0.0-alpha.85.tgz", - "integrity": "sha512-Y21eXPgHo81yDsOy9a79qsdCGaLvQpz4BO1Bv9xct/Kjioq2InnWFmcV2LuKzCZC7a/kaBlvJkE8ChR6BU6M8g==", + "version": "1.0.0-alpha.87", + "resolved": "https://registry.npmjs.org/@algorandfoundation/puya-ts/-/puya-ts-1.0.0-alpha.87.tgz", + "integrity": "sha512-la92IL3XUBO/WyL7sxx4MrieEg0Hyci+EANx1BntitVQCUmVXROIobw5N9YZbeoQdBZuwkqW/HRpHB2+ZO4qwA==", "hasInstallScript": true, "license": "MIT", "dependencies": { - "@algorandfoundation/algorand-typescript": "1.0.0-alpha.85", + "@algorandfoundation/algorand-typescript": "1.0.0-alpha.87", "arcsecond": "^5.0.0", "argparse": "^2.0.1", "chalk": "^5.4.1", diff --git a/package.json b/package.json index cbffb169..84ccac40 100644 --- a/package.json +++ b/package.json @@ -68,8 +68,8 @@ "vitest": "3.2.4" }, "dependencies": { - "@algorandfoundation/algorand-typescript": "1.0.0-alpha.85", - "@algorandfoundation/puya-ts": "1.0.0-alpha.85", + "@algorandfoundation/algorand-typescript": "1.0.0-alpha.87", + "@algorandfoundation/puya-ts": "1.0.0-alpha.87", "elliptic": "^6.6.1", "js-sha256": "^0.11.0", "js-sha3": "^0.9.3", diff --git a/src/constants.ts b/src/constants.ts index c2176509..13ec0202 100644 --- a/src/constants.ts +++ b/src/constants.ts @@ -45,7 +45,8 @@ export const DEFAULT_GLOBAL_GENESIS_HASH = Bytes( 133, 89, 181, 20, 120, 253, 137, 193, 118, 67, 208, 93, 21, 168, 174, 107, 16, 171, 71, 187, 109, 138, 49, 136, 17, 86, 230, 189, 59, 174, 149, 209, ]), -).toFixed({ length: 32 }) + { length: 32 }, +) /** @internal * algorand encoded address of 32 zero bytes diff --git a/src/impl/crypto.ts b/src/impl/crypto.ts index 2f3302d4..1f3d253a 100644 --- a/src/impl/crypto.ts +++ b/src/impl/crypto.ts @@ -16,7 +16,7 @@ import { Bytes, BytesCls, Uint64Cls } from './primitives' export const sha256 = (a: StubBytesCompat): bytes<32> => { const bytesA = BytesCls.fromCompat(a) const hashArray = js_sha256.sha256.create().update(bytesA.asUint8Array()).digest() - const hashBytes = Bytes(new Uint8Array(hashArray)).toFixed({ length: 32 }) + const hashBytes = Bytes(new Uint8Array(hashArray), { length: 32 }) return hashBytes } @@ -24,7 +24,7 @@ export const sha256 = (a: StubBytesCompat): bytes<32> => { export const sha3_256 = (a: StubBytesCompat): bytes<32> => { const bytesA = BytesCls.fromCompat(a) const hashArray = js_sha3.sha3_256.create().update(bytesA.asUint8Array()).digest() - const hashBytes = Bytes(new Uint8Array(hashArray)).toFixed({ length: 32 }) + const hashBytes = Bytes(new Uint8Array(hashArray), { length: 32 }) return hashBytes } @@ -32,7 +32,7 @@ export const sha3_256 = (a: StubBytesCompat): bytes<32> => { export const keccak256 = (a: StubBytesCompat): bytes<32> => { const bytesA = BytesCls.fromCompat(a) const hashArray = js_sha3.keccak256.create().update(bytesA.asUint8Array()).digest() - const hashBytes = Bytes(new Uint8Array(hashArray)).toFixed({ length: 32 }) + const hashBytes = Bytes(new Uint8Array(hashArray), { length: 32 }) return hashBytes } @@ -40,7 +40,7 @@ export const keccak256 = (a: StubBytesCompat): bytes<32> => { export const sha512_256 = (a: StubBytesCompat): bytes<32> => { const bytesA = BytesCls.fromCompat(a) const hashArray = js_sha512.sha512_256.create().update(bytesA.asUint8Array()).digest() - const hashBytes = Bytes(new Uint8Array(hashArray)).toFixed({ length: 32 }) + const hashBytes = Bytes(new Uint8Array(hashArray), { length: 32 }) return hashBytes } @@ -114,7 +114,7 @@ export const ecdsaPkRecover = ( const x = pubKey.getX().toArray('be') const y = pubKey.getY().toArray('be') - return [Bytes(x).toFixed({ length: 32 }), Bytes(y).toFixed({ length: 32 })] + return [Bytes(x, { length: 32 }), Bytes(y, { length: 32 })] } /** @internal */ @@ -127,7 +127,7 @@ export const ecdsaPkDecompress = (v: Ecdsa, a: StubBytesCompat): readonly [bytes const x = pubKey.getX().toArray('be') const y = pubKey.getY().toArray('be') - return [Bytes(x).toFixed({ length: 32 }), Bytes(y).toFixed({ length: 32 })] + return [Bytes(x, { length: 32 }), Bytes(y, { length: 32 })] } /** @internal */ diff --git a/src/impl/primitives.ts b/src/impl/primitives.ts index 9394b030..ce1d3bee 100644 --- a/src/impl/primitives.ts +++ b/src/impl/primitives.ts @@ -87,63 +87,95 @@ export function BigUint(v?: BigUintCompat | string): biguint { return BigUintCls.fromCompat(v).asAlgoTs() } +type ToFixedBytesOptions = { + /** + * The length for the bounded type + */ + length: TLength + /** + * The strategy to use for converting to a fixed length bytes type (default: 'assert-length') + * + * - 'assert-length': Asserts that the byte sequence has the specified length and fails if it differs + * - 'unsafe-cast': Reinterprets the byte sequence as a fixed length type without any checks. This will succeed even if the value + * is not of the specified length but will result in undefined behaviour for any code that makes use of this value. + * + */ + strategy?: 'assert-length' | 'unsafe-cast' +} + /** - * @internal * Create a byte array from a string interpolation template and compatible replacements * @param value * @param replacements */ -export function Bytes(value: TemplateStringsArray, ...replacements: BytesCompat[]): bytes +export function Bytes(value: TemplateStringsArray, ...replacements: BytesCompat[]): bytes /** - * @internal * Create a byte array from a utf8 string */ -export function Bytes(value: string): bytes +export function Bytes(value: string): bytes +/** + * Create a byte array from a utf8 string + */ +export function Bytes(value: string, options: ToFixedBytesOptions): bytes /** - * @internal * No op, returns the provided byte array. */ -export function Bytes(value: bytes): bytes +export function Bytes(value: bytes): bytes +/** + * No op, returns the provided byte array. + */ +export function Bytes(value: bytes, options: ToFixedBytesOptions): bytes /** - * @internal * Create a byte array from a biguint value encoded as a variable length big-endian number */ -export function Bytes(value: biguint): bytes +export function Bytes(value: biguint): bytes /** - * @internal - * Create a byte array from a uint64 value encoded as a fixed length 64-bit number + * Create a byte array from a biguint value encoded as a variable length big-endian number */ -export function Bytes(value: uint64): bytes +export function Bytes(value: biguint, options: ToFixedBytesOptions): bytes +/** + * Create a byte array from a uint64 value encoded as a a variable length 64-bit number + */ +export function Bytes(value: uint64): bytes +/** + * Create a byte array from a uint64 value encoded as a a variable length 64-bit number + */ +export function Bytes(value: uint64, options: ToFixedBytesOptions): bytes /** - * @internal * Create a byte array from an Iterable where each item is interpreted as a single byte and must be between 0 and 255 inclusively */ -export function Bytes(value: Iterable): bytes +export function Bytes(value: Iterable): bytes +/** + * Create a byte array from an Iterable where each item is interpreted as a single byte and must be between 0 and 255 inclusively + */ +export function Bytes(value: Iterable, options: ToFixedBytesOptions): bytes /** - * @internal * Create an empty byte array */ -export function Bytes(): bytes -export function Bytes( - value?: BytesCompat | TemplateStringsArray | biguint | uint64 | Iterable, - ...replacements: BytesCompat[] -): bytes { - if (isTemplateStringsArray(value)) { - return BytesCls.fromInterpolation(value, replacements).asAlgoTs() - } else if (typeof value === 'bigint' || value instanceof BigUintCls) { - return BigUintCls.fromCompat(value).toBytes().asAlgoTs() - } else if (typeof value === 'number' || value instanceof Uint64Cls) { - return Uint64Cls.fromCompat(value).toBytes().asAlgoTs() - } else if (typeof value === 'object' && Symbol.iterator in value) { - const valueItems = Array.from(value).map((v) => getNumber(v)) - const invalidValue = valueItems.find((v) => v < 0 && v > 255) - if (invalidValue) { - throw new CodeError(`Cannot convert ${invalidValue} to a byte`) - } - return new BytesCls(new Uint8Array(value)).asAlgoTs() - } else { - return BytesCls.fromCompat(value).asAlgoTs() - } +export function Bytes(): bytes +/** + * Create an empty byte array + */ +export function Bytes(options: ToFixedBytesOptions): bytes +export function Bytes( + value?: BytesCompat | TemplateStringsArray | biguint | uint64 | Iterable | ToFixedBytesOptions, + ...replacements: [ToFixedBytesOptions] | BytesCompat[] | undefined[] +): bytes { + // Handle the case where only options are provided (empty bytes with fixed length) + if (isOptionsOnly(value)) { + const options = value as ToFixedBytesOptions + const emptyBytes = new BytesCls(new Uint8Array(options.length)) + return emptyBytes.toFixed(options) + } + + // Convert the input value to a BytesCls instance + const result = convertValueToBytes(value, replacements) + + // Extract options from replacements if provided + const options = isTemplateStringsArray(value) ? undefined : extractOptionsFromReplacements(replacements) + + // Return either fixed-length or variable-length bytes + return options ? result.toFixed(options) : (result.asAlgoTs() as bytes) } /** @@ -151,16 +183,16 @@ export function Bytes( * Create a new bytes value from a hexadecimal encoded string * @param hex */ -Bytes.fromHex = (hex: string): bytes => { - return BytesCls.fromHex(hex).asAlgoTs() +Bytes.fromHex = (hex: string, options?: ToFixedBytesOptions): bytes => { + return options ? BytesCls.fromHex(hex).toFixed(options) : (BytesCls.fromHex(hex).asAlgoTs() as bytes) } /** * @internal * Create a new bytes value from a base 64 encoded string * @param b64 */ -Bytes.fromBase64 = (b64: string): bytes => { - return BytesCls.fromBase64(b64).asAlgoTs() +Bytes.fromBase64 = (b64: string, options?: ToFixedBytesOptions): bytes => { + return options ? BytesCls.fromBase64(b64).toFixed(options) : (BytesCls.fromBase64(b64).asAlgoTs() as bytes) } /** @@ -168,8 +200,110 @@ Bytes.fromBase64 = (b64: string): bytes => { * Create a new bytes value from a base 32 encoded string * @param b32 */ -Bytes.fromBase32 = (b32: string): bytes => { - return BytesCls.fromBase32(b32).asAlgoTs() +Bytes.fromBase32 = (b32: string, options?: ToFixedBytesOptions): bytes => { + return options ? BytesCls.fromBase32(b32).toFixed(options) : (BytesCls.fromBase32(b32).asAlgoTs() as bytes) +} + +/** + * Helper function to check if the value parameter is options-only (for empty bytes with fixed length) + */ +function isOptionsOnly( + value?: BytesCompat | TemplateStringsArray | biguint | uint64 | Iterable | ToFixedBytesOptions, +): value is ToFixedBytesOptions { + return ( + value !== null && + typeof value === 'object' && + !Array.isArray(value) && + !isTemplateStringsArray(value) && + !(Symbol.iterator in value) && + !(value instanceof BigUintCls) && + !(value instanceof Uint64Cls) && + !(value instanceof BytesCls) && + !(value instanceof Uint8Array) && + Object.keys(value).length <= 2 && + Object.keys(value).includes('length') + ) +} + +/** + * Helper function to convert various input types to BytesCls + */ +function convertValueToBytes( + value?: BytesCompat | TemplateStringsArray | biguint | uint64 | Iterable | ToFixedBytesOptions, + replacements?: [ToFixedBytesOptions] | BytesCompat[] | undefined[], +): BytesCls { + if (value === undefined) { + return new BytesCls(new Uint8Array(0)) + } + + if (isTemplateStringsArray(value)) { + return BytesCls.fromInterpolation(value, replacements as BytesCompat[]) + } + + if (typeof value === 'bigint' || value instanceof BigUintCls) { + return BigUintCls.fromCompat(value).toBytes() + } + + if (typeof value === 'number' || value instanceof Uint64Cls) { + return Uint64Cls.fromCompat(value).toBytes() + } + + if (isIterable(value)) { + return convertIterableToBytes(value) + } + + // Default case: treat as BytesCompat + return BytesCls.fromCompat(value as BytesCompat) +} + +/** + * Helper function to check if a value is iterable (but not string or Uint8Array) + */ +function isIterable(value: unknown): value is Iterable { + return ( + value !== null && + typeof value === 'object' && + Symbol.iterator in value && + !isTemplateStringsArray(value) && + !(value instanceof Uint8Array) && + !(value instanceof BytesCls) + ) +} + +/** + * Helper function to convert an iterable of numbers to BytesCls + */ +function convertIterableToBytes(value: Iterable): BytesCls { + const valueItems = Array.from(value).map((v) => getNumber(v)) + const invalidValue = valueItems.find((v) => v < 0 || v > 255) + if (invalidValue !== undefined) { + throw new CodeError(`Cannot convert ${invalidValue} to a byte`) + } + return new BytesCls(new Uint8Array(valueItems)) +} + +/** + * Helper function to extract options from the replacements parameter + */ +function extractOptionsFromReplacements( + replacements: [ToFixedBytesOptions] | BytesCompat[] | undefined[], +): ToFixedBytesOptions | undefined { + if (!replacements || replacements.length !== 1) { + return undefined + } + + const potentialOptions = replacements[0] + // Check if the replacement looks like options + if ( + typeof potentialOptions === 'object' && + potentialOptions !== null && + Object.keys(potentialOptions).length <= 2 && + Object.keys(potentialOptions).includes('length') + ) { + return potentialOptions as ToFixedBytesOptions + } + + return undefined } /** diff --git a/tests/artifacts/avm12/contract.algo.ts b/tests/artifacts/avm12/contract.algo.ts index a04763b4..92a6cf8e 100644 --- a/tests/artifacts/avm12/contract.algo.ts +++ b/tests/artifacts/avm12/contract.algo.ts @@ -14,7 +14,7 @@ import { @contract({ avmVersion: 12 }) export class Avm12Contract extends Contract { testFalconVerify() { - assert(!op.falconVerify(Bytes(), Bytes(), op.bzero(1793).toFixed({ length: 1793 }))) + assert(!op.falconVerify(Bytes(), Bytes(), op.bzero(1793))) } testRejectVersion() { diff --git a/tests/crypto-op-codes.algo.spec.ts b/tests/crypto-op-codes.algo.spec.ts index 9200e133..cc9c45ed 100644 --- a/tests/crypto-op-codes.algo.spec.ts +++ b/tests/crypto-op-codes.algo.spec.ts @@ -169,11 +169,11 @@ describe('crypto op codes', async () => { describe('ecdsaVerify', async () => { test('should be able to verify k1 signature', async ({ appClientCryptoOpsContract: appClient }) => { - const messageHash = Bytes.fromHex('f809fd0aa0bb0f20b354c6b2f86ea751957a4e262a546bd716f34f69b9516ae1').toFixed({ length: 32 }) - const sigR = Bytes.fromHex('f7f913754e5c933f3825d3aef22e8bf75cfe35a18bede13e15a6e4adcfe816d2').toFixed({ length: 32 }) - const sigS = Bytes.fromHex('0b5599159aa859d79677f33280848ae4c09c2061e8b5881af8507f8112966754').toFixed({ length: 32 }) - const pubkeyX = Bytes.fromHex('a710244d62747aa8db022ddd70617240adaf881b439e5f69993800e614214076').toFixed({ length: 32 }) - const pubkeyY = Bytes.fromHex('48d0d337704fe2c675909d2c93f7995e199156f302f63c74a8b96827b28d777b').toFixed({ length: 32 }) + const messageHash = Bytes.fromHex('f809fd0aa0bb0f20b354c6b2f86ea751957a4e262a546bd716f34f69b9516ae1', { length: 32 }) + const sigR = Bytes.fromHex('f7f913754e5c933f3825d3aef22e8bf75cfe35a18bede13e15a6e4adcfe816d2', { length: 32 }) + const sigS = Bytes.fromHex('0b5599159aa859d79677f33280848ae4c09c2061e8b5881af8507f8112966754', { length: 32 }) + const pubkeyX = Bytes.fromHex('a710244d62747aa8db022ddd70617240adaf881b439e5f69993800e614214076', { length: 32 }) + const pubkeyY = Bytes.fromHex('48d0d337704fe2c675909d2c93f7995e199156f302f63c74a8b96827b28d777b', { length: 32 }) const avmResult = await getAvmResult( { @@ -194,11 +194,11 @@ describe('crypto op codes', async () => { expect(result).toEqual(avmResult) }) test('should be able to verify r1 signature', async ({ appClientCryptoOpsContract: appClient }) => { - const messageHash = Bytes.fromHex('f809fd0aa0bb0f20b354c6b2f86ea751957a4e262a546bd716f34f69b9516ae1').toFixed({ length: 32 }) - const sigR = Bytes.fromHex('18d96c7cda4bc14d06277534681ded8a94828eb731d8b842e0da8105408c83cf').toFixed({ length: 32 }) - const sigS = Bytes.fromHex('7d33c61acf39cbb7a1d51c7126f1718116179adebd31618c4604a1f03b5c274a').toFixed({ length: 32 }) - const pubkeyX = Bytes.fromHex('f8140e3b2b92f7cbdc8196bc6baa9ce86cf15c18e8ad0145d50824e6fa890264').toFixed({ length: 32 }) - const pubkeyY = Bytes.fromHex('bd437b75d6f1db67155a95a0da4b41f2b6b3dc5d42f7db56238449e404a6c0a3').toFixed({ length: 32 }) + const messageHash = Bytes.fromHex('f809fd0aa0bb0f20b354c6b2f86ea751957a4e262a546bd716f34f69b9516ae1', { length: 32 }) + const sigR = Bytes.fromHex('18d96c7cda4bc14d06277534681ded8a94828eb731d8b842e0da8105408c83cf', { length: 32 }) + const sigS = Bytes.fromHex('7d33c61acf39cbb7a1d51c7126f1718116179adebd31618c4604a1f03b5c274a', { length: 32 }) + const pubkeyX = Bytes.fromHex('f8140e3b2b92f7cbdc8196bc6baa9ce86cf15c18e8ad0145d50824e6fa890264', { length: 32 }) + const pubkeyY = Bytes.fromHex('bd437b75d6f1db67155a95a0da4b41f2b6b3dc5d42f7db56238449e404a6c0a3', { length: 32 }) const avmResult = await getAvmResult( { @@ -293,9 +293,10 @@ describe('crypto op codes', async () => { const a = Bytes.fromHex('528b9e23d93d0e020a119d7ba213f6beb1c1f3495a217166ecd20f5a70e7c2d7') const b = Bytes.fromHex( '372a3afb42f55449c94aaa5f274f26543e77e8d8af4babee1a6fbc1c0391aa9e6e0b8d8d7f4ed045d5b517fea8ad3566025ae90d2f29f632e38384b4c4f5b9eb741c6e446b0f540c1b3761d814438b04', - ).toFixed({ length: 80 }) + { length: 80 }, + ) - const c = Bytes.fromHex('3a2740da7a0788ebb12a52154acbcca1813c128ca0b249e93f8eb6563fee418d').toFixed({ length: 32 }) + const c = Bytes.fromHex('3a2740da7a0788ebb12a52154acbcca1813c128ca0b249e93f8eb6563fee418d', { length: 32 }) test('should throw not available error', async () => { const mockedVrfVerify = op.vrfVerify as Mock @@ -313,7 +314,7 @@ describe('crypto op codes', async () => { asUint8Array(c), ) const mockedVrfVerify = op.vrfVerify as Mock - mockedVrfVerify.mockReturnValue([Bytes(new Uint8Array(avmResult[0])).toFixed({ length: 64 }), avmResult[1]]) + mockedVrfVerify.mockReturnValue([Bytes(new Uint8Array(avmResult[0]), { length: 64 }), avmResult[1]]) const result = op.vrfVerify(VrfVerify.VrfAlgorand, asBytes(a), b, c) expect(asUint8Array(result[0])).toEqual(new Uint8Array(avmResult[0])) @@ -338,7 +339,7 @@ describe('crypto op codes', async () => { asUint8Array(a), ) const mockedMimc = op.mimc as Mock - mockedMimc.mockReturnValue(Bytes(avmResult).toFixed({ length: 32 })) + mockedMimc.mockReturnValue(Bytes(avmResult, { length: 32 })) const result = op.mimc(MimcConfigurations.BN254Mp110, Bytes(a)) expect(result).toEqual(avmResult) @@ -366,9 +367,8 @@ describe('crypto op codes', async () => { describe('falconVerify', async () => { const a = Bytes.fromHex('528b9e23d93d0e020a119d7ba213f6beb1c1f3495a217166ecd20f5a70e7c2d7') - const b = op.bzero(1232).toFixed({ length: 1232 }) - - const c = op.bzero(1793).toFixed({ length: 1793 }) + const b = op.bzero(1232) + const c = op.bzero(1793) test('should throw not available error', async () => { const mockedFalconVerify = op.falconVerify as Mock @@ -396,9 +396,9 @@ const generateEcdsaTestData = (v: Ecdsa) => { const recoveryId = 0 // Recovery ID is typically 0 or 1 return { - data: Bytes(new Uint8Array(messageHash)).toFixed({ length: 32 }), - r: Bytes(new Uint8Array(signature.r.toArray('be', 32))).toFixed({ length: 32 }), - s: Bytes(new Uint8Array(signature.s.toArray('be', 32))).toFixed({ length: 32 }), + data: Bytes(new Uint8Array(messageHash), { length: 32 }), + r: Bytes(new Uint8Array(signature.r.toArray('be', 32)), { length: 32 }), + s: Bytes(new Uint8Array(signature.s.toArray('be', 32)), { length: 32 }), recoveryId: Uint64Cls.fromCompat(recoveryId), pubkeyX: Bytes(new Uint8Array(pk.slice(0, 32))), pubkeyY: Bytes(new Uint8Array(pk.slice(32))), diff --git a/tests/primitives/bytes.algo.spec.ts b/tests/primitives/bytes.algo.spec.ts index 46b733c4..d8f8e448 100644 --- a/tests/primitives/bytes.algo.spec.ts +++ b/tests/primitives/bytes.algo.spec.ts @@ -202,17 +202,17 @@ describe('Bytes', async () => { it('should be able to create fixed size bytes with no parameter', () => { const x = op.bzero(32) expect(x.length).toEqual(32) - expect(x).toEqual(Bytes.fromHex('0000000000000000000000000000000000000000000000000000000000000000').toFixed({ length: 32 })) - expect(x).toEqual(Bytes.fromBase64('AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=').toFixed({ length: 32 })) - expect(x).toEqual(Bytes.fromBase32('AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA==').toFixed({ length: 32 })) + expect(x).toEqual(Bytes.fromHex('0000000000000000000000000000000000000000000000000000000000000000', { length: 32 })) + expect(x).toEqual(Bytes.fromBase64('AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=', { length: 32 })) + expect(x).toEqual(Bytes.fromBase32('AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA==', { length: 32 })) }) it('should be able to create fixed size bytes with parameter', () => { - const x1 = Bytes(new Uint8Array(32)).toFixed({ length: 32 }) + const x1 = Bytes(new Uint8Array(32), { length: 32 }) expect(x1.length).toEqual(32) expect(x1).toEqual(op.bzero(32)) - const x2 = Bytes('abcdefghijklmnopqrstuvwxyz123456').toFixed({ length: 32 }) + const x2 = Bytes('abcdefghijklmnopqrstuvwxyz123456', { length: 32 }) expect(x2.length).toEqual(32) expect(x2).toEqual(Bytes('abcdefghijklmnopqrstuvwxyz123456')) expect(x2).toEqual(Bytes.fromHex('6162636465666768696a6b6c6d6e6f707172737475767778797a313233343536')) @@ -238,8 +238,8 @@ describe('Bytes', async () => { const x3 = convertBytes, 2>>(op.bzero(64), { strategy: 'unsafe-cast' }) expect(x3.length).toEqual(2) - expect(x3[0].bytes).toEqual(Bytes.fromHex('0000000000000000000000000000000000000000000000000000000000000000').toFixed({ length: 32 })) - expect(x3[1].bytes).toEqual(Bytes.fromHex('0000000000000000000000000000000000000000000000000000000000000000').toFixed({ length: 32 })) + expect(x3[0].bytes).toEqual(Bytes.fromHex('0000000000000000000000000000000000000000000000000000000000000000', { length: 32 })) + expect(x3[1].bytes).toEqual(Bytes.fromHex('0000000000000000000000000000000000000000000000000000000000000000', { length: 32 })) }) }) }) diff --git a/tests/state-op-codes.algo.spec.ts b/tests/state-op-codes.algo.spec.ts index c83055f9..3fffbefc 100644 --- a/tests/state-op-codes.algo.spec.ts +++ b/tests/state-op-codes.algo.spec.ts @@ -269,7 +269,7 @@ describe('State op codes', async () => { 'should return the correct field value of the asset', async ([methodName, expectedValue], { appClientStateAssetParamsContract: appClient, testAccount, assetFactory }) => { const creator = Account(Bytes.fromBase32(testAccount.addr.toString())) - const metadataHash = Bytes(`test${' '.repeat(28)}`).toFixed({ length: 32, strategy: 'assert-length' }) + const metadataHash = Bytes(`test${' '.repeat(28)}`, { length: 32, strategy: 'assert-length' }) const mockAsset = ctx.any.asset({ total: 100, decimals: 0, From acfb4f10a871dbe47a7b242ea7aff51e42aab5fa Mon Sep 17 00:00:00 2001 From: boblat Date: Tue, 21 Oct 2025 01:13:10 +0800 Subject: [PATCH 55/68] feat: add support for ABI validation (#99) --------- Co-authored-by: Neil Campbell --- docs/tg-concepts.md | 5 +++++ package-lock.json | 18 +++++++++--------- package.json | 4 ++-- src/impl/encoded-types/encoded-types.ts | 2 +- src/impl/validate-encoding.ts | 2 ++ src/internal/index.ts | 2 ++ tests/arc4/address.algo.spec.ts | 3 ++- .../arc4-primitive-ops/contract.algo.ts | 5 +++-- 8 files changed, 26 insertions(+), 15 deletions(-) create mode 100644 src/impl/validate-encoding.ts diff --git a/docs/tg-concepts.md b/docs/tg-concepts.md index 0bbc191d..014781c6 100644 --- a/docs/tg-concepts.md +++ b/docs/tg-concepts.md @@ -70,3 +70,8 @@ As explained in the [introduction](testing-guide.md), `algorand-typescript-testi 3. **Mockable**: Not implemented, but can be mocked or patched. For example, `op.onlineStake` can be mocked to return specific values or behaviours; otherwise, it raises a `NotImplementedError`. This category covers cases where native or emulated implementation in a unit test context is impractical or overly complex. For a full list of all public `algorand-typescript` types and their corresponding implementation category, refer to the [Coverage](./coverage.md) section. + +## Data Validation + +Algorand TypeScript and the puya compiler have functionality to perform validation of transaction inputs via the `--validate-abi-args`, `--validate-abi-return` CLI arguments, `arc4.abimethod({validateEncoding: ...})` decorator, `convertBytes(..., { strategy: 'validate' }) method` and `validateEncoding(...)` method. +The Algorand TypeScript Testing library does _NOT_ implement this validation behaviour, as you should test invalid inputs using an integrated test against a real Algorand network. diff --git a/package-lock.json b/package-lock.json index fc6185c1..fdcdb616 100644 --- a/package-lock.json +++ b/package-lock.json @@ -8,8 +8,8 @@ "name": "@algorandfoundation/algorand-typescript-testing", "version": "1.0.0", "dependencies": { - "@algorandfoundation/algorand-typescript": "1.0.0-alpha.87", - "@algorandfoundation/puya-ts": "1.0.0-alpha.87", + "@algorandfoundation/algorand-typescript": "1.0.0-alpha.90", + "@algorandfoundation/puya-ts": "1.0.0-alpha.90", "elliptic": "^6.6.1", "js-sha256": "^0.11.0", "js-sha3": "^0.9.3", @@ -76,21 +76,21 @@ } }, "node_modules/@algorandfoundation/algorand-typescript": { - "version": "1.0.0-alpha.87", - "resolved": "https://registry.npmjs.org/@algorandfoundation/algorand-typescript/-/algorand-typescript-1.0.0-alpha.87.tgz", - "integrity": "sha512-f9NaQ9j3tPS0quM8ylfBdRD9v0HxEfbcX2KzO6EFOI4RygubX9jc+i5hXTyB8jYXnr7gQJfiKZaLzIRP9eRnyA==", + "version": "1.0.0-alpha.90", + "resolved": "https://registry.npmjs.org/@algorandfoundation/algorand-typescript/-/algorand-typescript-1.0.0-alpha.90.tgz", + "integrity": "sha512-Q7PAbhZtt0uwwWAgfoUEwW5LwdIXUMaUyEPkGCorI9EfiXPDEE7lvdv1GlArVDIkwLwlNYgra1PmWwlYGd8KdQ==", "peerDependencies": { "tslib": "^2.6.2" } }, "node_modules/@algorandfoundation/puya-ts": { - "version": "1.0.0-alpha.87", - "resolved": "https://registry.npmjs.org/@algorandfoundation/puya-ts/-/puya-ts-1.0.0-alpha.87.tgz", - "integrity": "sha512-la92IL3XUBO/WyL7sxx4MrieEg0Hyci+EANx1BntitVQCUmVXROIobw5N9YZbeoQdBZuwkqW/HRpHB2+ZO4qwA==", + "version": "1.0.0-alpha.90", + "resolved": "https://registry.npmjs.org/@algorandfoundation/puya-ts/-/puya-ts-1.0.0-alpha.90.tgz", + "integrity": "sha512-v5Ai4B/M/V17zCXeo4hzS1BcrxKtiw5JM17MXTFO4Kz679BPod1RnKkOBMFH50n+IXHBRY8FJnmlZWTmUZYVHw==", "hasInstallScript": true, "license": "MIT", "dependencies": { - "@algorandfoundation/algorand-typescript": "1.0.0-alpha.87", + "@algorandfoundation/algorand-typescript": "1.0.0-alpha.90", "arcsecond": "^5.0.0", "argparse": "^2.0.1", "chalk": "^5.4.1", diff --git a/package.json b/package.json index 84ccac40..20772384 100644 --- a/package.json +++ b/package.json @@ -68,8 +68,8 @@ "vitest": "3.2.4" }, "dependencies": { - "@algorandfoundation/algorand-typescript": "1.0.0-alpha.87", - "@algorandfoundation/puya-ts": "1.0.0-alpha.87", + "@algorandfoundation/algorand-typescript": "1.0.0-alpha.90", + "@algorandfoundation/puya-ts": "1.0.0-alpha.90", "elliptic": "^6.6.1", "js-sha256": "^0.11.0", "js-sha3": "^0.9.3", diff --git a/src/impl/encoded-types/encoded-types.ts b/src/impl/encoded-types/encoded-types.ts index 66fbd312..66917891 100644 --- a/src/impl/encoded-types/encoded-types.ts +++ b/src/impl/encoded-types/encoded-types.ts @@ -1220,7 +1220,7 @@ export function decodeArc4( export function convertBytes( typeInfoString: string, bytes: StubBytesCompat, - options: { prefix?: 'none' | 'log'; strategy: 'unsafe-cast' }, + options: { prefix?: 'none' | 'log'; strategy: 'unsafe-cast' | 'validate' }, ): T { const typeInfo = JSON.parse(typeInfoString) return getEncoder(typeInfo)(bytes, typeInfo, options.prefix) diff --git a/src/impl/validate-encoding.ts b/src/impl/validate-encoding.ts new file mode 100644 index 00000000..c6c5d7bc --- /dev/null +++ b/src/impl/validate-encoding.ts @@ -0,0 +1,2 @@ +/** @internal */ +export function validateEncoding(_value: T) {} diff --git a/src/internal/index.ts b/src/internal/index.ts index ecfc4d34..fc5f8888 100644 --- a/src/internal/index.ts +++ b/src/internal/index.ts @@ -5,6 +5,8 @@ export { BaseContract, contract } from '../impl/base-contract' /** @internal */ export { clone } from '../impl/clone' /** @internal */ +export { validateEncoding } from '../impl/validate-encoding' +/** @internal */ export { compile } from '../impl/compiled' /** @internal */ export { abimethod, baremethod, Contract, readonly } from '../impl/contract' diff --git a/tests/arc4/address.algo.spec.ts b/tests/arc4/address.algo.spec.ts index a7d5ed5e..51c0abbf 100644 --- a/tests/arc4/address.algo.spec.ts +++ b/tests/arc4/address.algo.spec.ts @@ -71,7 +71,8 @@ describe('arc4.Address', () => { test.each(testData)('fromBytes method', (value) => { const sdkResult = getABIEncodedValue(asUint8Array(value), abiTypeString, {}) - const result = convertBytes
(value, { strategy: 'unsafe-cast' }) + // no actual validation in testing lib, just making sure 'validate' strategy value can be passed + const result = convertBytes
(value, { strategy: 'validate' }) expect(result.bytes).toEqual(sdkResult) }) diff --git a/tests/artifacts/arc4-primitive-ops/contract.algo.ts b/tests/artifacts/arc4-primitive-ops/contract.algo.ts index 8c28f085..60c718ee 100644 --- a/tests/artifacts/arc4-primitive-ops/contract.algo.ts +++ b/tests/artifacts/arc4-primitive-ops/contract.algo.ts @@ -1,15 +1,16 @@ import type { biguint, bytes, uint64 } from '@algorandfoundation/algorand-typescript' -import { arc4, BigUint, clone, emit } from '@algorandfoundation/algorand-typescript' +import { arc4, BigUint, clone, emit, validateEncoding } from '@algorandfoundation/algorand-typescript' import type { Bool, UFixed } from '@algorandfoundation/algorand-typescript/arc4' import { Byte, Contract, convertBytes, Str, Uint } from '@algorandfoundation/algorand-typescript/arc4' export class Arc4PrimitiveOpsContract extends Contract { - @arc4.abimethod() + @arc4.abimethod({ validateEncoding: 'unsafe-disabled' }) public verify_uintn_uintn_eq(a: bytes, b: bytes): boolean { const aBiguint = BigUint(a) const bBiguint = BigUint(b) const aUintN = new Uint<64>(aBiguint) const bUintN = new Uint<64>(bBiguint) + validateEncoding(aUintN) return aUintN === bUintN } @arc4.abimethod() From ff6188f258f3e6bee8d0ebbb8882783ecea376eb Mon Sep 17 00:00:00 2001 From: Bobby Lat Date: Thu, 23 Oct 2025 11:08:16 +0800 Subject: [PATCH 56/68] refactor: create new scope in beforeAll | beforeEach as part of creating TestFixture to be consistent with puya-ts and to reduce boilerplate code --- tests/arc4/bool.algo.spec.ts | 8 ++----- tests/arc4/byte.algo.spec.ts | 7 ++---- tests/arc4/circular-reference.algo.spec.ts | 8 ++----- tests/arc4/emit.algo.spec.ts | 8 ++----- tests/arc4/method-selector.algo.spec.ts | 8 ++----- tests/arc4/resource-encoding.algo.spec.ts | 8 ++----- tests/arc4/str.algo.spec.ts | 7 ++---- tests/arc4/ufixednxm.algo.spec.ts | 7 ++---- tests/arc4/uintn.algo.spec.ts | 7 ++---- tests/avm12.algo.spec.ts | 8 +++---- tests/crypto-op-codes.algo.spec.ts | 12 ++++------ tests/global-state-arc4-values.algo.spec.ts | 8 ++----- tests/itxn-compose.algo.spec.ts | 7 ++---- tests/local-state-arc4-values.algo.spec.ts | 8 ++----- tests/log.algo.spec.ts | 7 ++---- tests/multi-inheritance.algo.spec.ts | 7 ++---- tests/primitives/biguint.algo.spec.ts | 7 ++---- tests/primitives/bytes.algo.spec.ts | 8 ++----- tests/primitives/uint64.algo.spec.ts | 7 ++---- tests/pure-op-codes.algo.spec.ts | 8 ++----- tests/state-op-codes.algo.spec.ts | 26 ++++++++++----------- tests/switch-statements.algo.spec.ts | 7 ++---- tests/test-fixture.ts | 21 +++++++++++------ 23 files changed, 72 insertions(+), 137 deletions(-) diff --git a/tests/arc4/bool.algo.spec.ts b/tests/arc4/bool.algo.spec.ts index f805c1ba..1f8af80e 100644 --- a/tests/arc4/bool.algo.spec.ts +++ b/tests/arc4/bool.algo.spec.ts @@ -1,22 +1,18 @@ import { Bytes } from '@algorandfoundation/algorand-typescript' import { TestExecutionContext } from '@algorandfoundation/algorand-typescript-testing' import { Bool, convertBytes } from '@algorandfoundation/algorand-typescript/arc4' -import { afterEach, beforeAll, describe, expect } from 'vitest' +import { afterEach, describe, expect } from 'vitest' import { ABI_RETURN_VALUE_LOG_PREFIX } from '../../src/constants' import { asUint8Array } from '../../src/util' import { getAvmResult } from '../avm-invoker' import { createArc4TestFixture } from '../test-fixture' describe('arc4.Bool', async () => { - const [test, localnetFixture] = createArc4TestFixture('tests/artifacts/arc4-primitive-ops/contract.algo.ts', { + const test = createArc4TestFixture('tests/artifacts/arc4-primitive-ops/contract.algo.ts', { Arc4PrimitiveOpsContract: { deployParams: { createParams: { extraProgramPages: undefined } } }, }) const ctx = new TestExecutionContext() - beforeAll(async () => { - await localnetFixture.newScope() - }) - afterEach(() => { ctx.reset() }) diff --git a/tests/arc4/byte.algo.spec.ts b/tests/arc4/byte.algo.spec.ts index 2b95eab5..69cdd223 100644 --- a/tests/arc4/byte.algo.spec.ts +++ b/tests/arc4/byte.algo.spec.ts @@ -3,7 +3,7 @@ import { Bytes } from '@algorandfoundation/algorand-typescript' import { TestExecutionContext } from '@algorandfoundation/algorand-typescript-testing' import { Byte, convertBytes } from '@algorandfoundation/algorand-typescript/arc4' import { encodingUtil } from '@algorandfoundation/puya-ts' -import { afterEach, beforeAll, describe, expect } from 'vitest' +import { afterEach, describe, expect } from 'vitest' import { ABI_RETURN_VALUE_LOG_PREFIX, MAX_UINT64 } from '../../src/constants' import { asBigUintCls, asUint8Array } from '../../src/util' import { getAvmResult } from '../avm-invoker' @@ -11,15 +11,12 @@ import { createArc4TestFixture } from '../test-fixture' const invalidBytesLengthError = 'byte string must be 1 byte long' describe('arc4.Byte', async () => { - const [test, localnetFixture] = createArc4TestFixture('tests/artifacts/arc4-primitive-ops/contract.algo.ts', { + const test = createArc4TestFixture('tests/artifacts/arc4-primitive-ops/contract.algo.ts', { Arc4PrimitiveOpsContract: { deployParams: { createParams: { extraProgramPages: undefined } } }, }) const ctx = new TestExecutionContext() - beforeAll(async () => { - await localnetFixture.newScope() - }) afterEach(() => { ctx.reset() }) diff --git a/tests/arc4/circular-reference.algo.spec.ts b/tests/arc4/circular-reference.algo.spec.ts index fa4b8474..88b4ed02 100644 --- a/tests/arc4/circular-reference.algo.spec.ts +++ b/tests/arc4/circular-reference.algo.spec.ts @@ -1,6 +1,6 @@ import { algo } from '@algorandfoundation/algokit-utils' import { methodSelector } from '@algorandfoundation/algorand-typescript/arc4' -import { afterEach, beforeAll, describe, expect } from 'vitest' +import { afterEach, describe, expect } from 'vitest' import { ApplicationSpy } from '../../src/application-spy' import { TestExecutionContext } from '../../src/test-execution-context' import { ContractTwo } from '../artifacts/circurlar-reference/circular-reference-2.algo' @@ -9,15 +9,11 @@ import { createArc4TestFixture } from '../test-fixture' describe('circular reference', () => { const ctx = new TestExecutionContext() - const [test, localnetFixture] = createArc4TestFixture('tests/artifacts/circurlar-reference', { + const test = createArc4TestFixture('tests/artifacts/circurlar-reference', { ContractOne: { funding: algo(1) }, ContractTwo: { funding: algo(1) }, }) - beforeAll(async () => { - await localnetFixture.newScope() - }) - afterEach(() => { ctx.reset() }) diff --git a/tests/arc4/emit.algo.spec.ts b/tests/arc4/emit.algo.spec.ts index 28574ce5..1c7057e8 100644 --- a/tests/arc4/emit.algo.spec.ts +++ b/tests/arc4/emit.algo.spec.ts @@ -1,7 +1,7 @@ import type { biguint, bytes, uint64 } from '@algorandfoundation/algorand-typescript' import { arc4, BigUint, Bytes, emit, Uint64 } from '@algorandfoundation/algorand-typescript' import { TestExecutionContext } from '@algorandfoundation/algorand-typescript-testing' -import { afterEach, beforeAll, describe, expect } from 'vitest' +import { afterEach, describe, expect } from 'vitest' import { MAX_UINT512, MAX_UINT64 } from '../../src/constants' import { getAvmResultLog } from '../avm-invoker' @@ -41,15 +41,11 @@ class SwappedArc4 extends arc4.Struct<{ }> {} describe('arc4.emit', async () => { - const [test, localnetFixture] = createArc4TestFixture('tests/artifacts/arc4-primitive-ops/contract.algo.ts', { + const test = createArc4TestFixture('tests/artifacts/arc4-primitive-ops/contract.algo.ts', { Arc4PrimitiveOpsContract: { deployParams: { createParams: { extraProgramPages: undefined } } }, }) const ctx = new TestExecutionContext() - beforeAll(async () => { - await localnetFixture.newScope() - }) - afterEach(() => { ctx.reset() }) diff --git a/tests/arc4/method-selector.algo.spec.ts b/tests/arc4/method-selector.algo.spec.ts index 9aa0314f..3aad53f7 100644 --- a/tests/arc4/method-selector.algo.spec.ts +++ b/tests/arc4/method-selector.algo.spec.ts @@ -4,7 +4,7 @@ import { arc4, Bytes, Global, Uint64 } from '@algorandfoundation/algorand-typesc import { TestExecutionContext } from '@algorandfoundation/algorand-typescript-testing' import { DynamicArray, methodSelector } from '@algorandfoundation/algorand-typescript/arc4' import { encodingUtil } from '@algorandfoundation/puya-ts' -import { afterEach, beforeAll, describe, expect } from 'vitest' +import { afterEach, describe, expect } from 'vitest' import { toBytes } from '../../src/impl/encoded-types/encoded-types' import { encodeAddress } from '../../src/impl/reference' import { AnotherStruct, MyStruct, SignaturesContract } from '../artifacts/arc4-abi-method/contract.algo' @@ -15,17 +15,13 @@ const _FUNDED_ACCOUNT_SPENDING = Uint64(1234) describe('methodSelector', async () => { const ctx = new TestExecutionContext() - const [test, localnetFixture] = createArc4TestFixture('tests/artifacts/arc4-abi-method/contract.algo.ts', { + const test = createArc4TestFixture('tests/artifacts/arc4-abi-method/contract.algo.ts', { SignaturesContract: { deployParams: { createParams: { extraProgramPages: undefined, method: 'create' } }, funding: new AlgoAmount({ microAlgos: Global.minBalance + _FUNDED_ACCOUNT_SPENDING }), }, }) - beforeAll(async () => { - await localnetFixture.newScope() - }) - afterEach(() => { ctx.reset() }) diff --git a/tests/arc4/resource-encoding.algo.spec.ts b/tests/arc4/resource-encoding.algo.spec.ts index b8868a18..5c571569 100644 --- a/tests/arc4/resource-encoding.algo.spec.ts +++ b/tests/arc4/resource-encoding.algo.spec.ts @@ -1,20 +1,16 @@ import { algo } from '@algorandfoundation/algokit-utils' -import { afterEach, beforeAll, describe, expect } from 'vitest' +import { afterEach, describe, expect } from 'vitest' import { TestExecutionContext } from '../../src/test-execution-context' import { createArc4TestFixture } from '../test-fixture' describe('resource encoding', () => { const ctx = new TestExecutionContext() - const [test, localnetFixture] = createArc4TestFixture('tests/artifacts/resource-encoding/contract.algo.ts', { + const test = createArc4TestFixture('tests/artifacts/resource-encoding/contract.algo.ts', { ByIndex: {}, ByValue: {}, C2C: { funding: algo(1) }, }) - beforeAll(async () => { - await localnetFixture.newScope() - }) - afterEach(() => { ctx.reset() }) diff --git a/tests/arc4/str.algo.spec.ts b/tests/arc4/str.algo.spec.ts index 26ef037a..a393b2b6 100644 --- a/tests/arc4/str.algo.spec.ts +++ b/tests/arc4/str.algo.spec.ts @@ -2,21 +2,18 @@ import { Bytes } from '@algorandfoundation/algorand-typescript' import { TestExecutionContext } from '@algorandfoundation/algorand-typescript-testing' import { convertBytes, Str } from '@algorandfoundation/algorand-typescript/arc4' import { encodingUtil } from '@algorandfoundation/puya-ts' -import { afterEach, beforeAll, describe, expect } from 'vitest' +import { afterEach, describe, expect } from 'vitest' import { ABI_RETURN_VALUE_LOG_PREFIX, MAX_LOG_SIZE } from '../../src/constants' import { asUint8Array } from '../../src/util' import { getAvmResult } from '../avm-invoker' import { createArc4TestFixture } from '../test-fixture' describe('arc4.Str', async () => { - const [test, localnetFixture] = createArc4TestFixture('tests/artifacts/arc4-primitive-ops/contract.algo.ts', { + const test = createArc4TestFixture('tests/artifacts/arc4-primitive-ops/contract.algo.ts', { Arc4PrimitiveOpsContract: { deployParams: { createParams: { extraProgramPages: undefined } } }, }) const ctx = new TestExecutionContext() - beforeAll(async () => { - await localnetFixture.newScope() - }) afterEach(() => { ctx.reset() }) diff --git a/tests/arc4/ufixednxm.algo.spec.ts b/tests/arc4/ufixednxm.algo.spec.ts index fc39d9b3..1b965b33 100644 --- a/tests/arc4/ufixednxm.algo.spec.ts +++ b/tests/arc4/ufixednxm.algo.spec.ts @@ -3,7 +3,7 @@ import { Bytes } from '@algorandfoundation/algorand-typescript' import { TestExecutionContext } from '@algorandfoundation/algorand-typescript-testing' import { convertBytes, UFixed } from '@algorandfoundation/algorand-typescript/arc4' import { encodingUtil } from '@algorandfoundation/puya-ts' -import { afterEach, beforeAll, describe, expect } from 'vitest' +import { afterEach, describe, expect } from 'vitest' import { ABI_RETURN_VALUE_LOG_PREFIX } from '../../src/constants' import { asBigUint, asUint8Array } from '../../src/util' import { getAvmResult } from '../avm-invoker' @@ -11,14 +11,11 @@ import { createArc4TestFixture } from '../test-fixture' const invalidBytesLengthError = (length: number) => `byte string must correspond to a ufixed${length}` describe('arc4.UFixed', async () => { - const [test, localnetFixture] = createArc4TestFixture('tests/artifacts/arc4-primitive-ops/contract.algo.ts', { + const test = createArc4TestFixture('tests/artifacts/arc4-primitive-ops/contract.algo.ts', { Arc4PrimitiveOpsContract: { deployParams: { createParams: { extraProgramPages: undefined } } }, }) const ctx = new TestExecutionContext() - beforeAll(async () => { - await localnetFixture.newScope() - }) afterEach(() => { ctx.reset() }) diff --git a/tests/arc4/uintn.algo.spec.ts b/tests/arc4/uintn.algo.spec.ts index 1a76cda4..59c8ff6a 100644 --- a/tests/arc4/uintn.algo.spec.ts +++ b/tests/arc4/uintn.algo.spec.ts @@ -4,7 +4,7 @@ import { TestExecutionContext } from '@algorandfoundation/algorand-typescript-te import type { BitSize } from '@algorandfoundation/algorand-typescript/arc4' import { convertBytes, Uint, Uint16, Uint256, Uint32, Uint8 } from '@algorandfoundation/algorand-typescript/arc4' import { encodingUtil } from '@algorandfoundation/puya-ts' -import { afterEach, beforeAll, describe, expect } from 'vitest' +import { afterEach, describe, expect } from 'vitest' import { ABI_RETURN_VALUE_LOG_PREFIX, MAX_UINT512, MAX_UINT64 } from '../../src/constants' import { asBigUintCls, asUint8Array } from '../../src/util' import { getAvmResult } from '../avm-invoker' @@ -12,14 +12,11 @@ import { createArc4TestFixture } from '../test-fixture' const invalidBytesLengthError = (length: number) => `byte string must correspond to a uint${length}` describe('arc4.Uint', async () => { - const [test, localnetFixture] = createArc4TestFixture('tests/artifacts/arc4-primitive-ops/contract.algo.ts', { + const test = createArc4TestFixture('tests/artifacts/arc4-primitive-ops/contract.algo.ts', { Arc4PrimitiveOpsContract: { deployParams: { createParams: { extraProgramPages: undefined } } }, }) const ctx = new TestExecutionContext() - beforeAll(async () => { - await localnetFixture.newScope() - }) afterEach(() => { ctx.reset() }) diff --git a/tests/avm12.algo.spec.ts b/tests/avm12.algo.spec.ts index 943fcfc5..7f03a503 100644 --- a/tests/avm12.algo.spec.ts +++ b/tests/avm12.algo.spec.ts @@ -1,18 +1,16 @@ import { algos } from '@algorandfoundation/algokit-utils' import { ApplicationSpy, TestExecutionContext } from '@algorandfoundation/algorand-typescript-testing' -import { afterEach, beforeAll, describe, expect } from 'vitest' +import { afterEach, describe, expect } from 'vitest' import { Avm12Contract, ContractV0, ContractV1 } from './artifacts/avm12/contract.algo' import { createArc4TestFixture } from './test-fixture' describe('avm12', () => { - const [test, localnetFixture] = createArc4TestFixture('tests/artifacts/avm12/contract.algo.ts', { + const test = createArc4TestFixture('tests/artifacts/avm12/contract.algo.ts', { Avm12Contract: { funding: algos(1) }, }) const ctx = new TestExecutionContext() - beforeAll(async () => { - await localnetFixture.newScope() - }) + afterEach(() => { ctx.reset() }) diff --git a/tests/crypto-op-codes.algo.spec.ts b/tests/crypto-op-codes.algo.spec.ts index cc9c45ed..489dd6e4 100644 --- a/tests/crypto-op-codes.algo.spec.ts +++ b/tests/crypto-op-codes.algo.spec.ts @@ -7,7 +7,7 @@ import js_sha3 from 'js-sha3' import js_sha512 from 'js-sha512' import nacl from 'tweetnacl' import type { Mock } from 'vitest' -import { afterEach, beforeAll, describe, expect, vi } from 'vitest' +import { afterEach, describe, expect, vi } from 'vitest' import { TestExecutionContext } from '../src' import { LOGIC_DATA_PREFIX, MAX_BYTES_SIZE, PROGRAM_TAG } from '../src/constants' import { BytesCls, Uint64Cls } from '../src/impl/primitives' @@ -35,13 +35,9 @@ vi.mock('../src/impl/crypto', async (importOriginal) => { }) describe('crypto op codes', async () => { - const [test, localnetFixture] = createArc4TestFixture('tests/artifacts/crypto-ops/contract.algo.ts', { CryptoOpsContract: {} }) + const test = createArc4TestFixture('tests/artifacts/crypto-ops/contract.algo.ts', { CryptoOpsContract: {} }) const ctx = new TestExecutionContext() - beforeAll(async () => { - await localnetFixture.newScope() - }) - afterEach(() => { ctx.reset() }) @@ -125,7 +121,7 @@ describe('crypto op codes', async () => { }) describe('ed25519verify', async () => { - test('should return true for valid signature', async ({ appFactoryCryptoOpsContract }) => { + test('should return true for valid signature', async ({ appFactoryCryptoOpsContract, localnet }) => { const app = await appFactoryCryptoOpsContract.deploy({}) const approval = app.result.compiledApproval! const appCallTxn = ctx.any.txn.applicationCall({ @@ -133,7 +129,7 @@ describe('crypto op codes', async () => { }) const message = Bytes('Test message for ed25519 verification') - const account = await localnetFixture.context.generateAccount({ + const account = await localnet.context.generateAccount({ initialFunds: AlgoAmount.MicroAlgos(INITIAL_BALANCE_MICRO_ALGOS + 100_000), }) diff --git a/tests/global-state-arc4-values.algo.spec.ts b/tests/global-state-arc4-values.algo.spec.ts index 5e9cccf2..69ca2b80 100644 --- a/tests/global-state-arc4-values.algo.spec.ts +++ b/tests/global-state-arc4-values.algo.spec.ts @@ -2,7 +2,7 @@ import { Bytes, Uint64 } from '@algorandfoundation/algorand-typescript' import { TestExecutionContext } from '@algorandfoundation/algorand-typescript-testing' import type { ARC4Encoded } from '@algorandfoundation/algorand-typescript/arc4' import { Address, Bool, Byte, DynamicBytes, Str, Uint } from '@algorandfoundation/algorand-typescript/arc4' -import { afterEach, beforeAll, describe, expect } from 'vitest' +import { afterEach, describe, expect } from 'vitest' import type { DeliberateAny, FunctionKeys } from '../src/typescript-helpers' import { asUint8Array } from '../src/util' import { GlobalStateContract } from './artifacts/state-ops/contract.algo' @@ -10,15 +10,11 @@ import { getAvmResult } from './avm-invoker' import { createArc4TestFixture } from './test-fixture' describe('ARC4 AppGlobal values', async () => { - const [test, localnetFixture] = createArc4TestFixture('tests/artifacts/state-ops/contract.algo.ts', { + const test = createArc4TestFixture('tests/artifacts/state-ops/contract.algo.ts', { GlobalStateContract: {}, }) const ctx = new TestExecutionContext() - beforeAll(async () => { - await localnetFixture.newScope() - }) - afterEach(() => { ctx.reset() }) diff --git a/tests/itxn-compose.algo.spec.ts b/tests/itxn-compose.algo.spec.ts index 6eec7eef..7a2a243b 100644 --- a/tests/itxn-compose.algo.spec.ts +++ b/tests/itxn-compose.algo.spec.ts @@ -2,20 +2,17 @@ import { algos } from '@algorandfoundation/algokit-utils' import { Bytes, TransactionType } from '@algorandfoundation/algorand-typescript' import { TestExecutionContext } from '@algorandfoundation/algorand-typescript-testing' import { Address } from '@algorandfoundation/algorand-typescript/arc4' -import { afterEach, beforeAll, describe, expect } from 'vitest' +import { afterEach, describe, expect } from 'vitest' import { ItxnComposeAlgo, VerifierContract } from './artifacts/itxn-compose/contract.algo' import { createArc4TestFixture } from './test-fixture' describe('itxn compose', async () => { - const [test, localnetFixture] = createArc4TestFixture('tests/artifacts/itxn-compose/contract.algo.ts', { + const test = createArc4TestFixture('tests/artifacts/itxn-compose/contract.algo.ts', { ItxnComposeAlgo: { funding: algos(5) }, VerifierContract: {}, }) const ctx = new TestExecutionContext() - beforeAll(async () => { - await localnetFixture.newScope() - }) afterEach(() => { ctx.reset() }) diff --git a/tests/local-state-arc4-values.algo.spec.ts b/tests/local-state-arc4-values.algo.spec.ts index 3aa893ac..4e5d9f84 100644 --- a/tests/local-state-arc4-values.algo.spec.ts +++ b/tests/local-state-arc4-values.algo.spec.ts @@ -4,7 +4,7 @@ import { TestExecutionContext } from '@algorandfoundation/algorand-typescript-te import type { ARC4Encoded } from '@algorandfoundation/algorand-typescript/arc4' import { Address, Bool, Byte, DynamicBytes, Str, Uint } from '@algorandfoundation/algorand-typescript/arc4' -import { afterEach, beforeAll, describe, expect } from 'vitest' +import { afterEach, describe, expect } from 'vitest' import { OnApplicationComplete } from '../src/constants' import type { DeliberateAny } from '../src/typescript-helpers' @@ -13,15 +13,11 @@ import { getAvmResult } from './avm-invoker' import { createArc4TestFixture } from './test-fixture' describe('ARC4 AppLocal values', async () => { - const [test, localnetFixture] = createArc4TestFixture('tests/artifacts/state-ops/contract.algo.ts', { + const test = createArc4TestFixture('tests/artifacts/state-ops/contract.algo.ts', { LocalStateContract: {}, }) const ctx = new TestExecutionContext() - beforeAll(async () => { - await localnetFixture.newScope() - }) - afterEach(() => { ctx.reset() }) diff --git a/tests/log.algo.spec.ts b/tests/log.algo.spec.ts index a5507711..8d0186af 100644 --- a/tests/log.algo.spec.ts +++ b/tests/log.algo.spec.ts @@ -12,7 +12,7 @@ import { Uint32, Uint8, } from '@algorandfoundation/algorand-typescript/arc4' -import { afterEach, beforeAll, describe, expect } from 'vitest' +import { afterEach, describe, expect } from 'vitest' import { MAX_UINT512, MAX_UINT64 } from '../src/constants' import type { ApplicationCallTransaction } from '../src/impl/transactions' import { asBigUint, asBigUintCls, asUint8Array } from '../src/util' @@ -21,14 +21,11 @@ import { getAvmResultLog } from './avm-invoker' import { createArc4TestFixture } from './test-fixture' describe('log', async () => { - const [test, localnetFixture] = createArc4TestFixture('tests/artifacts/primitive-ops/contract.algo.ts', { + const test = createArc4TestFixture('tests/artifacts/primitive-ops/contract.algo.ts', { PrimitiveOpsContract: { deployParams: { createParams: { extraProgramPages: undefined } } }, }) const ctx = new TestExecutionContext() - beforeAll(async () => { - await localnetFixture.newScope() - }) afterEach(() => { ctx.reset() }) diff --git a/tests/multi-inheritance.algo.spec.ts b/tests/multi-inheritance.algo.spec.ts index 186d4852..00456919 100644 --- a/tests/multi-inheritance.algo.spec.ts +++ b/tests/multi-inheritance.algo.spec.ts @@ -1,17 +1,14 @@ import { TestExecutionContext } from '@algorandfoundation/algorand-typescript-testing' import { afterEach } from 'node:test' -import { beforeAll, describe, expect } from 'vitest' +import { describe, expect } from 'vitest' import { MultiBases } from './artifacts/multi-inheritance/contract.algo' import { getAvmResult } from './avm-invoker' import { createArc4TestFixture } from './test-fixture' describe('multi-inheritance', async () => { - const [test, localnetFixture] = createArc4TestFixture('tests/artifacts/multi-inheritance/contract.algo.ts', { MultiBases: {} }) + const test = createArc4TestFixture('tests/artifacts/multi-inheritance/contract.algo.ts', { MultiBases: {} }) const ctx = new TestExecutionContext() - beforeAll(async () => { - await localnetFixture.newScope() - }) afterEach(() => ctx.reset()) test('should be able to call methods from super classes', async ({ appClientMultiBases: appClient }) => { diff --git a/tests/primitives/biguint.algo.spec.ts b/tests/primitives/biguint.algo.spec.ts index 7ec55626..2702ba18 100644 --- a/tests/primitives/biguint.algo.spec.ts +++ b/tests/primitives/biguint.algo.spec.ts @@ -3,7 +3,7 @@ import type { biguint } from '@algorandfoundation/algorand-typescript' import { BigUint, Bytes, Uint64 } from '@algorandfoundation/algorand-typescript' import { encodingUtil } from '@algorandfoundation/puya-ts' -import { beforeAll, describe, expect } from 'vitest' +import { describe, expect } from 'vitest' import { BIGUINT_OVERFLOW_UNDERFLOW_MESSAGE, MAX_UINT512, MAX_UINT64 } from '../../src/constants' import { BigUintCls } from '../../src/impl/primitives' @@ -12,12 +12,9 @@ import { getAvmResult, getAvmResultRaw } from '../avm-invoker' import { createArc4TestFixture } from '../test-fixture' describe('BigUint', async () => { - const [test, localnetFixture] = createArc4TestFixture('tests/artifacts/primitive-ops/contract.algo.ts', { + const test = createArc4TestFixture('tests/artifacts/primitive-ops/contract.algo.ts', { PrimitiveOpsContract: { deployParams: { createParams: { extraProgramPages: undefined } } }, }) - beforeAll(async () => { - await localnetFixture.newScope() - }) describe.each(['eq', 'ne', 'lt', 'le', 'gt', 'ge'])('logical operators', async (op) => { const operator = (function () { diff --git a/tests/primitives/bytes.algo.spec.ts b/tests/primitives/bytes.algo.spec.ts index d8f8e448..a87ebbae 100644 --- a/tests/primitives/bytes.algo.spec.ts +++ b/tests/primitives/bytes.algo.spec.ts @@ -1,7 +1,7 @@ import type { bytes } from '@algorandfoundation/algorand-typescript' import { Bytes, FixedArray, op } from '@algorandfoundation/algorand-typescript' import { encodingUtil } from '@algorandfoundation/puya-ts' -import { beforeAll, describe, expect, it } from 'vitest' +import { describe, expect, it } from 'vitest' import { MAX_BYTES_SIZE } from '../../src/constants' import type { Byte, StaticArray } from '@algorandfoundation/algorand-typescript/arc4' @@ -14,14 +14,10 @@ import { createArc4TestFixture } from '../test-fixture' import { getSha256Hash, padUint8Array } from '../util' describe('Bytes', async () => { - const [test, localnetFixture] = createArc4TestFixture('tests/artifacts/primitive-ops/contract.algo.ts', { + const test = createArc4TestFixture('tests/artifacts/primitive-ops/contract.algo.ts', { PrimitiveOpsContract: { deployParams: { createParams: { extraProgramPages: undefined } } }, }) - beforeAll(async () => { - await localnetFixture.newScope() - }) - describe.each([ ['', '', 0, 0], ['1', '', 0, 0], diff --git a/tests/primitives/uint64.algo.spec.ts b/tests/primitives/uint64.algo.spec.ts index fb205e5d..36d2412c 100644 --- a/tests/primitives/uint64.algo.spec.ts +++ b/tests/primitives/uint64.algo.spec.ts @@ -1,6 +1,6 @@ import type { uint64 } from '@algorandfoundation/algorand-typescript' import { Uint64 } from '@algorandfoundation/algorand-typescript' -import { beforeAll, describe, expect } from 'vitest' +import { describe, expect } from 'vitest' import { MAX_UINT64, UINT64_OVERFLOW_UNDERFLOW_MESSAGE } from '../../src/constants' import { Uint64Cls } from '../../src/impl/primitives' import { asUint64 } from '../../src/util' @@ -8,13 +8,10 @@ import { getAvmResult } from '../avm-invoker' import { createArc4TestFixture } from '../test-fixture' describe('Unit64', async () => { - const [test, localnetFixture] = createArc4TestFixture('tests/artifacts/primitive-ops/contract.algo.ts', { + const test = createArc4TestFixture('tests/artifacts/primitive-ops/contract.algo.ts', { PrimitiveOpsContract: { deployParams: { createParams: { extraProgramPages: undefined } } }, }) - beforeAll(async () => { - await localnetFixture.newScope() - }) describe.each(['eq', 'ne', 'lt', 'le', 'gt', 'ge'])('logical operators', async (op) => { const operator = (function () { switch (op) { diff --git a/tests/pure-op-codes.algo.spec.ts b/tests/pure-op-codes.algo.spec.ts index 6d5482bd..a361b111 100644 --- a/tests/pure-op-codes.algo.spec.ts +++ b/tests/pure-op-codes.algo.spec.ts @@ -1,7 +1,7 @@ import type { bytes, uint64 } from '@algorandfoundation/algorand-typescript' import { Base64, BigUint, Bytes, err, Uint64 } from '@algorandfoundation/algorand-typescript' import { encodingUtil } from '@algorandfoundation/puya-ts' -import { afterEach, beforeAll, describe, expect } from 'vitest' +import { afterEach, describe, expect } from 'vitest' import { TestExecutionContext } from '../src' import { BIGUINT_OVERFLOW_UNDERFLOW_MESSAGE, @@ -21,15 +21,11 @@ const extractOutOfBoundError = /extraction (start|end) \d+ is beyond length/ const sqrtMaxUint64 = 4294967295n describe('Pure op codes', async () => { - const [test, localnetFixture] = createArc4TestFixture('tests/artifacts/miscellaneous-ops/contract.algo.ts', { + const test = createArc4TestFixture('tests/artifacts/miscellaneous-ops/contract.algo.ts', { MiscellaneousOpsContract: {}, }) const ctx = new TestExecutionContext() - beforeAll(async () => { - await localnetFixture.newScope() - }) - afterEach(() => { ctx.reset() }) diff --git a/tests/state-op-codes.algo.spec.ts b/tests/state-op-codes.algo.spec.ts index 158b4dc8..c31f33a9 100644 --- a/tests/state-op-codes.algo.spec.ts +++ b/tests/state-op-codes.algo.spec.ts @@ -3,7 +3,7 @@ import type { AppClient } from '@algorandfoundation/algokit-utils/types/app-clie import type { bytes, uint64 } from '@algorandfoundation/algorand-typescript' import { Account, arc4, Bytes, Global, OnCompleteAction, op, TransactionType, Uint64 } from '@algorandfoundation/algorand-typescript' import { DynamicBytes } from '@algorandfoundation/algorand-typescript/arc4' -import { afterEach, beforeAll, describe, expect } from 'vitest' +import { afterEach, describe, expect } from 'vitest' import { TestExecutionContext } from '../src' import { ABI_RETURN_VALUE_LOG_PREFIX, MIN_TXN_FEE, OnApplicationComplete, ZERO_ADDRESS } from '../src/constants' import { lazyContext } from '../src/context-helpers/internal-context' @@ -33,7 +33,7 @@ import { generateTestAsset, getAvmResult, INITIAL_BALANCE_MICRO_ALGOS } from './ import { createArc4TestFixture } from './test-fixture' describe('State op codes', async () => { - const [test, localnetFixture] = createArc4TestFixture('tests/artifacts/state-ops/contract.algo.ts', { + const test = createArc4TestFixture('tests/artifacts/state-ops/contract.algo.ts', { ItxnDemoContract: {}, ITxnOpsContract: {}, StateAcctParamsGetContract: {}, @@ -47,9 +47,6 @@ describe('State op codes', async () => { }) const ctx = new TestExecutionContext() - beforeAll(async () => { - await localnetFixture.newScope() - }) afterEach(() => { ctx.reset() }) @@ -68,8 +65,8 @@ describe('State op codes', async () => { ['verify_acct_total_assets', 0], ['verify_acct_total_boxes', 0], ['verify_acct_total_box_bytes', 0], - ])('%s should return %s', async ([methodName, expectedValue], { appClientStateAcctParamsGetContract: appClient }) => { - const dummyAccountAddress = await localnetFixture.context.generateAccount({ + ])('%s should return %s', async ([methodName, expectedValue], { appClientStateAcctParamsGetContract: appClient, localnet }) => { + const dummyAccountAddress = await localnet.context.generateAccount({ initialFunds: AlgoAmount.MicroAlgos(INITIAL_BALANCE_MICRO_ALGOS + 100_000), }) const dummyAccount = Bytes.fromBase32(dummyAccountAddress.addr.toString()) @@ -102,8 +99,11 @@ describe('State op codes', async () => { expect(mockResult).toEqual(expectedValue) }) - test('should return true when account is eligible for incentive', async ({ appClientStateAcctParamsGetContract: appClient }) => { - const dummyAccountAddress = await localnetFixture.context.generateAccount({ + test('should return true when account is eligible for incentive', async ({ + appClientStateAcctParamsGetContract: appClient, + localnet, + }) => { + const dummyAccountAddress = await localnet.context.generateAccount({ initialFunds: AlgoAmount.MicroAlgos(INITIAL_BALANCE_MICRO_ALGOS), }) const dummyAccount = Bytes.fromBase32(dummyAccountAddress.addr.toString()) @@ -135,8 +135,8 @@ describe('State op codes', async () => { expect(avmResult).toEqual(true) }) - test('should return last round as last proposed and last hearbeat by default', async () => { - const dummyAccountAddress = await localnetFixture.context.generateAccount({ + test('should return last round as last proposed and last hearbeat by default', async ({ localnet }) => { + const dummyAccountAddress = await localnet.context.generateAccount({ initialFunds: AlgoAmount.MicroAlgos(INITIAL_BALANCE_MICRO_ALGOS), }) const dummyAccount = Bytes.fromBase32(dummyAccountAddress.addr.toString()) @@ -152,8 +152,8 @@ describe('State op codes', async () => { expect(lastHeartbeat).toEqual([Global.round, true]) }) - test('should return configured round as last proposed and last hearbeat', async () => { - const dummyAccountAddress = await localnetFixture.context.generateAccount({ + test('should return configured round as last proposed and last hearbeat', async ({ localnet }) => { + const dummyAccountAddress = await localnet.context.generateAccount({ initialFunds: AlgoAmount.MicroAlgos(INITIAL_BALANCE_MICRO_ALGOS), }) const dummyAccount = Bytes.fromBase32(dummyAccountAddress.addr.toString()) diff --git a/tests/switch-statements.algo.spec.ts b/tests/switch-statements.algo.spec.ts index 64aac7cf..5987d924 100644 --- a/tests/switch-statements.algo.spec.ts +++ b/tests/switch-statements.algo.spec.ts @@ -1,11 +1,8 @@ -import { beforeAll, describe } from 'vitest' +import { describe } from 'vitest' import { createArc4TestFixture } from './test-fixture' describe('switch statements', () => { - const [test, localnetFixture] = createArc4TestFixture('tests/artifacts/switch-statements/contract.algo.ts', { DemoContract: {} }) - beforeAll(async () => { - await localnetFixture.newScope() - }) + const test = createArc4TestFixture('tests/artifacts/switch-statements/contract.algo.ts', { DemoContract: {} }) test('runs', async ({ appClientDemoContract }) => { await appClientDemoContract.send.call({ method: 'run', args: [] }) diff --git a/tests/test-fixture.ts b/tests/test-fixture.ts index e54169fe..6c46d216 100644 --- a/tests/test-fixture.ts +++ b/tests/test-fixture.ts @@ -13,8 +13,8 @@ import { compile, CompileOptions, LoggingContext, processInputPaths } from '@alg import type { Use } from '@vitest/runner/types' import { OnApplicationComplete } from 'algosdk' import fs from 'fs' -import type { ExpectStatic } from 'vitest' -import { test } from 'vitest' +import type { beforeEach, ExpectStatic } from 'vitest' +import { beforeAll, test } from 'vitest' import { invariant } from '../src/errors' import type { DeliberateAny } from '../src/typescript-helpers' import { generateTempDir } from './util' @@ -26,7 +26,7 @@ const algorandTestFixture = (localnetFixture: AlgorandFixture) => testAccount: AlgorandFixture['context']['testAccount'] assetFactory: (assetCreateParams: AssetCreateParams) => Promise }>({ - localnet: async ({ expect: _expect }, use) => { + localnet: async ({ expect: _ }, use) => { await use(localnetFixture) }, testAccount: async ({ localnet }, use) => { @@ -77,7 +77,11 @@ type ProgramInvoker = { type BaseFixtureContextFor = { [key in T as `${key}Invoker`]: ProgramInvoker } -export function createBaseTestFixture(path: string, contracts: TContracts[]) { +export function createBaseTestFixture( + path: string, + contracts: TContracts[], + newScopeAt: typeof beforeAll | typeof beforeEach = beforeAll, +) { const lazyCompile = createLazyCompiler(path, { outputArc56: false, outputBytecode: true }) const localnet = algorandFixture({ testAccountFunding: microAlgos(100_000_000_000), @@ -128,7 +132,8 @@ export function createBaseTestFixture(path: stri } } const extendedTest = algorandTestFixture(localnet).extend>(ctx) - return [extendedTest, localnet] as readonly [typeof extendedTest, AlgorandFixture] + newScopeAt(localnet.newScope) + return extendedTest } type Arc4FixtureContextFor = { @@ -147,6 +152,7 @@ type ContractConfig = { export function createArc4TestFixture( path: string, contracts: Record | TContracts[], + newScopeAt: typeof beforeAll | typeof beforeEach = beforeAll, ) { const lazyCompile = createLazyCompiler(path, { outputArc56: true, outputBytecode: false }) const localnet = algorandFixture({ @@ -178,7 +184,7 @@ export function createArc4TestFixture( } } - const ctx: DeliberateAny = { localnet } + const ctx: DeliberateAny = {} for (const [contractName, config] of getContracts()) { ctx[`appSpec${contractName}`] = async ({ expect }: { expect: ExpectStatic }, use: Use) => { await use(await getAppSpec(expect, contractName)) @@ -212,7 +218,8 @@ export function createArc4TestFixture( } const extendedTest = algorandTestFixture(localnet).extend>(ctx) - return [extendedTest, localnet] as readonly [typeof extendedTest, AlgorandFixture] + newScopeAt(localnet.newScope) + return extendedTest } type CompilationArtifacts = { From 7ccaf1a85bc45e908638bdea4e48208d76416cfc Mon Sep 17 00:00:00 2001 From: Bobby Lat Date: Thu, 23 Oct 2025 14:11:32 +0800 Subject: [PATCH 57/68] refactor: update test fixture factory methods to take options object --- tests/arc4/bool.algo.spec.ts | 7 +++- tests/arc4/byte.algo.spec.ts | 7 +++- tests/arc4/circular-reference.algo.spec.ts | 9 +++-- tests/arc4/emit.algo.spec.ts | 7 +++- tests/arc4/method-selector.algo.spec.ts | 11 ++++-- tests/arc4/resource-encoding.algo.spec.ts | 11 ++++-- tests/arc4/str.algo.spec.ts | 7 +++- tests/arc4/ufixednxm.algo.spec.ts | 7 +++- tests/arc4/uintn.algo.spec.ts | 7 +++- tests/avm12.algo.spec.ts | 7 +++- tests/crypto-op-codes.algo.spec.ts | 2 +- tests/global-state-arc4-values.algo.spec.ts | 7 +++- tests/itxn-compose.algo.spec.ts | 9 +++-- tests/local-state-arc4-values.algo.spec.ts | 7 +++- tests/log.algo.spec.ts | 7 +++- tests/multi-inheritance.algo.spec.ts | 2 +- tests/primitives/biguint.algo.spec.ts | 7 +++- tests/primitives/bytes.algo.spec.ts | 7 +++- tests/primitives/uint64.algo.spec.ts | 7 +++- tests/pure-op-codes.algo.spec.ts | 7 +++- tests/state-op-codes.algo.spec.ts | 25 ++++++------ tests/switch-statements.algo.spec.ts | 2 +- tests/test-fixture.ts | 42 ++++++++++++++++----- 23 files changed, 145 insertions(+), 66 deletions(-) diff --git a/tests/arc4/bool.algo.spec.ts b/tests/arc4/bool.algo.spec.ts index 1f8af80e..78743453 100644 --- a/tests/arc4/bool.algo.spec.ts +++ b/tests/arc4/bool.algo.spec.ts @@ -8,8 +8,11 @@ import { getAvmResult } from '../avm-invoker' import { createArc4TestFixture } from '../test-fixture' describe('arc4.Bool', async () => { - const test = createArc4TestFixture('tests/artifacts/arc4-primitive-ops/contract.algo.ts', { - Arc4PrimitiveOpsContract: { deployParams: { createParams: { extraProgramPages: undefined } } }, + const test = createArc4TestFixture({ + path: 'tests/artifacts/arc4-primitive-ops/contract.algo.ts', + contracts: { + Arc4PrimitiveOpsContract: { deployParams: { createParams: { extraProgramPages: undefined } } }, + }, }) const ctx = new TestExecutionContext() diff --git a/tests/arc4/byte.algo.spec.ts b/tests/arc4/byte.algo.spec.ts index 69cdd223..c284fc85 100644 --- a/tests/arc4/byte.algo.spec.ts +++ b/tests/arc4/byte.algo.spec.ts @@ -11,8 +11,11 @@ import { createArc4TestFixture } from '../test-fixture' const invalidBytesLengthError = 'byte string must be 1 byte long' describe('arc4.Byte', async () => { - const test = createArc4TestFixture('tests/artifacts/arc4-primitive-ops/contract.algo.ts', { - Arc4PrimitiveOpsContract: { deployParams: { createParams: { extraProgramPages: undefined } } }, + const test = createArc4TestFixture({ + path: 'tests/artifacts/arc4-primitive-ops/contract.algo.ts', + contracts: { + Arc4PrimitiveOpsContract: { deployParams: { createParams: { extraProgramPages: undefined } } }, + }, }) const ctx = new TestExecutionContext() diff --git a/tests/arc4/circular-reference.algo.spec.ts b/tests/arc4/circular-reference.algo.spec.ts index 88b4ed02..62029b91 100644 --- a/tests/arc4/circular-reference.algo.spec.ts +++ b/tests/arc4/circular-reference.algo.spec.ts @@ -9,9 +9,12 @@ import { createArc4TestFixture } from '../test-fixture' describe('circular reference', () => { const ctx = new TestExecutionContext() - const test = createArc4TestFixture('tests/artifacts/circurlar-reference', { - ContractOne: { funding: algo(1) }, - ContractTwo: { funding: algo(1) }, + const test = createArc4TestFixture({ + path: 'tests/artifacts/circurlar-reference', + contracts: { + ContractOne: { funding: algo(1) }, + ContractTwo: { funding: algo(1) }, + }, }) afterEach(() => { diff --git a/tests/arc4/emit.algo.spec.ts b/tests/arc4/emit.algo.spec.ts index 1c7057e8..5a9fa69d 100644 --- a/tests/arc4/emit.algo.spec.ts +++ b/tests/arc4/emit.algo.spec.ts @@ -41,8 +41,11 @@ class SwappedArc4 extends arc4.Struct<{ }> {} describe('arc4.emit', async () => { - const test = createArc4TestFixture('tests/artifacts/arc4-primitive-ops/contract.algo.ts', { - Arc4PrimitiveOpsContract: { deployParams: { createParams: { extraProgramPages: undefined } } }, + const test = createArc4TestFixture({ + path: 'tests/artifacts/arc4-primitive-ops/contract.algo.ts', + contracts: { + Arc4PrimitiveOpsContract: { deployParams: { createParams: { extraProgramPages: undefined } } }, + }, }) const ctx = new TestExecutionContext() diff --git a/tests/arc4/method-selector.algo.spec.ts b/tests/arc4/method-selector.algo.spec.ts index 3aad53f7..94c9c15b 100644 --- a/tests/arc4/method-selector.algo.spec.ts +++ b/tests/arc4/method-selector.algo.spec.ts @@ -15,10 +15,13 @@ const _FUNDED_ACCOUNT_SPENDING = Uint64(1234) describe('methodSelector', async () => { const ctx = new TestExecutionContext() - const test = createArc4TestFixture('tests/artifacts/arc4-abi-method/contract.algo.ts', { - SignaturesContract: { - deployParams: { createParams: { extraProgramPages: undefined, method: 'create' } }, - funding: new AlgoAmount({ microAlgos: Global.minBalance + _FUNDED_ACCOUNT_SPENDING }), + const test = createArc4TestFixture({ + path: 'tests/artifacts/arc4-abi-method/contract.algo.ts', + contracts: { + SignaturesContract: { + deployParams: { createParams: { extraProgramPages: undefined, method: 'create' } }, + funding: new AlgoAmount({ microAlgos: Global.minBalance + _FUNDED_ACCOUNT_SPENDING }), + }, }, }) diff --git a/tests/arc4/resource-encoding.algo.spec.ts b/tests/arc4/resource-encoding.algo.spec.ts index 5c571569..6f03753c 100644 --- a/tests/arc4/resource-encoding.algo.spec.ts +++ b/tests/arc4/resource-encoding.algo.spec.ts @@ -5,10 +5,13 @@ import { createArc4TestFixture } from '../test-fixture' describe('resource encoding', () => { const ctx = new TestExecutionContext() - const test = createArc4TestFixture('tests/artifacts/resource-encoding/contract.algo.ts', { - ByIndex: {}, - ByValue: {}, - C2C: { funding: algo(1) }, + const test = createArc4TestFixture({ + path: 'tests/artifacts/resource-encoding/contract.algo.ts', + contracts: { + ByIndex: {}, + ByValue: {}, + C2C: { funding: algo(1) }, + }, }) afterEach(() => { diff --git a/tests/arc4/str.algo.spec.ts b/tests/arc4/str.algo.spec.ts index a393b2b6..38352ce9 100644 --- a/tests/arc4/str.algo.spec.ts +++ b/tests/arc4/str.algo.spec.ts @@ -9,8 +9,11 @@ import { getAvmResult } from '../avm-invoker' import { createArc4TestFixture } from '../test-fixture' describe('arc4.Str', async () => { - const test = createArc4TestFixture('tests/artifacts/arc4-primitive-ops/contract.algo.ts', { - Arc4PrimitiveOpsContract: { deployParams: { createParams: { extraProgramPages: undefined } } }, + const test = createArc4TestFixture({ + path: 'tests/artifacts/arc4-primitive-ops/contract.algo.ts', + contracts: { + Arc4PrimitiveOpsContract: { deployParams: { createParams: { extraProgramPages: undefined } } }, + }, }) const ctx = new TestExecutionContext() diff --git a/tests/arc4/ufixednxm.algo.spec.ts b/tests/arc4/ufixednxm.algo.spec.ts index 1b965b33..16ec4d16 100644 --- a/tests/arc4/ufixednxm.algo.spec.ts +++ b/tests/arc4/ufixednxm.algo.spec.ts @@ -11,8 +11,11 @@ import { createArc4TestFixture } from '../test-fixture' const invalidBytesLengthError = (length: number) => `byte string must correspond to a ufixed${length}` describe('arc4.UFixed', async () => { - const test = createArc4TestFixture('tests/artifacts/arc4-primitive-ops/contract.algo.ts', { - Arc4PrimitiveOpsContract: { deployParams: { createParams: { extraProgramPages: undefined } } }, + const test = createArc4TestFixture({ + path: 'tests/artifacts/arc4-primitive-ops/contract.algo.ts', + contracts: { + Arc4PrimitiveOpsContract: { deployParams: { createParams: { extraProgramPages: undefined } } }, + }, }) const ctx = new TestExecutionContext() diff --git a/tests/arc4/uintn.algo.spec.ts b/tests/arc4/uintn.algo.spec.ts index 59c8ff6a..bb346da8 100644 --- a/tests/arc4/uintn.algo.spec.ts +++ b/tests/arc4/uintn.algo.spec.ts @@ -12,8 +12,11 @@ import { createArc4TestFixture } from '../test-fixture' const invalidBytesLengthError = (length: number) => `byte string must correspond to a uint${length}` describe('arc4.Uint', async () => { - const test = createArc4TestFixture('tests/artifacts/arc4-primitive-ops/contract.algo.ts', { - Arc4PrimitiveOpsContract: { deployParams: { createParams: { extraProgramPages: undefined } } }, + const test = createArc4TestFixture({ + path: 'tests/artifacts/arc4-primitive-ops/contract.algo.ts', + contracts: { + Arc4PrimitiveOpsContract: { deployParams: { createParams: { extraProgramPages: undefined } } }, + }, }) const ctx = new TestExecutionContext() diff --git a/tests/avm12.algo.spec.ts b/tests/avm12.algo.spec.ts index 7f03a503..3bcb7581 100644 --- a/tests/avm12.algo.spec.ts +++ b/tests/avm12.algo.spec.ts @@ -5,8 +5,11 @@ import { Avm12Contract, ContractV0, ContractV1 } from './artifacts/avm12/contrac import { createArc4TestFixture } from './test-fixture' describe('avm12', () => { - const test = createArc4TestFixture('tests/artifacts/avm12/contract.algo.ts', { - Avm12Contract: { funding: algos(1) }, + const test = createArc4TestFixture({ + path: 'tests/artifacts/avm12/contract.algo.ts', + contracts: { + Avm12Contract: { funding: algos(1) }, + }, }) const ctx = new TestExecutionContext() diff --git a/tests/crypto-op-codes.algo.spec.ts b/tests/crypto-op-codes.algo.spec.ts index 489dd6e4..ee1c1681 100644 --- a/tests/crypto-op-codes.algo.spec.ts +++ b/tests/crypto-op-codes.algo.spec.ts @@ -35,7 +35,7 @@ vi.mock('../src/impl/crypto', async (importOriginal) => { }) describe('crypto op codes', async () => { - const test = createArc4TestFixture('tests/artifacts/crypto-ops/contract.algo.ts', { CryptoOpsContract: {} }) + const test = createArc4TestFixture({ path: 'tests/artifacts/crypto-ops/contract.algo.ts', contracts: { CryptoOpsContract: {} } }) const ctx = new TestExecutionContext() afterEach(() => { diff --git a/tests/global-state-arc4-values.algo.spec.ts b/tests/global-state-arc4-values.algo.spec.ts index 69ca2b80..433662e9 100644 --- a/tests/global-state-arc4-values.algo.spec.ts +++ b/tests/global-state-arc4-values.algo.spec.ts @@ -10,8 +10,11 @@ import { getAvmResult } from './avm-invoker' import { createArc4TestFixture } from './test-fixture' describe('ARC4 AppGlobal values', async () => { - const test = createArc4TestFixture('tests/artifacts/state-ops/contract.algo.ts', { - GlobalStateContract: {}, + const test = createArc4TestFixture({ + path: 'tests/artifacts/state-ops/contract.algo.ts', + contracts: { + GlobalStateContract: {}, + }, }) const ctx = new TestExecutionContext() diff --git a/tests/itxn-compose.algo.spec.ts b/tests/itxn-compose.algo.spec.ts index 7a2a243b..1c2fedcc 100644 --- a/tests/itxn-compose.algo.spec.ts +++ b/tests/itxn-compose.algo.spec.ts @@ -7,9 +7,12 @@ import { ItxnComposeAlgo, VerifierContract } from './artifacts/itxn-compose/cont import { createArc4TestFixture } from './test-fixture' describe('itxn compose', async () => { - const test = createArc4TestFixture('tests/artifacts/itxn-compose/contract.algo.ts', { - ItxnComposeAlgo: { funding: algos(5) }, - VerifierContract: {}, + const test = createArc4TestFixture({ + path: 'tests/artifacts/itxn-compose/contract.algo.ts', + contracts: { + ItxnComposeAlgo: { funding: algos(5) }, + VerifierContract: {}, + }, }) const ctx = new TestExecutionContext() diff --git a/tests/local-state-arc4-values.algo.spec.ts b/tests/local-state-arc4-values.algo.spec.ts index 4e5d9f84..a6c2e17a 100644 --- a/tests/local-state-arc4-values.algo.spec.ts +++ b/tests/local-state-arc4-values.algo.spec.ts @@ -13,8 +13,11 @@ import { getAvmResult } from './avm-invoker' import { createArc4TestFixture } from './test-fixture' describe('ARC4 AppLocal values', async () => { - const test = createArc4TestFixture('tests/artifacts/state-ops/contract.algo.ts', { - LocalStateContract: {}, + const test = createArc4TestFixture({ + path: 'tests/artifacts/state-ops/contract.algo.ts', + contracts: { + LocalStateContract: {}, + }, }) const ctx = new TestExecutionContext() diff --git a/tests/log.algo.spec.ts b/tests/log.algo.spec.ts index 8d0186af..24bc3a4c 100644 --- a/tests/log.algo.spec.ts +++ b/tests/log.algo.spec.ts @@ -21,8 +21,11 @@ import { getAvmResultLog } from './avm-invoker' import { createArc4TestFixture } from './test-fixture' describe('log', async () => { - const test = createArc4TestFixture('tests/artifacts/primitive-ops/contract.algo.ts', { - PrimitiveOpsContract: { deployParams: { createParams: { extraProgramPages: undefined } } }, + const test = createArc4TestFixture({ + path: 'tests/artifacts/primitive-ops/contract.algo.ts', + contracts: { + PrimitiveOpsContract: { deployParams: { createParams: { extraProgramPages: undefined } } }, + }, }) const ctx = new TestExecutionContext() diff --git a/tests/multi-inheritance.algo.spec.ts b/tests/multi-inheritance.algo.spec.ts index 00456919..4773d052 100644 --- a/tests/multi-inheritance.algo.spec.ts +++ b/tests/multi-inheritance.algo.spec.ts @@ -6,7 +6,7 @@ import { getAvmResult } from './avm-invoker' import { createArc4TestFixture } from './test-fixture' describe('multi-inheritance', async () => { - const test = createArc4TestFixture('tests/artifacts/multi-inheritance/contract.algo.ts', { MultiBases: {} }) + const test = createArc4TestFixture({ path: 'tests/artifacts/multi-inheritance/contract.algo.ts', contracts: { MultiBases: {} } }) const ctx = new TestExecutionContext() afterEach(() => ctx.reset()) diff --git a/tests/primitives/biguint.algo.spec.ts b/tests/primitives/biguint.algo.spec.ts index 2702ba18..b0025020 100644 --- a/tests/primitives/biguint.algo.spec.ts +++ b/tests/primitives/biguint.algo.spec.ts @@ -12,8 +12,11 @@ import { getAvmResult, getAvmResultRaw } from '../avm-invoker' import { createArc4TestFixture } from '../test-fixture' describe('BigUint', async () => { - const test = createArc4TestFixture('tests/artifacts/primitive-ops/contract.algo.ts', { - PrimitiveOpsContract: { deployParams: { createParams: { extraProgramPages: undefined } } }, + const test = createArc4TestFixture({ + path: 'tests/artifacts/primitive-ops/contract.algo.ts', + contracts: { + PrimitiveOpsContract: { deployParams: { createParams: { extraProgramPages: undefined } } }, + }, }) describe.each(['eq', 'ne', 'lt', 'le', 'gt', 'ge'])('logical operators', async (op) => { diff --git a/tests/primitives/bytes.algo.spec.ts b/tests/primitives/bytes.algo.spec.ts index a87ebbae..19ab4b34 100644 --- a/tests/primitives/bytes.algo.spec.ts +++ b/tests/primitives/bytes.algo.spec.ts @@ -14,8 +14,11 @@ import { createArc4TestFixture } from '../test-fixture' import { getSha256Hash, padUint8Array } from '../util' describe('Bytes', async () => { - const test = createArc4TestFixture('tests/artifacts/primitive-ops/contract.algo.ts', { - PrimitiveOpsContract: { deployParams: { createParams: { extraProgramPages: undefined } } }, + const test = createArc4TestFixture({ + path: 'tests/artifacts/primitive-ops/contract.algo.ts', + contracts: { + PrimitiveOpsContract: { deployParams: { createParams: { extraProgramPages: undefined } } }, + }, }) describe.each([ diff --git a/tests/primitives/uint64.algo.spec.ts b/tests/primitives/uint64.algo.spec.ts index 36d2412c..b895266f 100644 --- a/tests/primitives/uint64.algo.spec.ts +++ b/tests/primitives/uint64.algo.spec.ts @@ -8,8 +8,11 @@ import { getAvmResult } from '../avm-invoker' import { createArc4TestFixture } from '../test-fixture' describe('Unit64', async () => { - const test = createArc4TestFixture('tests/artifacts/primitive-ops/contract.algo.ts', { - PrimitiveOpsContract: { deployParams: { createParams: { extraProgramPages: undefined } } }, + const test = createArc4TestFixture({ + path: 'tests/artifacts/primitive-ops/contract.algo.ts', + contracts: { + PrimitiveOpsContract: { deployParams: { createParams: { extraProgramPages: undefined } } }, + }, }) describe.each(['eq', 'ne', 'lt', 'le', 'gt', 'ge'])('logical operators', async (op) => { diff --git a/tests/pure-op-codes.algo.spec.ts b/tests/pure-op-codes.algo.spec.ts index a361b111..9bc95519 100644 --- a/tests/pure-op-codes.algo.spec.ts +++ b/tests/pure-op-codes.algo.spec.ts @@ -21,8 +21,11 @@ const extractOutOfBoundError = /extraction (start|end) \d+ is beyond length/ const sqrtMaxUint64 = 4294967295n describe('Pure op codes', async () => { - const test = createArc4TestFixture('tests/artifacts/miscellaneous-ops/contract.algo.ts', { - MiscellaneousOpsContract: {}, + const test = createArc4TestFixture({ + path: 'tests/artifacts/miscellaneous-ops/contract.algo.ts', + contracts: { + MiscellaneousOpsContract: {}, + }, }) const ctx = new TestExecutionContext() diff --git a/tests/state-op-codes.algo.spec.ts b/tests/state-op-codes.algo.spec.ts index c31f33a9..4b5441ca 100644 --- a/tests/state-op-codes.algo.spec.ts +++ b/tests/state-op-codes.algo.spec.ts @@ -33,17 +33,20 @@ import { generateTestAsset, getAvmResult, INITIAL_BALANCE_MICRO_ALGOS } from './ import { createArc4TestFixture } from './test-fixture' describe('State op codes', async () => { - const test = createArc4TestFixture('tests/artifacts/state-ops/contract.algo.ts', { - ItxnDemoContract: {}, - ITxnOpsContract: {}, - StateAcctParamsGetContract: {}, - StateAppGlobalContract: {}, - StateAppGlobalExContract: {}, - StateAppLocalContract: {}, - StateAppLocalExContract: {}, - StateAppParamsContract: {}, - StateAssetHoldingContract: {}, - StateAssetParamsContract: {}, + const test = createArc4TestFixture({ + path: 'tests/artifacts/state-ops/contract.algo.ts', + contracts: { + ItxnDemoContract: {}, + ITxnOpsContract: {}, + StateAcctParamsGetContract: {}, + StateAppGlobalContract: {}, + StateAppGlobalExContract: {}, + StateAppLocalContract: {}, + StateAppLocalExContract: {}, + StateAppParamsContract: {}, + StateAssetHoldingContract: {}, + StateAssetParamsContract: {}, + }, }) const ctx = new TestExecutionContext() diff --git a/tests/switch-statements.algo.spec.ts b/tests/switch-statements.algo.spec.ts index 5987d924..8cf03334 100644 --- a/tests/switch-statements.algo.spec.ts +++ b/tests/switch-statements.algo.spec.ts @@ -2,7 +2,7 @@ import { describe } from 'vitest' import { createArc4TestFixture } from './test-fixture' describe('switch statements', () => { - const test = createArc4TestFixture('tests/artifacts/switch-statements/contract.algo.ts', { DemoContract: {} }) + const test = createArc4TestFixture({ path: 'tests/artifacts/switch-statements/contract.algo.ts', contracts: { DemoContract: {} } }) test('runs', async ({ appClientDemoContract }) => { await appClientDemoContract.send.call({ method: 'run', args: [] }) diff --git a/tests/test-fixture.ts b/tests/test-fixture.ts index 6c46d216..24066228 100644 --- a/tests/test-fixture.ts +++ b/tests/test-fixture.ts @@ -77,11 +77,22 @@ type ProgramInvoker = { type BaseFixtureContextFor = { [key in T as `${key}Invoker`]: ProgramInvoker } -export function createBaseTestFixture( - path: string, - contracts: TContracts[], - newScopeAt: typeof beforeAll | typeof beforeEach = beforeAll, -) { +/** + * Creates a base test fixture for testing compiled Algorand smart contracts. + * + * @param options - Configuration options for the test fixture + * @param options.path - Path to the TypeScript file containing the contracts + * @param options.contracts - Array of contract names to create fixtures for + * @param options.newScopeAt - When to create a new test scope. Defaults to `beforeAll` for shared state across tests. + * Use `beforeEach` to create a fresh state for each test. + */ +export function createBaseTestFixture(options: { + path: string + contracts: TContracts[] + /** When to create a new test scope. Defaults to `beforeAll`. Use `beforeEach` for fresh state per test. */ + newScopeAt?: typeof beforeAll | typeof beforeEach +}) { + const { path, contracts, newScopeAt = beforeAll } = options const lazyCompile = createLazyCompiler(path, { outputArc56: false, outputBytecode: true }) const localnet = algorandFixture({ testAccountFunding: microAlgos(100_000_000_000), @@ -149,11 +160,22 @@ type ContractConfig = { funding?: AlgoAmount } -export function createArc4TestFixture( - path: string, - contracts: Record | TContracts[], - newScopeAt: typeof beforeAll | typeof beforeEach = beforeAll, -) { +/** + * Creates an ARC-4 test fixture for testing Algorand ARC-4 smart contracts. + * + * @param options - Configuration options for the test fixture + * @param options.path - Path to the TypeScript file containing the ARC-4 contracts + * @param options.contracts - Contract configuration as either an array of names or pairs of name with deployment config + * @param options.newScopeAt - When to create a new` test scope. Defaults to `beforeAll` for shared state across tests. + * Use `beforeEach` to create a fresh state for each test. + */ +export function createArc4TestFixture(options: { + path: string + contracts: Record | TContracts[] + /** When to create a new test scope. Defaults to `beforeAll`. Use `beforeEach` for fresh state per test. */ + newScopeAt?: typeof beforeAll | typeof beforeEach +}) { + const { path, contracts, newScopeAt = beforeAll } = options const lazyCompile = createLazyCompiler(path, { outputArc56: true, outputBytecode: false }) const localnet = algorandFixture({ testAccountFunding: microAlgos(100_000_000_000), From 5d28f00f4237ba9fdb89055c8f0853d34e8b92da Mon Sep 17 00:00:00 2001 From: Bobby Lat Date: Thu, 23 Oct 2025 14:20:07 +0800 Subject: [PATCH 58/68] fix audit vulnerability --- package-lock.json | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/package-lock.json b/package-lock.json index fdcdb616..9ae2a017 100644 --- a/package-lock.json +++ b/package-lock.json @@ -13941,9 +13941,9 @@ } }, "node_modules/vite": { - "version": "7.1.5", - "resolved": "https://registry.npmjs.org/vite/-/vite-7.1.5.tgz", - "integrity": "sha512-4cKBO9wR75r0BeIWWWId9XK9Lj6La5X846Zw9dFfzMRw38IlTk2iCcUt6hsyiDRcPidc55ZParFYDXi0nXOeLQ==", + "version": "7.1.11", + "resolved": "https://registry.npmjs.org/vite/-/vite-7.1.11.tgz", + "integrity": "sha512-uzcxnSDVjAopEUjljkWh8EIrg6tlzrjFUfMcR1EVsRDGwf/ccef0qQPRyOrROwhrTDaApueq+ja+KLPlzR/zdg==", "dev": true, "license": "MIT", "dependencies": { From 8989a8ab61074df084409794220bad1e81ac5255 Mon Sep 17 00:00:00 2001 From: Bobby Lat Date: Tue, 28 Oct 2025 16:16:52 +0800 Subject: [PATCH 59/68] feat: update puya-ts dependency versions --- package-lock.json | 50 ++++++++++++-------------------- package.json | 4 +-- src/test-transformer/visitors.ts | 7 +++-- 3 files changed, 24 insertions(+), 37 deletions(-) diff --git a/package-lock.json b/package-lock.json index 9ae2a017..def1a33e 100644 --- a/package-lock.json +++ b/package-lock.json @@ -8,8 +8,8 @@ "name": "@algorandfoundation/algorand-typescript-testing", "version": "1.0.0", "dependencies": { - "@algorandfoundation/algorand-typescript": "1.0.0-alpha.90", - "@algorandfoundation/puya-ts": "1.0.0-alpha.90", + "@algorandfoundation/algorand-typescript": "1.0.0-alpha.99", + "@algorandfoundation/puya-ts": "1.0.0-alpha.99", "elliptic": "^6.6.1", "js-sha256": "^0.11.0", "js-sha3": "^0.9.3", @@ -76,21 +76,26 @@ } }, "node_modules/@algorandfoundation/algorand-typescript": { - "version": "1.0.0-alpha.90", - "resolved": "https://registry.npmjs.org/@algorandfoundation/algorand-typescript/-/algorand-typescript-1.0.0-alpha.90.tgz", - "integrity": "sha512-Q7PAbhZtt0uwwWAgfoUEwW5LwdIXUMaUyEPkGCorI9EfiXPDEE7lvdv1GlArVDIkwLwlNYgra1PmWwlYGd8KdQ==", + "version": "1.0.0-alpha.99", + "resolved": "https://registry.npmjs.org/@algorandfoundation/algorand-typescript/-/algorand-typescript-1.0.0-alpha.99.tgz", + "integrity": "sha512-GI8sOdCEpV/fiAsgtmYsRnDCDRGlhRK6+AVXjypm2uhNcKlOcFNtifi/UTjM898XyYiruu/fo8UX89fquowJpw==", "peerDependencies": { "tslib": "^2.6.2" } }, "node_modules/@algorandfoundation/puya-ts": { - "version": "1.0.0-alpha.90", - "resolved": "https://registry.npmjs.org/@algorandfoundation/puya-ts/-/puya-ts-1.0.0-alpha.90.tgz", - "integrity": "sha512-v5Ai4B/M/V17zCXeo4hzS1BcrxKtiw5JM17MXTFO4Kz679BPod1RnKkOBMFH50n+IXHBRY8FJnmlZWTmUZYVHw==", + "version": "1.0.0-alpha.99", + "resolved": "https://registry.npmjs.org/@algorandfoundation/puya-ts/-/puya-ts-1.0.0-alpha.99.tgz", + "integrity": "sha512-spPVx7WVX6aFNowxzgjimisCZ1uzjip0dKWGTtd0DNhkw6/EsI5w1BcwkTyDTsWh7Py72jpUeUqrKwdce1U4Lw==", + "bundleDependencies": [ + "vscode-jsonrpc", + "vscode-languageserver", + "vscode-languageserver-textdocument" + ], "hasInstallScript": true, "license": "MIT", "dependencies": { - "@algorandfoundation/algorand-typescript": "1.0.0-alpha.90", + "@algorandfoundation/algorand-typescript": "1.0.0-alpha.99", "arcsecond": "^5.0.0", "argparse": "^2.0.1", "chalk": "^5.4.1", @@ -98,18 +103,17 @@ "cross-spawn": "7.0.6", "glob": "^11.0.3", "minimatch": "^10.0.3", + "pathe": "^2.0.3", "polytype": "^0.17.0", - "rxjs": "^7.8.2", "signal-exit": "^4.1.0", "tar": "^7.4.3", "tslib": "^2.8.1", "typescript": "^5.8.3", - "upath": "^2.0.1", + "vscode-jsonrpc": "^8.2.0", "vscode-languageserver": "^9.0.1", "vscode-languageserver-textdocument": "^1.0.12", "vscode-uri": "^3.1.0", - "which": "^5.0.0", - "zod": "^4.0.5" + "which": "^5.0.0" }, "bin": { "download-puya-binary": "bin/download-puya-binary.mjs", @@ -11497,7 +11501,6 @@ "version": "2.0.3", "resolved": "https://registry.npmjs.org/pathe/-/pathe-2.0.3.tgz", "integrity": "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==", - "dev": true, "license": "MIT" }, "node_modules/pathval": { @@ -12153,15 +12156,6 @@ "queue-microtask": "^1.2.2" } }, - "node_modules/rxjs": { - "version": "7.8.2", - "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-7.8.2.tgz", - "integrity": "sha512-dhKf903U/PQZY6boNNtAGdWbG85WAbjT/1xYoZIC7FAY0yWapOBQVsVrDl58W86//e1VpMNBtRV4MaXfdMySFA==", - "license": "Apache-2.0", - "dependencies": { - "tslib": "^2.1.0" - } - }, "node_modules/safe-array-concat": { "version": "1.1.3", "resolved": "https://registry.npmjs.org/safe-array-concat/-/safe-array-concat-1.1.3.tgz", @@ -13896,6 +13890,7 @@ "version": "2.0.1", "resolved": "https://registry.npmjs.org/upath/-/upath-2.0.1.tgz", "integrity": "sha512-1uEe95xksV1O0CYKXo8vQvN1JEbtJp7lb7C5U9HMsIp6IVwntkH/oNUzyVNQSd4S1sYk2FpSSW44FqMc8qee5w==", + "dev": true, "license": "MIT", "engines": { "node": ">=4", @@ -14582,15 +14577,6 @@ "funding": { "url": "https://github.com/sponsors/sindresorhus" } - }, - "node_modules/zod": { - "version": "4.0.8", - "resolved": "https://registry.npmjs.org/zod/-/zod-4.0.8.tgz", - "integrity": "sha512-+MSh9cZU9r3QKlHqrgHMTSr3QwMGv4PLfR0M4N/sYWV5/x67HgXEhIGObdBkpnX8G78pTgWnIrBL2lZcNJOtfg==", - "license": "MIT", - "funding": { - "url": "https://github.com/sponsors/colinhacks" - } } } } diff --git a/package.json b/package.json index 20772384..aa736709 100644 --- a/package.json +++ b/package.json @@ -68,8 +68,8 @@ "vitest": "3.2.4" }, "dependencies": { - "@algorandfoundation/algorand-typescript": "1.0.0-alpha.90", - "@algorandfoundation/puya-ts": "1.0.0-alpha.90", + "@algorandfoundation/algorand-typescript": "1.0.0-alpha.99", + "@algorandfoundation/puya-ts": "1.0.0-alpha.99", "elliptic": "^6.6.1", "js-sha256": "^0.11.0", "js-sha3": "^0.9.3", diff --git a/src/test-transformer/visitors.ts b/src/test-transformer/visitors.ts index 37bde05e..da41ea95 100644 --- a/src/test-transformer/visitors.ts +++ b/src/test-transformer/visitors.ts @@ -1,4 +1,4 @@ -import { LoggingContext, ptypes, SourceLocation, TypeResolver } from '@algorandfoundation/puya-ts' +import { AbsolutePath, LoggingContext, ptypes, SourceLocation, TypeResolver } from '@algorandfoundation/puya-ts' import path from 'path' import ts from 'typescript' import { CodeError } from '../errors' @@ -56,7 +56,8 @@ export class SourceFileVisitor { this.context = { ...context, currentDirectory: program.getCurrentDirectory() } const typeChecker = program.getTypeChecker() const loggingContext = LoggingContext.create() - const typeResolver = new TypeResolver(typeChecker, program.getCurrentDirectory()) + const programDir = AbsolutePath.resolve({ path: program.getCurrentDirectory() }) + const typeResolver = new TypeResolver(typeChecker, programDir) this.helper = { additionalStatements: [], resolveType(node: ts.Node): ptypes.PType { @@ -83,7 +84,7 @@ export class SourceFileVisitor { }, sourceLocation(node: ts.Node): SourceLocation { try { - return SourceLocation.fromNode(node, program.getCurrentDirectory()) + return SourceLocation.fromNode(node, programDir) } catch { return SourceLocation.None } From c22ecdab2afab634c68107131359026ebc285fa4 Mon Sep 17 00:00:00 2001 From: Bobby Lat Date: Tue, 28 Oct 2025 17:00:11 +0800 Subject: [PATCH 60/68] refactor: manually publish production releases from release branch only --- .github/workflows/gh-pages.yml | 10 +++---- .github/workflows/prod-release.yml | 44 ++++++++++++++++++++++++++++++ .github/workflows/release.yml | 25 +++++++++++++++-- 3 files changed, 71 insertions(+), 8 deletions(-) create mode 100644 .github/workflows/prod-release.yml diff --git a/.github/workflows/gh-pages.yml b/.github/workflows/gh-pages.yml index 895b0c9a..0a595b07 100644 --- a/.github/workflows/gh-pages.yml +++ b/.github/workflows/gh-pages.yml @@ -1,9 +1,9 @@ name: 'Run typedoc and publish to pages' on: - push: - branches: - - main + workflow_call: + workflow_dispatch: + jobs: build-and-publish-docs: runs-on: ubuntu-latest @@ -15,10 +15,10 @@ jobs: - name: Checkout source code uses: actions/checkout@v4 - - name: Use Node.js 21.x + - name: Use Node.js 22.x uses: actions/setup-node@v4 with: - node-version: 21.x + node-version: 22.x - name: Npm install run: npm ci --ignore-scripts diff --git a/.github/workflows/prod-release.yml b/.github/workflows/prod-release.yml new file mode 100644 index 00000000..fe901129 --- /dev/null +++ b/.github/workflows/prod-release.yml @@ -0,0 +1,44 @@ +name: Prod Publish + +on: + workflow_dispatch: + +permissions: + contents: write + +jobs: + prod_release: + runs-on: ubuntu-latest + steps: + - name: Generate bot token + uses: actions/create-github-app-token@v1 + id: app_token + with: + app-id: ${{ secrets.BOT_ID }} + private-key: ${{ secrets.BOT_SK }} + + - uses: actions/checkout@v4 + with: + # Fetch entire repository history so we can determine version number from it + fetch-depth: 0 + token: ${{ steps.app_token.outputs.token }} + + - name: Set Git user as GitHub actions + run: git config --global user.email "179917785+engineering-ci[bot]@users.noreply.github.com" && git config --global user.name "engineering-ci[bot]" + + - name: Merge main -> release + uses: devmasx/merge-branch@854d3ac71ed1e9deb668e0074781b81fdd6e771f + with: + type: now + from_branch: main + target_branch: release + github_token: ${{ steps.app_token.outputs.token }} + + - name: Merge release -> main + uses: devmasx/merge-branch@854d3ac71ed1e9deb668e0074781b81fdd6e771f + with: + type: now + from_branch: release + target_branch: main + message: Merge release back to main to get version increment [no ci] + github_token: ${{ steps.app_token.outputs.token }} diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 196c81f6..956c0a2a 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -7,6 +7,12 @@ on: - main - release workflow_dispatch: + inputs: + publish-prod-release: + description: "Publishing a production release? If false (default), won't create a production release." + type: boolean + required: false + default: false concurrency: release @@ -15,6 +21,9 @@ permissions: issues: write pull-requests: write +env: + PUBLISH_RELEASE: ${{ (github.ref_name != 'release' || inputs.publish-prod-release) && 'true' || 'false' }} + jobs: build: name: 'Build @algorandfoundation/algorand-typescript-testing' @@ -49,7 +58,7 @@ jobs: fetch-depth: 0 token: ${{ steps.app_token.outputs.token }} - - name: Use Node.js 20.x + - name: Use Node.js 22.x uses: actions/setup-node@v4 with: node-version: 22.x @@ -67,10 +76,20 @@ jobs: GITHUB_TOKEN: ${{ steps.app_token.outputs.token }} - name: Publish @algorandfoundation/algorand-typescript-testing + if: ${{ env.PUBLISH_RELEASE == 'true' }} uses: JS-DevTools/npm-publish@v3 with: token: ${{ secrets.NPM_TOKEN }} package: artifacts/algo-ts-testing/package.json access: 'public' - # Tagging 'main' branch with latest for now, even though it's beta because we don't have a non-beta - tag: ${{ github.ref_name == 'alpha' && 'alpha' || github.ref_name == 'main' && 'latest' || github.ref_name == 'release' && 'latest' || 'pre-release' }} + tag: ${{ github.ref_name == 'alpha' && 'alpha' || github.ref_name == 'main' && 'beta' || github.ref_name == 'release' && 'latest' || 'pre-release' }} + + publish-docs: + name: Publish docs + needs: release + if: ${{ inputs.publish-prod-release }} + uses: ./.github/workflows/gh-pages.yml + permissions: + contents: read + pages: write + id-token: write From 8d65dfd99686d418ac1f8a78e5728af6490fd736 Mon Sep 17 00:00:00 2001 From: Bobby Lat Date: Wed, 29 Oct 2025 11:38:27 +0800 Subject: [PATCH 61/68] remove extra flag to control prod release from release branch --- .github/workflows/release.yml | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 956c0a2a..8c80acac 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -7,12 +7,6 @@ on: - main - release workflow_dispatch: - inputs: - publish-prod-release: - description: "Publishing a production release? If false (default), won't create a production release." - type: boolean - required: false - default: false concurrency: release @@ -21,9 +15,6 @@ permissions: issues: write pull-requests: write -env: - PUBLISH_RELEASE: ${{ (github.ref_name != 'release' || inputs.publish-prod-release) && 'true' || 'false' }} - jobs: build: name: 'Build @algorandfoundation/algorand-typescript-testing' @@ -76,7 +67,6 @@ jobs: GITHUB_TOKEN: ${{ steps.app_token.outputs.token }} - name: Publish @algorandfoundation/algorand-typescript-testing - if: ${{ env.PUBLISH_RELEASE == 'true' }} uses: JS-DevTools/npm-publish@v3 with: token: ${{ secrets.NPM_TOKEN }} From ba873587a4b3a14366c5d8e6e2cde4ecd35db6c4 Mon Sep 17 00:00:00 2001 From: Bobby Lat Date: Wed, 29 Oct 2025 17:00:16 +0800 Subject: [PATCH 62/68] comment out publish steps to make sure 1.0 is not published again --- .github/workflows/release.yml | 31 +++++++++++++++---------------- 1 file changed, 15 insertions(+), 16 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 8c80acac..be5c836a 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -66,20 +66,19 @@ jobs: env: GITHUB_TOKEN: ${{ steps.app_token.outputs.token }} - - name: Publish @algorandfoundation/algorand-typescript-testing - uses: JS-DevTools/npm-publish@v3 - with: - token: ${{ secrets.NPM_TOKEN }} - package: artifacts/algo-ts-testing/package.json - access: 'public' - tag: ${{ github.ref_name == 'alpha' && 'alpha' || github.ref_name == 'main' && 'beta' || github.ref_name == 'release' && 'latest' || 'pre-release' }} + # - name: Publish @algorandfoundation/algorand-typescript-testing + # uses: JS-DevTools/npm-publish@v3 + # with: + # token: ${{ secrets.NPM_TOKEN }} + # package: artifacts/algo-ts-testing/package.json + # access: 'public' + # tag: ${{ github.ref_name == 'alpha' && 'alpha' || github.ref_name == 'main' && 'beta' || github.ref_name == 'release' && 'latest' || 'pre-release' }} - publish-docs: - name: Publish docs - needs: release - if: ${{ inputs.publish-prod-release }} - uses: ./.github/workflows/gh-pages.yml - permissions: - contents: read - pages: write - id-token: write + # publish-docs: + # name: Publish docs + # needs: release + # uses: ./.github/workflows/gh-pages.yml + # permissions: + # contents: read + # pages: write + # id-token: write From 254abaeb1bd9580456e52a9f5aea7c3193bd919c Mon Sep 17 00:00:00 2001 From: Bobby Lat Date: Thu, 30 Oct 2025 10:47:41 +0800 Subject: [PATCH 63/68] publish docs only from release branch --- .github/workflows/release.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index be5c836a..1d0b402a 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -77,6 +77,7 @@ jobs: # publish-docs: # name: Publish docs # needs: release + # if: github.ref_name == 'release' # uses: ./.github/workflows/gh-pages.yml # permissions: # contents: read From 186f333c351e9e4792ef6aa311304ce5513ab601 Mon Sep 17 00:00:00 2001 From: Bobby Lat Date: Thu, 30 Oct 2025 13:30:06 +0800 Subject: [PATCH 64/68] skip tagging a new beta version for main branch --- .github/workflows/release.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 1d0b402a..bb22d44b 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -62,6 +62,7 @@ jobs: path: artifacts - name: Generate semantic version for @algorandfoundation/algorand-typescript-testing + if: github.ref_name != 'main' run: npx semantic-release env: GITHUB_TOKEN: ${{ steps.app_token.outputs.token }} From 486a67a48b468fe76ccf7c50736dd0b4f438f860 Mon Sep 17 00:00:00 2001 From: Bobby Lat Date: Thu, 30 Oct 2025 14:46:28 +0800 Subject: [PATCH 65/68] chore: temporarily disable success comments --- .releaserc.json | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/.releaserc.json b/.releaserc.json index b4397a62..1eb16215 100644 --- a/.releaserc.json +++ b/.releaserc.json @@ -63,6 +63,11 @@ "pkgRoot": "artifacts/algo-ts-testing" } ], - "@semantic-release/github" + [ + "@semantic-release/github", + { + "successComment": false + } + ] ] } From b8f0fb320b3342cccfaad00c6d8f493d7f87cc16 Mon Sep 17 00:00:00 2001 From: Bobby Lat Date: Thu, 30 Oct 2025 14:48:17 +0800 Subject: [PATCH 66/68] chore: temporarily prevent beta release --- .github/workflows/release.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index bb22d44b..dbbf3496 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -68,6 +68,7 @@ jobs: GITHUB_TOKEN: ${{ steps.app_token.outputs.token }} # - name: Publish @algorandfoundation/algorand-typescript-testing + # if: github.ref_name != 'main' # uses: JS-DevTools/npm-publish@v3 # with: # token: ${{ secrets.NPM_TOKEN }} From f0045af197c7898d987dc04cf565191d528bcf5f Mon Sep 17 00:00:00 2001 From: Bobby Lat Date: Thu, 30 Oct 2025 15:06:00 +0800 Subject: [PATCH 67/68] update puya-ts dependencies --- package-lock.json | 18 +++++++++--------- package.json | 4 ++-- 2 files changed, 11 insertions(+), 11 deletions(-) diff --git a/package-lock.json b/package-lock.json index 8161dff0..3ec18809 100644 --- a/package-lock.json +++ b/package-lock.json @@ -8,8 +8,8 @@ "name": "@algorandfoundation/algorand-typescript-testing", "version": "1.0.0", "dependencies": { - "@algorandfoundation/algorand-typescript": "1.0.0-alpha.99", - "@algorandfoundation/puya-ts": "1.0.0-alpha.99", + "@algorandfoundation/algorand-typescript": "1.0.1", + "@algorandfoundation/puya-ts": "1.0.1", "elliptic": "^6.6.1", "js-sha256": "^0.11.0", "js-sha3": "^0.9.3", @@ -76,17 +76,17 @@ } }, "node_modules/@algorandfoundation/algorand-typescript": { - "version": "1.0.0-alpha.99", - "resolved": "https://registry.npmjs.org/@algorandfoundation/algorand-typescript/-/algorand-typescript-1.0.0-alpha.99.tgz", - "integrity": "sha512-GI8sOdCEpV/fiAsgtmYsRnDCDRGlhRK6+AVXjypm2uhNcKlOcFNtifi/UTjM898XyYiruu/fo8UX89fquowJpw==", + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@algorandfoundation/algorand-typescript/-/algorand-typescript-1.0.1.tgz", + "integrity": "sha512-XRn1D2/wU66j878/MmSS2kIkq8M8EgECiMeLMzatFIztVZclh3R3z+4CvXbF/WzluWUXCC7cyYmRWttrvqfYTQ==", "peerDependencies": { "tslib": "^2.6.2" } }, "node_modules/@algorandfoundation/puya-ts": { - "version": "1.0.0-alpha.99", - "resolved": "https://registry.npmjs.org/@algorandfoundation/puya-ts/-/puya-ts-1.0.0-alpha.99.tgz", - "integrity": "sha512-spPVx7WVX6aFNowxzgjimisCZ1uzjip0dKWGTtd0DNhkw6/EsI5w1BcwkTyDTsWh7Py72jpUeUqrKwdce1U4Lw==", + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@algorandfoundation/puya-ts/-/puya-ts-1.0.1.tgz", + "integrity": "sha512-rTDLzp8ER0O5ueVWb/rrDQ17qGXFaqi+QG/4bRg0gCoJkCYkRhH19XDFn4VlPTD/E2MUepPl5qWduZe+h1Zy3g==", "bundleDependencies": [ "vscode-jsonrpc", "vscode-languageserver", @@ -95,7 +95,7 @@ "hasInstallScript": true, "license": "MIT", "dependencies": { - "@algorandfoundation/algorand-typescript": "1.0.0-alpha.99", + "@algorandfoundation/algorand-typescript": "1.0.1", "arcsecond": "^5.0.0", "argparse": "^2.0.1", "chalk": "^5.4.1", diff --git a/package.json b/package.json index 0410d3fd..78f1cd24 100644 --- a/package.json +++ b/package.json @@ -68,8 +68,8 @@ "vitest": "3.2.4" }, "dependencies": { - "@algorandfoundation/algorand-typescript": "1.0.0-alpha.99", - "@algorandfoundation/puya-ts": "1.0.0-alpha.99", + "@algorandfoundation/algorand-typescript": "1.0.1", + "@algorandfoundation/puya-ts": "1.0.1", "elliptic": "^6.6.1", "js-sha256": "^0.11.0", "js-sha3": "^0.9.3", From 6967cd91996e98bd7bbeab1c8cebb3af0107f77b Mon Sep 17 00:00:00 2001 From: Neil Campbell Date: Thu, 30 Oct 2025 15:12:42 +0800 Subject: [PATCH 68/68] chore: tweak release message --- .github/workflows/prod-release.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/workflows/prod-release.yml b/.github/workflows/prod-release.yml index fe901129..d7e529f8 100644 --- a/.github/workflows/prod-release.yml +++ b/.github/workflows/prod-release.yml @@ -40,5 +40,6 @@ jobs: type: now from_branch: release target_branch: main - message: Merge release back to main to get version increment [no ci] github_token: ${{ steps.app_token.outputs.token }} + env: + INPUT_MESSAGE: 'Merge release back to main to get version increment [no ci]'