Skip to content

Commit 3ffe395

Browse files
committed
feat: implement stubs for compile functions
1 parent b61fd52 commit 3ffe395

File tree

14 files changed

+420
-15
lines changed

14 files changed

+420
-15
lines changed
Lines changed: 136 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,136 @@
1+
import type { bytes, uint64 } from '@algorandfoundation/algorand-typescript'
2+
import { assert, compile, Contract, itxn } from '@algorandfoundation/algorand-typescript'
3+
import { decodeArc4, encodeArc4, methodSelector, OnCompleteAction } from '@algorandfoundation/algorand-typescript/arc4'
4+
import { Hello, HelloTemplate, HelloTemplateCustomPrefix, LargeProgram, TerribleCustodialAccount } from './precompiled-apps.algo'
5+
6+
export class HelloFactory extends Contract {
7+
test_compile_contract() {
8+
const compiled = compile(Hello)
9+
10+
const helloApp = itxn
11+
.applicationCall({
12+
appArgs: [methodSelector('create(string)void'), encodeArc4('hello')],
13+
approvalProgram: compiled.approvalProgram,
14+
clearStateProgram: compiled.clearStateProgram,
15+
globalNumBytes: 1,
16+
})
17+
.submit().createdApp
18+
19+
const txn = itxn
20+
.applicationCall({
21+
appArgs: [methodSelector('greet(string)string'), encodeArc4('world')],
22+
appId: helloApp,
23+
})
24+
.submit()
25+
const result = decodeArc4<string>(txn.lastLog, 'log')
26+
27+
assert(result === 'hello world')
28+
29+
itxn
30+
.applicationCall({
31+
appId: helloApp,
32+
appArgs: [methodSelector('delete()void')],
33+
onCompletion: OnCompleteAction.DeleteApplication,
34+
})
35+
.submit()
36+
}
37+
38+
test_compile_contract_with_template() {
39+
const compiled = compile(HelloTemplate, { templateVars: { GREETING: 'hey' } })
40+
41+
const helloApp = itxn
42+
.applicationCall({
43+
appArgs: [methodSelector('create()void')],
44+
approvalProgram: compiled.approvalProgram,
45+
clearStateProgram: compiled.clearStateProgram,
46+
globalNumBytes: 1,
47+
})
48+
.submit().createdApp
49+
50+
const txn = itxn
51+
.applicationCall({
52+
appArgs: [methodSelector('greet(string)string'), encodeArc4('world')],
53+
appId: helloApp,
54+
})
55+
.submit()
56+
const result = decodeArc4<string>(txn.lastLog, 'log')
57+
58+
assert(result === 'hey world')
59+
60+
itxn
61+
.applicationCall({
62+
appId: helloApp,
63+
appArgs: [methodSelector('delete()void')],
64+
onCompletion: OnCompleteAction.DeleteApplication,
65+
})
66+
.submit()
67+
}
68+
69+
test_compile_contract_with_template_and_custom_prefix() {
70+
const compiled = compile(HelloTemplateCustomPrefix, { templateVars: { GREETING: 'bonjour' }, templateVarsPrefix: 'PRFX_' })
71+
72+
const helloApp = itxn
73+
.applicationCall({
74+
appArgs: [methodSelector('create()void')],
75+
approvalProgram: compiled.approvalProgram,
76+
clearStateProgram: compiled.clearStateProgram,
77+
globalNumBytes: 1,
78+
})
79+
.submit().createdApp
80+
81+
const txn = itxn
82+
.applicationCall({
83+
appArgs: [methodSelector('greet(string)string'), encodeArc4('world')],
84+
appId: helloApp,
85+
})
86+
.submit()
87+
const result = decodeArc4<string>(txn.lastLog, 'log')
88+
89+
assert(result === 'bonjour world')
90+
91+
itxn
92+
.applicationCall({
93+
appId: helloApp,
94+
appArgs: [methodSelector('delete()void')],
95+
onCompletion: OnCompleteAction.DeleteApplication,
96+
})
97+
.submit()
98+
}
99+
100+
test_compile_contract_large() {
101+
const compiled = compile(LargeProgram)
102+
103+
const largeApp = itxn
104+
.applicationCall({
105+
approvalProgram: compiled.approvalProgram,
106+
clearStateProgram: compiled.clearStateProgram,
107+
extraProgramPages: compiled.extraProgramPages,
108+
globalNumBytes: compiled.globalBytes,
109+
})
110+
.submit().createdApp
111+
112+
const txn = itxn
113+
.applicationCall({
114+
appArgs: [methodSelector('getBigBytesLength()uint64')],
115+
appId: largeApp,
116+
})
117+
.submit()
118+
const result = decodeArc4<uint64>(txn.lastLog, 'log')
119+
120+
assert(result === 4096)
121+
122+
itxn
123+
.applicationCall({
124+
appId: largeApp,
125+
appArgs: [methodSelector('delete()void')],
126+
onCompletion: OnCompleteAction.DeleteApplication,
127+
})
128+
.submit()
129+
}
130+
131+
test_compile_logic_sig(account: bytes) {
132+
const compiled = compile(TerribleCustodialAccount)
133+
134+
assert(compiled.account.bytes === account)
135+
}
136+
}
Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
import { arc4 } from '@algorandfoundation/algorand-typescript'
2+
import { TestExecutionContext } from '@algorandfoundation/algorand-typescript-testing'
3+
import { afterEach, describe, it } from 'vitest'
4+
import { ABI_RETURN_VALUE_LOG_PREFIX, MAX_BYTES_SIZE } from '../../src/constants'
5+
import { asUint64Cls } from '../../src/util'
6+
import { HelloFactory } from './contract.algo'
7+
import { Hello, HelloTemplate, HelloTemplateCustomPrefix, LargeProgram, TerribleCustodialAccount } from './precompiled-apps.algo'
8+
9+
describe('pre compiled app calls', () => {
10+
const ctx = new TestExecutionContext()
11+
afterEach(() => {
12+
ctx.reset()
13+
})
14+
15+
it('should be able to compile and call a precompiled app', () => {
16+
// Arrange
17+
const helloApp = ctx.any.application({
18+
approvalProgram: ctx.any.bytes(20),
19+
appLogs: [ABI_RETURN_VALUE_LOG_PREFIX.concat(new arc4.Str('hello world').bytes)],
20+
})
21+
ctx.setCompiledApp(Hello, helloApp.id)
22+
23+
const contract = ctx.contract.create(HelloFactory)
24+
25+
// Act
26+
contract.test_compile_contract()
27+
})
28+
29+
it('should be able to compile with template vars and call a precompiled app', () => {
30+
// Arrange
31+
const helloTemplateApp = ctx.any.application({
32+
approvalProgram: ctx.any.bytes(20),
33+
appLogs: [ABI_RETURN_VALUE_LOG_PREFIX.concat(new arc4.Str('hey world').bytes)],
34+
})
35+
ctx.setCompiledApp(HelloTemplate, helloTemplateApp.id)
36+
37+
const contract = ctx.contract.create(HelloFactory)
38+
39+
// Act
40+
contract.test_compile_contract_with_template()
41+
})
42+
43+
it('should be able to compile with template vars and custom prefix', () => {
44+
// Arrange
45+
const helloTemplateCustomPrefixApp = ctx.any.application({
46+
approvalProgram: ctx.any.bytes(20),
47+
appLogs: [ABI_RETURN_VALUE_LOG_PREFIX.concat(new arc4.Str('bonjour world').bytes)],
48+
})
49+
ctx.setCompiledApp(HelloTemplateCustomPrefix, helloTemplateCustomPrefixApp.id)
50+
51+
const contract = ctx.contract.create(HelloFactory)
52+
53+
// Act
54+
contract.test_compile_contract_with_template_and_custom_prefix()
55+
})
56+
57+
it('should be able to compile large program', () => {
58+
// Arrange
59+
const largeProgramApp = ctx.any.application({
60+
approvalProgram: ctx.any.bytes(20),
61+
appLogs: [ABI_RETURN_VALUE_LOG_PREFIX.concat(asUint64Cls(MAX_BYTES_SIZE).toBytes().asAlgoTs())],
62+
})
63+
ctx.setCompiledApp(LargeProgram, largeProgramApp.id)
64+
65+
const contract = ctx.contract.create(HelloFactory)
66+
67+
// Act
68+
contract.test_compile_contract_large()
69+
})
70+
71+
it('should be able to compile logic sig', () => {
72+
// Arrange
73+
const terribleCustodialAccount = ctx.any.account()
74+
ctx.setCompiledLogicSig(TerribleCustodialAccount, terribleCustodialAccount)
75+
76+
const contract = ctx.contract.create(HelloFactory)
77+
78+
// Act
79+
contract.test_compile_logic_sig(terribleCustodialAccount.bytes)
80+
})
81+
})
Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
import { abimethod, Contract, GlobalState, LogicSig, op, TemplateVar } from '@algorandfoundation/algorand-typescript'
2+
3+
abstract class HelloBase extends Contract {
4+
greeting = GlobalState({ initialValue: '' })
5+
6+
@abimethod({ allowActions: 'DeleteApplication' })
7+
delete() {}
8+
9+
@abimethod({ allowActions: 'UpdateApplication' })
10+
update() {}
11+
12+
greet(name: string): string {
13+
return `${this.greeting.value} ${name}`
14+
}
15+
}
16+
17+
export class Hello extends HelloBase {
18+
@abimethod({ onCreate: 'require' })
19+
create(greeting: string) {
20+
this.greeting.value = greeting
21+
}
22+
}
23+
24+
export class HelloTemplate extends HelloBase {
25+
constructor() {
26+
super()
27+
this.greeting.value = TemplateVar<string>('GREETING')
28+
}
29+
30+
@abimethod({ onCreate: 'require' })
31+
create() {}
32+
}
33+
34+
export class HelloTemplateCustomPrefix extends HelloBase {
35+
constructor() {
36+
super()
37+
this.greeting.value = TemplateVar<string>('GREETING', 'PRFX_')
38+
}
39+
40+
@abimethod({ onCreate: 'require' })
41+
create() {}
42+
}
43+
44+
function getBigBytes() {
45+
return op.bzero(4096)
46+
}
47+
48+
export class LargeProgram extends Contract {
49+
getBigBytesLength() {
50+
return getBigBytes().length
51+
}
52+
53+
@abimethod({ allowActions: 'DeleteApplication' })
54+
delete() {}
55+
}
56+
57+
/**
58+
* This logic sig can be used to create a custodial account that will allow any transaction to transfer its
59+
* funds/assets.
60+
*/
61+
export class TerribleCustodialAccount extends LogicSig {
62+
program() {
63+
return true
64+
}
65+
}

examples/rollup.config.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ const config: RollupOptions = {
1313
'examples/voting/contract.algo.ts',
1414
'examples/simple-voting/contract.algo.ts',
1515
'examples/zk-whitelist/contract.algo.ts',
16+
'examples/precompiled/contract.algo.ts',
1617
],
1718
output: [
1819
{

package-lock.json

Lines changed: 5 additions & 4 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -63,7 +63,7 @@
6363
"tslib": "^2.6.2"
6464
},
6565
"dependencies": {
66-
"@algorandfoundation/algorand-typescript": "^0.0.1-alpha.23",
66+
"@algorandfoundation/algorand-typescript": "^0.0.1-alpha.24",
6767
"@algorandfoundation/puya-ts": "^1.0.0-alpha.36",
6868
"elliptic": "^6.5.7",
6969
"js-sha256": "^0.11.0",

src/impl/compiled.ts

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
import {
2+
Account,
3+
BaseContract,
4+
CompileContractOptions,
5+
CompiledContract,
6+
CompiledLogicSig,
7+
CompileLogicSigOptions,
8+
LogicSig,
9+
} from '@algorandfoundation/algorand-typescript'
10+
import { lazyContext } from '../context-helpers/internal-context'
11+
import { ConstructorFor } from '../typescript-helpers'
12+
import { ApplicationData } from './application'
13+
14+
export function compileImpl(
15+
artefact: ConstructorFor<BaseContract> | ConstructorFor<LogicSig>,
16+
options?: CompileContractOptions | CompileLogicSigOptions,
17+
): CompiledLogicSig | CompiledContract {
18+
let app: ApplicationData | undefined
19+
let account: Account | undefined
20+
const compiledApp = lazyContext.value.getCompiledApp(artefact as ConstructorFor<BaseContract>)
21+
const compiledLogicSig = lazyContext.value.getCompiledLogicSig(artefact as ConstructorFor<LogicSig>)
22+
if (compiledApp !== undefined) {
23+
app = lazyContext.ledger.applicationDataMap.get(compiledApp[1])
24+
}
25+
if (compiledLogicSig !== undefined) {
26+
account = compiledLogicSig[1]
27+
}
28+
if (options?.templateVars) {
29+
Object.entries(options.templateVars).forEach(([key, value]) => {
30+
lazyContext.value.setTemplateVar(key, value, options.templateVarsPrefix)
31+
})
32+
}
33+
return new Proxy({} as CompiledLogicSig | CompiledContract, {
34+
get: (_target, prop) => {
35+
switch (prop) {
36+
case 'approvalProgram':
37+
return app?.application.approvalProgram ?? [lazyContext.any.bytes(10), lazyContext.any.bytes(10)]
38+
case 'clearStateProgram':
39+
return app?.application.clearStateProgram ?? [lazyContext.any.bytes(10), lazyContext.any.bytes(10)]
40+
case 'extraProgramPages':
41+
return (options as CompileContractOptions)?.extraProgramPages ?? app?.application.extraProgramPages ?? lazyContext.any.uint64()
42+
case 'globalUints':
43+
return (options as CompileContractOptions)?.globalUints ?? app?.application.globalNumUint ?? lazyContext.any.uint64()
44+
case 'globalBytes':
45+
return (options as CompileContractOptions)?.globalBytes ?? app?.application.globalNumBytes ?? lazyContext.any.uint64()
46+
case 'localUints':
47+
return (options as CompileContractOptions)?.localUints ?? app?.application.localNumUint ?? lazyContext.any.uint64()
48+
case 'localBytes':
49+
return (options as CompileContractOptions)?.localBytes ?? app?.application.localNumBytes ?? lazyContext.any.uint64()
50+
case 'account':
51+
return account ?? lazyContext.any.account()
52+
}
53+
},
54+
})
55+
}

0 commit comments

Comments
 (0)