diff --git a/docs/testing-guide/concepts.md b/docs/testing-guide/concepts.md index c892fea3..fd5887ce 100644 --- a/docs/testing-guide/concepts.md +++ b/docs/testing-guide/concepts.md @@ -64,3 +64,8 @@ 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. + +## 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, 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 4b1844e5..f48312a5 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-beta.73", + "@algorandfoundation/puya-ts": "1.0.0-beta.73", "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-beta.73", + "resolved": "https://registry.npmjs.org/@algorandfoundation/algorand-typescript/-/algorand-typescript-1.0.0-beta.73.tgz", + "integrity": "sha512-rLqyXp3z1mDqWLjovKhVlOgLANdl2xuMGa8fJ/g1EE3kiWXf1yLuV3d2ojLQTnpNZEZeBTLV7+wtD0l8B2n5pg==", "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-beta.73", + "resolved": "https://registry.npmjs.org/@algorandfoundation/puya-ts/-/puya-ts-1.0.0-beta.73.tgz", + "integrity": "sha512-mrO/1IijDFfE8J9cYC4nkhe/yKlSt+e7vsViw0iYX/iTe8/sa10UybSLjV3cIwRzDcdvExEoLwzJDY7fhhbFpg==", "hasInstallScript": true, "license": "MIT", "dependencies": { - "@algorandfoundation/algorand-typescript": "1.0.0-beta.65", + "@algorandfoundation/algorand-typescript": "1.0.0-beta.73", "arcsecond": "^5.0.0", "argparse": "^2.0.1", "chalk": "^5.4.1", @@ -5220,11 +5220,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" }, @@ -10152,9 +10155,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": { @@ -11799,14 +11802,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" @@ -12151,18 +12154,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.11", + "resolved": "https://registry.npmjs.org/vite/-/vite-7.1.11.tgz", + "integrity": "sha512-uzcxnSDVjAopEUjljkWh8EIrg6tlzrjFUfMcR1EVsRDGwf/ccef0qQPRyOrROwhrTDaApueq+ja+KLPlzR/zdg==", "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 16bec8c2..40489f59 100644 --- a/package.json +++ b/package.json @@ -66,8 +66,8 @@ "vitest": "3.2.4" }, "dependencies": { - "@algorandfoundation/algorand-typescript": "1.0.0-beta.65", - "@algorandfoundation/puya-ts": "1.0.0-beta.65", + "@algorandfoundation/algorand-typescript": "1.0.0-beta.73", + "@algorandfoundation/puya-ts": "1.0.0-beta.73", "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..7f60d8f5 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, @@ -417,8 +418,8 @@ export class StaticArrayImpl ) } - get native(): TItem[] { - return this.items + get native(): NTuple { + return this.items as NTuple } static fromBytesImpl( @@ -433,7 +434,7 @@ export class StaticArrayImpl } const result = new StaticArrayImpl(typeInfo) result.uint8ArrayValue = asUint8Array(bytesValue) - return result + return result as StaticArrayImpl } static getMaxBytesLength(typeInfo: TypeInfo): number { @@ -1086,6 +1087,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 +1106,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[]) => { @@ -1359,3 +1363,17 @@ 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/pure.ts b/src/impl/pure.ts index a27b2f04..17935421 100644 --- a/src/impl/pure.ts +++ b/src/impl/pure.ts @@ -45,7 +45,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/state.ts b/src/impl/state.ts index 130cb59f..b58d144e 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' @@ -136,6 +136,17 @@ export class BoxCls { 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 } @@ -151,6 +162,35 @@ export class BoxCls { return (val: Uint8Array) => getEncoder(valueType)(val, 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 { if (!this.exists) { throw new InternalError('Box has not been created') @@ -164,8 +204,17 @@ export class BoxCls { lazyContext.ledger.setMatrialisedBox(this.#app, this.key, materialised) 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) } 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 c265ce36..2959fea8 100644 --- a/src/internal/index.ts +++ b/src/internal/index.ts @@ -13,6 +13,7 @@ export { Box, BoxMap, BoxRef, GlobalState, LocalState } from '../impl/state' export { TemplateVarImpl as TemplateVar } from '../impl/template-var' export { Txn } from '../impl/txn' export { urangeImpl as urange } from '../impl/urange' +export { validateEncoding } from '../impl/validate-encoding' export { assert, err } from '../util' export * as arc4 from './arc4' export * as op from './op' 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/artifacts/arc4-primitive-ops/contract.algo.ts b/tests/artifacts/arc4-primitive-ops/contract.algo.ts index 7922b5d0..73d0f671 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 { bytes } from '@algorandfoundation/algorand-typescript' -import { arc4, BigUint, emit } from '@algorandfoundation/algorand-typescript' +import { arc4, BigUint, emit, validateEncoding } from '@algorandfoundation/algorand-typescript' import type { Bool, UFixedNxM } from '@algorandfoundation/algorand-typescript/arc4' import { Byte, Contract, interpretAsArc4, Str, UintN } 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 UintN<64>(aBiguint) const bUintN = new UintN<64>(bBiguint) + validateEncoding(aUintN) return aUintN === bUintN } @arc4.abimethod() diff --git a/tests/pure-op-codes.spec.ts b/tests/pure-op-codes.spec.ts index f0693a45..6d5482bd 100644 --- a/tests/pure-op-codes.spec.ts +++ b/tests/pure-op-codes.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.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) + }) + }) + }) })