Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
237 changes: 111 additions & 126 deletions package-lock.json

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,7 @@
},
"dependencies": {
"@algorandfoundation/algorand-typescript": "^0.0.1-alpha.23",
"@algorandfoundation/puya-ts": "^1.0.0-alpha.35",
"@algorandfoundation/puya-ts": "^1.0.0-alpha.36",
"elliptic": "^6.5.7",
"js-sha256": "^0.11.0",
"js-sha3": "^0.9.3",
Expand Down
132 changes: 120 additions & 12 deletions src/impl/encoded-types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,14 +24,20 @@ import {
BITS_IN_BYTE,
UINT64_SIZE,
} from '../constants'
import { lazyContext } from '../context-helpers/internal-context'
import { fromBytes, TypeInfo } from '../encoders'
import { DeliberateAny } from '../typescript-helpers'
import { asBigUint, asBigUintCls, asBytesCls, asUint64, asUint8Array, conactUint8Arrays, uint8ArrayToNumber } from '../util'
import { asBigInt, asBigUint, asBigUintCls, asBytesCls, asUint64, asUint8Array, conactUint8Arrays, uint8ArrayToNumber } from '../util'
import { AccountCls } from './account'
import { ApplicationCls } from './application'
import { AssetCls } from './asset'
import { ApplicationTransaction } 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 internal.primitives.BytesCls(encodingUtil.bigIntToUint8Array(BigInt(length), ABI_LENGTH_SIZE))

type CompatForArc4Int<N extends BitSize> = N extends 8 | 16 | 32 | 64 ? Uint64Compat : BigUintCompat
export class UintNImpl<N extends BitSize> extends UintN<N> {
private value: Uint8Array
Expand Down Expand Up @@ -99,7 +105,7 @@ export class UFixedNxMImpl<N extends BitSize, M extends number> extends UFixedNx
private value: Uint8Array
private bitSize: N
private precision: M
private typeInfo: TypeInfo
typeInfo: TypeInfo

constructor(typeInfo: TypeInfo | string, v: `${number}.${number}`) {
super(v)
Expand Down Expand Up @@ -163,7 +169,10 @@ export class UFixedNxMImpl<N extends BitSize, M extends number> extends UFixedNx
export class ByteImpl extends Byte {
private value: UintNImpl<8>

constructor(typeInfo: TypeInfo | string, v?: CompatForArc4Int<8>) {
constructor(
public typeInfo: TypeInfo | string,
v?: CompatForArc4Int<8>,
) {
super(v)
this.value = new UintNImpl<8>(typeInfo, v)
}
Expand Down Expand Up @@ -202,7 +211,10 @@ export class ByteImpl extends Byte {
export class StrImpl extends Str {
private value: Uint8Array

constructor(_typeInfo: TypeInfo | string, s?: StringCompat) {
constructor(
public typeInfo: TypeInfo | string,
s?: StringCompat,
) {
super()
const bytesValue = asBytesCls(s ?? '')
const bytesLength = encodeLength(bytesValue.length.asNumber())
Expand Down Expand Up @@ -244,7 +256,10 @@ const FALSE_BIGINT_VALUE = 0n
export class BoolImpl extends Bool {
private value: Uint8Array

constructor(_typeInfo: TypeInfo | string, v?: boolean) {
constructor(
public typeInfo: TypeInfo | string,
v?: boolean,
) {
super(v)
this.value = encodingUtil.bigIntToUint8Array(v ? TRUE_BIGINT_VALUE : FALSE_BIGINT_VALUE, 1)
}
Expand Down Expand Up @@ -320,8 +335,8 @@ const arrayProxyHandler = <TItem>() => ({
export class StaticArrayImpl<TItem extends ARC4Encoded, TLength extends number> extends StaticArray<TItem, TLength> {
private value?: TItem[]
private uint8ArrayValue?: Uint8Array
private typeInfo: TypeInfo
private size: number
typeInfo: TypeInfo
genericArgs: StaticArrayGenericArgs

constructor(typeInfo: TypeInfo | string, ...items: TItem[] & { length: TLength })
Expand Down Expand Up @@ -383,6 +398,10 @@ export class StaticArrayImpl<TItem extends ARC4Encoded, TLength extends number>
return StaticArrayImpl.fromBytesImpl(this.bytes, JSON.stringify(this.typeInfo)) as StaticArrayImpl<TItem, TLength>
}

get native(): TItem[] {
return this.items
}

static fromBytesImpl(
value: internal.primitives.StubBytesCompat | Uint8Array,
typeInfo: string | TypeInfo,
Expand Down Expand Up @@ -425,7 +444,7 @@ export class StaticArrayImpl<TItem extends ARC4Encoded, TLength extends number>
}

export class AddressImpl extends Address {
private typeInfo: TypeInfo
typeInfo: TypeInfo
private value: StaticArrayImpl<ByteImpl, 32>

constructor(typeInfo: TypeInfo | string, value?: Account | string | bytes) {
Expand Down Expand Up @@ -499,7 +518,7 @@ const readLength = (value: Uint8Array): readonly [number, Uint8Array] => {
export class DynamicArrayImpl<TItem extends ARC4Encoded> extends DynamicArray<TItem> {
private value?: TItem[]
private uint8ArrayValue?: Uint8Array
private typeInfo: TypeInfo
typeInfo: TypeInfo
genericArgs: DynamicArrayGenericArgs

constructor(typeInfo: TypeInfo | string, ...items: TItem[]) {
Expand Down Expand Up @@ -554,6 +573,10 @@ export class DynamicArrayImpl<TItem extends ARC4Encoded> extends DynamicArray<TI
return DynamicArrayImpl.fromBytesImpl(this.bytes, JSON.stringify(this.typeInfo)) as DynamicArrayImpl<TItem>
}

get native(): TItem[] {
return this.items
}

push(...values: TItem[]) {
const items = this.items
items.push(...values)
Expand Down Expand Up @@ -594,7 +617,7 @@ export class DynamicArrayImpl<TItem extends ARC4Encoded> extends DynamicArray<TI
export class TupleImpl<TTuple extends [ARC4Encoded, ...ARC4Encoded[]]> extends Tuple<TTuple> {
private value?: TTuple
private uint8ArrayValue?: Uint8Array
private typeInfo: TypeInfo
typeInfo: TypeInfo
genericArgs: TypeInfo[]

constructor(typeInfo: TypeInfo | string)
Expand Down Expand Up @@ -691,7 +714,6 @@ export class TupleImpl<TTuple extends [ARC4Encoded, ...ARC4Encoded[]]> extends T
type StructConstraint = Record<string, ARC4Encoded>
export class StructImpl<T extends StructConstraint> extends (Struct<StructConstraint> as DeliberateAny) {
private uint8ArrayValue?: Uint8Array
private typeInfo: TypeInfo
genericArgs: Record<string, TypeInfo>

constructor(typeInfo: TypeInfo | string, value: T = {} as T) {
Expand Down Expand Up @@ -737,6 +759,10 @@ export class StructImpl<T extends StructConstraint> extends (Struct<StructConstr
return result as T
}

get native(): T {
return this.items
}

private decodeAsProperties() {
if (this.uint8ArrayValue) {
const values = decode(this.uint8ArrayValue, Object.values(this.genericArgs))
Expand Down Expand Up @@ -769,7 +795,7 @@ export class StructImpl<T extends StructConstraint> extends (Struct<StructConstr
}

export class DynamicBytesImpl extends DynamicBytes {
private typeInfo: TypeInfo
typeInfo: TypeInfo
private value: DynamicArrayImpl<ByteImpl>

constructor(typeInfo: TypeInfo | string, value?: bytes | string) {
Expand Down Expand Up @@ -825,7 +851,7 @@ export class DynamicBytesImpl extends DynamicBytes {

export class StaticBytesImpl extends StaticBytes {
private value: StaticArrayImpl<ByteImpl, number>
private typeInfo: TypeInfo
typeInfo: TypeInfo

constructor(typeInfo: TypeInfo | string, value?: bytes | string) {
super(value)
Expand Down Expand Up @@ -1161,3 +1187,85 @@ export const getArc4TypeName = (typeInfo: TypeInfo): string | undefined => {
else if (typeof name === 'function') return name(typeInfo)
return undefined
}

export function decodeArc4Impl<T>(sourceTypeInfoString: string, bytes: internal.primitives.StubBytesCompat): T {
const sourceTypeInfo = JSON.parse(sourceTypeInfoString)
const encoder = getArc4Encoder(sourceTypeInfo)
const source = encoder(bytes, sourceTypeInfo)

return getNativeValue(source) as T
}

export function encodeArc4Impl<T>(_targetTypeInfoString: string, source: T): bytes {
const arc4Encoded = getArc4Encoded(source)
return arc4Encoded.bytes
}

const getNativeValue = (value: DeliberateAny): DeliberateAny => {
const native = (value as DeliberateAny).native
if (Array.isArray(native)) {
return native.map((item) => getNativeValue(item))
} else if (native instanceof internal.primitives.AlgoTsPrimitiveCls) {
return native
} else if (typeof native === 'object') {
return Object.fromEntries(Object.entries(native).map(([key, value]) => [key, getNativeValue(value)]))
}
return native
}

const getArc4Encoded = (value: DeliberateAny): ARC4Encoded => {
if (value instanceof ARC4Encoded) {
return value
}
if (value instanceof AccountCls) {
const index = (lazyContext.activeGroup.activeTransaction as ApplicationTransaction).apat.indexOf(value)
return new UintNImpl({ name: 'UintN<64>', genericArgs: [{ name: '64' }] }, asBigInt(index))
}
if (value instanceof AssetCls) {
const index = (lazyContext.activeGroup.activeTransaction as ApplicationTransaction).apas.indexOf(value)
return new UintNImpl({ name: 'UintN<64>', genericArgs: [{ name: '64' }] }, asBigInt(index))
}
if (value instanceof ApplicationCls) {
const index = (lazyContext.activeGroup.activeTransaction as ApplicationTransaction).apfa.indexOf(value)
return new UintNImpl({ name: 'UintN<64>', genericArgs: [{ name: '64' }] }, asBigInt(index))
}
if (typeof value === 'boolean') {
return new BoolImpl({ name: 'Bool' }, value)
}
if (value instanceof internal.primitives.Uint64Cls || typeof value === 'number') {
return new UintNImpl({ name: 'UintN<64>', genericArgs: [{ name: '64' }] }, asBigInt(value))
}
if (value instanceof internal.primitives.BigUintCls) {
return new UintNImpl({ name: 'UintN<512>', genericArgs: [{ name: '512' }] }, value.asBigInt())
}
if (typeof value === 'bigint') {
return new UintNImpl({ name: 'UintN<512>', genericArgs: [{ name: '512' }] }, value)
}
if (value instanceof internal.primitives.BytesCls) {
return new DynamicBytesImpl(
{ name: 'DynamicBytes', genericArgs: { elementType: { name: 'Byte', genericArgs: [{ name: '8' }] } } },
value.asAlgoTs(),
)
}
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))
}, [])
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 []))
}
if (typeof value === 'object') {
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 typeInfo = { name: 'Struct', 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]])))
}

throw internal.errors.codeError(`Unsupported type for encoding: ${typeof value}`)
}
9 changes: 9 additions & 0 deletions src/impl/transactions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -307,6 +307,15 @@ export class ApplicationTransaction extends TransactionBase implements gtxn.Appl
get lastLog() {
return this.#appLogs.at(-1) ?? lazyContext.getApplicationData(this.appId.id).appLogs.at(-1) ?? Bytes()
}
get apat() {
return this.#accounts
}
get apas() {
return this.#assets
}
get apfa() {
return this.#apps
}
appArgs(index: internal.primitives.StubUint64Compat): bytes {
return toBytes(this.#appArgs[asNumber(index)])
}
Expand Down
1 change: 1 addition & 0 deletions src/runtime-helpers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import { nameOfType } from './util'

export { attachAbiMetadata } from './abi-metadata'
export * from './impl/encoded-types'
export { decodeArc4Impl, encodeArc4Impl } from './impl/encoded-types'
export { ensureBudgetImpl } from './impl/ensure-budget'
export { TemplateVarImpl } from './impl/template-var'

Expand Down
10 changes: 6 additions & 4 deletions src/test-transformer/node-factory.ts
Original file line number Diff line number Diff line change
Expand Up @@ -94,17 +94,19 @@ export const nodeFactory = {
)
},

callStubbedFunction(functionName: string, node: ts.CallExpression, typeInfo?: TypeInfo) {
const infoString = JSON.stringify(typeInfo)
callStubbedFunction(functionName: string, node: ts.CallExpression, ...typeInfos: (TypeInfo | undefined)[]) {
const infoStringArray = typeInfos.length ? typeInfos.map((typeInfo) => JSON.stringify(typeInfo)) : undefined
const updatedPropertyAccessExpression = factory.createPropertyAccessExpression(
factory.createIdentifier('runtimeHelpers'),
`${functionName}Impl`,
)

const typeInfoArgs = infoStringArray
? infoStringArray?.filter((s) => !!s).map((infoString) => factory.createStringLiteral(infoString))
: undefined
return factory.createCallExpression(
updatedPropertyAccessExpression,
node.typeArguments,
[infoString ? factory.createStringLiteral(infoString) : undefined, ...(node.arguments ?? [])].filter((arg) => !!arg),
[...(typeInfoArgs ?? []), ...(node.arguments ?? [])].filter((arg) => !!arg),
)
},
} satisfies Record<string, (...args: DeliberateAny[]) => ts.Node>
11 changes: 10 additions & 1 deletion src/test-transformer/visitors.ts
Original file line number Diff line number Diff line change
Expand Up @@ -111,7 +111,13 @@ class ExpressionVisitor {
}
if (ts.isCallExpression(updatedNode)) {
const stubbedFunctionName = tryGetStubbedFunctionName(updatedNode, this.helper)
updatedNode = stubbedFunctionName ? nodeFactory.callStubbedFunction(stubbedFunctionName, updatedNode, info) : updatedNode
const infos = [info]
if (isCallingDecodeArc4(stubbedFunctionName)) {
const targetType = ptypes.ptypeToArc4EncodedType(type, this.helper.sourceLocation(node))
const targetTypeInfo = getGenericTypeInfo(targetType)
infos[0] = targetTypeInfo
}
updatedNode = stubbedFunctionName ? nodeFactory.callStubbedFunction(stubbedFunctionName, updatedNode, ...infos) : updatedNode
}
return isGeneric
? nodeFactory.captureGenericTypeInfo(ts.visitEachChild(updatedNode, this.visit, this.context), JSON.stringify(info))
Expand Down Expand Up @@ -289,6 +295,7 @@ const isGenericType = (type: ptypes.PType): boolean =>
ptypes.StaticArrayType,
ptypes.UFixedNxMType,
ptypes.UintNType,
ptypes.TuplePType,
)

const isArc4EncodedType = (type: ptypes.PType): boolean =>
Expand Down Expand Up @@ -356,3 +363,5 @@ const tryGetStubbedFunctionName = (node: ts.CallExpression, helper: VisitorHelpe
const stubbedFunctionNames = ['interpretAsArc4', 'decodeArc4', 'encodeArc4', 'TemplateVar', 'ensureBudget']
return stubbedFunctionNames.includes(functionName) ? functionName : undefined
}

const isCallingDecodeArc4 = (functionName: string | undefined): boolean => ['decodeArc4', 'encodeArc4'].includes(functionName ?? '')
7 changes: 5 additions & 2 deletions src/value-generators/arc4.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,10 @@ export class Arc4ValueGenerator {
* */
address(): arc4.Address {
const source = new AvmValueGenerator().account()
const result = new AddressImpl({ name: 'StaticArray', genericArgs: { elementType: { name: 'Byte' }, size: { name: '32' } } }, source)
const result = new AddressImpl(
{ name: 'StaticArray', genericArgs: { elementType: { name: 'Byte', genericArgs: [{ name: '8' }] }, size: { name: '32' } } },
source,
)
return result
}

Expand Down Expand Up @@ -93,7 +96,7 @@ export class Arc4ValueGenerator {
* */
dynamicBytes(n: number): arc4.DynamicBytes {
return new DynamicBytesImpl(
{ name: 'DynamicArray', genericArgs: { elementType: { name: 'Byte' } } },
{ name: 'DynamicBytes', genericArgs: { elementType: { name: 'Byte', genericArgs: [{ name: '8' }] } } },
getRandomBytes(n / BITS_IN_BYTE).asAlgoTs(),
)
}
Expand Down
Loading
Loading