-
Notifications
You must be signed in to change notification settings - Fork 1
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #20 from algorandfoundation/feat-precompiled
feat: implement stubs for compile functions
- Loading branch information
Showing
14 changed files
with
420 additions
and
15 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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) | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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) | ||
}) | ||
}) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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() | ||
} | ||
}, | ||
}) | ||
} |
Oops, something went wrong.