Skip to content

Commit c71995b

Browse files
committed
feat: add stub implementation of arc4EncodedLength
1 parent d4b217d commit c71995b

File tree

5 files changed

+100
-7
lines changed

5 files changed

+100
-7
lines changed

src/impl/encoded-types.ts

Lines changed: 39 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ import {
2929
ALGORAND_ADDRESS_BYTE_LENGTH,
3030
ALGORAND_CHECKSUM_BYTE_LENGTH,
3131
BITS_IN_BYTE,
32+
UINT512_SIZE,
3233
UINT64_SIZE,
3334
} from '../constants'
3435
import { lazyContext } from '../context-helpers/internal-context'
@@ -441,7 +442,7 @@ export class StaticArrayImpl<TItem extends ARC4Encoded, TLength extends number>
441442
const childTypes = Array(arraySize).fill(genericArgs.elementType)
442443
let i = 0
443444
let size = 0
444-
if (genericArgs.elementType.name === 'Bool') {
445+
if (['Bool', 'boolean'].includes(genericArgs.elementType.name)) {
445446
while (i < childTypes.length) {
446447
const after = findBoolTypes(childTypes, i, 1)
447448
const boolNum = after + 1
@@ -716,7 +717,7 @@ export class TupleImpl<TTuple extends [ARC4Encoded, ...ARC4Encoded[]]> extends T
716717

717718
while (i < genericArgs.length) {
718719
const childType = genericArgs[i]
719-
if (childType.name === 'Bool') {
720+
if (['Bool', 'boolean'].includes(childType.name)) {
720721
const after = findBoolTypes(genericArgs, i, 1)
721722
const boolNum = after + 1
722723
size += Math.floor(boolNum / BITS_IN_BYTE)
@@ -821,6 +822,27 @@ export class StructImpl<T extends StructConstraint> extends (Struct<StructConstr
821822
const genericArgs = Object.values(t.genericArgs as Record<string, TypeInfo>)
822823
return `(${genericArgs.map(getArc4TypeName).join(',')})`
823824
}
825+
826+
static getMaxBytesLength(typeInfo: TypeInfo): number {
827+
const genericArgs = Object.values(typeInfo.genericArgs as Record<string, TypeInfo>)
828+
let i = 0
829+
let size = 0
830+
831+
while (i < genericArgs.length) {
832+
const childType = genericArgs[i]
833+
if (['Bool', 'boolean'].includes(childType.name)) {
834+
const after = findBoolTypes(genericArgs, i, 1)
835+
const boolNum = after + 1
836+
size += Math.floor(boolNum / BITS_IN_BYTE)
837+
size += boolNum % BITS_IN_BYTE ? 1 : 0
838+
i += after
839+
} else {
840+
size += getMaxLengthOfStaticContentType(childType)
841+
}
842+
i += 1
843+
}
844+
return size
845+
}
824846
}
825847

826848
export class DynamicBytesImpl extends DynamicBytes {
@@ -981,7 +1003,7 @@ const decode = (value: Uint8Array, childTypes: TypeInfo[]) => {
9811003
dynamicSegments.push([dynamicIndex, -1])
9821004
valuePartitions.push(new Uint8Array())
9831005
arrayIndex += ABI_LENGTH_SIZE
984-
} else if (childType.name === 'Bool') {
1006+
} else if (['Bool', 'boolean'].includes(childType.name)) {
9851007
const before = findBoolTypes(childTypes, i, -1)
9861008
let after = findBoolTypes(childTypes, i, 1)
9871009

@@ -1043,7 +1065,7 @@ const findBoolTypes = (values: TypeInfo[], index: number, delta: number): number
10431065
const length = values.length
10441066
while (true) {
10451067
const curr = index + delta * until
1046-
if (values[curr].name === 'Bool') {
1068+
if (['Bool', 'boolean'].includes(values[curr].name)) {
10471069
if ((curr != length - 1 && delta > 0) || (curr > 0 && delta < 0)) {
10481070
until += 1
10491071
} else {
@@ -1059,6 +1081,12 @@ const findBoolTypes = (values: TypeInfo[], index: number, delta: number): number
10591081

10601082
const getMaxLengthOfStaticContentType = (type: TypeInfo): number => {
10611083
switch (trimGenericTypeName(type.name)) {
1084+
case 'uint64':
1085+
return UINT64_SIZE / BITS_IN_BYTE
1086+
case 'biguint':
1087+
return UINT512_SIZE / BITS_IN_BYTE
1088+
case 'boolean':
1089+
return 1
10621090
case 'Address':
10631091
return AddressImpl.getMaxBytesLength(type)
10641092
case 'Byte':
@@ -1073,6 +1101,8 @@ const getMaxLengthOfStaticContentType = (type: TypeInfo): number => {
10731101
return StaticBytesImpl.getMaxBytesLength(type)
10741102
case 'Tuple':
10751103
return TupleImpl.getMaxBytesLength(type)
1104+
case 'Struct':
1105+
return StructImpl.getMaxBytesLength(type)
10761106
}
10771107
throw new CodeError(`unsupported type ${type.name}`)
10781108
}
@@ -1324,3 +1354,8 @@ export const getArc4Encoded = (value: DeliberateAny): ARC4Encoded => {
13241354

13251355
throw new CodeError(`Unsupported type for encoding: ${typeof value}`)
13261356
}
1357+
1358+
export const arc4EncodedLengthImpl = (typeInfoString: string): uint64 => {
1359+
const typeInfo = JSON.parse(typeInfoString)
1360+
return getMaxLengthOfStaticContentType(typeInfo)
1361+
}

src/runtime-helpers.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ import { nameOfType } from './util'
1212
export { attachAbiMetadata } from './abi-metadata'
1313
export { emitImpl } from './impl/emit'
1414
export * from './impl/encoded-types'
15-
export { decodeArc4Impl, encodeArc4Impl } from './impl/encoded-types'
15+
export { arc4EncodedLengthImpl, decodeArc4Impl, encodeArc4Impl } from './impl/encoded-types'
1616

1717
export function switchableValue(x: unknown): bigint | string | boolean {
1818
if (typeof x === 'boolean') return x

src/test-transformer/visitors.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -166,6 +166,8 @@ class ExpressionVisitor {
166166
const targetType = ptypes.ptypeToArc4EncodedType(type, this.helper.sourceLocation(node))
167167
const targetTypeInfo = getGenericTypeInfo(targetType)
168168
infoArg = targetTypeInfo
169+
} else if (isCallingArc4EncodedLength(stubbedFunctionName)) {
170+
infoArg = this.helper.resolveTypeParameters(updatedNode).map(getGenericTypeInfo)[0]
169171
}
170172

171173
updatedNode = stubbedFunctionName
@@ -420,10 +422,11 @@ const tryGetStubbedFunctionName = (node: ts.CallExpression, helper: VisitorHelpe
420422
if (sourceFileName && !algotsModulePaths.some((s) => sourceFileName.includes(s))) return undefined
421423
}
422424
const functionName = functionSymbol?.getName() ?? identityExpression.text
423-
const stubbedFunctionNames = ['interpretAsArc4', 'decodeArc4', 'encodeArc4', 'emit', 'methodSelector']
425+
const stubbedFunctionNames = ['interpretAsArc4', 'decodeArc4', 'encodeArc4', 'emit', 'methodSelector', 'arc4EncodedLength']
424426
return stubbedFunctionNames.includes(functionName) ? functionName : undefined
425427
}
426428

427429
const isCallingDecodeArc4 = (functionName: string | undefined): boolean => ['decodeArc4', 'encodeArc4'].includes(functionName ?? '')
430+
const isCallingArc4EncodedLength = (functionName: string | undefined): boolean => 'arc4EncodedLength' === (functionName ?? '')
428431
const isCallingEmit = (functionName: string | undefined): boolean => 'emit' === (functionName ?? '')
429432
const isCallingMethodSelector = (functionName: string | undefined): boolean => 'methodSelector' === (functionName ?? '')

tests/arc4/encode-decode-arc4.spec.ts

Lines changed: 33 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,17 @@
11
import type { biguint, bytes, uint64 } from '@algorandfoundation/algorand-typescript'
22
import { Bytes } from '@algorandfoundation/algorand-typescript'
3-
import { Bool, decodeArc4, DynamicBytes, encodeArc4, Str, Struct, Tuple, UintN } from '@algorandfoundation/algorand-typescript/arc4'
3+
import type { Address, StaticArray, StaticBytes, UFixedNxM, UintN64 } from '@algorandfoundation/algorand-typescript/arc4'
4+
import {
5+
arc4EncodedLength,
6+
Bool,
7+
decodeArc4,
8+
DynamicBytes,
9+
encodeArc4,
10+
Str,
11+
Struct,
12+
Tuple,
13+
UintN,
14+
} from '@algorandfoundation/algorand-typescript/arc4'
415
import { describe, expect, test } from 'vitest'
516
import { MAX_UINT128 } from '../../src/constants'
617
import type { StubBytesCompat } from '../../src/impl/primitives'
@@ -122,6 +133,27 @@ describe('encodeArc4', () => {
122133
})
123134
})
124135

136+
class StaticStruct extends Struct<{
137+
a: UintN64
138+
b: StaticArray<Bool, 10>
139+
c: Bool
140+
d: StaticBytes<32>
141+
e: Address
142+
f: StaticArray<UFixedNxM<256, 16>, 10>
143+
}> {}
144+
describe('arc4EncodedLength', () => {
145+
test('should return the correct length', () => {
146+
expect(arc4EncodedLength<uint64>()).toEqual(8)
147+
expect(arc4EncodedLength<biguint>()).toEqual(64)
148+
expect(arc4EncodedLength<boolean>()).toEqual(1)
149+
expect(arc4EncodedLength<UintN<512>>()).toEqual(64)
150+
expect(arc4EncodedLength<[uint64, uint64, boolean]>()).toEqual(17)
151+
expect(arc4EncodedLength<[uint64, uint64, boolean, boolean]>()).toEqual(17)
152+
expect(arc4EncodedLength<Tuple<[StaticArray<Bool, 10>, Bool]>>()).toEqual(3)
153+
expect(arc4EncodedLength<StaticStruct>()).toEqual(395)
154+
})
155+
})
156+
125157
const compareNativeValues = (a: DeliberateAny, b: DeliberateAny) => {
126158
if (Array.isArray(a)) {
127159
for (let i = 0; i < a.length; i++) {

tests/arc4/tuple.spec.ts

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,29 @@ const otherAbiString = new Str('hello')
2626
const otherAbiUint8 = new UintN<8>(42)
2727

2828
const testData = [
29+
{
30+
abiTypeString: '(bool[10],bool,bool)',
31+
nativeValues() {
32+
return [
33+
[nativeBool, nativeBool, nativeBool, nativeBool, nativeBool, nativeBool, nativeBool, nativeBool, nativeBool, nativeBool],
34+
nativeBool,
35+
nativeBool,
36+
]
37+
},
38+
abiValues() {
39+
return [
40+
new StaticArray(abiBool, abiBool, abiBool, abiBool, abiBool, abiBool, abiBool, abiBool, abiBool, abiBool),
41+
abiBool,
42+
abiBool,
43+
] as readonly [StaticArray<Bool, 10>, Bool, Bool]
44+
},
45+
tuple() {
46+
return new Tuple<[StaticArray<Bool, 10>, Bool, Bool]>(...this.abiValues())
47+
},
48+
create(value: StubBytesCompat) {
49+
return interpretAsArc4<Tuple<[StaticArray<Bool, 10>, Bool, Bool]>>(asBytes(value))
50+
},
51+
},
2952
{
3053
abiTypeString: '(uint8,bool,bool,address)',
3154
nativeValues() {

0 commit comments

Comments
 (0)