Skip to content

Commit 9fb1668

Browse files
committed
feat: implement stubs for Box create, and no arg ctors
1 parent eb2a40b commit 9fb1668

File tree

9 files changed

+399
-58
lines changed

9 files changed

+399
-58
lines changed

package-lock.json

Lines changed: 9 additions & 9 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -66,8 +66,8 @@
6666
"vitest": "3.0.9"
6767
},
6868
"dependencies": {
69-
"@algorandfoundation/algorand-typescript": "1.0.0-beta.65",
70-
"@algorandfoundation/puya-ts": "1.0.0-beta.65",
69+
"@algorandfoundation/algorand-typescript": "1.0.0-alpha.47",
70+
"@algorandfoundation/puya-ts": "1.0.0-alpha.47",
7171
"elliptic": "^6.6.1",
7272
"js-sha256": "^0.11.0",
7373
"js-sha3": "^0.9.3",

src/encoders.ts

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import { ARC4Encoded } from '@algorandfoundation/algorand-typescript/arc4'
33
import { encodingUtil } from '@algorandfoundation/puya-ts'
44
import { InternalError } from './errors'
55
import { BytesBackedCls, Uint64BackedCls } from './impl/base'
6-
import { arc4Encoders, encodeArc4Impl, getArc4Encoder } from './impl/encoded-types'
6+
import { arc4Encoders, encodeArc4Impl, getArc4Encoder, tryArc4EncodedLengthImpl } from './impl/encoded-types'
77
import { BigUint, Uint64, type StubBytesCompat } from './impl/primitives'
88
import { AccountCls, ApplicationCls, AssetCls } from './impl/reference'
99
import type { DeliberateAny } from './typescript-helpers'
@@ -89,3 +89,8 @@ export const toBytes = (val: unknown): bytes => {
8989
}
9090
throw new InternalError(`Invalid type for bytes: ${nameOfType(val)}`)
9191
}
92+
93+
export const minLengthForType = (typeInfo: TypeInfo): number => {
94+
const minArc4StaticLength = tryArc4EncodedLengthImpl(typeInfo)
95+
return minArc4StaticLength ?? 0
96+
}

src/impl/encoded-types.ts

Lines changed: 69 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import type {
22
Account as AccountType,
33
BigUintCompat,
44
bytes,
5+
NTuple,
56
StringCompat,
67
uint64,
78
Uint64Compat,
@@ -127,14 +128,14 @@ export class UFixedNxMImpl<N extends BitSize, M extends number> extends UFixedNx
127128
private precision: M
128129
typeInfo: TypeInfo
129130

130-
constructor(typeInfo: TypeInfo | string, v: `${number}.${number}`) {
131+
constructor(typeInfo: TypeInfo | string, v?: `${number}.${number}`) {
131132
super(v)
132133
this.typeInfo = typeof typeInfo === 'string' ? JSON.parse(typeInfo) : typeInfo
133134
const genericArgs = this.typeInfo.genericArgs as uFixedNxMGenericArgs
134135
this.bitSize = UFixedNxMImpl.getMaxBitsLength(this.typeInfo) as N
135136
this.precision = parseInt(genericArgs.m.name, 10) as M
136137

137-
const trimmedValue = trimTrailingDecimalZeros(v)
138+
const trimmedValue = trimTrailingDecimalZeros(v ?? '0.0')
138139
assert(regExpNxM(this.precision).test(trimmedValue), `expected positive decimal literal with max of ${this.precision} decimal places`)
139140

140141
const bigIntValue = BigInt(trimmedValue.replace('.', ''))
@@ -308,7 +309,7 @@ const checkItemTypeName = (type: TypeInfo, value: ARC4Encoded) => {
308309
}
309310
type StaticArrayGenericArgs = { elementType: TypeInfo; size: TypeInfo }
310311
const arrayProxyHandler = <TItem>() => ({
311-
get(target: { items: TItem[] }, prop: PropertyKey) {
312+
get(target: { items: readonly TItem[] }, prop: PropertyKey) {
312313
const idx = prop ? parseInt(prop.toString(), 10) : NaN
313314
if (!isNaN(idx)) {
314315
if (idx >= 0 && idx < target.items.length) return target.items[idx]
@@ -337,8 +338,9 @@ const arrayProxyHandler = <TItem>() => ({
337338
return Reflect.set(target, prop, value)
338339
},
339340
})
341+
const isInitialisingFromBytesSymbol = Symbol('IsInitialisingFromBytes')
340342
export class StaticArrayImpl<TItem extends ARC4Encoded, TLength extends number> extends StaticArray<TItem, TLength> {
341-
private value?: TItem[]
343+
private value?: NTuple<TItem, TLength>
342344
private uint8ArrayValue?: Uint8Array
343345
private size: number
344346
typeInfo: TypeInfo
@@ -347,23 +349,33 @@ export class StaticArrayImpl<TItem extends ARC4Encoded, TLength extends number>
347349
constructor(typeInfo: TypeInfo | string, ...items: TItem[] & { length: TLength })
348350
constructor(typeInfo: TypeInfo | string, ...items: TItem[])
349351
constructor(typeInfo: TypeInfo | string, ...items: TItem[] & { length: TLength }) {
350-
super(...(items as DeliberateAny))
352+
// if first item is the symbol, we are initialising from bytes
353+
// so we don't need to pass the items to the super constructor
354+
const isInitialisingFromBytes = items.length === 1 && (items[0] as DeliberateAny) === isInitialisingFromBytesSymbol
355+
super(...(isInitialisingFromBytes ? [] : (items as DeliberateAny)))
356+
351357
this.typeInfo = typeof typeInfo === 'string' ? JSON.parse(typeInfo) : typeInfo
352358
this.genericArgs = this.typeInfo.genericArgs as StaticArrayGenericArgs
353-
354359
this.size = parseInt(this.genericArgs.size.name, 10)
355-
if (items.length && items.length !== this.size) {
356-
throw new CodeError(`expected ${this.size} items, not ${items.length}`)
357-
}
358360

359-
assert(areAllARC4Encoded(items), 'expected ARC4 type')
361+
// if we are not initialising from bytes, we need to check and set the items
362+
if (!isInitialisingFromBytes) {
363+
if (items.length && items.length !== this.size) {
364+
throw new CodeError(`expected ${this.size} items, not ${items.length}`)
365+
}
360366

361-
items.forEach((item) => {
362-
checkItemTypeName(this.genericArgs.elementType, item)
363-
})
367+
assert(areAllARC4Encoded(items), 'expected ARC4 type')
364368

365-
this.value = items.length ? items : undefined
369+
items.forEach((item) => {
370+
checkItemTypeName(this.genericArgs.elementType, item)
371+
})
366372

373+
if (items.length) {
374+
this.value = items as NTuple<TItem, TLength>
375+
} else {
376+
this.uint8ArrayValue = new Uint8Array(StaticArrayImpl.getMaxBytesLength(this.typeInfo))
377+
}
378+
}
367379
return new Proxy(this, arrayProxyHandler<TItem>()) as StaticArrayImpl<TItem, TLength>
368380
}
369381

@@ -382,10 +394,10 @@ export class StaticArrayImpl<TItem extends ARC4Encoded, TLength extends number>
382394
return this.size
383395
}
384396

385-
get items(): TItem[] {
397+
get items(): NTuple<TItem, TLength> {
386398
if (this.uint8ArrayValue) {
387399
const childTypes = Array(this.size).fill(this.genericArgs.elementType)
388-
this.value = decode(this.uint8ArrayValue, childTypes) as TItem[]
400+
this.value = decode(this.uint8ArrayValue, childTypes) as NTuple<TItem, TLength>
389401
this.uint8ArrayValue = undefined
390402
return this.value
391403
} else if (this.value) {
@@ -417,7 +429,7 @@ export class StaticArrayImpl<TItem extends ARC4Encoded, TLength extends number>
417429
)
418430
}
419431

420-
get native(): TItem[] {
432+
get native(): NTuple<TItem, TLength> {
421433
return this.items
422434
}
423435

@@ -431,7 +443,8 @@ export class StaticArrayImpl<TItem extends ARC4Encoded, TLength extends number>
431443
assert(bytesValue.slice(0, 4).equals(ABI_RETURN_VALUE_LOG_PREFIX), 'ABI return prefix not found')
432444
bytesValue = bytesValue.slice(4)
433445
}
434-
const result = new StaticArrayImpl(typeInfo)
446+
// pass the symbol to the constructor to let it know we are initialising from bytes
447+
const result = new StaticArrayImpl<ARC4Encoded, number>(typeInfo, isInitialisingFromBytesSymbol as DeliberateAny)
435448
result.uint8ArrayValue = asUint8Array(bytesValue)
436449
return result
437450
}
@@ -504,7 +517,7 @@ export class AddressImpl extends Address {
504517
return Account(this.value.bytes)
505518
}
506519

507-
get items(): ByteImpl[] {
520+
get items(): readonly ByteImpl[] {
508521
return this.value.items
509522
}
510523

@@ -646,18 +659,27 @@ export class TupleImpl<TTuple extends [ARC4Encoded, ...ARC4Encoded[]]> extends T
646659
typeInfo: TypeInfo
647660
genericArgs: TypeInfo[]
648661

649-
constructor(typeInfo: TypeInfo | string)
650662
constructor(typeInfo: TypeInfo | string, ...items: TTuple) {
651-
super(...items)
663+
// if first item is the symbol, we are initialising from bytes
664+
// so we don't need to pass the items to the super constructor
665+
const isInitialisingFromBytes = items.length === 1 && (items[0] as DeliberateAny) === isInitialisingFromBytesSymbol
666+
super(...(isInitialisingFromBytes ? ([] as DeliberateAny) : items))
652667
this.typeInfo = typeof typeInfo === 'string' ? JSON.parse(typeInfo) : typeInfo
653668
this.genericArgs = Object.values(this.typeInfo.genericArgs as Record<string, TypeInfo>)
654669

655-
assert(areAllARC4Encoded(items), 'expected ARC4 type')
670+
// if we are not initialising from bytes, we need to check and set the items
671+
if (!isInitialisingFromBytes) {
672+
assert(areAllARC4Encoded(items), 'expected ARC4 type')
656673

657-
items.forEach((item, index) => {
658-
checkItemTypeName(this.genericArgs[index], item)
659-
})
660-
this.value = items.length ? items : undefined
674+
items.forEach((item, index) => {
675+
checkItemTypeName(this.genericArgs[index], item)
676+
})
677+
if (items.length) {
678+
this.value = items
679+
} else {
680+
this.uint8ArrayValue = new Uint8Array(TupleImpl.getMaxBytesLength(this.typeInfo))
681+
}
682+
}
661683
}
662684

663685
get bytes(): bytes {
@@ -705,7 +727,8 @@ export class TupleImpl<TTuple extends [ARC4Encoded, ...ARC4Encoded[]]> extends T
705727
assert(bytesValue.slice(0, 4).equals(ABI_RETURN_VALUE_LOG_PREFIX), 'ABI return prefix not found')
706728
bytesValue = bytesValue.slice(4)
707729
}
708-
const result = new TupleImpl(typeInfo)
730+
// pass the symbol to the constructor to let it know we are initialising from bytes
731+
const result = new TupleImpl(typeInfo, isInitialisingFromBytesSymbol as DeliberateAny)
709732
result.uint8ArrayValue = asUint8Array(bytesValue)
710733
return result
711734
}
@@ -920,9 +943,9 @@ export class StaticBytesImpl extends StaticBytes {
920943

921944
constructor(typeInfo: TypeInfo | string, value?: bytes | string) {
922945
super(value)
923-
const uint8ArrayValue = asUint8Array(value ?? new Uint8Array())
924-
this.value = StaticArrayImpl.fromBytesImpl(uint8ArrayValue, typeInfo) as StaticArrayImpl<ByteImpl, number>
925946
this.typeInfo = typeof typeInfo === 'string' ? JSON.parse(typeInfo) : typeInfo
947+
const uint8ArrayValue = asUint8Array(value ?? new Uint8Array(StaticBytesImpl.getMaxBytesLength(this.typeInfo)))
948+
this.value = StaticArrayImpl.fromBytesImpl(uint8ArrayValue, typeInfo) as StaticArrayImpl<ByteImpl, number>
926949
return new Proxy(this, arrayProxyHandler<ByteImpl>()) as StaticBytesImpl
927950
}
928951

@@ -1086,6 +1109,8 @@ const getMaxLengthOfStaticContentType = (type: TypeInfo): number => {
10861109
case 'biguint':
10871110
return UINT512_SIZE / BITS_IN_BYTE
10881111
case 'boolean':
1112+
return 8
1113+
case 'Bool':
10891114
return 1
10901115
case 'Address':
10911116
return AddressImpl.getMaxBytesLength(type)
@@ -1103,8 +1128,9 @@ const getMaxLengthOfStaticContentType = (type: TypeInfo): number => {
11031128
return TupleImpl.getMaxBytesLength(type)
11041129
case 'Struct':
11051130
return StructImpl.getMaxBytesLength(type)
1131+
default:
1132+
throw new CodeError(`unsupported type ${type.name}`)
11061133
}
1107-
throw new CodeError(`unsupported type ${type.name}`)
11081134
}
11091135

11101136
const encode = (values: ARC4Encoded[]) => {
@@ -1338,7 +1364,7 @@ export const getArc4Encoded = (value: DeliberateAny): ARC4Encoded => {
13381364
}, [])
13391365
const genericArgs: TypeInfo[] = result.map((x) => (x as DeliberateAny).typeInfo)
13401366
const typeInfo = { name: `Tuple<[${genericArgs.map((x) => x.name).join(',')}]>`, genericArgs }
1341-
return new TupleImpl(typeInfo, ...(result as []))
1367+
return new TupleImpl(typeInfo, ...(result as [ARC4Encoded, ...ARC4Encoded[]]))
13421368
}
13431369
if (typeof value === 'object') {
13441370
const result = Object.values(value).reduce((acc: ARC4Encoded[], cur: DeliberateAny) => {
@@ -1359,3 +1385,15 @@ export const arc4EncodedLengthImpl = (typeInfoString: string): uint64 => {
13591385
const typeInfo = JSON.parse(typeInfoString)
13601386
return getMaxLengthOfStaticContentType(typeInfo)
13611387
}
1388+
1389+
export const tryArc4EncodedLengthImpl = (typeInfoString: string | TypeInfo): uint64 | undefined => {
1390+
const typeInfo = typeof typeInfoString === 'string' ? JSON.parse(typeInfoString) : typeInfoString
1391+
try {
1392+
return getMaxLengthOfStaticContentType(typeInfo)
1393+
} catch (e) {
1394+
if (e instanceof CodeError && e.message.startsWith('unsupported type')) {
1395+
return undefined
1396+
}
1397+
throw e
1398+
}
1399+
}

src/impl/primitives.ts

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -564,9 +564,9 @@ export class BytesCls extends AlgoTsPrimitiveCls {
564564

565565
export const arrayUtil = new (class ArrayUtil {
566566
arrayAt(arrayLike: Uint8Array, index: StubUint64Compat): Uint8Array
567-
arrayAt<T>(arrayLike: T[], index: StubUint64Compat): T
568-
arrayAt<T>(arrayLike: T[] | Uint8Array, index: StubUint64Compat): T | Uint8Array
569-
arrayAt<T>(arrayLike: T[] | Uint8Array, index: StubUint64Compat): T | Uint8Array {
567+
arrayAt<T>(arrayLike: readonly T[], index: StubUint64Compat): T
568+
arrayAt<T>(arrayLike: readonly T[] | Uint8Array, index: StubUint64Compat): T | Uint8Array
569+
arrayAt<T>(arrayLike: readonly T[] | Uint8Array, index: StubUint64Compat): T | Uint8Array {
570570
const indexNum = getNumber(index)
571571
if (arrayLike instanceof Uint8Array) {
572572
const res = arrayLike.slice(indexNum, indexNum === -1 ? undefined : indexNum + 1)
@@ -576,9 +576,13 @@ export const arrayUtil = new (class ArrayUtil {
576576
return arrayLike.at(indexNum) ?? avmError('Index out of bounds')
577577
}
578578
arraySlice(arrayLike: Uint8Array, start: undefined | StubUint64Compat, end: undefined | StubUint64Compat): Uint8Array
579-
arraySlice<T>(arrayLike: T[], start: undefined | StubUint64Compat, end: undefined | StubUint64Compat): T[]
580-
arraySlice<T>(arrayLike: T[] | Uint8Array, start: undefined | StubUint64Compat, end: undefined | StubUint64Compat): Uint8Array | T[]
581-
arraySlice<T>(arrayLike: T[] | Uint8Array, start: undefined | StubUint64Compat, end: undefined | StubUint64Compat) {
579+
arraySlice<T>(arrayLike: readonly T[], start: undefined | StubUint64Compat, end: undefined | StubUint64Compat): T[]
580+
arraySlice<T>(
581+
arrayLike: readonly T[] | Uint8Array,
582+
start: undefined | StubUint64Compat,
583+
end: undefined | StubUint64Compat,
584+
): Uint8Array | T[]
585+
arraySlice<T>(arrayLike: readonly T[] | Uint8Array, start: undefined | StubUint64Compat, end: undefined | StubUint64Compat) {
582586
const startNum = start === undefined ? undefined : getNumber(start)
583587
const endNum = end === undefined ? undefined : getNumber(end)
584588
if (arrayLike instanceof Uint8Array) {

0 commit comments

Comments
 (0)