Skip to content

Commit 3adae24

Browse files
committed
refactor: add stub implementation for interpretAsArc4 method
1 parent 162d638 commit 3adae24

17 files changed

+1944
-321
lines changed

src/encoders.ts

Lines changed: 17 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,62 +1,61 @@
1-
import { biguint, BigUint, bytes, Bytes, internal, TransactionType, uint64, Uint64 } from '@algorandfoundation/algorand-typescript'
1+
import { biguint, BigUint, bytes, internal, TransactionType, uint64, Uint64 } from '@algorandfoundation/algorand-typescript'
22
import { OnCompleteAction } from '@algorandfoundation/algorand-typescript/arc4'
33
import { AccountCls } from './impl/account'
44
import { ApplicationCls } from './impl/application'
55
import { AssetCls } from './impl/asset'
6+
import { arc4Encoders, getArc4Encoder } from './impl/encoded-types'
7+
import { DeliberateAny } from './typescript-helpers'
8+
import { asBytes, asUint8Array } from './util'
69

7-
export interface TypeInfo {
10+
export type TypeInfo = {
811
name: string
912
genericArgs?: TypeInfo[] | Record<string, TypeInfo>
1013
}
1114

12-
type fromBytes<T> = (val: Uint8Array, typeInfo: TypeInfo) => T
15+
export type fromBytes<T> = (val: Uint8Array | internal.primitives.StubBytesCompat, typeInfo: TypeInfo, prefix?: 'none' | 'log') => T
1316

1417
const booleanFromBytes: fromBytes<boolean> = (val) => {
15-
return internal.encodingUtil.uint8ArrayToBigInt(val) > 0n
18+
return internal.encodingUtil.uint8ArrayToBigInt(asUint8Array(val)) > 0n
1619
}
1720

1821
const bigUintFromBytes: fromBytes<biguint> = (val) => {
19-
return BigUint(internal.encodingUtil.uint8ArrayToBigInt(val))
22+
return BigUint(internal.encodingUtil.uint8ArrayToBigInt(asUint8Array(val)))
2023
}
2124

2225
const bytesFromBytes: fromBytes<bytes> = (val) => {
23-
return Bytes(val)
26+
return asBytes(val)
2427
}
2528

2629
const stringFromBytes: fromBytes<string> = (val) => {
27-
return Bytes(val).toString()
30+
return asBytes(val).toString()
2831
}
2932

3033
const uint64FromBytes: fromBytes<uint64> = (val) => {
31-
return Uint64(internal.encodingUtil.uint8ArrayToBigInt(val))
34+
return Uint64(internal.encodingUtil.uint8ArrayToBigInt(asUint8Array(val)))
3235
}
3336

3437
const onCompletionFromBytes: fromBytes<OnCompleteAction> = (val) => {
35-
return Uint64(internal.encodingUtil.uint8ArrayToBigInt(val)) as OnCompleteAction
38+
return Uint64(internal.encodingUtil.uint8ArrayToBigInt(asUint8Array(val))) as OnCompleteAction
3639
}
3740

3841
const transactionTypeFromBytes: fromBytes<TransactionType> = (val) => {
39-
return Uint64(internal.encodingUtil.uint8ArrayToBigInt(val)) as TransactionType
42+
return Uint64(internal.encodingUtil.uint8ArrayToBigInt(asUint8Array(val))) as TransactionType
4043
}
4144

42-
export const encoders = {
45+
export const encoders: Record<string, fromBytes<DeliberateAny>> = {
4346
account: AccountCls.fromBytes,
4447
application: ApplicationCls.fromBytes,
4548
asset: AssetCls.fromBytes,
46-
'bool(ean)?': booleanFromBytes,
49+
boolean: booleanFromBytes,
4750
biguint: bigUintFromBytes,
4851
bytes: bytesFromBytes,
4952
string: stringFromBytes,
5053
uint64: uint64FromBytes,
5154
OnCompleteAction: onCompletionFromBytes,
5255
TransactionType: transactionTypeFromBytes,
53-
// 'Tuple<*>': tupleFromBytes,
56+
...arc4Encoders,
5457
}
5558

5659
export const getEncoder = <T>(typeInfo: TypeInfo): fromBytes<T> => {
57-
const encoder = Object.entries(encoders).find(([k, _]) => new RegExp(`^${k}$`, 'i').test(typeInfo.name))?.[1]
58-
if (!encoder) {
59-
throw new Error(`No encoder found for type ${typeInfo.name}`)
60-
}
61-
return encoder as fromBytes<T>
60+
return getArc4Encoder<T>(typeInfo, encoders)
6261
}

src/impl/base.ts

Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { Bytes, bytes, Uint64, uint64 } from '@algorandfoundation/algorand-typescript'
1+
import { bytes, internal, Uint64, uint64 } from '@algorandfoundation/algorand-typescript'
22
import { encodingUtil } from '@algorandfoundation/puya-ts'
33
import type { TypeInfo } from '../encoders'
44

@@ -14,8 +14,12 @@ export abstract class BytesBackedCls {
1414
// this.#typeInfo = typeInfo
1515
}
1616

17-
static fromBytes<T extends BytesBackedCls>(this: { new (v: bytes, typeInfo?: TypeInfo): T }, value: Uint8Array, typeInfo?: TypeInfo) {
18-
return new this(Bytes(value), typeInfo)
17+
static fromBytes<T extends BytesBackedCls>(
18+
this: { new (v: bytes, typeInfo?: TypeInfo): T },
19+
value: internal.primitives.StubBytesCompat | Uint8Array,
20+
typeInfo?: TypeInfo,
21+
) {
22+
return new this(internal.primitives.BytesCls.fromCompat(value).asAlgoTs(), typeInfo)
1923
}
2024
}
2125

@@ -30,8 +34,9 @@ export abstract class Uint64BackedCls {
3034
this.#value = value
3135
}
3236

33-
static fromBytes<T extends Uint64BackedCls>(this: { new (v: uint64): T }, value: Uint8Array) {
34-
const uint64Value = Uint64(encodingUtil.uint8ArrayToBigInt(value))
37+
static fromBytes<T extends Uint64BackedCls>(this: { new (v: uint64): T }, value: internal.primitives.StubBytesCompat | Uint8Array) {
38+
const uint8ArrayValue = value instanceof Uint8Array ? value : internal.primitives.BytesCls.fromCompat(value).asUint8Array()
39+
const uint64Value = Uint64(encodingUtil.uint8ArrayToBigInt(uint8ArrayValue))
3540
return new this(uint64Value)
3641
}
3742
}

src/impl/encoded-types.ts

Lines changed: 118 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
11
import { BigUintCompat, Bytes, bytes, internal, StringCompat, Uint64Compat } from '@algorandfoundation/algorand-typescript'
2-
import { BitSize, Bool, Byte, Str, UFixedNxM, UintN } from '@algorandfoundation/algorand-typescript/arc4'
2+
import { ARC4Encoded, BitSize, Bool, Byte, Str, UFixedNxM, UintN } from '@algorandfoundation/algorand-typescript/arc4'
33
import { encodingUtil } from '@algorandfoundation/puya-ts'
44
import assert from 'assert'
55
import { ABI_RETURN_VALUE_LOG_PREFIX, BITS_IN_BYTE, UINT64_SIZE } from '../constants'
6-
import { TypeInfo } from '../encoders'
6+
import { fromBytes, TypeInfo } from '../encoders'
77
import { DeliberateAny } from '../typescript-helpers'
88
import { asBigUint, asBigUintCls, asBytesCls, asUint64, asUint8Array } from '../util'
99

@@ -15,10 +15,10 @@ type CompatForArc4Int<N extends BitSize> = N extends 8 | 16 | 32 | 64 ? Uint64Co
1515
export class UintNImpl<N extends BitSize> extends UintN<N> {
1616
private value: Uint8Array
1717
private bitSize: N
18-
private typeInfo: TypeInfo
18+
typeInfo: TypeInfo
1919

2020
constructor(typeInfoString: string, v?: CompatForArc4Int<N>) {
21-
super(v)
21+
super()
2222
this.typeInfo = JSON.parse(typeInfoString)
2323
this.bitSize = parseInt((this.typeInfo.genericArgs as TypeInfo[])![0].name, 10) as N
2424

@@ -40,32 +40,45 @@ export class UintNImpl<N extends BitSize> extends UintN<N> {
4040
return Bytes(this.value)
4141
}
4242

43-
static fromBytesImpl(typeInfo: string, value: internal.primitives.StubBytesCompat): UintNImpl<BitSize> {
44-
const result = new UintNImpl<BitSize>(typeInfo)
45-
result.value = asUint8Array(value)
46-
return result
43+
equals(other: this): boolean {
44+
if (!(other instanceof UintNImpl) || JSON.stringify(this.typeInfo) !== JSON.stringify(other.typeInfo)) {
45+
throw new internal.errors.CodeError(`Expected expression of type ${this.typeInfo.name}, got ${other.typeInfo.name}`)
46+
}
47+
return this.bytes.equals(other.bytes)
4748
}
4849

49-
static fromLogImpl(typeInfo: string, value: internal.primitives.StubBytesCompat): UintNImpl<BitSize> {
50-
const bytesValue = asBytesCls(value)
51-
assert(bytesValue.slice(0, 4).equals(ABI_RETURN_VALUE_LOG_PREFIX), 'ABI return prefix not found')
52-
return UintNImpl.fromBytesImpl(typeInfo, bytesValue.slice(4))
50+
static fromBytesImpl(
51+
value: internal.primitives.StubBytesCompat | Uint8Array,
52+
typeInfo: string | TypeInfo,
53+
prefix: 'none' | 'log' = 'none',
54+
): UintNImpl<BitSize> {
55+
let bytesValue = asBytesCls(value)
56+
if (prefix === 'log') {
57+
assert(bytesValue.slice(0, 4).equals(ABI_RETURN_VALUE_LOG_PREFIX), 'ABI return prefix not found')
58+
bytesValue = bytesValue.slice(4)
59+
}
60+
const typeInfoString = typeof typeInfo === 'string' ? typeInfo : JSON.stringify(typeInfo)
61+
const result = new UintNImpl<BitSize>(typeInfoString)
62+
result.value = asUint8Array(bytesValue)
63+
return result
5364
}
5465
}
5566

5667
const regExpNxM = (maxPrecision: number) => new RegExp(`^\\d*\\.?\\d{0,${maxPrecision}}$`)
5768
const trimTrailingDecimalZeros = (v: string) => v.replace(/(\d+\.\d*?)0+$/, '$1').replace(/\.$/, '')
69+
type uFixedNxMGenericArgs = { n: TypeInfo; m: TypeInfo }
5870
export class UFixedNxMImpl<N extends BitSize, M extends number> extends UFixedNxM<N, M> {
5971
private value: Uint8Array
60-
private typeInfo: TypeInfo
6172
private bitSize: N
6273
private precision: M
74+
private typeInfo: TypeInfo
6375

6476
constructor(typeInfoString: string, v: `${number}.${number}`) {
6577
super(v)
6678
this.typeInfo = JSON.parse(typeInfoString)
67-
this.bitSize = parseInt((this.typeInfo.genericArgs as TypeInfo[])![0].name, 10) as N
68-
this.precision = parseInt((this.typeInfo.genericArgs as TypeInfo[])![1].name, 10) as M
79+
const genericArgs = this.typeInfo.genericArgs as uFixedNxMGenericArgs
80+
this.bitSize = parseInt(genericArgs.n.name, 10) as N
81+
this.precision = parseInt(genericArgs.m.name, 10) as M
6982

7083
const trimmedValue = trimTrailingDecimalZeros(v)
7184
assert(regExpNxM(this.precision).test(trimmedValue), `expected positive decimal literal with max of ${this.precision} decimal places`)
@@ -86,28 +99,34 @@ export class UFixedNxMImpl<N extends BitSize, M extends number> extends UFixedNx
8699
return Bytes(this.value)
87100
}
88101

89-
equals(other: UFixedNxM<DeliberateAny, DeliberateAny>): boolean {
90-
const otherImpl = other as UFixedNxMImpl<DeliberateAny, DeliberateAny>
91-
return this.bitSize === otherImpl.bitSize && this.precision === otherImpl.precision && this.value === otherImpl.value
102+
equals(other: this): boolean {
103+
if (!(other instanceof UFixedNxMImpl) || JSON.stringify(this.typeInfo) !== JSON.stringify(other.typeInfo)) {
104+
throw new internal.errors.CodeError(`Expected expression of type ${this.typeInfo.name}, got ${other.typeInfo.name}`)
105+
}
106+
return this.bytes.equals(other.bytes)
92107
}
93108

94-
static fromBytesImpl(typeInfo: string, value: internal.primitives.StubBytesCompat): UFixedNxM<BitSize, number> {
95-
const result = new UFixedNxMImpl<BitSize, number>(typeInfo, '0.0')
96-
result.value = asUint8Array(value)
109+
static fromBytesImpl(
110+
value: internal.primitives.StubBytesCompat | Uint8Array,
111+
typeInfo: string | TypeInfo,
112+
prefix: 'none' | 'log' = 'none',
113+
): UFixedNxM<BitSize, number> {
114+
let bytesValue = asBytesCls(value)
115+
if (prefix === 'log') {
116+
assert(bytesValue.slice(0, 4).equals(ABI_RETURN_VALUE_LOG_PREFIX), 'ABI return prefix not found')
117+
bytesValue = bytesValue.slice(4)
118+
}
119+
const typeInfoString = typeof typeInfo === 'string' ? typeInfo : JSON.stringify(typeInfo)
120+
const result = new UFixedNxMImpl<BitSize, number>(typeInfoString, '0.0')
121+
result.value = asUint8Array(bytesValue)
97122
return result
98123
}
99-
100-
static fromLogImpl(typeInfo: string, value: internal.primitives.StubBytesCompat): UFixedNxM<BitSize, number> {
101-
const bytesValue = asBytesCls(value)
102-
assert(bytesValue.slice(0, 4).equals(ABI_RETURN_VALUE_LOG_PREFIX), 'ABI return prefix not found')
103-
return UFixedNxMImpl.fromBytesImpl(typeInfo, bytesValue.slice(4))
104-
}
105124
}
106125

107126
export class ByteImpl extends Byte {
108127
private value: UintNImpl<8>
109128

110-
constructor(typeInfoString: string, v: CompatForArc4Int<8>) {
129+
constructor(typeInfoString: string, v?: CompatForArc4Int<8>) {
111130
super(v)
112131
this.value = new UintNImpl<8>(typeInfoString, v)
113132
}
@@ -120,19 +139,30 @@ export class ByteImpl extends Byte {
120139
return this.value.bytes
121140
}
122141

123-
static fromBytesImpl(typeInfo: string, value: internal.primitives.StubBytesCompat): Byte {
124-
return UintNImpl.fromBytesImpl(typeInfo, value) as Byte
142+
equals(other: this): boolean {
143+
if (!(other instanceof ByteImpl) || JSON.stringify(this.value.typeInfo) !== JSON.stringify(other.value.typeInfo)) {
144+
throw new internal.errors.CodeError(`Expected expression of type ${this.value.typeInfo.name}, got ${other.value.typeInfo.name}`)
145+
}
146+
return this.bytes.equals(other.bytes)
125147
}
126148

127-
static fromLogImpl(typeInfo: string, value: internal.primitives.StubBytesCompat): Byte {
128-
return UintNImpl.fromLogImpl(typeInfo, value) as Byte
149+
static fromBytesImpl(
150+
value: internal.primitives.StubBytesCompat | Uint8Array,
151+
typeInfo: string | TypeInfo,
152+
prefix: 'none' | 'log' = 'none',
153+
): ByteImpl {
154+
const uintNValue = UintNImpl.fromBytesImpl(value, typeInfo, prefix) as UintNImpl<8>
155+
const typeInfoString = typeof typeInfo === 'string' ? typeInfo : JSON.stringify(typeInfo)
156+
const result = new ByteImpl(typeInfoString)
157+
result.value = uintNValue
158+
return result
129159
}
130160
}
131161

132162
export class StrImpl extends Str {
133163
private value: Uint8Array
134164

135-
constructor(s?: StringCompat) {
165+
constructor(_typeInfoString: string, s?: StringCompat) {
136166
super()
137167
const bytesValue = asBytesCls(s ?? '')
138168
const bytesLength = encodeLength(bytesValue.length.asNumber())
@@ -146,16 +176,27 @@ export class StrImpl extends Str {
146176
return Bytes(this.value)
147177
}
148178

149-
static fromBytesImpl(bytes: internal.primitives.StubBytesCompat): StrImpl {
150-
const strValue = new StrImpl()
151-
strValue.value = asUint8Array(bytes)
152-
return strValue
179+
equals(other: this): boolean {
180+
if (!(other instanceof StrImpl)) {
181+
throw new internal.errors.CodeError(`Expected expression of type ${Str}, got ${(other as object).constructor.name}`)
182+
}
183+
return this.bytes.equals(other.bytes)
153184
}
154185

155-
static fromLogImpl(value: internal.primitives.StubBytesCompat): StrImpl {
156-
const bytesValue = asBytesCls(value)
157-
assert(bytesValue.slice(0, 4).equals(ABI_RETURN_VALUE_LOG_PREFIX), 'ABI return prefix not found')
158-
return StrImpl.fromBytesImpl(bytesValue.slice(4))
186+
static fromBytesImpl(
187+
value: internal.primitives.StubBytesCompat | Uint8Array,
188+
typeInfo: string | TypeInfo,
189+
prefix: 'none' | 'log' = 'none',
190+
): StrImpl {
191+
let bytesValue = asBytesCls(value)
192+
if (prefix === 'log') {
193+
assert(bytesValue.slice(0, 4).equals(ABI_RETURN_VALUE_LOG_PREFIX), 'ABI return prefix not found')
194+
bytesValue = bytesValue.slice(4)
195+
}
196+
const typeInfoString = typeof typeInfo === 'string' ? typeInfo : JSON.stringify(typeInfo)
197+
const result = new StrImpl(typeInfoString)
198+
result.value = asUint8Array(bytesValue)
199+
return result
159200
}
160201
}
161202
const TRUE_BIGINT_VALUE = 128n
@@ -164,7 +205,7 @@ const FALSE_BIGINT_VALUE = 0n
164205
export class BoolImpl extends Bool {
165206
private value: Uint8Array
166207

167-
constructor(v?: boolean) {
208+
constructor(_typeInfoString: string, v?: boolean) {
168209
super(v)
169210
this.value = encodingUtil.bigIntToUint8Array(v ? TRUE_BIGINT_VALUE : FALSE_BIGINT_VALUE, 1)
170211
}
@@ -177,15 +218,43 @@ export class BoolImpl extends Bool {
177218
return Bytes(this.value)
178219
}
179220

180-
static fromBytesImpl(value: internal.primitives.StubBytesCompat): BoolImpl {
181-
const result = new BoolImpl()
182-
result.value = asUint8Array(value)
221+
static fromBytesImpl(
222+
value: internal.primitives.StubBytesCompat | Uint8Array,
223+
typeInfo: string | TypeInfo,
224+
prefix: 'none' | 'log' = 'none',
225+
): BoolImpl {
226+
let bytesValue = asBytesCls(value)
227+
if (prefix === 'log') {
228+
assert(bytesValue.slice(0, 4).equals(ABI_RETURN_VALUE_LOG_PREFIX), 'ABI return prefix not found')
229+
bytesValue = bytesValue.slice(4)
230+
}
231+
const typeInfoString = typeof typeInfo === 'string' ? typeInfo : JSON.stringify(typeInfo)
232+
const result = new BoolImpl(typeInfoString)
233+
result.value = asUint8Array(bytesValue)
183234
return result
184235
}
236+
}
237+
238+
export function interpretAsArc4Impl<T extends ARC4Encoded>(
239+
typeInfoString: string,
240+
bytes: internal.primitives.StubBytesCompat,
241+
prefix: 'none' | 'log' = 'none',
242+
): T {
243+
const typeInfo = JSON.parse(typeInfoString)
244+
return getArc4Encoder<T>(typeInfo)(bytes, typeInfo, prefix)
245+
}
185246

186-
static fromLogImpl(value: internal.primitives.StubBytesCompat): BoolImpl {
187-
const bytesValue = asBytesCls(value)
188-
assert(bytesValue.slice(0, 4).equals(ABI_RETURN_VALUE_LOG_PREFIX), 'ABI return prefix not found')
189-
return BoolImpl.fromBytesImpl(bytesValue.slice(4))
247+
export const arc4Encoders: Record<string, fromBytes<DeliberateAny>> = {
248+
Bool: BoolImpl.fromBytesImpl,
249+
Byte: ByteImpl.fromBytesImpl,
250+
Str: StrImpl.fromBytesImpl,
251+
'UintN<.*>': UintNImpl.fromBytesImpl,
252+
'UFixedNxM<.*>': UFixedNxMImpl.fromBytesImpl,
253+
}
254+
export const getArc4Encoder = <T>(typeInfo: TypeInfo, encoders?: Record<string, fromBytes<DeliberateAny>>): fromBytes<T> => {
255+
const encoder = Object.entries(encoders ?? arc4Encoders).find(([k, _]) => new RegExp(`^${k}$`, 'i').test(typeInfo.name))?.[1]
256+
if (!encoder) {
257+
throw new Error(`No encoder found for type ${typeInfo.name}`)
190258
}
259+
return encoder as fromBytes<T>
191260
}

0 commit comments

Comments
 (0)