Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: Add missing itxn fields and fix compatible type resolution #93

Merged
merged 3 commits into from
Feb 1, 2025
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
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