Skip to content

Commit 05a92d1

Browse files
committed
feat: recognise conventional methods and use appropriate
OnCompleteAction as default
1 parent 51d68f6 commit 05a92d1

File tree

6 files changed

+66
-9
lines changed

6 files changed

+66
-9
lines changed

examples/auction/contract.algo.ts

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,6 @@ export class Auction extends Contract {
2626

2727
claimableAmount = LocalState<uint64>()
2828

29-
@abimethod({ allowActions: 'NoOp', onCreate: 'require' })
3029
public createApplication(): void {
3130
this.auctionEnd.value = 0
3231
this.previousBid.value = 0
@@ -82,7 +81,7 @@ export class Auction extends Contract {
8281
}
8382

8483
@abimethod({ allowActions: 'OptIn' })
85-
public optInToApplication(): void {}
84+
public optInToApplication(): void { }
8685

8786
public bid(payment: gtxn.PaymentTxn): void {
8887
/// Ensure auction hasn't ended

src/abi-metadata.ts

Lines changed: 40 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import type { OnCompleteActionStr } from '@algorandfoundation/algorand-typescript'
22
import type { CreateOptions } from '@algorandfoundation/algorand-typescript/arc4'
33
import js_sha512 from 'js-sha512'
4+
import { ConventionalRouting } from './constants'
45
import type { TypeInfo } from './encoders'
56
import { Arc4MethodConfigSymbol, Contract } from './impl/contract'
67
import { getArc4TypeName as getArc4TypeNameForARC4Encoded } from './impl/encoded-types'
@@ -22,7 +23,12 @@ export const attachAbiMetadata = (contract: { new (): Contract }, methodName: st
2223
metadataStore.set(contract, {})
2324
}
2425
const metadatas: Record<string, AbiMetadata> = metadataStore.get(contract) as Record<string, AbiMetadata>
25-
metadatas[methodName] = metadata
26+
const conventionalRoutingConfig = getConventionalRoutingConfig(methodName)
27+
metadatas[methodName] = {
28+
...metadata,
29+
allowActions: metadata.allowActions ?? conventionalRoutingConfig?.allowActions,
30+
onCreate: metadata.onCreate ?? conventionalRoutingConfig?.onCreate,
31+
}
2632
}
2733

2834
export const getContractAbiMetadata = <T extends Contract>(contract: T | { new (): T }): Record<string, AbiMetadata> => {
@@ -110,3 +116,36 @@ const getArc4TypeName = (t: TypeInfo): string => {
110116
}
111117
return entry
112118
}
119+
120+
/**
121+
* Get routing properties inferred by conventional naming
122+
* @param methodName The name of the method
123+
*/
124+
const getConventionalRoutingConfig = (methodName: string): Pick<AbiMetadata, 'allowActions' | 'onCreate'> | undefined => {
125+
switch (methodName) {
126+
case ConventionalRouting.methodNames.closeOutOfApplication:
127+
return {
128+
allowActions: ['CloseOut'],
129+
onCreate: 'disallow',
130+
}
131+
case ConventionalRouting.methodNames.createApplication:
132+
return {
133+
onCreate: 'require',
134+
}
135+
case ConventionalRouting.methodNames.deleteApplication:
136+
return {
137+
allowActions: ['DeleteApplication'],
138+
}
139+
case ConventionalRouting.methodNames.optInToApplication:
140+
return {
141+
allowActions: ['OptIn'],
142+
}
143+
case ConventionalRouting.methodNames.updateApplication:
144+
return {
145+
allowActions: ['UpdateApplication'],
146+
onCreate: 'disallow',
147+
}
148+
default:
149+
return undefined
150+
}
151+
}

src/constants.ts

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,3 +66,13 @@ export enum OnApplicationComplete {
6666
UpdateApplicationOC = 4,
6767
DeleteApplicationOC = 5,
6868
}
69+
70+
export const ConventionalRouting = {
71+
methodNames: {
72+
closeOutOfApplication: 'closeOutOfApplication',
73+
createApplication: 'createApplication',
74+
deleteApplication: 'deleteApplication',
75+
optInToApplication: 'optInToApplication',
76+
updateApplication: 'updateApplication',
77+
},
78+
}

src/impl/c2c.ts

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -89,8 +89,15 @@ export function abiCall<TArgs extends DeliberateAny[], TReturn>(
8989
methodArgs: TypedApplicationCallFields<TArgs>,
9090
contract?: Contract | { new (): Contract },
9191
): { itxn: ApplicationCallInnerTxn; returnValue: TReturn | undefined } {
92+
const abiMetadata = contract ? getContractMethodAbiMetadata(contract, method.name) : undefined
9293
const selector = methodSelector(method, contract)
93-
const itxnContext = ApplicationCallInnerTxnContext.createFromTypedApplicationCallFields<TReturn>(methodArgs, selector)
94+
const itxnContext = ApplicationCallInnerTxnContext.createFromTypedApplicationCallFields<TReturn>(
95+
{
96+
...methodArgs,
97+
onCompletion: methodArgs.onCompletion ?? abiMetadata?.allowActions?.map((action) => OnCompleteAction[action])[0],
98+
},
99+
selector,
100+
)
94101
invokeCallback(itxnContext)
95102

96103
return {

src/subcontexts/contract-context.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -279,6 +279,9 @@ const getContractOptions = (contract: BaseContract): ContractOptionsParameter |
279279
}
280280

281281
const hasCreateMethods = (contract: Contract) => {
282+
const createFn = Reflect.get(contract, 'createApplication')
283+
if (createFn !== undefined && typeof createFn === 'function') return true
284+
282285
const metadatas = getContractAbiMetadata(contract)
283286
return Object.values(metadatas).some((metadata) => (metadata.onCreate ?? 'disallow') !== 'disallow')
284287
}

tests/references/arc4-contract.spec.ts

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import type { Account, bytes, uint64 } from '@algorandfoundation/algorand-typescript'
2-
import { arc4, assert, BaseContract, Bytes, contract, Contract, Global, Txn, Uint64 } from '@algorandfoundation/algorand-typescript'
2+
import { assert, BaseContract, Bytes, contract, Contract, Global, Txn, Uint64 } from '@algorandfoundation/algorand-typescript'
33
import { TestExecutionContext } from '@algorandfoundation/algorand-typescript-testing'
44
import { afterEach, describe, expect, it } from 'vitest'
55
import { lazyContext } from '../../src/context-helpers/internal-context'
@@ -39,8 +39,7 @@ class ContractARC4Create extends Contract {
3939
this.#stateTotals = Uint64()
4040
}
4141

42-
@arc4.abimethod({ onCreate: 'require' })
43-
create(val: uint64): void {
42+
createApplication(val: uint64): void {
4443
this.arg1 = val
4544
assert(Global.currentApplicationId.globalNumBytes === 4)
4645
assert(Global.currentApplicationId.globalNumUint === 5)
@@ -82,7 +81,7 @@ describe('arc4 contract creation', () => {
8281

8382
const contract = ctx.contract.create(ContractARC4Create)
8483
ctx.txn.createScope([ctx.any.txn.applicationCall({ appId: ctx.ledger.getApplicationForContract(contract), sender })]).execute(() => {
85-
contract.create(arg1)
84+
contract.createApplication(arg1)
8685
expect(contract.arg1).toEqual(arg1)
8786
expect(contract.creator).toEqual(sender)
8887
})
@@ -96,7 +95,7 @@ describe('arc4 contract creation', () => {
9695
const appData = lazyContext.getApplicationData(contract)
9796
expect(appData.isCreating).toBe(true)
9897

99-
contract.create(arg1)
98+
contract.createApplication(arg1)
10099

101100
expect(appData.isCreating).toBe(false)
102101
expect(contract.arg1).toEqual(arg1)

0 commit comments

Comments
 (0)