Skip to content

Commit 176463f

Browse files
committed
fix: abiCall throws unknown contract error for calls made to contracts with @contract decorator
- do not use .prototype.constructor.name which can be changed during transpilation as per [this commit](https://github.com/evanw/esbuild/pull/3167/files#diff-47ba81a23cc6828c328cf8d6b3be85292b39bd7ed1032957067b80b7947bce39) in esbuild - capture ClassDeclaration.name as contract name instead
1 parent 33ed9bd commit 176463f

File tree

8 files changed

+106
-7
lines changed

8 files changed

+106
-7
lines changed

src/abi-metadata.ts

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,8 +22,14 @@ const metadataStore: WeakMap<{ new (): Contract }, Record<string, AbiMetadata>>
2222
const contractSymbolMap: Map<string, symbol> = new Map()
2323
const contractMap: WeakMap<symbol, { new (): Contract }> = new WeakMap()
2424
/** @internal */
25-
export const attachAbiMetadata = (contract: { new (): Contract }, methodName: string, metadata: AbiMetadata, fileName: string): void => {
26-
const contractFullName = `${fileName}::${contract.prototype.constructor.name}`
25+
export const attachAbiMetadata = (
26+
contract: { new (): Contract },
27+
methodName: string,
28+
metadata: AbiMetadata,
29+
fileName: string,
30+
contractName: string,
31+
): void => {
32+
const contractFullName = `${fileName}::${contractName}`
2733
if (!contractSymbolMap.has(contractFullName)) {
2834
contractSymbolMap.set(contractFullName, Symbol(contractFullName))
2935
}

src/test-transformer/node-factory.ts

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -81,7 +81,13 @@ export const nodeFactory = {
8181
factory.createCallExpression(
8282
factory.createPropertyAccessExpression(factory.createIdentifier('runtimeHelpers'), factory.createIdentifier('attachAbiMetadata')),
8383
undefined,
84-
[classIdentifier, methodName, metadata, factory.createStringLiteral(sourceFileName)],
84+
[
85+
classIdentifier,
86+
methodName,
87+
metadata,
88+
factory.createStringLiteral(sourceFileName),
89+
factory.createStringLiteral(classIdentifier.text),
90+
],
8591
),
8692
)
8793
},

src/test-transformer/visitors.ts

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -355,6 +355,7 @@ class MethodDecVisitor extends FunctionOrMethodVisitor {
355355

356356
class ClassVisitor {
357357
private isArc4: boolean
358+
private _sourceFileName: string | undefined
358359
constructor(
359360
private context: Context,
360361
private helper: VisitorHelper,
@@ -368,15 +369,23 @@ class ClassVisitor {
368369
return this.visit(this.classDec) as ts.ClassDeclaration
369370
}
370371

372+
private get sourceFileName(): string {
373+
if (!this._sourceFileName) {
374+
this._sourceFileName = normalisePath(this.classDec.parent.getSourceFile().fileName, this.context.currentDirectory)
375+
}
376+
return this._sourceFileName
377+
}
378+
371379
private visit = (node: ts.Node): ts.Node => {
372380
if (ts.isMethodDeclaration(node)) {
373381
if (this.classDec.name && this.isArc4) {
374382
const methodType = this.helper.resolveType(node)
375383
if (methodType instanceof ptypes.FunctionPType) {
376384
const argTypes = methodType.parameters.map((p) => JSON.stringify(getGenericTypeInfo(p[1])))
377385
const returnType = JSON.stringify(getGenericTypeInfo(methodType.returnType))
378-
const sourceFileName = normalisePath(this.classDec.parent.getSourceFile().fileName, this.context.currentDirectory)
379-
this.helper.additionalStatements.push(nodeFactory.attachMetaData(sourceFileName, this.classDec.name, node, argTypes, returnType))
386+
this.helper.additionalStatements.push(
387+
nodeFactory.attachMetaData(this.sourceFileName, this.classDec.name, node, argTypes, returnType),
388+
)
380389
}
381390
}
382391

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
import { ApplicationSpy } from '@algorandfoundation/algorand-typescript-testing'
2+
import { methodSelector } from '@algorandfoundation/algorand-typescript/arc4'
3+
import { afterEach, describe, expect, test } from 'vitest'
4+
import { TestExecutionContext } from '../../src/test-execution-context'
5+
import { Hello } from '../artifacts/abicall-decorated/contract.algo'
6+
import { DecoratedGreeter } from '../artifacts/abicall-decorated/decorated-greeter.algo'
7+
8+
describe('abicalll polytype ', () => {
9+
const ctx = new TestExecutionContext()
10+
11+
afterEach(() => {
12+
ctx.reset()
13+
})
14+
15+
test('test call contract one', async () => {
16+
const greeter = ctx.contract.create(DecoratedGreeter)
17+
const hello = ctx.contract.create(Hello)
18+
19+
const greeterApp = ctx.ledger.getApplicationForContract(greeter)
20+
21+
hello.createApplication(greeterApp)
22+
23+
const spy = new ApplicationSpy()
24+
spy.onAbiCall(methodSelector(DecoratedGreeter.prototype.greet), (itxnContext) => {
25+
itxnContext.setReturnValue('Hello, World, from Algorand')
26+
})
27+
ctx.addApplicationSpy(spy)
28+
29+
const result = hello.greet()
30+
expect(result).toEqual('Hello, World, from Algorand')
31+
})
32+
})
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
import type { Application } from '@algorandfoundation/algorand-typescript'
2+
import { Contract, GlobalState, abimethod } from '@algorandfoundation/algorand-typescript'
3+
import { abiCall } from '@algorandfoundation/algorand-typescript/arc4'
4+
import type { DecoratedGreeter } from './decorated-greeter.algo'
5+
6+
export class Hello extends Contract {
7+
greeterApp = GlobalState<Application>({ key: 'greeterApp' })
8+
9+
@abimethod({ onCreate: 'require' })
10+
createApplication(greeterApp: Application): void {
11+
this.greeterApp.value = greeterApp
12+
}
13+
14+
greet(): string {
15+
abiCall<typeof DecoratedGreeter.prototype.setGreeting>({ appId: this.greeterApp.value, args: ['Hello'] })
16+
abiCall<typeof DecoratedGreeter.prototype.setName>({ appId: this.greeterApp.value, args: ['World'] })
17+
18+
const { returnValue } = abiCall<typeof DecoratedGreeter.prototype.greet>({ appId: this.greeterApp.value, args: ['from Algorand'] })
19+
20+
return returnValue
21+
}
22+
}
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
import { abimethod, contract, Contract, GlobalState } from '@algorandfoundation/algorand-typescript'
2+
3+
@contract({ name: 'Greeter', avmVersion: 11 })
4+
export class DecoratedGreeter extends Contract {
5+
greeting = GlobalState({ initialValue: '' })
6+
name = GlobalState({ initialValue: '' })
7+
8+
@abimethod()
9+
setGreeting(greeting: string) {
10+
this.greeting.value = greeting
11+
}
12+
13+
@abimethod()
14+
setName(name: string) {
15+
this.name.value = name
16+
}
17+
18+
@abimethod()
19+
greet(from: string): string {
20+
return `${this.greeting.value}, ${this.name.value}, ${from}`
21+
}
22+
}

tests/artifacts/circurlar-reference/circular-reference-2.algo.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,9 @@
11
import type { Application } from '@algorandfoundation/algorand-typescript'
2-
import { Contract, log } from '@algorandfoundation/algorand-typescript'
2+
import { contract, Contract, log } from '@algorandfoundation/algorand-typescript'
33
import { abiCall } from '@algorandfoundation/algorand-typescript/arc4'
44
import type { ContractOne } from './circular-reference.algo'
55

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

tests/artifacts/circurlar-reference/circular-reference.algo.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,9 @@
11
import type { Application } from '@algorandfoundation/algorand-typescript'
2-
import { Contract, log } from '@algorandfoundation/algorand-typescript'
2+
import { contract, Contract, log } from '@algorandfoundation/algorand-typescript'
33
import { abiCall } from '@algorandfoundation/algorand-typescript/arc4'
44
import type { ContractTwo } from './circular-reference-2.algo'
55

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

0 commit comments

Comments
 (0)