Skip to content
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
10 changes: 8 additions & 2 deletions src/abi-metadata.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,8 +22,14 @@ const metadataStore: WeakMap<{ new (): Contract }, Record<string, AbiMetadata>>
const contractSymbolMap: Map<string, symbol> = new Map()
const contractMap: WeakMap<symbol, { new (): Contract }> = new WeakMap()
/** @internal */
export const attachAbiMetadata = (contract: { new (): Contract }, methodName: string, metadata: AbiMetadata, fileName: string): void => {
const contractFullName = `${fileName}::${contract.prototype.constructor.name}`
export const attachAbiMetadata = (
contract: { new (): Contract },
methodName: string,
metadata: AbiMetadata,
fileName: string,
contractName: string,
): void => {
const contractFullName = `${fileName}::${contractName}`
if (!contractSymbolMap.has(contractFullName)) {
contractSymbolMap.set(contractFullName, Symbol(contractFullName))
}
Expand Down
8 changes: 7 additions & 1 deletion src/test-transformer/node-factory.ts
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,13 @@ export const nodeFactory = {
factory.createCallExpression(
factory.createPropertyAccessExpression(factory.createIdentifier('runtimeHelpers'), factory.createIdentifier('attachAbiMetadata')),
undefined,
[classIdentifier, methodName, metadata, factory.createStringLiteral(sourceFileName)],
[
classIdentifier,
methodName,
metadata,
factory.createStringLiteral(sourceFileName),
factory.createStringLiteral(classIdentifier.text),
],
),
)
},
Expand Down
13 changes: 11 additions & 2 deletions src/test-transformer/visitors.ts
Original file line number Diff line number Diff line change
Expand Up @@ -355,6 +355,7 @@ class MethodDecVisitor extends FunctionOrMethodVisitor {

class ClassVisitor {
private isArc4: boolean
private _sourceFileName: string | undefined
constructor(
private context: Context,
private helper: VisitorHelper,
Expand All @@ -368,15 +369,23 @@ class ClassVisitor {
return this.visit(this.classDec) as ts.ClassDeclaration
}

private get sourceFileName(): string {
if (!this._sourceFileName) {
this._sourceFileName = normalisePath(this.classDec.parent.getSourceFile().fileName, this.context.currentDirectory)
}
return this._sourceFileName
}

private visit = (node: ts.Node): ts.Node => {
if (ts.isMethodDeclaration(node)) {
if (this.classDec.name && this.isArc4) {
const methodType = this.helper.resolveType(node)
if (methodType instanceof ptypes.FunctionPType) {
const argTypes = methodType.parameters.map((p) => JSON.stringify(getGenericTypeInfo(p[1])))
const returnType = JSON.stringify(getGenericTypeInfo(methodType.returnType))
const sourceFileName = normalisePath(this.classDec.parent.getSourceFile().fileName, this.context.currentDirectory)
this.helper.additionalStatements.push(nodeFactory.attachMetaData(sourceFileName, this.classDec.name, node, argTypes, returnType))
this.helper.additionalStatements.push(
nodeFactory.attachMetaData(this.sourceFileName, this.classDec.name, node, argTypes, returnType),
)
}
}

Expand Down
32 changes: 32 additions & 0 deletions tests/arc4/abicall-decorated.algo.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import { ApplicationSpy } from '@algorandfoundation/algorand-typescript-testing'
import { methodSelector } from '@algorandfoundation/algorand-typescript/arc4'
import { afterEach, describe, expect, test } from 'vitest'
import { TestExecutionContext } from '../../src/test-execution-context'
import { Hello } from '../artifacts/abicall-decorated/contract.algo'
import { DecoratedGreeter } from '../artifacts/abicall-decorated/decorated-greeter.algo'

describe('abicalll polytype ', () => {
const ctx = new TestExecutionContext()

afterEach(() => {
ctx.reset()
})

test('test call contract one', async () => {
const greeter = ctx.contract.create(DecoratedGreeter)
const hello = ctx.contract.create(Hello)

const greeterApp = ctx.ledger.getApplicationForContract(greeter)

hello.createApplication(greeterApp)

const spy = new ApplicationSpy()
spy.onAbiCall(methodSelector(DecoratedGreeter.prototype.greet), (itxnContext) => {
itxnContext.setReturnValue('Hello, World, from Algorand')
})
ctx.addApplicationSpy(spy)

const result = hello.greet()
expect(result).toEqual('Hello, World, from Algorand')
})
})
22 changes: 22 additions & 0 deletions tests/artifacts/abicall-decorated/contract.algo.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import type { Application } from '@algorandfoundation/algorand-typescript'
import { Contract, GlobalState, abimethod } from '@algorandfoundation/algorand-typescript'
import { abiCall } from '@algorandfoundation/algorand-typescript/arc4'
import type { DecoratedGreeter } from './decorated-greeter.algo'

export class Hello extends Contract {
greeterApp = GlobalState<Application>({ key: 'greeterApp' })

@abimethod({ onCreate: 'require' })
createApplication(greeterApp: Application): void {
this.greeterApp.value = greeterApp
}

greet(): string {
abiCall<typeof DecoratedGreeter.prototype.setGreeting>({ appId: this.greeterApp.value, args: ['Hello'] })
abiCall<typeof DecoratedGreeter.prototype.setName>({ appId: this.greeterApp.value, args: ['World'] })

const { returnValue } = abiCall<typeof DecoratedGreeter.prototype.greet>({ appId: this.greeterApp.value, args: ['from Algorand'] })

return returnValue
}
}
22 changes: 22 additions & 0 deletions tests/artifacts/abicall-decorated/decorated-greeter.algo.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import { abimethod, contract, Contract, GlobalState } from '@algorandfoundation/algorand-typescript'

@contract({ name: 'Greeter', avmVersion: 11 })
export class DecoratedGreeter extends Contract {
greeting = GlobalState({ initialValue: '' })
name = GlobalState({ initialValue: '' })

@abimethod()
setGreeting(greeting: string) {
this.greeting.value = greeting
}

@abimethod()
setName(name: string) {
this.name.value = name
}

@abimethod()
greet(from: string): string {
return `${this.greeting.value}, ${this.name.value}, ${from}`
}
}
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
import type { Application } from '@algorandfoundation/algorand-typescript'
import { Contract, log } from '@algorandfoundation/algorand-typescript'
import { contract, Contract, log } from '@algorandfoundation/algorand-typescript'
import { abiCall } from '@algorandfoundation/algorand-typescript/arc4'
import type { ContractOne } from './circular-reference.algo'

@contract({ name: 'ContractTwo' })
export class ContractTwo extends Contract {
test(appId: Application) {
const result = abiCall<typeof ContractOne.prototype.receiver>({ appId, args: [appId] })
Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
import type { Application } from '@algorandfoundation/algorand-typescript'
import { Contract, log } from '@algorandfoundation/algorand-typescript'
import { contract, Contract, log } from '@algorandfoundation/algorand-typescript'
import { abiCall } from '@algorandfoundation/algorand-typescript/arc4'
import type { ContractTwo } from './circular-reference-2.algo'

@contract({ name: 'ContractOne' })
export class ContractOne extends Contract {
test(appId: Application) {
const result = abiCall<typeof ContractTwo.prototype.receiver>({ appId, args: [appId] })
Expand Down