Skip to content

Commit

Permalink
Merge pull request #91 from algorandfoundation/fix/assert-match-txn
Browse files Browse the repository at this point in the history
fix: Type resolution of singleton types when used in generics such as with assertMatch statements
  • Loading branch information
tristanmenzel authored Jan 31, 2025
2 parents 2db052b + 413de1a commit dc9ad74
Show file tree
Hide file tree
Showing 65 changed files with 9,709 additions and 827 deletions.
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.

4 changes: 2 additions & 2 deletions packages/algo-ts/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.13",
"version": "1.0.0-beta.14",
"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
3 changes: 1 addition & 2 deletions packages/algo-ts/src/base-contract.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
import { Contract } from './arc4'
import { uint64 } from './primitives'
import { ConstructorFor } from './typescript-helpers'

Expand Down Expand Up @@ -75,7 +74,7 @@ type ContractOptions = {
*/
export const ContractOptionsSymbol = Symbol('ContractOptions')
export function contract(options: ContractOptions) {
return <T extends ConstructorFor<Contract>>(contract: T, ctx: ClassDecoratorContext) => {
return <T extends ConstructorFor<BaseContract>>(contract: T, ctx: ClassDecoratorContext) => {
ctx.addInitializer(function () {
Object.defineProperty(this, ContractOptionsSymbol, {
value: options,
Expand Down
2 changes: 1 addition & 1 deletion src/awst_build/eb/reference/asset.ts
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,7 @@ export class AssetExpressionBuilder extends Uint64BackedReferenceTypeExpressionB
fieldOpCode: 'asset_params_get',
fieldMapping: {
total: ['AssetTotal', uint64PType],
decimal: ['AssetDecimals', uint64PType],
decimals: ['AssetDecimals', uint64PType],
defaultFrozen: ['AssetDefaultFrozen', boolPType],
unitName: ['AssetUnitName', bytesPType],
name: ['AssetName', bytesPType],
Expand Down
27 changes: 26 additions & 1 deletion src/awst_build/eb/storage/box/box-ref.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import { wtypes } from '../../../../awst/wtypes'

import { invariant } from '../../../../util'
import type { PType } from '../../../ptypes'
import { boolPType, BoxRefPType, boxRefType, bytesPType, stringPType, uint64PType, voidPType } from '../../../ptypes'
import { boolPType, BoxRefPType, boxRefType, bytesPType, stringPType, TuplePType, uint64PType, voidPType } from '../../../ptypes'
import { instanceEb } from '../../../type-registry'
import { FunctionBuilder, type NodeBuilder } from '../../index'
import { parseFunctionArgs } from '../../util/arg-parsing'
Expand Down Expand Up @@ -56,6 +56,8 @@ export class BoxRefExpressionBuilder extends BoxProxyExpressionBuilder<BoxRefPTy
return new BoxRefReplaceFunctionBuilder(boxValueExpr)
case 'exists':
return boxExists(boxValueExpr, sourceLocation)
case 'maybe':
return new BoxRefMaybeFunctionBuilder(boxValueExpr)
case 'length': {
return boxLength(boxValueExpr, sourceLocation)
}
Expand Down Expand Up @@ -217,3 +219,26 @@ export class BoxRefSpliceFunctionBuilder extends BoxRefBaseFunctionBuilder {
)
}
}

class BoxRefMaybeFunctionBuilder extends BoxRefBaseFunctionBuilder {
call(args: ReadonlyArray<NodeBuilder>, typeArgs: ReadonlyArray<PType>, sourceLocation: SourceLocation): NodeBuilder {
parseFunctionArgs({
args,
typeArgs,
funcName: 'BoxRef.maybe',
callLocation: sourceLocation,
genericTypeArgs: 0,
argSpec: () => [],
})
const type = new TuplePType({ items: [bytesPType, boolPType] })

return instanceEb(
nodeFactory.stateGetEx({
sourceLocation,
wtype: type.wtype,
field: this.boxValue,
}),
type,
)
}
}
9 changes: 9 additions & 0 deletions src/awst_build/type-resolver.ts
Original file line number Diff line number Diff line change
Expand Up @@ -199,6 +199,15 @@ export class TypeResolver {

const typeName = this.getTypeName(tsType, sourceLocation)

if (typeName.name === '__type' && typeName.module.startsWith(Constants.algoTsPackage)) {
// We are likely dealing with `typeof X` where X is a singleton exported by algo-ts
const declarationNode = tsType.symbol.getDeclarations()?.[0]?.parent

if (declarationNode && ts.isVariableDeclaration(declarationNode)) {
return this.resolve(declarationNode.name, sourceLocation)
}
}

if (typeName.fullName === ClusteredPrototype.fullName) {
return this.resolveClusteredPrototype(tsType, sourceLocation)
}
Expand Down
4 changes: 4 additions & 0 deletions tests/approvals/assert-match.algo.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,10 @@ import { assertMatch, Contract, Global, Txn } from '@algorandfoundation/algorand

export class AssertMatchContract extends Contract {
public testPay(pay: gtxn.PaymentTxn): boolean {
assertMatch(Txn, {
fee: { greaterThan: 0 },
})

assertMatch(pay, {
amount: { between: [100_000, 105_000] },
sender: Txn.sender,
Expand Down
23 changes: 23 additions & 0 deletions tests/approvals/asset-proxy.algo.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import type { Asset } from '@algorandfoundation/algorand-typescript'
import { Contract, log, Txn } from '@algorandfoundation/algorand-typescript'

export class AssetProxyAlgo extends Contract {
testAsset(asset: Asset): void {
log(asset.id)
log(asset.total)
log(asset.decimals)
log(asset.defaultFrozen)
log(asset.unitName)
log(asset.name)
log(asset.url)
log(asset.metadataHash)
log(asset.manager)
log(asset.reserve)
log(asset.freeze)
log(asset.clawback)
log(asset.creator)

log(asset.balance(Txn.sender))
log(asset.frozen(Txn.sender))
}
}
3 changes: 3 additions & 0 deletions tests/approvals/box-proxies.algo.ts
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,9 @@ function testBoxRef(box: BoxRef, length: uint64) {
const someBytes = Bytes.fromHex('FFFFFFFF')
box.put(someBytes)

const maybeBox = box.maybe()
assert(maybeBox[1])

assert(box.value === Bytes.fromHex('FFFFFFFF'))
box.splice(1, 1, Bytes.fromHex('00'))
assert(box.value === Bytes.fromHex('FF00FFFF'))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,13 @@ testPay:
// tests/approvals/assert-match.algo.ts:5
// public testPay(pay: gtxn.PaymentTxn): boolean {
proto 1 1
// tests/approvals/assert-match.algo.ts:6-13
// tests/approvals/assert-match.algo.ts:6-8
// assertMatch(Txn, {
// fee: { greaterThan: 0 },
// })
txn Fee
assert // assert target is match for conditions
// tests/approvals/assert-match.algo.ts:10-17
// assertMatch(pay, {
// amount: { between: [100_000, 105_000] },
// sender: Txn.sender,
Expand All @@ -79,10 +85,10 @@ testPay:
frame_dig -1
gtxns Amount
dup
// tests/approvals/assert-match.algo.ts:7
// tests/approvals/assert-match.algo.ts:11
// amount: { between: [100_000, 105_000] },
pushint 105000 // 105000
// tests/approvals/assert-match.algo.ts:6-13
// tests/approvals/assert-match.algo.ts:10-17
// assertMatch(pay, {
// amount: { between: [100_000, 105_000] },
// sender: Txn.sender,
Expand All @@ -94,10 +100,10 @@ testPay:
<=
bz testPay_bool_false@8
frame_dig 0
// tests/approvals/assert-match.algo.ts:7
// tests/approvals/assert-match.algo.ts:11
// amount: { between: [100_000, 105_000] },
pushint 100000 // 100000
// tests/approvals/assert-match.algo.ts:6-13
// tests/approvals/assert-match.algo.ts:10-17
// assertMatch(pay, {
// amount: { between: [100_000, 105_000] },
// sender: Txn.sender,
Expand All @@ -110,10 +116,10 @@ testPay:
bz testPay_bool_false@8
frame_dig -1
gtxns Sender
// tests/approvals/assert-match.algo.ts:8
// tests/approvals/assert-match.algo.ts:12
// sender: Txn.sender,
txn Sender
// tests/approvals/assert-match.algo.ts:6-13
// tests/approvals/assert-match.algo.ts:10-17
// assertMatch(pay, {
// amount: { between: [100_000, 105_000] },
// sender: Txn.sender,
Expand All @@ -126,10 +132,10 @@ testPay:
bz testPay_bool_false@8
frame_dig -1
gtxns Receiver
// tests/approvals/assert-match.algo.ts:9
// tests/approvals/assert-match.algo.ts:13
// receiver: Global.currentApplicationAddress,
global CurrentApplicationAddress
// tests/approvals/assert-match.algo.ts:6-13
// tests/approvals/assert-match.algo.ts:10-17
// assertMatch(pay, {
// amount: { between: [100_000, 105_000] },
// sender: Txn.sender,
Expand All @@ -142,10 +148,10 @@ testPay:
bz testPay_bool_false@8
frame_dig -1
gtxns CloseRemainderTo
// tests/approvals/assert-match.algo.ts:10
// tests/approvals/assert-match.algo.ts:14
// closeRemainderTo: Global.zeroAddress,
global ZeroAddress
// tests/approvals/assert-match.algo.ts:6-13
// tests/approvals/assert-match.algo.ts:10-17
// assertMatch(pay, {
// amount: { between: [100_000, 105_000] },
// sender: Txn.sender,
Expand All @@ -158,10 +164,10 @@ testPay:
bz testPay_bool_false@8
frame_dig -1
gtxns FirstValid
// tests/approvals/assert-match.algo.ts:11
// tests/approvals/assert-match.algo.ts:15
// firstValid: { greaterThan: 1 },
intc_0 // 1
// tests/approvals/assert-match.algo.ts:6-13
// tests/approvals/assert-match.algo.ts:10-17
// assertMatch(pay, {
// amount: { between: [100_000, 105_000] },
// sender: Txn.sender,
Expand All @@ -174,10 +180,10 @@ testPay:
bz testPay_bool_false@8
frame_dig -1
gtxns LastValid
// tests/approvals/assert-match.algo.ts:12
// tests/approvals/assert-match.algo.ts:16
// lastValid: { lessThan: 2 ** 40 },
pushint 1099511627776 // 1099511627776
// tests/approvals/assert-match.algo.ts:6-13
// tests/approvals/assert-match.algo.ts:10-17
// assertMatch(pay, {
// amount: { between: [100_000, 105_000] },
// sender: Txn.sender,
Expand All @@ -191,7 +197,7 @@ testPay:
intc_0 // 1

testPay_bool_merge@9:
// tests/approvals/assert-match.algo.ts:6-13
// tests/approvals/assert-match.algo.ts:10-17
// assertMatch(pay, {
// amount: { between: [100_000, 105_000] },
// sender: Txn.sender,
Expand All @@ -201,7 +207,7 @@ testPay_bool_merge@9:
// lastValid: { lessThan: 2 ** 40 },
// })
assert // assert target is match for conditions
// tests/approvals/assert-match.algo.ts:14
// tests/approvals/assert-match.algo.ts:18
// return true
intc_0 // 1
swap
Expand Down

Large diffs are not rendered by default.

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
Expand Up @@ -50,43 +50,46 @@ subroutine tests/approvals/assert-match.algo.ts::AssertMatchContract.__puya_arc4

subroutine tests/approvals/assert-match.algo.ts::AssertMatchContract.testPay(pay: uint64) -> bool:
block@0: // L5
let tmp%0#0: uint64 = ((gtxns Amount) pay#0)
let tmp%1#0: bool = (<= tmp%0#0 105000u)
goto tmp%1#0 ? block@1 : block@8
block@1: // and_contd_L6
let tmp%0#0: uint64 = (txn Fee)
let tmp%1#0: bool = (> tmp%0#0 0u)
(assert tmp%1#0) // assert target is match for conditions
let tmp%2#0: uint64 = ((gtxns Amount) pay#0)
let tmp%3#0: bool = (>= tmp%2#0 100000u)
goto tmp%3#0 ? block@2 : block@8
block@2: // and_contd_L6
let tmp%4#0: bytes = ((gtxns Sender) pay#0)
let tmp%5#0: bytes = (txn Sender)
let tmp%6#0: bool = (== tmp%4#0 tmp%5#0)
goto tmp%6#0 ? block@3 : block@8
block@3: // and_contd_L6
let tmp%7#0: bytes = ((gtxns Receiver) pay#0)
let tmp%8#0: bytes = (global CurrentApplicationAddress)
let tmp%9#0: bool = (== tmp%7#0 tmp%8#0)
goto tmp%9#0 ? block@4 : block@8
block@4: // and_contd_L6
let tmp%10#0: bytes = ((gtxns CloseRemainderTo) pay#0)
let tmp%11#0: bytes = (global ZeroAddress)
let tmp%12#0: bool = (== tmp%10#0 tmp%11#0)
goto tmp%12#0 ? block@5 : block@8
block@5: // and_contd_L6
let tmp%13#0: uint64 = ((gtxns FirstValid) pay#0)
let tmp%14#0: bool = (> tmp%13#0 1u)
goto tmp%14#0 ? block@6 : block@8
block@6: // and_contd_L6
let tmp%15#0: uint64 = ((gtxns LastValid) pay#0)
let tmp%16#0: bool = (< tmp%15#0 1099511627776u)
goto tmp%16#0 ? block@7 : block@8
block@7: // bool_true_L6
let tmp%3#0: bool = (<= tmp%2#0 105000u)
goto tmp%3#0 ? block@1 : block@8
block@1: // and_contd_L10
let tmp%4#0: uint64 = ((gtxns Amount) pay#0)
let tmp%5#0: bool = (>= tmp%4#0 100000u)
goto tmp%5#0 ? block@2 : block@8
block@2: // and_contd_L10
let tmp%6#0: bytes = ((gtxns Sender) pay#0)
let tmp%7#0: bytes = (txn Sender)
let tmp%8#0: bool = (== tmp%6#0 tmp%7#0)
goto tmp%8#0 ? block@3 : block@8
block@3: // and_contd_L10
let tmp%9#0: bytes = ((gtxns Receiver) pay#0)
let tmp%10#0: bytes = (global CurrentApplicationAddress)
let tmp%11#0: bool = (== tmp%9#0 tmp%10#0)
goto tmp%11#0 ? block@4 : block@8
block@4: // and_contd_L10
let tmp%12#0: bytes = ((gtxns CloseRemainderTo) pay#0)
let tmp%13#0: bytes = (global ZeroAddress)
let tmp%14#0: bool = (== tmp%12#0 tmp%13#0)
goto tmp%14#0 ? block@5 : block@8
block@5: // and_contd_L10
let tmp%15#0: uint64 = ((gtxns FirstValid) pay#0)
let tmp%16#0: bool = (> tmp%15#0 1u)
goto tmp%16#0 ? block@6 : block@8
block@6: // and_contd_L10
let tmp%17#0: uint64 = ((gtxns LastValid) pay#0)
let tmp%18#0: bool = (< tmp%17#0 1099511627776u)
goto tmp%18#0 ? block@7 : block@8
block@7: // bool_true_L10
let and_result%0#0: bool = 1u
goto block@9
block@8: // bool_false_L6
block@8: // bool_false_L10
let and_result%0#1: bool = 0u
goto block@9
block@9: // bool_merge_L6
block@9: // bool_merge_L10
let and_result%0#2: bool = φ(and_result%0#0 <- block@7, and_result%0#1 <- block@8)
(assert and_result%0#2) // assert target is match for conditions
return 1u
Expand Down
1 change: 1 addition & 0 deletions tests/approvals/out/o1/assert-match/assert-match.awst
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ contract AssertMatchContract

testPay(pay: group_transaction_pay): bool
{
assert(txn<Fee>() > 0, comment=assert target is match for conditions)
assert(gtxns<Amount>(pay) <= 105000 and gtxns<Amount>(pay) >= 100000 and gtxns<Sender>(pay) == txn<Sender>() and gtxns<Receiver>(pay) == global<CurrentApplicationAddress>() and gtxns<CloseRemainderTo>(pay) == global<ZeroAddress>() and gtxns<FirstValid>(pay) > 1 and gtxns<LastValid>(pay) < 1099511627776, comment=assert target is match for conditions)
return True
}
Expand Down
Loading

0 comments on commit dc9ad74

Please sign in to comment.