Skip to content

Commit

Permalink
- UserFunc requiring ScriptContext and/or CurrentScript
Browse files Browse the repository at this point in the history
- prerelease version bump
  • Loading branch information
christianschmitz committed Aug 16, 2024
1 parent 83552b5 commit ac26700
Show file tree
Hide file tree
Showing 5 changed files with 138 additions and 68 deletions.
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@helios-lang/compiler",
"version": "0.17.0-40",
"version": "0.17.0-41",
"description": "Helios is a Domain Specific Language that compiles to Plutus-Core (i.e. Cardano on-chain validator scripts). Helios is a non-Haskell alternative to Plutus. With this library you can compile Helios scripts and build Cardano transactions, all you need to build 100% client-side dApps for Cardano.",
"main": "src/index.js",
"types": "types/index.d.ts",
Expand Down
140 changes: 84 additions & 56 deletions src/program/Program.js
Original file line number Diff line number Diff line change
Expand Up @@ -184,6 +184,8 @@ export class Program {
)
prev[fnName] = newEntryPoint
}

res[moduleName] = prev
}

allModules.forEach((m) => {
Expand Down Expand Up @@ -273,78 +275,104 @@ export class Program {
* * dependency on own hash through methods defined on the ScriptContext
* * dependency on hashes of other validators or dependency on own precalculated hash (eg. unoptimized program should use hash of optimized program)
* @param {{
* dependsOnOwnHash: boolean,
* hashDependencies: Record<string, string>,
* optimize: boolean,
* dependsOnOwnHash: boolean
* hashDependencies: Record<string, string>
* optimize: boolean
* makeParamSubstitutable?: boolean
* }} options
* @returns {SourceMappedString}
*/
toIR(options) {
const ctx = new ToIRContext({
optimize: options.optimize,
return genProgramEntryPointIR(this.entryPoint, {
...options,
isTestnet: this.isForTestnet,
makeParamsSubstitutable: options.makeParamSubstitutable
name: this.name,
purpose: this.purpose,
validatorTypes: this.props.validatorTypes
})
}

/**
* @type {Definitions}
*/
const extra = new Map()

// inject hashes of other validators
Object.entries(options.hashDependencies).forEach(([depName, dep]) => {
dep = dep.startsWith("#") ? dep : `#${dep}`
/**
* @returns {string}
*/
toString() {
return this.entryPoint.toString()
}
}

const key = `__helios__scripts__${depName}`
extra.set(key, $`${PARAM_IR_MACRO}("${key}", ${dep})`)
})
/**
* @param {EntryPoint} entryPoint
* @param {{
* dependsOnOwnHash: boolean
* hashDependencies: Record<string, string>
* optimize: boolean
* makeParamSubstitutable?: boolean
* isTestnet: boolean
* name: string
* purpose: string
* validatorTypes?: ScriptTypes
* dummyCurrentScript?: boolean
* }} options
*/
export function genProgramEntryPointIR(entryPoint, options) {
const ctx = new ToIRContext({
optimize: options.optimize,
isTestnet: options.isTestnet,
makeParamsSubstitutable: options.makeParamSubstitutable
})

if (options.dependsOnOwnHash) {
const key = `__helios__scripts__${this.name}`

const ir = expectSome(
/** @type {Record<string, SourceMappedString>} */ ({
mixed: $(
`__helios__scriptcontext__get_current_script_hash()`
),
spending: $(
`__helios__scriptcontext__get_current_validator_hash()`
),
minting: $(
`__helios__scriptcontext__get_current_minting_policy_hash()`
),
staking: $(
`__helios__scriptcontext__get_current_staking_validator_hash()`
)
})[this.purpose]
)
/**
* @type {Definitions}
*/
const extra = new Map()

// inject hashes of other validators
Object.entries(options.hashDependencies).forEach(([depName, dep]) => {
dep = dep.startsWith("#") ? dep : `#${dep}`

const key = `__helios__scripts__${depName}`
extra.set(key, $`${PARAM_IR_MACRO}("${key}", ${dep})`)
})

if (options.dependsOnOwnHash) {
const key = `__helios__scripts__${options.name}`

const ir = expectSome(
/** @type {Record<string, SourceMappedString>} */ ({
mixed: $(`__helios__scriptcontext__get_current_script_hash()`),
spending: $(
`__helios__scriptcontext__get_current_validator_hash()`
),
minting: $(
`__helios__scriptcontext__get_current_minting_policy_hash()`
),
staking: $(
`__helios__scriptcontext__get_current_staking_validator_hash()`
)
})[options.purpose]
)

extra.set(key, ir)
}
extra.set(key, ir)
}

// also add script enum __is methods
if (this.props.validatorTypes) {
Object.keys(this.props.validatorTypes).forEach((scriptName) => {
const key = `__helios__script__${scriptName}____is`
if (options.dummyCurrentScript) {
extra.set(`__helios__scriptcontext__current_script`, $`#`)
}

// only way to instantiate a Script is via ScriptContext::current_script
// also add script enum __is methods
if (options.validatorTypes) {
Object.keys(options.validatorTypes).forEach((scriptName) => {
const key = `__helios__script__${scriptName}____is`

const ir = $`(_) -> {
${this.name == scriptName ? "true" : "false"}
}`
// only way to instantiate a Script is via ScriptContext::current_script

extra.set(key, ir)
})
}
const ir = $`(_) -> {
${options.name == scriptName ? "true" : "false"}
}`

return this.entryPoint.toIR(ctx, extra)
extra.set(key, ir)
})
}

/**
* @returns {string}
*/
toString() {
return this.entryPoint.toString()
}
return entryPoint.toIR(ctx, extra)
}
49 changes: 39 additions & 10 deletions src/program/multi.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
*/

import { readHeader } from "@helios-lang/compiler-utils"
import { collectParams, prepare as prepareIR } from "@helios-lang/ir"
import { $, collectParams, prepare as prepareIR } from "@helios-lang/ir"
import { expectSome } from "@helios-lang/type-utils"
import {
MintingPolicyHashType,
Expand All @@ -14,11 +14,12 @@ import {
scriptHashType
} from "../typecheck/index.js"
import { Module } from "./Module.js"
import { IR_PARSE_OPTIONS, Program } from "./Program.js"
import { IR_PARSE_OPTIONS, Program, genProgramEntryPointIR } from "./Program.js"
import { VERSION } from "./version.js"
import { ToIRContext } from "../codegen/ToIRContext.js"

/**
* @typedef {import("../codegen/index.js").Definitions} Definitions
* @typedef {import("../typecheck/index.js").DataType} DataType
* @typedef {import("../typecheck/index.js").TypeSchema} TypeSchema
* @typedef {import("./EntryPoint.js").EntryPoint} EntryPoint
Expand Down Expand Up @@ -86,7 +87,11 @@ export function getScriptHashType(purpose) {
* }}
*/
export function analyzeMulti(validatorSources, moduleSources) {
/**
* @type {Record<string, ScriptHashType>}
*/
const validatorTypes = getValidatorTypes(validatorSources)

const validatorPrograms = createPrograms(
validatorSources,
moduleSources,
Expand All @@ -111,6 +116,7 @@ export function analyzeMulti(validatorSources, moduleSources) {

analyzedValidators[p.name] = analyzeValidator(
p,
validatorTypes,
dag,
allTypes,
allFunctions
Expand All @@ -125,6 +131,7 @@ export function analyzeMulti(validatorSources, moduleSources) {
if (!(name in analyzedModules)) {
analyzedModules[name] = analyzeModule(
m,
validatorTypes,
allModules,
allTypes,
allFunctions
Expand All @@ -141,12 +148,19 @@ export function analyzeMulti(validatorSources, moduleSources) {

/**
* @param {Program} program
* @param {Record<string, ScriptHashType>} validatorTypes
* @param {Record<string, string[]>} dag
* @param {Record<string, Record<string, DataType>>} allTypes
* @param {Record<string, Record<string, EntryPoint>>} allFunctions
* @returns {AnalyzedValidator}
*/
function analyzeValidator(program, dag, allTypes, allFunctions) {
function analyzeValidator(
program,
validatorTypes,
dag,
allTypes,
allFunctions
) {
const name = program.name
const purpose = program.purpose
const hashDependencies = dag[name]
Expand All @@ -167,21 +181,21 @@ function analyzeValidator(program, dag, allTypes, allFunctions) {
moduleDepedencies: moduleDeps,
sourceCode: program.entryPoint.mainModule.sourceCode.content,
types: createTypeSchemas(moduleTypes),
functions: analyzeFunctions(moduleFunctions),
functions: analyzeFunctions(moduleFunctions, validatorTypes),
Redeemer: redeemer,
Datum: datum
}
}

/**
*
* @param {Module} m
* @param {Record<string, ScriptHashType>} validatorTypes
* @param {Module[]} allModules
* @param {Record<string, Record<string, DataType>>} allTypes
* @param {Record<string, Record<string, EntryPoint>>} allFunctions
* @returns {AnalyzedModule}
*/
function analyzeModule(m, allModules, allTypes, allFunctions) {
function analyzeModule(m, validatorTypes, allModules, allTypes, allFunctions) {
const name = m.name.value

const moduleDeps = m.filterDependencies(allModules).map((m) => m.name.value)
Expand All @@ -194,25 +208,40 @@ function analyzeModule(m, allModules, allTypes, allFunctions) {
moduleDepedencies: moduleDeps,
sourceCode: m.sourceCode.content,
types: createTypeSchemas(moduleTypes),
functions: analyzeFunctions(moduleFunctions)
functions: analyzeFunctions(moduleFunctions, validatorTypes)
}
}

/**
* @param {Record<string, EntryPoint>} fns
* @param {Record<string, ScriptHashType>} validatorTypes
* @returns {Record<string, AnalyzedFunction>}
*/
function analyzeFunctions(fns) {
function analyzeFunctions(fns, validatorTypes) {
return Object.fromEntries(
Object.entries(fns).map(([key, fn]) => {
const main = fn.mainFunc

const ctx = new ToIRContext({ optimize: false, isTestnet: false })
const ir = fn.toIR(ctx)
const ir = genProgramEntryPointIR(fn, {
optimize: false,
isTestnet: false,
dependsOnOwnHash: false,
hashDependencies: Object.fromEntries(
Array.from(Object.keys(validatorTypes)).map((name) => [
name,
"#"
])
),
validatorTypes: validatorTypes,
purpose: "testing",
name: main.name.value,
dummyCurrentScript: true
})

const requiresCurrentScript = ir.includes(
"__helios__scriptcontext__current_script"
)

const requiresScriptContext = ir.includes(
"__helios__scriptcontext__data"
)
Expand Down
13 changes: 13 additions & 0 deletions src/program/multi.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,10 @@ describe(analyzeMulti.name, () => {
it("DAG ok for two validators", () => {
const src1 = `spending checks_mint
import { tx } from ScriptContext
func test_int() -> Int {
0
}
func main(_, _) -> Bool {
tx.minted.get_policy(Scripts::always_succeeds).length > 0
}`
Expand All @@ -22,6 +26,15 @@ describe(analyzeMulti.name, () => {
])

deepEqual(validators["always_succeeds"].hashDependencies, [])

strictEqual(
"test_int" in validators["checks_mint"].functions &&
validators["checks_mint"].functions["test_int"]
.requiresScriptContext &&
!validators["checks_mint"].functions["test_int"]
.requiresCurrentScript,
true
)
})

it("invalid DAG throws an error", () => {
Expand Down
2 changes: 1 addition & 1 deletion src/program/version.js
Original file line number Diff line number Diff line change
@@ -1 +1 @@
export const VERSION = "0.17.0-40"
export const VERSION = "0.17.0-41"

0 comments on commit ac26700

Please sign in to comment.