Skip to content

Commit d13f0e0

Browse files
committed
implement functions to create itxn and add tests for those
1 parent 482e0eb commit d13f0e0

File tree

5 files changed

+193
-10
lines changed

5 files changed

+193
-10
lines changed

src/impl/inner-transactions.ts

Lines changed: 60 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -158,21 +158,73 @@ export class ApplicationInnerTxn extends ApplicationTransaction implements itxn.
158158
}
159159
}
160160

161-
export const createInnerTxn = (fields: InnerTxnFields): InnerTxn => {
161+
export const createInnerTxn = <TFields extends InnerTxnFields, T extends InnerTxn>(fields: TFields): T => {
162162
switch (fields.type) {
163163
case TransactionType.Payment:
164-
return new PaymentInnerTxn(fields)
164+
return new PaymentInnerTxn(fields) as T
165165
case TransactionType.AssetConfig:
166-
return new AssetConfigInnerTxn(fields)
166+
return new AssetConfigInnerTxn(fields) as T
167167
case TransactionType.AssetTransfer:
168-
return new AssetTransferInnerTxn(fields as itxn.AssetTransferFields)
168+
return new AssetTransferInnerTxn(fields as itxn.AssetTransferFields) as T
169169
case TransactionType.AssetFreeze:
170-
return new AssetFreezeInnerTxn(fields as itxn.AssetFreezeFields)
170+
return new AssetFreezeInnerTxn(fields as itxn.AssetFreezeFields) as T
171171
case TransactionType.ApplicationCall:
172-
return new ApplicationInnerTxn(fields)
172+
return new ApplicationInnerTxn(fields) as unknown as T
173173
case TransactionType.KeyRegistration:
174-
return new KeyRegistrationInnerTxn(fields)
174+
return new KeyRegistrationInnerTxn(fields) as T
175175
default:
176-
internal.errors.internalError(`Invalid inner transaction type: ${fields.type}`)
176+
throw new internal.errors.InternalError(`Invalid inner transaction type: ${fields.type}`)
177+
}
178+
}
179+
180+
export function submitGroup<TFields extends itxn.InnerTxnList>(...transactionFields: TFields): itxn.TxnFor<TFields> {
181+
return transactionFields.map((f: (typeof transactionFields)[number]) => f.submit()) as itxn.TxnFor<TFields>
182+
}
183+
export function payment(fields: itxn.PaymentFields): itxn.PaymentItxnParams {
184+
ensureItxnGroupBegin()
185+
return new ItxnParams<itxn.PaymentFields, itxn.PaymentInnerTxn>(fields, TransactionType.Payment)
186+
}
187+
export function keyRegistration(fields: itxn.KeyRegistrationFields): itxn.KeyRegistrationItxnParams {
188+
ensureItxnGroupBegin()
189+
return new ItxnParams<itxn.KeyRegistrationFields, itxn.KeyRegistrationInnerTxn>(fields, TransactionType.KeyRegistration)
190+
}
191+
export function assetConfig(fields: itxn.AssetConfigFields): itxn.AssetConfigItxnParams {
192+
ensureItxnGroupBegin()
193+
return new ItxnParams<itxn.AssetConfigFields, itxn.AssetConfigInnerTxn>(fields, TransactionType.AssetConfig)
194+
}
195+
export function assetTransfer(fields: itxn.AssetTransferFields): itxn.AssetTransferItxnParams {
196+
ensureItxnGroupBegin()
197+
return new ItxnParams<itxn.AssetTransferFields, itxn.AssetTransferInnerTxn>(fields, TransactionType.AssetTransfer)
198+
}
199+
export function assetFreeze(fields: itxn.AssetFreezeFields): itxn.AssetFreezeItxnParams {
200+
ensureItxnGroupBegin()
201+
return new ItxnParams<itxn.AssetFreezeFields, itxn.AssetFreezeInnerTxn>(fields, TransactionType.AssetFreeze)
202+
}
203+
export function applicationCall(fields: itxn.ApplicationCallFields): itxn.ApplicationCallItxnParams {
204+
ensureItxnGroupBegin()
205+
return new ItxnParams<itxn.ApplicationCallFields, itxn.ApplicationInnerTxn>(fields, TransactionType.ApplicationCall)
206+
}
207+
208+
export class ItxnParams<TFields extends InnerTxnFields, TTransaction extends InnerTxn> {
209+
#fields: TFields & { type: TransactionType }
210+
constructor(fields: TFields, type: TransactionType) {
211+
this.#fields = { ...fields, type }
212+
}
213+
submit(): TTransaction {
214+
return createInnerTxn<InnerTxnFields, TTransaction>(this.#fields)
215+
}
216+
217+
set(p: Partial<TFields>) {
218+
Object.assign(this.#fields, p)
219+
}
220+
221+
copy() {
222+
return new ItxnParams<TFields, TTransaction>(this.#fields, this.#fields.type)
223+
}
224+
}
225+
226+
const ensureItxnGroupBegin = () => {
227+
if (!lazyContext.activeGroup.constructingItxnGroup.length) {
228+
lazyContext.activeGroup.beginInnerTransactionGroup()
177229
}
178230
}

src/subcontexts/transaction-context.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -183,7 +183,7 @@ export class TransactionGroup {
183183
if (!this.constructingItxnGroup.length) {
184184
internal.errors.internalError('itxn next without itxn begin')
185185
}
186-
this.constructingItxnGroup.push({} as InnerTxnFields)
186+
this.constructingItxnGroup.push({ type: TransactionType.Payment } as InnerTxnFields)
187187
}
188188

189189
submitInnerTransactionGroup() {

src/test-execution-context.ts

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,15 @@ import { ContractContext } from './subcontexts/contract-context'
1010
import { LedgerContext } from './subcontexts/ledger-context'
1111
import { TransactionContext } from './subcontexts/transaction-context'
1212
import { ValueGenerator } from './value-generators'
13+
import {
14+
submitGroup as itxnSubmitGroup,
15+
payment as itxnPayment,
16+
keyRegistration as itxnKeyRegistration,
17+
assetConfig as itxnAssetConfig,
18+
assetTransfer as itxnAssetTransfer,
19+
assetFreeze as itxnAssetFreeze,
20+
applicationCall as itxnApplicationCall,
21+
} from './impl/inner-transactions'
1322

1423
export class TestExecutionContext implements internal.ExecutionContext {
1524
#applicationLogs: Map<bigint, bytes[]>
@@ -90,6 +99,18 @@ export class TestExecutionContext implements internal.ExecutionContext {
9099
}
91100
}
92101

102+
get itxn() {
103+
return {
104+
submitGroup: itxnSubmitGroup,
105+
payment: itxnPayment,
106+
keyRegistration: itxnKeyRegistration,
107+
assetConfig: itxnAssetConfig,
108+
assetTransfer: itxnAssetTransfer,
109+
assetFreeze: itxnAssetFreeze,
110+
applicationCall: itxnApplicationCall,
111+
}
112+
}
113+
93114
reset() {
94115
this.#applicationLogs.clear()
95116
this.#contractContext = new ContractContext()

tests/artifacts/state-ops/contract.algo.ts

Lines changed: 99 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,12 @@ import {
44
arc4,
55
assert,
66
Asset,
7+
BaseContract,
78
Bytes,
89
bytes,
10+
Global,
11+
GlobalState,
12+
itxn,
913
op,
1014
TransactionType,
1115
Txn,
@@ -375,3 +379,98 @@ export class ITxnOpsContract extends arc4.Contract {
375379
assert(op.GITxn.typeEnum(1) == TransactionType.Payment)
376380
}
377381
}
382+
383+
const APPROVE = Bytes('\x09\x81\x01')
384+
export class ItxnDemoContract extends BaseContract {
385+
name = GlobalState({ initialValue: Bytes() })
386+
387+
public approvalProgram(): boolean {
388+
if (Txn.numAppArgs) {
389+
switch (Txn.applicationArgs(0)) {
390+
case Bytes('test1'):
391+
this.test1()
392+
break
393+
case Bytes('test2'):
394+
this.test2()
395+
break
396+
case Bytes('test3'):
397+
case Bytes('test4'):
398+
break
399+
}
400+
}
401+
return true
402+
}
403+
404+
private test1() {
405+
this.name.value = Bytes('AST1')
406+
407+
const assetParams = itxn.assetConfig({
408+
total: 1000,
409+
assetName: this.name.value,
410+
unitName: 'unit',
411+
decimals: 3,
412+
manager: Global.currentApplicationAddress,
413+
reserve: Global.currentApplicationAddress,
414+
})
415+
416+
this.name.value = Bytes('AST2')
417+
const asset1_txn = assetParams.submit()
418+
assetParams.set({
419+
assetName: this.name.value,
420+
})
421+
const asset2_txn = assetParams.submit()
422+
423+
assert(asset1_txn.assetName === Bytes('AST1'), 'asset1_txn is correct')
424+
assert(asset2_txn.assetName === Bytes('AST2'), 'asset2_txn is correct')
425+
assert(asset1_txn.createdAsset.name === Bytes('AST1'), 'created asset 1 is correct')
426+
assert(asset2_txn.createdAsset.name === Bytes('AST2'), 'created asset 2 is correct')
427+
428+
const appCreateParams = itxn.applicationCall({
429+
approvalProgram: Bytes.fromHex('098101'),
430+
clearStateProgram: Bytes.fromHex('098101'),
431+
fee: 0,
432+
})
433+
434+
assetParams.set({
435+
assetName: 'AST3',
436+
})
437+
438+
const [appCreateTxn, asset3_txn] = itxn.submitGroup(appCreateParams, assetParams)
439+
440+
assert(appCreateTxn.appId, 'app is created')
441+
assert(asset3_txn.assetName === Bytes('AST3'), 'asset3_txn is correct')
442+
443+
appCreateParams.set({
444+
note: '3rd',
445+
})
446+
assetParams.set({
447+
note: '3rd',
448+
})
449+
itxn.submitGroup(appCreateParams, assetParams)
450+
}
451+
452+
private test2() {
453+
let createAppParams: itxn.ApplicationCallItxnParams
454+
if (Txn.numAppArgs) {
455+
const args = [Bytes('1'), Bytes('2')] as const
456+
createAppParams = itxn.applicationCall({
457+
approvalProgram: APPROVE,
458+
clearStateProgram: APPROVE,
459+
appArgs: args,
460+
onCompletion: arc4.OnCompleteAction.NoOp,
461+
note: 'with args param set',
462+
})
463+
} else {
464+
createAppParams = itxn.applicationCall({
465+
approvalProgram: APPROVE,
466+
clearStateProgram: APPROVE,
467+
appArgs: [Bytes('3'), '4', Bytes('5')],
468+
note: 'no args param set',
469+
})
470+
}
471+
const createAppTxn = createAppParams.submit()
472+
assert(createAppTxn.appArgs(0) === Bytes('1'), 'correct args used 1')
473+
assert(createAppTxn.appArgs(1) === Bytes('2'), 'correct args used 2')
474+
assert(createAppTxn.note === Bytes('with args param set'))
475+
}
476+
}

tests/state-op-codes.spec.ts

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ import { AccountCls } from '../src/impl/account'
1818
import { asBigInt, asNumber, asUint64Cls } from '../src/util'
1919
import { AppExpectingEffects } from './artifacts/created-app-asset/contract.algo'
2020
import {
21+
ItxnDemoContract,
2122
ITxnOpsContract,
2223
StateAcctParamsGetContract,
2324
StateAppParamsContract,
@@ -352,8 +353,18 @@ describe('State op codes', async () => {
352353
expect(asNumber((ctx.txn.lastActive as ApplicationTransaction).numLogs)).toEqual(0)
353354
expect((ctx.txn.lastActive as ApplicationTransaction).lastLog).toEqual(Bytes(''))
354355

355-
// Test created_app and created_asset (should be created for these transactions)
356+
// Test created_app (should be created for these transactions)
356357
expect(appItxn.createdApp).toBeTruthy()
357358
})
359+
360+
it('should be able to invoke demo contract', async () => {
361+
const contract = ctx.contract.create(ItxnDemoContract)
362+
ctx.txn.createScope([ctx.any.txn.applicationCall({ appArgs: [Bytes('test1')] })]).execute(() => {
363+
contract.approvalProgram()
364+
})
365+
ctx.txn.createScope([ctx.any.txn.applicationCall({ appArgs: [Bytes('test2')] })]).execute(() => {
366+
contract.approvalProgram()
367+
})
368+
})
358369
})
359370
})

0 commit comments

Comments
 (0)