Skip to content

Commit

Permalink
Merge pull request #93 from algorandfoundation/fix/itxn-bugs
Browse files Browse the repository at this point in the history
fix: Add missing itxn fields and fix compatible type resolution
  • Loading branch information
tristanmenzel authored Feb 1, 2025
2 parents dc9ad74 + 757a8e0 commit 8d3dbc2
Show file tree
Hide file tree
Showing 38 changed files with 37,409 additions and 3,874 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/ci-all.yml
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,6 @@ jobs:
pre-test-script: |
pipx install algokit --python 3.12.6
algokit localnet reset --update
pipx install puyapy --python 3.12.6
npx tsx scripts/install-puyapy.ts
test-script: npm run test:ci
output-test-results: true
2 changes: 1 addition & 1 deletion package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion packages/algo-ts/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@algorandfoundation/algorand-typescript",
"version": "1.0.0-beta.14",
"version": "1.0.0-beta.15",
"description": "This package contains definitions for the types which comprise Algorand TypeScript which can be compiled to run on the Algorand Virtual Machine using the Puya compiler.",
"private": false,
"main": "index.js",
Expand Down
108 changes: 72 additions & 36 deletions packages/algo-ts/src/itxn.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ import { NoImplementation } from './impl/errors'
import { bytes, uint64 } from './primitives'
import type { Account, Application, Asset } from './reference'
import type * as txnTypes from './transactions'
import { DeliberateAny } from './typescript-helpers'

const isItxn = Symbol('isItxn')

Expand All @@ -26,17 +25,31 @@ export interface ApplicationInnerTxn extends txnTypes.ApplicationTxn {
[isItxn]?: true
}

type AccountInput = Account | bytes | string
type AssetInput = Asset | uint64
type ApplicationInput = Application | uint64

export interface CommonTransactionFields {
/**
* 32 byte address
*/
sender?: Account | string
sender?: AccountInput

/**
* microalgos
*/
fee?: uint64

/**
* round number
*/
firstValid?: uint64

/**
* UNIX timestamp of block before txn.FirstValid. Fails if negative
*/
firstValidTime?: uint64

/**
* Any data up to 1024 bytes
*/
Expand All @@ -50,7 +63,7 @@ export interface CommonTransactionFields {
/**
* 32 byte Sender's new AuthAddr
*/
rekeyTo?: Account | string
rekeyTo?: AccountInput
}

export interface PaymentFields extends CommonTransactionFields {
Expand All @@ -62,11 +75,11 @@ export interface PaymentFields extends CommonTransactionFields {
/**
* The address of the receiver
*/
receiver?: Account
receiver?: AccountInput
/**
* If set, bring the sender balance to 0 and send all remaining balance to this address
*/
closeRemainderTo?: Account
closeRemainderTo?: AccountInput
}
export interface KeyRegistrationFields extends CommonTransactionFields {
/**
Expand Down Expand Up @@ -106,22 +119,22 @@ export interface KeyRegistrationFields extends CommonTransactionFields {
}
export interface AssetTransferFields extends CommonTransactionFields {
/** The asset being transferred */
xferAsset: Asset
xferAsset: AssetInput
/** The amount of the asset being transferred */
assetAmount?: uint64
/** The clawback target */
assetSender?: Account
assetSender?: AccountInput
/** The receiver of the asset */
assetReceiver?: Account
assetReceiver?: AccountInput
/** The address to close the asset to */
assetCloseTo?: Account
assetCloseTo?: AccountInput
}
export interface AssetConfigFields extends CommonTransactionFields {
configAsset?: Asset
manager?: Account
reserve?: Account
freeze?: Account
clawback?: Account
configAsset?: AssetInput
manager?: AccountInput
reserve?: AccountInput
freeze?: AccountInput
clawback?: AccountInput
assetName?: string | bytes
unitName?: string | bytes
total?: uint64
Expand All @@ -131,12 +144,12 @@ export interface AssetConfigFields extends CommonTransactionFields {
metadataHash?: bytes
}
export interface AssetFreezeFields extends CommonTransactionFields {
freezeAsset: Asset | uint64
freezeAccount?: Account | string
freezeAsset: AssetInput
freezeAccount?: AccountInput
frozen?: boolean
}
export interface ApplicationCallFields extends CommonTransactionFields {
appId?: Application | uint64
appId?: ApplicationInput
approvalProgram?: bytes | readonly [...bytes[]]
clearStateProgram?: bytes | readonly [...bytes[]]
onCompletion?: OnCompleteAction | uint64
Expand All @@ -146,32 +159,55 @@ export interface ApplicationCallFields extends CommonTransactionFields {
localNumBytes?: uint64
extraProgramPages?: uint64
appArgs?: readonly [...unknown[]]
accounts?: readonly [...Account[]]
assets?: readonly [...Asset[]]
apps?: readonly [...Application[]]
accounts?: readonly [...AccountInput[]]
assets?: readonly [...AssetInput[]]
apps?: readonly [...ApplicationInput[]]
}

export type InnerTransaction<TFields, TTransaction> = {
submit(): TTransaction
set(p: Partial<TFields>): void
copy(): InnerTransaction<TFields, TTransaction>
}
export type InnerTransaction =
| PaymentItxnParams
| KeyRegistrationItxnParams
| AssetConfigItxnParams
| AssetTransferItxnParams
| AssetFreezeItxnParams
| ApplicationCallItxnParams

export type InnerTxnList = [...InnerTransaction<DeliberateAny, DeliberateAny>[]]
export type InnerTxnList = [...InnerTransaction[]]

export type TxnFor<TFields extends InnerTxnList> = TFields extends [
InnerTransaction<DeliberateAny, infer TTxn>,
...infer TRest extends InnerTxnList,
]
export type TxnFor<TFields extends InnerTxnList> = TFields extends [{ submit(): infer TTxn }, ...infer TRest extends InnerTxnList]
? [TTxn, ...TxnFor<TRest>]
: []

export type PaymentItxnParams = InnerTransaction<PaymentFields, PaymentInnerTxn>
export type KeyRegistrationItxnParams = InnerTransaction<KeyRegistrationFields, KeyRegistrationInnerTxn>
export type AssetConfigItxnParams = InnerTransaction<AssetConfigFields, AssetConfigInnerTxn>
export type AssetTransferItxnParams = InnerTransaction<AssetTransferFields, AssetTransferInnerTxn>
export type AssetFreezeItxnParams = InnerTransaction<AssetFreezeFields, AssetFreezeInnerTxn>
export type ApplicationCallItxnParams = InnerTransaction<ApplicationCallFields, ApplicationInnerTxn>
export interface PaymentItxnParams {
submit(): PaymentInnerTxn
set(p: Partial<PaymentFields>): void
copy(): PaymentItxnParams
}
export interface KeyRegistrationItxnParams {
submit(): KeyRegistrationInnerTxn
set(p: Partial<KeyRegistrationFields>): void
copy(): KeyRegistrationItxnParams
}
export interface AssetConfigItxnParams {
submit(): AssetConfigInnerTxn
set(p: Partial<AssetConfigFields>): void
copy(): AssetConfigItxnParams
}
export interface AssetTransferItxnParams {
submit(): AssetTransferInnerTxn
set(p: Partial<AssetTransferFields>): void
copy(): AssetTransferItxnParams
}
export interface AssetFreezeItxnParams {
submit(): AssetFreezeInnerTxn
set(p: Partial<AssetFreezeFields>): void
copy(): AssetFreezeItxnParams
}
export interface ApplicationCallItxnParams {
submit(): ApplicationInnerTxn
set(p: Partial<ApplicationCallFields>): void
copy(): ApplicationCallItxnParams
}

export function submitGroup<TFields extends InnerTxnList>(...transactionFields: TFields): TxnFor<TFields> {
throw new NoImplementation()
Expand Down
10 changes: 10 additions & 0 deletions scripts/install-puyapy.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import { spawnSync } from 'node:child_process'
import { Constants } from '../src/constants'

function installPuyapy(version: string) {
spawnSync('pipx', ['install', `puyapy==${version}`, '--python', '3.12'], {
stdio: 'inherit',
})
}

installPuyapy(Constants.targetedPuyaVersion)
34 changes: 22 additions & 12 deletions src/awst/intrinsic-factory.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,21 +3,31 @@ import { bigIntToUint8Array } from '../util'
import { nodeFactory } from './node-factory'
import type { Expression } from './nodes'
import * as awst from './nodes'
import { BytesEncoding } from './nodes'
import type { SourceLocation } from './source-location'
import { BytesConstant, BytesEncoding, StringConstant } from './nodes'
import { SourceLocation } from './source-location'
import { wtypes } from './wtypes'

export const intrinsicFactory = {
bytesConcat({
left,
right,
sourceLocation,
}: {
left: awst.Expression
right: awst.Expression
sourceLocation: SourceLocation
}): awst.IntrinsicCall {
// invariant(left.wtype.equals(right.wtype), 'left and right operand wtypes must match')
bytesConcat({ left, right, sourceLocation }: { left: awst.Expression; right: awst.Expression; sourceLocation: SourceLocation }) {
if (left.wtype.equals(right.wtype)) {
if (left instanceof BytesConstant && right instanceof BytesConstant) {
const concatValue = new Uint8Array(left.value.length + right.value.length)
concatValue.set(left.value, 0)
concatValue.set(right.value, left.value.length)
return nodeFactory.bytesConstant({
value: concatValue,
wtype: left.wtype,
encoding: left.encoding,
sourceLocation: SourceLocation.fromLocations(left.sourceLocation, right.sourceLocation),
})
} else if (left instanceof StringConstant && right instanceof StringConstant) {
return nodeFactory.stringConstant({
value: left.value + right.value,
sourceLocation: SourceLocation.fromLocations(left.sourceLocation, right.sourceLocation),
})
}
}

return nodeFactory.intrinsicCall({
sourceLocation,
stackArgs: [left, right],
Expand Down
6 changes: 6 additions & 0 deletions src/awst/json-serialize-awst.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,12 @@ export class AwstSerializer extends SnakeCaseSerializer<RootNode[]> {

protected serializerFunction(key: string, value: unknown): unknown {
if (typeof value === 'bigint') {
if (value < 0n) {
if (value < Number.MIN_SAFE_INTEGER) {
throw new InternalError(`Cannot safely serialize ${value} to JSON`)
}
return Number(value)
}
return `${value}`
}
if (value instanceof Set) {
Expand Down
14 changes: 9 additions & 5 deletions src/awst_build/eb/arc4/arrays.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ import { SliceFunctionBuilder } from '../shared/slice-function-builder'
import { UInt64ExpressionBuilder } from '../uint64-expression-builder'
import { requireExpressionOfType } from '../util'
import { parseFunctionArgs } from '../util/arg-parsing'
import { resolveCompatExpression } from '../util/resolve-compat-builder'
import { Arc4EncodedBaseExpressionBuilder } from './base'

export class DynamicArrayClassBuilder extends ClassBuilder {
Expand Down Expand Up @@ -180,7 +181,7 @@ export class StaticBytesClassBuilder extends ClassBuilder {
callLocation: sourceLocation,
funcName: `${this.ptype.name} constructor`,
genericTypeArgs: 1,
argSpec: (a) => [a.optional(bytesPType)],
argSpec: (a) => [a.optional(bytesPType, stringPType)],
})
const resultPType = StaticBytesGeneric.parameterise([length])

Expand All @@ -196,7 +197,7 @@ export class StaticBytesClassBuilder extends ClassBuilder {
resultPType,
)
}
const value = initialValue.resolve()
const value = resolveCompatExpression(initialValue, bytesPType)
if (value instanceof BytesConstant) {
codeInvariant(value.value.length === byteLength, `Value should have byte length of ${byteLength}`, sourceLocation)
return instanceEb(
Expand Down Expand Up @@ -231,7 +232,7 @@ export class DynamicBytesClassBuilder extends ClassBuilder {
callLocation: sourceLocation,
funcName: `${this.ptype.name} constructor`,
genericTypeArgs: 0,
argSpec: (a) => [a.optional(bytesPType)],
argSpec: (a) => [a.optional(bytesPType, stringPType)],
})
const resultPType = DynamicBytesType

Expand All @@ -245,7 +246,8 @@ export class DynamicBytesClassBuilder extends ClassBuilder {
resultPType,
)
}
const value = initialValue.resolve()

const value = resolveCompatExpression(initialValue, bytesPType)
if (value instanceof BytesConstant) {
return instanceEb(
nodeFactory.bytesConstant({
Expand Down Expand Up @@ -431,7 +433,9 @@ export class AddressExpressionBuilder extends ArrayExpressionBuilder<StaticArray
case 'length':
return new UInt64ExpressionBuilder(nodeFactory.uInt64Constant({ value: this.ptype.arraySize, sourceLocation }))
case 'native':
return new AccountExpressionBuilder(this.toBytes(sourceLocation))
return new AccountExpressionBuilder(
nodeFactory.reinterpretCast({ expr: this.toBytes(sourceLocation), sourceLocation, wtype: wtypes.accountWType }),
)
}
return super.memberAccess(name, sourceLocation)
}
Expand Down
21 changes: 21 additions & 0 deletions src/awst_build/eb/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -106,10 +106,20 @@ export abstract class InstanceBuilder<TPType extends PType = PType> extends Node
abstract resolve(): awst.Expression
abstract resolveLValue(): awst.LValue

/**
* Returns a boolean indicating if the current builder can be resolved to the target type.
* Resolvable meaning it may have a different type, but would be assignable to the target type in TypeScript
* without a cast.
* @param ptype
*/
resolvableToPType(ptype: PTypeOrClass): boolean {
return this.ptype.equalsOrInstanceOf(ptype)
}

/**
* Attempts to resolve the value held by this builder to the target type.
* @param ptype
*/
resolveToPType(ptype: PTypeOrClass): InstanceBuilder {
if (this.ptype.equalsOrInstanceOf(ptype)) {
return this
Expand Down Expand Up @@ -177,6 +187,17 @@ export abstract class InstanceBuilder<TPType extends PType = PType> extends Node
sourceLocation,
})
}

reinterpretCast(target: PType, sourceLocation?: SourceLocation) {
return instanceEb(
nodeFactory.reinterpretCast({
expr: this.resolve(),
sourceLocation: sourceLocation ?? this.sourceLocation,
wtype: target.wtypeOrThrow,
}),
target,
)
}
}

export abstract class ClassBuilder extends NodeBuilder {
Expand Down
11 changes: 9 additions & 2 deletions src/awst_build/eb/literal/big-int-literal-expression-builder.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,10 +35,17 @@ export class BigIntLiteralExpressionBuilder extends LiteralExpressionBuilder {
}

resolvableToPType(ptype: PTypeOrClass): boolean {
const isUnsigned = ptype.equals(biguintPType) || ptype.equals(uint64PType)
if (this.ptype instanceof NumericLiteralPType || this.ptype.equals(numberPType)) {
return ptype.equals(biguintPType) || ptype.equals(uint64PType) || ptype.equals(numberPType) || ptype.equals(this.ptype)
if (isUnsigned) {
return this.value >= 0n
}
return ptype.equals(numberPType) || ptype.equals(this.ptype)
} else if (this.ptype instanceof BigIntLiteralPType || this.ptype.equals(bigIntPType)) {
return ptype.equals(biguintPType) || ptype.equals(uint64PType) || ptype.equals(bigIntPType) || ptype.equals(this.ptype)
if (isUnsigned) {
return this.value >= 0n
}
return ptype.equals(bigIntPType) || ptype.equals(this.ptype)
}
return false
}
Expand Down
Loading

0 comments on commit 8d3dbc2

Please sign in to comment.