@helios-lang/contract-utils
Advanced tools
Comparing version 0.1.1 to 0.1.2
{ | ||
"name": "@helios-lang/contract-utils", | ||
"version": "0.1.1", | ||
"version": "0.1.2", | ||
"description": "Convenience and type-safety utilities for using Helios validators from within Typescript", | ||
@@ -31,3 +31,10 @@ "main": "src/index.js", | ||
"singleQuote": false | ||
}, | ||
"dependencies": { | ||
"@helios-lang/codec-utils": "^0.1.23", | ||
"@helios-lang/compiler-utils": "^0.1.4", | ||
"@helios-lang/ledger": "^0.1.9", | ||
"@helios-lang/type-utils": "^0.1.3", | ||
"@helios-lang/uplc": "^0.1.0" | ||
} | ||
} |
161
src/hl2ts.js
#!/usr/bin/env node | ||
import { promises, readFileSync } from "node:fs" | ||
import { dirname, join, resolve } from "node:path" | ||
import { readHeader, translateImportPaths } from "@helios-lang/compiler-utils" | ||
import { TypescriptWriter } from "./codegen/index.js" | ||
import { loadLibrary } from "./lib/index.js" | ||
/** | ||
* @typedef {import("./lib/Lib.js").Lib} Lib | ||
*/ | ||
/** | ||
* @typedef {import("./lib/Lib.js").SourceDetails} SourceDetails | ||
*/ | ||
/** | ||
* @typedef {import("./lib/Lib.js").ModuleDetails} ModuleDetails | ||
*/ | ||
/** | ||
* @typedef {import("./lib/Lib.js").ValidatorDetails} ValidatorDetails | ||
*/ | ||
async function main() { | ||
@@ -9,6 +29,143 @@ console.log("hl2ts") | ||
const lib = await loadLibrary() | ||
const filePaths = await listFiles(process.cwd(), ".hl") | ||
const files = readFiles(filePaths) | ||
const sources = preparseFiles(files) | ||
const { modules, validators } = typeCheck(lib, sources) | ||
console.log(lib.version) | ||
const w = new TypescriptWriter() | ||
console.log(modules, validators) | ||
w.writeModules(modules) | ||
w.writeValidators(validators) | ||
const [js, dts] = w.finalize() | ||
console.log(js) | ||
console.log(dts) | ||
} | ||
main() | ||
main() | ||
/** | ||
* @param {string} dir | ||
* @param {string} ext | ||
* @returns {Promise<string[]>} | ||
*/ | ||
async function listFiles(dir, ext) { | ||
const entries = await promises.readdir(dir, { withFileTypes: true }) | ||
const files = await Promise.all( | ||
entries.map((entry) => { | ||
const res = resolve(dir, entry.name) | ||
if (entry.isDirectory()) { | ||
if (entry.name.endsWith("node_modules")) { | ||
return [] | ||
} else { | ||
return listFiles(res, ext) | ||
} | ||
} else { | ||
return res | ||
} | ||
}) | ||
) | ||
return files.flat().filter((name) => name.endsWith(ext)) | ||
} | ||
/** | ||
* @param {string[]} filePaths | ||
* @returns {{[path: string]: string}} | ||
*/ | ||
function readFiles(filePaths) { | ||
return Object.fromEntries( | ||
filePaths.map((f) => { | ||
return [f, readFileSync(f).toString()] | ||
}) | ||
) | ||
} | ||
/** | ||
* | ||
* @param {{[path: string]: string}} files | ||
* @returns {{[name: string]: SourceDetails}} | ||
*/ | ||
function preparseFiles(files) { | ||
const partialSources = {} | ||
for (let path in files) { | ||
const [purpose, name] = readHeader(files[path]) | ||
partialSources[path] = { | ||
name: name, | ||
purpose: purpose, | ||
sourceCode: files[path] | ||
} | ||
} | ||
/** | ||
* @type {{[name: string]: SourceDetails}} | ||
*/ | ||
const sources = {} | ||
for (let path in files) { | ||
const sourceCode = translateImportPaths(files[path], (relPath) => { | ||
const absPath = resolve(join(dirname(path), relPath)) | ||
const d = partialSources[absPath] | ||
if (!d) { | ||
throw new Error(`'${relPath}' not found`) | ||
} | ||
return d.name | ||
}) | ||
sources[partialSources[path].name] = { | ||
...partialSources[path], | ||
sourceCode: sourceCode | ||
} | ||
} | ||
return sources | ||
} | ||
/** | ||
* @param {{[name: string]: SourceDetails}} sources | ||
* @returns {[{[name: string]: SourceDetails}, {[name: string]: SourceDetails}]} | ||
*/ | ||
function splitValidatorsAndModules(sources) { | ||
/** | ||
* @type {{[name: string]: SourceDetails}} | ||
*/ | ||
const validators = {} | ||
/** | ||
* @type {{[name: string]: SourceDetails}} | ||
*/ | ||
const modules = {} | ||
for (let key in sources) { | ||
const v = sources[key] | ||
if (v.purpose == "module") { | ||
modules[key] = v | ||
} else if ( | ||
v.purpose == "spending" || | ||
v.purpose == "minting" || | ||
v.purpose == "staking" | ||
) { | ||
validators[key] = v | ||
} | ||
} | ||
return [validators, modules] | ||
} | ||
/** | ||
* @param {Lib} lib | ||
* @param {{[name: string]: SourceDetails}} sources | ||
* @returns {{modules: {[name: string]: ModuleDetails}, validators: {[name: string]: ValidatorDetails}}} | ||
*/ | ||
function typeCheck(lib, sources) { | ||
const [validators, modules] = splitValidatorsAndModules(sources) | ||
return lib.typeCheck(validators, modules) | ||
} |
@@ -1,1 +0,1 @@ | ||
export { loadLibrary } from "./load.js" | ||
export { loadLibrary } from "./load.js" |
/** | ||
* @typedef {{ | ||
* version: string | ||
* typeCheck: (validators: {[name: string]: SourceDetails}, modules: {[name: string]: SourceDetails}) => ({ | ||
* modules: {[name: string]: ModuleDetails}, validators: {[name: string]: ValidatorDetails}}) | ||
* }} Lib | ||
@@ -8,2 +10,16 @@ */ | ||
/** | ||
* @typedef {import("../codegen/index.js").TypeSchema} InternalTypeDetails | ||
* @typedef {import("../codegen/index.js").Module} ModuleDetails | ||
* @typedef {import("../codegen/index.js").Validator} ValidatorDetails | ||
*/ | ||
/** | ||
* @typedef {{ | ||
* name: string | ||
* purpose: string | ||
* sourceCode: string | ||
* }} SourceDetails | ||
*/ | ||
/** | ||
* @param {{VERSION: string}} lib | ||
@@ -13,3 +29,3 @@ * @returns {number[]} | ||
export function getVersion(lib) { | ||
return lib.VERSION.split(".").map(v => Number(v)) | ||
} | ||
return lib.VERSION.split(".").map((v) => Number(v)) | ||
} |
/** | ||
* @typedef {import("./Lib.js").Lib} Lib | ||
* @typedef {import("./Lib.js").SourceDetails} SourceDetails | ||
*/ | ||
/** | ||
* @typedef {import("../codegen/index.js").Module} ModuleDetails | ||
* @typedef {import("../codegen/index.js").Validator} ValidatorDetails | ||
* @typedef {import("../codegen/index.js").TypeSchema} InternalTypeDetails | ||
*/ | ||
/** | ||
* @typedef {{ | ||
* [name: string]: string[] | ||
* }} DagDependencies | ||
*/ | ||
/** | ||
* @typedef {{ | ||
* }} HashType | ||
*/ | ||
/** | ||
* @typedef {{ | ||
* includes: (m: string) => boolean | ||
* }} IR | ||
*/ | ||
/** | ||
* @typedef {{ | ||
* name: string | ||
* }} EnumStatement | ||
*/ | ||
/** | ||
* @typedef {{ | ||
* name: string | ||
* }} StructStatement | ||
*/ | ||
/** | ||
* @typedef {StructStatement | EnumStatement | any} Statement | ||
*/ | ||
/** | ||
* @typedef {{ | ||
* name: {value: string} | ||
* filterDependencies: (all: Module[]) => Module[] | ||
* statements: Statement[] | ||
* }} Module | ||
*/ | ||
/** | ||
* @typedef {{ | ||
* name: string | ||
* toIR: (ctx: any, extra: Map<string, IR>) => IR | ||
* types: UserTypes | ||
* mainImportedModules: Module[] | ||
* mainModule: Module | ||
* mainArgTypes: DataType[] | ||
* }} Program | ||
*/ | ||
/** | ||
* @typedef {{ | ||
* typeDetails?: TypeDetails | ||
* }} DataType | ||
*/ | ||
/** | ||
* @typedef {{ | ||
* type: string | ||
* } | { | ||
* type: "List" | ||
* itemType: TypeSchema | ||
* } | { | ||
* type: "Map" | ||
* keyType: TypeSchema | ||
* valueType: TypeSchema | ||
* } | { | ||
* type: "Option" | ||
* someType: TypeSchema | ||
* } | { | ||
* type: "Struct" | ||
* fieldTypes: NamedTypeSchema[] | ||
* } | { | ||
* type: "Enum" | ||
* variantTypes: {name: string, fieldTypes: NamedTypeSchema[]}[] | ||
* }} TypeSchema | ||
*/ | ||
/** | ||
* @typedef {{ | ||
* name: string | ||
* } & TypeSchema} NamedTypeSchema | ||
*/ | ||
/** | ||
* @typedef {{ | ||
* inputType: string | ||
* outputType: string | ||
* internalType: TypeSchema | ||
* }} TypeDetails | ||
*/ | ||
/** | ||
* @typedef {{ | ||
* }} UserTypes | ||
*/ | ||
/** | ||
* @implements {Lib} | ||
@@ -10,3 +115,3 @@ */ | ||
/** | ||
* @param {any} lib | ||
* @param {any} lib | ||
*/ | ||
@@ -23,2 +128,209 @@ constructor(lib) { | ||
} | ||
} | ||
/** | ||
* @private | ||
* @param {string} purpose | ||
* @returns {HashType} | ||
*/ | ||
getValidatorType(purpose) { | ||
switch (purpose) { | ||
case "spending": | ||
return this.lib.ValidatorHashType | ||
case "minting": | ||
return this.lib.MintingPolicyHashType | ||
case "staking": | ||
return this.lib.StakingValidatorHashType | ||
default: | ||
throw new Error("unhandled validator type") | ||
} | ||
} | ||
/** | ||
* @private | ||
* @param {SourceDetails[]} validators | ||
* @param {SourceDetails[]} modules | ||
* @param {{[name: string]: HashType}} validatorTypes | ||
* @returns {Program[]} | ||
*/ | ||
createPrograms(validators, modules, validatorTypes) { | ||
const moduleSrcs = modules.map((m) => m.sourceCode) | ||
return validators.map((v) => | ||
this.lib.Program.newInternal( | ||
v.sourceCode, | ||
moduleSrcs, | ||
validatorTypes, | ||
{ | ||
allowPosParams: false, | ||
invertEntryPoint: true | ||
} | ||
) | ||
) | ||
} | ||
/** | ||
* @private | ||
* @param {Program} program | ||
* @param {{[name: string]: HashType}} validatorTypes | ||
* @returns {IR} | ||
*/ | ||
toTestIR(program, validatorTypes) { | ||
const extra = new Map() | ||
for (let validatorName in validatorTypes) { | ||
extra.set( | ||
`__helios__scripts__${validatorName}`, | ||
new this.lib.IR(`#`) | ||
) | ||
} | ||
return program.toIR(new this.lib.ToIRContext(false), extra) | ||
} | ||
/** | ||
* @private | ||
* @param {Program[]} validators | ||
* @param {{[name: string]: HashType}} validatorTypes | ||
* @returns {DagDependencies} | ||
*/ | ||
buildDagDependencies(validators, validatorTypes) { | ||
/** | ||
* @type {DagDependencies} | ||
*/ | ||
const dag = {} | ||
validators.forEach((v) => { | ||
const ir = this.toTestIR(v, validatorTypes) | ||
dag[v.name] = Object.keys(validatorTypes).filter((name) => | ||
ir.includes(`__helios__scripts__${name}`) | ||
) | ||
}) | ||
return dag | ||
} | ||
/** | ||
* @param {{[name: string]: SourceDetails}} validators | ||
* @param {{[name: string]: SourceDetails}} modules | ||
* @returns {{modules: {[name: string]: ModuleDetails}, validators: {[name: string]: ValidatorDetails}}} | ||
*/ | ||
typeCheck(validators, modules) { | ||
const validatorTypes = Object.fromEntries( | ||
Object.values(validators).map((v) => [ | ||
v.name, | ||
this.getValidatorType(v.purpose) | ||
]) | ||
) | ||
// create validator programs | ||
let validatorPrograms = this.createPrograms( | ||
Object.values(validators), | ||
Object.values(modules), | ||
validatorTypes | ||
) | ||
// build dag | ||
const dag = this.buildDagDependencies(validatorPrograms, validatorTypes) | ||
// sort the validators according to the dag, a valid order should exist | ||
// validatorPrograms = this.sortValidators(validatorPrograms, dag) | ||
/** | ||
* @type {{[name: string]: ValidatorDetails}} | ||
*/ | ||
const validatorDetails = {} | ||
/** | ||
* @type {{[name: string]: ModuleDetails}} | ||
*/ | ||
const moduleDetails = {} | ||
for (let v of validatorPrograms) { | ||
const hashDependencies = dag[v.name] | ||
const allModules = v.mainImportedModules | ||
const moduleDepedencies = v.mainModule | ||
.filterDependencies(allModules) | ||
.map((m) => m.name.value) | ||
const purpose = validators[v.name].purpose | ||
validatorDetails[v.name] = { | ||
name: v.name, | ||
purpose: validators[v.name].purpose, | ||
sourceCode: validators[v.name].sourceCode, | ||
hashDependencies: hashDependencies, | ||
moduleDepedencies: moduleDepedencies, | ||
types: {}, | ||
Redeemer: this.getInternalTypeDetails( | ||
v.mainArgTypes[purpose == "spending" ? 1 : 0] | ||
), | ||
Datum: | ||
purpose == "spending" | ||
? this.getInternalTypeDetails(v.mainArgTypes[0]) | ||
: undefined | ||
} | ||
for (let m of v.mainImportedModules) { | ||
if (!(m.name.value in moduleDetails)) { | ||
moduleDetails[m.name.value] = { | ||
name: m.name.value, | ||
purpose: "module", | ||
sourceCode: modules[m.name.value].sourceCode, | ||
moduleDepedencies: m | ||
.filterDependencies(allModules) | ||
.map((m) => m.name.value), | ||
types: {} // not yet exported | ||
} | ||
} | ||
} | ||
} | ||
return { modules: moduleDetails, validators: validatorDetails } | ||
} | ||
/** | ||
* @param {TypeSchema} it | ||
* @returns {InternalTypeDetails} | ||
*/ | ||
convertInternalType(it) { | ||
if ("itemType" in it) { | ||
return { listItemType: this.convertInternalType(it.itemType) } | ||
} else if ("someType" in it) { | ||
return { optionSomeType: this.convertInternalType(it.someType) } | ||
} else if ("fieldTypes" in it) { | ||
return { | ||
structFieldTypes: it.fieldTypes.map((ft) => ({ | ||
name: ft.name, | ||
type: this.convertInternalType(ft) | ||
})) | ||
} | ||
} else if ("keyType" in it) { | ||
return { | ||
mapKeyType: this.convertInternalType(it.keyType), | ||
mapValueType: this.convertInternalType(it.valueType) | ||
} | ||
} else if ("variantTypes" in it) { | ||
return { | ||
enumVariantTypes: it.variantTypes.map((vt) => ({ | ||
name: vt.name, | ||
fieldTypes: vt.fieldTypes.map((ft) => ({ | ||
name: ft.name, | ||
type: this.convertInternalType(ft) | ||
})) | ||
})) | ||
} | ||
} else { | ||
return { primitiveType: it.type } | ||
} | ||
} | ||
/** | ||
* @param {DataType} dt | ||
* @returns {InternalTypeDetails} | ||
*/ | ||
getInternalTypeDetails(dt) { | ||
const it = dt?.typeDetails?.internalType ?? { type: "Any" } | ||
return this.convertInternalType(it) | ||
} | ||
} |
@@ -0,1 +1,3 @@ | ||
import { existsSync } from "node:fs" | ||
import { dirname, join } from "node:path" | ||
import { getVersion } from "./Lib.js" | ||
@@ -8,6 +10,19 @@ import { Lib_v0_16 } from "./Libg_v0_16.js" | ||
const packageNames = [ | ||
"@hyperionbt/helios" | ||
] | ||
/** | ||
* @returns {string} | ||
*/ | ||
function findRootDir() { | ||
let dir = process.cwd() | ||
while (dir != "/") { | ||
if (existsSync(join(dir, "package.json"))) { | ||
return dir | ||
} else { | ||
dir = dirname(dir) | ||
} | ||
} | ||
throw new Error("package.json not found") | ||
} | ||
/** | ||
@@ -17,9 +32,15 @@ * @returns {Promise<Lib>} | ||
export async function loadLibrary() { | ||
const rootDir = findRootDir() | ||
const packageNames = ["@hyperionbt/helios/helios.js"] | ||
for (let packageName of packageNames) { | ||
try { | ||
const lib = await eval(`import("${packageName}")`) | ||
const lib = await eval( | ||
`import("${rootDir}/node_modules/${packageName}")` | ||
) | ||
const [major, minor] = getVersion(lib) | ||
switch(major) { | ||
switch (major) { | ||
case 0: | ||
@@ -31,10 +52,13 @@ switch (minor) { | ||
default: | ||
throw new Error(`compiler version ${lib.VERSION} not supported`) | ||
throw new Error( | ||
`compiler version ${lib.VERSION} not supported` | ||
) | ||
} | ||
} catch(_e) { | ||
} catch (_e) { | ||
console.error(_e) | ||
continue | ||
} | ||
} | ||
throw new Error("compiler not installed") | ||
} | ||
} |
Major refactor
Supply chain riskPackage has recently undergone a major refactor. It may be unstable or indicate significant internal changes. Use caution when updating to versions that include significant changes.
Found 1 instance in 1 package
43372
26
1300
5
2
+ Added@helios-lang/ledger@^0.1.9
+ Added@helios-lang/uplc@^0.1.0
+ Added@helios-lang/cbor@0.1.22(transitive)
+ Added@helios-lang/codec-utils@0.1.360.2.1(transitive)
+ Added@helios-lang/compiler-utils@0.1.63(transitive)
+ Added@helios-lang/crypto@0.1.17(transitive)
+ Added@helios-lang/era@0.1.4(transitive)
+ Added@helios-lang/ledger@0.1.49(transitive)
+ Added@helios-lang/type-utils@0.1.25(transitive)
+ Added@helios-lang/uplc@0.1.38(transitive)