Skip to content

Commit 8571d92

Browse files
authored
Merge pull request #77 from algorandfoundation/feat/itxn-compose
feat: implement ItxnCompose type for composing variable length transaction groups
2 parents 438bf68 + 363614f commit 8571d92

File tree

13 files changed

+334
-44
lines changed

13 files changed

+334
-44
lines changed

docs/code/subcontexts/transaction-context/classes/ItxnGroup.md

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66

77
# Class: ItxnGroup
88

9-
Defined in: [src/subcontexts/transaction-context.ts:485](https://github.com/algorandfoundation/algorand-typescript-testing/blob/main/src/subcontexts/transaction-context.ts#L485)
9+
Defined in: [src/subcontexts/transaction-context.ts:496](https://github.com/algorandfoundation/algorand-typescript-testing/blob/main/src/subcontexts/transaction-context.ts#L496)
1010

1111
Represents a group of inner transactions.
1212

@@ -16,7 +16,7 @@ Represents a group of inner transactions.
1616

1717
> **new ItxnGroup**(`itxns`): `ItxnGroup`
1818
19-
Defined in: [src/subcontexts/transaction-context.ts:487](https://github.com/algorandfoundation/algorand-typescript-testing/blob/main/src/subcontexts/transaction-context.ts#L487)
19+
Defined in: [src/subcontexts/transaction-context.ts:498](https://github.com/algorandfoundation/algorand-typescript-testing/blob/main/src/subcontexts/transaction-context.ts#L498)
2020

2121
#### Parameters
2222

@@ -34,15 +34,15 @@ Defined in: [src/subcontexts/transaction-context.ts:487](https://github.com/algo
3434

3535
> **itxns**: `InnerTxn`[] = `[]`
3636
37-
Defined in: [src/subcontexts/transaction-context.ts:486](https://github.com/algorandfoundation/algorand-typescript-testing/blob/main/src/subcontexts/transaction-context.ts#L486)
37+
Defined in: [src/subcontexts/transaction-context.ts:497](https://github.com/algorandfoundation/algorand-typescript-testing/blob/main/src/subcontexts/transaction-context.ts#L497)
3838

3939
## Methods
4040

4141
### getApplicationCallInnerTxn()
4242

4343
> **getApplicationCallInnerTxn**(`index`?): `ApplicationCallInnerTxn`
4444
45-
Defined in: [src/subcontexts/transaction-context.ts:496](https://github.com/algorandfoundation/algorand-typescript-testing/blob/main/src/subcontexts/transaction-context.ts#L496)
45+
Defined in: [src/subcontexts/transaction-context.ts:507](https://github.com/algorandfoundation/algorand-typescript-testing/blob/main/src/subcontexts/transaction-context.ts#L507)
4646

4747
Gets an application inner transaction by index.
4848

@@ -66,7 +66,7 @@ The application inner transaction.
6666

6767
> **getAssetConfigInnerTxn**(`index`?): `AssetConfigInnerTxn`
6868
69-
Defined in: [src/subcontexts/transaction-context.ts:505](https://github.com/algorandfoundation/algorand-typescript-testing/blob/main/src/subcontexts/transaction-context.ts#L505)
69+
Defined in: [src/subcontexts/transaction-context.ts:516](https://github.com/algorandfoundation/algorand-typescript-testing/blob/main/src/subcontexts/transaction-context.ts#L516)
7070

7171
Gets an asset configuration inner transaction by index.
7272

@@ -90,7 +90,7 @@ The asset configuration inner transaction.
9090

9191
> **getAssetFreezeInnerTxn**(`index`?): `AssetFreezeInnerTxn`
9292
93-
Defined in: [src/subcontexts/transaction-context.ts:523](https://github.com/algorandfoundation/algorand-typescript-testing/blob/main/src/subcontexts/transaction-context.ts#L523)
93+
Defined in: [src/subcontexts/transaction-context.ts:534](https://github.com/algorandfoundation/algorand-typescript-testing/blob/main/src/subcontexts/transaction-context.ts#L534)
9494

9595
Gets an asset freeze inner transaction by index.
9696

@@ -114,7 +114,7 @@ The asset freeze inner transaction.
114114

115115
> **getAssetTransferInnerTxn**(`index`?): `AssetTransferInnerTxn`
116116
117-
Defined in: [src/subcontexts/transaction-context.ts:514](https://github.com/algorandfoundation/algorand-typescript-testing/blob/main/src/subcontexts/transaction-context.ts#L514)
117+
Defined in: [src/subcontexts/transaction-context.ts:525](https://github.com/algorandfoundation/algorand-typescript-testing/blob/main/src/subcontexts/transaction-context.ts#L525)
118118

119119
Gets an asset transfer inner transaction by index.
120120

@@ -138,7 +138,7 @@ The asset transfer inner transaction.
138138

139139
> **getInnerTxn**(`index`?): `InnerTxn`
140140
141-
Defined in: [src/subcontexts/transaction-context.ts:550](https://github.com/algorandfoundation/algorand-typescript-testing/blob/main/src/subcontexts/transaction-context.ts#L550)
141+
Defined in: [src/subcontexts/transaction-context.ts:561](https://github.com/algorandfoundation/algorand-typescript-testing/blob/main/src/subcontexts/transaction-context.ts#L561)
142142

143143
Gets an inner transaction by index.
144144

@@ -162,7 +162,7 @@ The inner transaction.
162162

163163
> **getKeyRegistrationInnerTxn**(`index`?): `KeyRegistrationInnerTxn`
164164
165-
Defined in: [src/subcontexts/transaction-context.ts:532](https://github.com/algorandfoundation/algorand-typescript-testing/blob/main/src/subcontexts/transaction-context.ts#L532)
165+
Defined in: [src/subcontexts/transaction-context.ts:543](https://github.com/algorandfoundation/algorand-typescript-testing/blob/main/src/subcontexts/transaction-context.ts#L543)
166166

167167
Gets a key registration inner transaction by index.
168168

@@ -186,7 +186,7 @@ The key registration inner transaction.
186186

187187
> **getPaymentInnerTxn**(`index`?): `PaymentInnerTxn`
188188
189-
Defined in: [src/subcontexts/transaction-context.ts:541](https://github.com/algorandfoundation/algorand-typescript-testing/blob/main/src/subcontexts/transaction-context.ts#L541)
189+
Defined in: [src/subcontexts/transaction-context.ts:552](https://github.com/algorandfoundation/algorand-typescript-testing/blob/main/src/subcontexts/transaction-context.ts#L552)
190190

191191
Gets a payment inner transaction by index.
192192

docs/code/subcontexts/transaction-context/classes/TransactionGroup.md

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -196,7 +196,7 @@ If there is already an inner transaction group being constructed or the active t
196196

197197
> **getApplicationCallTransaction**(`index`?): `ApplicationCallTransaction`
198198
199-
Defined in: [src/subcontexts/transaction-context.ts:394](https://github.com/algorandfoundation/algorand-typescript-testing/blob/main/src/subcontexts/transaction-context.ts#L394)
199+
Defined in: [src/subcontexts/transaction-context.ts:405](https://github.com/algorandfoundation/algorand-typescript-testing/blob/main/src/subcontexts/transaction-context.ts#L405)
200200

201201
Gets an application transaction by index.
202202

@@ -220,7 +220,7 @@ The application transaction.
220220

221221
> **getAssetConfigTransaction**(`index`?): `AssetConfigTransaction`
222222
223-
Defined in: [src/subcontexts/transaction-context.ts:403](https://github.com/algorandfoundation/algorand-typescript-testing/blob/main/src/subcontexts/transaction-context.ts#L403)
223+
Defined in: [src/subcontexts/transaction-context.ts:414](https://github.com/algorandfoundation/algorand-typescript-testing/blob/main/src/subcontexts/transaction-context.ts#L414)
224224

225225
Gets an asset configuration transaction by index.
226226

@@ -244,7 +244,7 @@ The asset configuration transaction.
244244

245245
> **getAssetFreezeTransaction**(`index`?): `AssetFreezeTransaction`
246246
247-
Defined in: [src/subcontexts/transaction-context.ts:421](https://github.com/algorandfoundation/algorand-typescript-testing/blob/main/src/subcontexts/transaction-context.ts#L421)
247+
Defined in: [src/subcontexts/transaction-context.ts:432](https://github.com/algorandfoundation/algorand-typescript-testing/blob/main/src/subcontexts/transaction-context.ts#L432)
248248

249249
Gets an asset freeze transaction by index.
250250

@@ -268,7 +268,7 @@ The asset freeze transaction.
268268

269269
> **getAssetTransferTransaction**(`index`?): `AssetTransferTransaction`
270270
271-
Defined in: [src/subcontexts/transaction-context.ts:412](https://github.com/algorandfoundation/algorand-typescript-testing/blob/main/src/subcontexts/transaction-context.ts#L412)
271+
Defined in: [src/subcontexts/transaction-context.ts:423](https://github.com/algorandfoundation/algorand-typescript-testing/blob/main/src/subcontexts/transaction-context.ts#L423)
272272

273273
Gets an asset transfer transaction by index.
274274

@@ -292,7 +292,7 @@ The asset transfer transaction.
292292

293293
> **getItxnGroup**(`index`?): [`ItxnGroup`](ItxnGroup.md)
294294
295-
Defined in: [src/subcontexts/transaction-context.ts:376](https://github.com/algorandfoundation/algorand-typescript-testing/blob/main/src/subcontexts/transaction-context.ts#L376)
295+
Defined in: [src/subcontexts/transaction-context.ts:387](https://github.com/algorandfoundation/algorand-typescript-testing/blob/main/src/subcontexts/transaction-context.ts#L387)
296296

297297
Gets an inner transaction group by index.
298298

@@ -320,7 +320,7 @@ If the index is invalid or there are no previous inner transactions.
320320

321321
> **getKeyRegistrationTransaction**(`index`?): `KeyRegistrationTransaction`
322322
323-
Defined in: [src/subcontexts/transaction-context.ts:430](https://github.com/algorandfoundation/algorand-typescript-testing/blob/main/src/subcontexts/transaction-context.ts#L430)
323+
Defined in: [src/subcontexts/transaction-context.ts:441](https://github.com/algorandfoundation/algorand-typescript-testing/blob/main/src/subcontexts/transaction-context.ts#L441)
324324

325325
Gets a key registration transaction by index.
326326

@@ -344,7 +344,7 @@ The key registration transaction.
344344

345345
> **getPaymentTransaction**(`index`?): `PaymentTransaction`
346346
347-
Defined in: [src/subcontexts/transaction-context.ts:439](https://github.com/algorandfoundation/algorand-typescript-testing/blob/main/src/subcontexts/transaction-context.ts#L439)
347+
Defined in: [src/subcontexts/transaction-context.ts:450](https://github.com/algorandfoundation/algorand-typescript-testing/blob/main/src/subcontexts/transaction-context.ts#L450)
348348

349349
Gets a payment transaction by index.
350350

@@ -408,7 +408,7 @@ The scratch space.
408408

409409
> **getTransaction**(`index`?): `Transaction`
410410
411-
Defined in: [src/subcontexts/transaction-context.ts:448](https://github.com/algorandfoundation/algorand-typescript-testing/blob/main/src/subcontexts/transaction-context.ts#L448)
411+
Defined in: [src/subcontexts/transaction-context.ts:459](https://github.com/algorandfoundation/algorand-typescript-testing/blob/main/src/subcontexts/transaction-context.ts#L459)
412412

413413
Gets a transaction by index.
414414

@@ -432,7 +432,7 @@ The transaction.
432432

433433
> **lastItxnGroup**(): [`ItxnGroup`](ItxnGroup.md)
434434
435-
Defined in: [src/subcontexts/transaction-context.ts:366](https://github.com/algorandfoundation/algorand-typescript-testing/blob/main/src/subcontexts/transaction-context.ts#L366)
435+
Defined in: [src/subcontexts/transaction-context.ts:377](https://github.com/algorandfoundation/algorand-typescript-testing/blob/main/src/subcontexts/transaction-context.ts#L377)
436436

437437
Gets the last inner transaction group.
438438

docs/coverage.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@ See which `algorand-typescript` stubs are implemented by the `algorand-typescrip
3737
| ensureBudget | Emulated |
3838
| err | Native |
3939
| interpretAsArc4 | Native |
40+
| itxnCompose | Emulated |
4041
| log | Emulated |
4142
| logicSig | Emulated |
4243
| logicsig | Emulated |

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@
2323
"test:ci": "vitest run --coverage --reporter junit --outputFile test-results.xml",
2424
"script:documentation": "typedoc",
2525
"script:build-examples": "rollup --config examples/rollup.config.ts --configPlugin typescript",
26-
"script:compile-examples": "puya-ts --out-dir data ./examples/*/",
26+
"script:compile-examples": "puya-ts --out-dir data ./examples/*/ --puya-path=/home/parallels/.local/bin/puya",
2727
"script:refresh-test-artifacts": "puya-ts --out-dir data ./tests/artifacts/*/"
2828
},
2929
"devDependencies": {

src/impl/c2c.ts

Lines changed: 15 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,7 @@ export function compileArc4<TContract extends Contract>(
4343
},
4444
selector,
4545
)
46-
invokeCallback(itxnContext)
46+
invokeAbiCall(itxnContext)
4747
return {
4848
itxn: itxnContext,
4949
returnValue: itxnContext.loggedReturnValue,
@@ -57,7 +57,7 @@ export function compileArc4<TContract extends Contract>(
5757
...getCommonApplicationCallFields(app, options),
5858
...methodArgs,
5959
})
60-
invokeCallback(itxnContext)
60+
invokeAbiCall(itxnContext)
6161
return itxnContext
6262
},
6363
approvalProgram: app?.application.approvalProgram ?? [lazyContext.any.bytes(10), lazyContext.any.bytes(10)],
@@ -70,7 +70,7 @@ export function compileArc4<TContract extends Contract>(
7070
} as unknown as ContractProxy<TContract>
7171
}
7272

73-
const invokeCallback = (itxnContext: ApplicationCallInnerTxnContext) => {
73+
export const invokeAbiCall = (itxnContext: ApplicationCallInnerTxnContext) => {
7474
lazyContext.value.notifyApplicationSpies(itxnContext)
7575
lazyContext.txn.activeGroup.addInnerTransactionGroup(...(itxnContext.itxns ?? []), itxnContext)
7676
}
@@ -84,21 +84,29 @@ const getCommonApplicationCallFields = (app: ApplicationData | undefined, option
8484
localNumBytes: options?.localBytes ?? app?.application.localNumBytes ?? lazyContext.any.uint64(),
8585
})
8686

87-
export function abiCall<TArgs extends DeliberateAny[], TReturn>(
87+
export function getApplicationCallInnerTxnContext<TArgs extends DeliberateAny[], TReturn = void>(
8888
method: InstanceMethod<Contract, TArgs, TReturn>,
8989
methodArgs: TypedApplicationCallFields<TArgs>,
9090
contract?: Contract | { new (): Contract },
91-
): { itxn: ApplicationCallInnerTxn; returnValue: TReturn | undefined } {
91+
) {
9292
const abiMetadata = contract ? getContractMethodAbiMetadata(contract, method.name) : undefined
9393
const selector = methodSelector(method, contract)
94-
const itxnContext = ApplicationCallInnerTxnContext.createFromTypedApplicationCallFields<TReturn>(
94+
return ApplicationCallInnerTxnContext.createFromTypedApplicationCallFields<TReturn>(
9595
{
9696
...methodArgs,
9797
onCompletion: methodArgs.onCompletion ?? abiMetadata?.allowActions?.map((action) => OnCompleteAction[action])[0],
9898
},
9999
selector,
100100
)
101-
invokeCallback(itxnContext)
101+
}
102+
export function abiCall<TArgs extends DeliberateAny[], TReturn>(
103+
method: InstanceMethod<Contract, TArgs, TReturn>,
104+
methodArgs: TypedApplicationCallFields<TArgs>,
105+
contract?: Contract | { new (): Contract },
106+
): { itxn: ApplicationCallInnerTxn; returnValue: TReturn | undefined } {
107+
const itxnContext = getApplicationCallInnerTxnContext(method, methodArgs, contract)
108+
109+
invokeAbiCall(itxnContext)
102110

103111
return {
104112
itxn: itxnContext,

src/impl/inner-transactions.ts

Lines changed: 18 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ import { lazyContext } from '../context-helpers/internal-context'
1212
import { InternalError, invariant } from '../errors'
1313
import { extractArraysFromArgs } from '../subcontexts/contract-context'
1414
import type { DeliberateAny } from '../typescript-helpers'
15-
import { asBytes, asNumber, asUint8Array } from '../util'
15+
import { asBytes, asNumber, asUint64, asUint8Array } from '../util'
1616
import { getApp } from './app-params'
1717
import { getAsset } from './asset-params'
1818
import { encodeArc4Impl } from './encoded-types'
@@ -247,16 +247,27 @@ export class ItxnParams<TFields extends InnerTxnFields, TTransaction extends Inn
247247
return this.#fields.type === TransactionType.ApplicationCall
248248
}
249249

250-
submit(): TTransaction {
250+
createInnerTxns(): TTransaction[] {
251251
let itxnContext: ApplicationCallInnerTxnContext | undefined
252-
253252
if (this.isApplicationCall()) {
254253
itxnContext = ApplicationCallInnerTxnContext.createFromFields(this.#fields)
255-
lazyContext.value.notifyApplicationSpies(itxnContext)
256254
}
257-
const innerTxn = (itxnContext ?? createInnerTxn<InnerTxnFields>(this.#fields)) as unknown as TTransaction
258-
lazyContext.txn.activeGroup.addInnerTransactionGroup(innerTxn)
259-
return innerTxn
255+
const innerTxns = [
256+
...(itxnContext?.itxns ?? []),
257+
itxnContext ?? createInnerTxn<InnerTxnFields>(this.#fields),
258+
] as unknown as TTransaction[]
259+
return innerTxns
260+
}
261+
262+
submit(): TTransaction {
263+
const innerTxns = this.createInnerTxns()
264+
innerTxns.forEach((itxn, index) => Object.assign(itxn, { groupIndex: asUint64(index) }))
265+
const lastInnerTxn = innerTxns.at(-1)
266+
if (lastInnerTxn instanceof ApplicationCallInnerTxnContext) {
267+
lazyContext.value.notifyApplicationSpies(lastInnerTxn)
268+
}
269+
lazyContext.txn.activeGroup.addInnerTransactionGroup(...innerTxns)
270+
return lastInnerTxn as TTransaction
260271
}
261272

262273
set(p: Partial<TFields>) {

src/impl/itxn-compose.ts

Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
import {
2+
type AnyTransactionComposeFields,
3+
type ApplicationCallComposeFields,
4+
type AssetConfigComposeFields,
5+
type AssetFreezeComposeFields,
6+
type AssetTransferComposeFields,
7+
type ComposeItxnParams,
8+
type Contract,
9+
type ItxnCompose,
10+
type KeyRegistrationComposeFields,
11+
type PaymentComposeFields,
12+
} from '@algorandfoundation/algorand-typescript'
13+
import type { TypedApplicationCallFields } from '@algorandfoundation/algorand-typescript/arc4'
14+
import { lazyContext } from '../context-helpers/internal-context'
15+
import type { DeliberateAny, InstanceMethod } from '../typescript-helpers'
16+
import { getApplicationCallInnerTxnContext } from './c2c'
17+
18+
class ItxnComposeImpl {
19+
begin(fields: PaymentComposeFields): void
20+
begin(fields: KeyRegistrationComposeFields): void
21+
begin(fields: AssetConfigComposeFields): void
22+
begin(fields: AssetTransferComposeFields): void
23+
begin(fields: AssetFreezeComposeFields): void
24+
begin(fields: ApplicationCallComposeFields): void
25+
begin(fields: AnyTransactionComposeFields): void
26+
begin(fields: ComposeItxnParams): void
27+
begin<TArgs extends DeliberateAny[]>(
28+
method: InstanceMethod<Contract, TArgs>,
29+
fields: TypedApplicationCallFields<TArgs>,
30+
contract?: Contract | { new (): Contract },
31+
): void
32+
begin<TArgs extends DeliberateAny[]>(...args: unknown[]): void {
33+
lazyContext.txn.activeGroup.constructingItxnGroup.push(
34+
args.length === 1
35+
? (args[0] as AnyTransactionComposeFields)
36+
: getApplicationCallInnerTxnContext(
37+
args[0] as InstanceMethod<Contract, TArgs>,
38+
args[1] as TypedApplicationCallFields<TArgs>,
39+
args[2] as Contract | { new (): Contract },
40+
),
41+
)
42+
}
43+
44+
next(fields: PaymentComposeFields): void
45+
next(fields: KeyRegistrationComposeFields): void
46+
next(fields: AssetConfigComposeFields): void
47+
next(fields: AssetTransferComposeFields): void
48+
next(fields: AssetFreezeComposeFields): void
49+
next(fields: ApplicationCallComposeFields): void
50+
next(fields: AnyTransactionComposeFields): void
51+
next(fields: ComposeItxnParams): void
52+
next<TArgs extends DeliberateAny[]>(_method: InstanceMethod<Contract, TArgs>, _fields: TypedApplicationCallFields<TArgs>): void
53+
next<TArgs extends DeliberateAny[]>(...args: unknown[]): void {
54+
lazyContext.txn.activeGroup.constructingItxnGroup.push(
55+
args.length === 1
56+
? (args[0] as AnyTransactionComposeFields)
57+
: getApplicationCallInnerTxnContext(
58+
args[0] as InstanceMethod<Contract, TArgs>,
59+
args[1] as TypedApplicationCallFields<TArgs>,
60+
args[2] as Contract | { new (): Contract },
61+
),
62+
)
63+
}
64+
65+
submit(): void {
66+
lazyContext.txn.activeGroup.submitInnerTransactionGroup()
67+
}
68+
}
69+
70+
export const itxnCompose: ItxnCompose = new ItxnComposeImpl()

src/internal/index.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,3 +45,5 @@ export const itxn = {
4545
assetFreeze,
4646
applicationCall,
4747
}
48+
49+
export { itxnCompose } from '../impl/itxn-compose'

src/subcontexts/transaction-context.ts

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ import type {
1515
KeyRegistrationInnerTxn,
1616
PaymentInnerTxn,
1717
} from '../impl/inner-transactions'
18-
import { createInnerTxn } from '../impl/inner-transactions'
18+
import { ApplicationCallInnerTxnContext, createInnerTxn, ItxnParams } from '../impl/inner-transactions'
1919
import type { InnerTxn, InnerTxnFields } from '../impl/itxn'
2020
import type { StubBytesCompat, StubUint64Compat } from '../impl/primitives'
2121
import type {
@@ -353,8 +353,19 @@ export class TransactionGroup {
353353
if (this.constructingItxnGroup.length > TRANSACTION_GROUP_MAX_SIZE) {
354354
throw new InternalError(`Cannot submit more than ${TRANSACTION_GROUP_MAX_SIZE} inner transactions at once`)
355355
}
356-
const itxns = this.constructingItxnGroup.map((t) => createInnerTxn(t))
356+
const itxns = this.constructingItxnGroup.flatMap((t) =>
357+
t instanceof ApplicationCallInnerTxnContext
358+
? [...(t.itxns ?? []), t]
359+
: t instanceof ItxnParams
360+
? t.createInnerTxns()
361+
: [createInnerTxn(t)],
362+
)
357363
itxns.forEach((itxn, index) => Object.assign(itxn, { groupIndex: asUint64(index) }))
364+
for (const itxn of itxns) {
365+
if (itxn instanceof ApplicationCallInnerTxnContext) {
366+
lazyContext.value.notifyApplicationSpies(itxn)
367+
}
368+
}
358369
this.itxnGroups.push(new ItxnGroup(itxns))
359370
this.constructingItxnGroup = []
360371
}

0 commit comments

Comments
 (0)