From ac26700ffc221f06a26a1affabc13f44ad0301b0 Mon Sep 17 00:00:00 2001 From: Christian Schmitz Date: Fri, 16 Aug 2024 08:04:08 -0600 Subject: [PATCH] - UserFunc requiring ScriptContext and/or CurrentScript - prerelease version bump --- package.json | 2 +- src/program/Program.js | 140 +++++++++++++++++++++++--------------- src/program/multi.js | 49 ++++++++++--- src/program/multi.test.js | 13 ++++ src/program/version.js | 2 +- 5 files changed, 138 insertions(+), 68 deletions(-) diff --git a/package.json b/package.json index f09c1baa..d1c2174b 100644 --- a/package.json +++ b/package.json @@ -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", diff --git a/src/program/Program.js b/src/program/Program.js index 3db053d2..b0dec877 100644 --- a/src/program/Program.js +++ b/src/program/Program.js @@ -184,6 +184,8 @@ export class Program { ) prev[fnName] = newEntryPoint } + + res[moduleName] = prev } allModules.forEach((m) => { @@ -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, - * optimize: boolean, + * dependsOnOwnHash: boolean + * hashDependencies: Record + * 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 + * 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} */ ({ - 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} */ ({ + 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) } diff --git a/src/program/multi.js b/src/program/multi.js index e13b3432..ef985aad 100644 --- a/src/program/multi.js +++ b/src/program/multi.js @@ -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, @@ -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 @@ -86,7 +87,11 @@ export function getScriptHashType(purpose) { * }} */ export function analyzeMulti(validatorSources, moduleSources) { + /** + * @type {Record} + */ const validatorTypes = getValidatorTypes(validatorSources) + const validatorPrograms = createPrograms( validatorSources, moduleSources, @@ -111,6 +116,7 @@ export function analyzeMulti(validatorSources, moduleSources) { analyzedValidators[p.name] = analyzeValidator( p, + validatorTypes, dag, allTypes, allFunctions @@ -125,6 +131,7 @@ export function analyzeMulti(validatorSources, moduleSources) { if (!(name in analyzedModules)) { analyzedModules[name] = analyzeModule( m, + validatorTypes, allModules, allTypes, allFunctions @@ -141,12 +148,19 @@ export function analyzeMulti(validatorSources, moduleSources) { /** * @param {Program} program + * @param {Record} validatorTypes * @param {Record} dag * @param {Record>} allTypes * @param {Record>} 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] @@ -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} validatorTypes * @param {Module[]} allModules * @param {Record>} allTypes * @param {Record>} 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) @@ -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} fns + * @param {Record} validatorTypes * @returns {Record} */ -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" ) diff --git a/src/program/multi.test.js b/src/program/multi.test.js index 12a64125..ed22b5a3 100644 --- a/src/program/multi.test.js +++ b/src/program/multi.test.js @@ -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 }` @@ -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", () => { diff --git a/src/program/version.js b/src/program/version.js index 2e1862a3..96e6198e 100644 --- a/src/program/version.js +++ b/src/program/version.js @@ -1 +1 @@ -export const VERSION = "0.17.0-40" +export const VERSION = "0.17.0-41"