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
136 changes: 136 additions & 0 deletions examples/precompiled/contract.algo.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,136 @@
import type { bytes, uint64 } from '@algorandfoundation/algorand-typescript'
import { assert, compile, Contract, itxn } from '@algorandfoundation/algorand-typescript'
import { decodeArc4, encodeArc4, methodSelector, OnCompleteAction } from '@algorandfoundation/algorand-typescript/arc4'
import { Hello, HelloTemplate, HelloTemplateCustomPrefix, LargeProgram, TerribleCustodialAccount } from './precompiled-apps.algo'

export class HelloFactory extends Contract {
test_compile_contract() {
const compiled = compile(Hello)

const helloApp = itxn
.applicationCall({
appArgs: [methodSelector('create(string)void'), encodeArc4('hello')],
approvalProgram: compiled.approvalProgram,
clearStateProgram: compiled.clearStateProgram,
globalNumBytes: 1,
})
.submit().createdApp

const txn = itxn
.applicationCall({
appArgs: [methodSelector('greet(string)string'), encodeArc4('world')],
appId: helloApp,
})
.submit()
const result = decodeArc4<string>(txn.lastLog, 'log')

assert(result === 'hello world')

itxn
.applicationCall({
appId: helloApp,
appArgs: [methodSelector('delete()void')],
onCompletion: OnCompleteAction.DeleteApplication,
})
.submit()
}

test_compile_contract_with_template() {
const compiled = compile(HelloTemplate, { templateVars: { GREETING: 'hey' } })

const helloApp = itxn
.applicationCall({
appArgs: [methodSelector('create()void')],
approvalProgram: compiled.approvalProgram,
clearStateProgram: compiled.clearStateProgram,
globalNumBytes: 1,
})
.submit().createdApp

const txn = itxn
.applicationCall({
appArgs: [methodSelector('greet(string)string'), encodeArc4('world')],
appId: helloApp,
})
.submit()
const result = decodeArc4<string>(txn.lastLog, 'log')

assert(result === 'hey world')

itxn
.applicationCall({
appId: helloApp,
appArgs: [methodSelector('delete()void')],
onCompletion: OnCompleteAction.DeleteApplication,
})
.submit()
}

test_compile_contract_with_template_and_custom_prefix() {
const compiled = compile(HelloTemplateCustomPrefix, { templateVars: { GREETING: 'bonjour' }, templateVarsPrefix: 'PRFX_' })

const helloApp = itxn
.applicationCall({
appArgs: [methodSelector('create()void')],
approvalProgram: compiled.approvalProgram,
clearStateProgram: compiled.clearStateProgram,
globalNumBytes: 1,
})
.submit().createdApp

const txn = itxn
.applicationCall({
appArgs: [methodSelector('greet(string)string'), encodeArc4('world')],
appId: helloApp,
})
.submit()
const result = decodeArc4<string>(txn.lastLog, 'log')

assert(result === 'bonjour world')

itxn
.applicationCall({
appId: helloApp,
appArgs: [methodSelector('delete()void')],
onCompletion: OnCompleteAction.DeleteApplication,
})
.submit()
}

test_compile_contract_large() {
const compiled = compile(LargeProgram)

const largeApp = itxn
.applicationCall({
approvalProgram: compiled.approvalProgram,
clearStateProgram: compiled.clearStateProgram,
extraProgramPages: compiled.extraProgramPages,
globalNumBytes: compiled.globalBytes,
})
.submit().createdApp

const txn = itxn
.applicationCall({
appArgs: [methodSelector('getBigBytesLength()uint64')],
appId: largeApp,
})
.submit()
const result = decodeArc4<uint64>(txn.lastLog, 'log')

assert(result === 4096)

itxn
.applicationCall({
appId: largeApp,
appArgs: [methodSelector('delete()void')],
onCompletion: OnCompleteAction.DeleteApplication,
})
.submit()
}

test_compile_logic_sig(account: bytes) {
const compiled = compile(TerribleCustodialAccount)

assert(compiled.account.bytes === account)
}
}
81 changes: 81 additions & 0 deletions examples/precompiled/contract.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
import { arc4 } from '@algorandfoundation/algorand-typescript'
import { TestExecutionContext } from '@algorandfoundation/algorand-typescript-testing'
import { afterEach, describe, it } from 'vitest'
import { ABI_RETURN_VALUE_LOG_PREFIX, MAX_BYTES_SIZE } from '../../src/constants'
import { asUint64Cls } from '../../src/util'
import { HelloFactory } from './contract.algo'
import { Hello, HelloTemplate, HelloTemplateCustomPrefix, LargeProgram, TerribleCustodialAccount } from './precompiled-apps.algo'

describe('pre compiled app calls', () => {
const ctx = new TestExecutionContext()
afterEach(() => {
ctx.reset()
})

it('should be able to compile and call a precompiled app', () => {
// Arrange
const helloApp = ctx.any.application({
approvalProgram: ctx.any.bytes(20),
appLogs: [ABI_RETURN_VALUE_LOG_PREFIX.concat(new arc4.Str('hello world').bytes)],
})
ctx.setCompiledApp(Hello, helloApp.id)

const contract = ctx.contract.create(HelloFactory)

// Act
contract.test_compile_contract()
})

it('should be able to compile with template vars and call a precompiled app', () => {
// Arrange
const helloTemplateApp = ctx.any.application({
approvalProgram: ctx.any.bytes(20),
appLogs: [ABI_RETURN_VALUE_LOG_PREFIX.concat(new arc4.Str('hey world').bytes)],
})
ctx.setCompiledApp(HelloTemplate, helloTemplateApp.id)

const contract = ctx.contract.create(HelloFactory)

// Act
contract.test_compile_contract_with_template()
})

it('should be able to compile with template vars and custom prefix', () => {
// Arrange
const helloTemplateCustomPrefixApp = ctx.any.application({
approvalProgram: ctx.any.bytes(20),
appLogs: [ABI_RETURN_VALUE_LOG_PREFIX.concat(new arc4.Str('bonjour world').bytes)],
})
ctx.setCompiledApp(HelloTemplateCustomPrefix, helloTemplateCustomPrefixApp.id)

const contract = ctx.contract.create(HelloFactory)

// Act
contract.test_compile_contract_with_template_and_custom_prefix()
})

it('should be able to compile large program', () => {
// Arrange
const largeProgramApp = ctx.any.application({
approvalProgram: ctx.any.bytes(20),
appLogs: [ABI_RETURN_VALUE_LOG_PREFIX.concat(asUint64Cls(MAX_BYTES_SIZE).toBytes().asAlgoTs())],
})
ctx.setCompiledApp(LargeProgram, largeProgramApp.id)

const contract = ctx.contract.create(HelloFactory)

// Act
contract.test_compile_contract_large()
})

it('should be able to compile logic sig', () => {
// Arrange
const terribleCustodialAccount = ctx.any.account()
ctx.setCompiledLogicSig(TerribleCustodialAccount, terribleCustodialAccount)

const contract = ctx.contract.create(HelloFactory)

// Act
contract.test_compile_logic_sig(terribleCustodialAccount.bytes)
})
})
65 changes: 65 additions & 0 deletions examples/precompiled/precompiled-apps.algo.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
import { abimethod, Contract, GlobalState, LogicSig, op, TemplateVar } from '@algorandfoundation/algorand-typescript'

abstract class HelloBase extends Contract {
greeting = GlobalState({ initialValue: '' })

@abimethod({ allowActions: 'DeleteApplication' })
delete() {}

@abimethod({ allowActions: 'UpdateApplication' })
update() {}

greet(name: string): string {
return `${this.greeting.value} ${name}`
}
}

export class Hello extends HelloBase {
@abimethod({ onCreate: 'require' })
create(greeting: string) {
this.greeting.value = greeting
}
}

export class HelloTemplate extends HelloBase {
constructor() {
super()
this.greeting.value = TemplateVar<string>('GREETING')
}

@abimethod({ onCreate: 'require' })
create() {}
}

export class HelloTemplateCustomPrefix extends HelloBase {
constructor() {
super()
this.greeting.value = TemplateVar<string>('GREETING', 'PRFX_')
}

@abimethod({ onCreate: 'require' })
create() {}
}

function getBigBytes() {
return op.bzero(4096)
}

export class LargeProgram extends Contract {
getBigBytesLength() {
return getBigBytes().length
}

@abimethod({ allowActions: 'DeleteApplication' })
delete() {}
}

/**
* This logic sig can be used to create a custodial account that will allow any transaction to transfer its
* funds/assets.
*/
export class TerribleCustodialAccount extends LogicSig {
program() {
return true
}
}
1 change: 1 addition & 0 deletions examples/rollup.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ const config: RollupOptions = {
'examples/voting/contract.algo.ts',
'examples/simple-voting/contract.algo.ts',
'examples/zk-whitelist/contract.algo.ts',
'examples/precompiled/contract.algo.ts',
],
output: [
{
Expand Down
9 changes: 5 additions & 4 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@
"tslib": "^2.6.2"
},
"dependencies": {
"@algorandfoundation/algorand-typescript": "^0.0.1-alpha.23",
"@algorandfoundation/algorand-typescript": "^0.0.1-alpha.24",
"@algorandfoundation/puya-ts": "^1.0.0-alpha.36",
"elliptic": "^6.5.7",
"js-sha256": "^0.11.0",
Expand Down
55 changes: 55 additions & 0 deletions src/impl/compiled.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
import {
Account,
BaseContract,
CompileContractOptions,
CompiledContract,
CompiledLogicSig,
CompileLogicSigOptions,
LogicSig,
} from '@algorandfoundation/algorand-typescript'
import { lazyContext } from '../context-helpers/internal-context'
import { ConstructorFor } from '../typescript-helpers'
import { ApplicationData } from './application'

export function compileImpl(
artefact: ConstructorFor<BaseContract> | ConstructorFor<LogicSig>,
options?: CompileContractOptions | CompileLogicSigOptions,
): CompiledLogicSig | CompiledContract {
let app: ApplicationData | undefined
let account: Account | undefined
const compiledApp = lazyContext.value.getCompiledApp(artefact as ConstructorFor<BaseContract>)
const compiledLogicSig = lazyContext.value.getCompiledLogicSig(artefact as ConstructorFor<LogicSig>)
if (compiledApp !== undefined) {
app = lazyContext.ledger.applicationDataMap.get(compiledApp[1])
}
if (compiledLogicSig !== undefined) {
account = compiledLogicSig[1]
}
if (options?.templateVars) {
Object.entries(options.templateVars).forEach(([key, value]) => {
lazyContext.value.setTemplateVar(key, value, options.templateVarsPrefix)
})
}
return new Proxy({} as CompiledLogicSig | CompiledContract, {
get: (_target, prop) => {
switch (prop) {
case 'approvalProgram':
return app?.application.approvalProgram ?? [lazyContext.any.bytes(10), lazyContext.any.bytes(10)]
case 'clearStateProgram':
return app?.application.clearStateProgram ?? [lazyContext.any.bytes(10), lazyContext.any.bytes(10)]
case 'extraProgramPages':
return (options as CompileContractOptions)?.extraProgramPages ?? app?.application.extraProgramPages ?? lazyContext.any.uint64()
case 'globalUints':
return (options as CompileContractOptions)?.globalUints ?? app?.application.globalNumUint ?? lazyContext.any.uint64()
case 'globalBytes':
return (options as CompileContractOptions)?.globalBytes ?? app?.application.globalNumBytes ?? lazyContext.any.uint64()
case 'localUints':
return (options as CompileContractOptions)?.localUints ?? app?.application.localNumUint ?? lazyContext.any.uint64()
case 'localBytes':
return (options as CompileContractOptions)?.localBytes ?? app?.application.localNumBytes ?? lazyContext.any.uint64()
case 'account':
return account ?? lazyContext.any.account()
}
},
})
}
Loading
Loading