UNPKG

scrypt-ts-transpiler

Version:

```bash npm i npx scryptlib download npm t ```

1,086 lines (1,085 loc) 174 kB
"use strict"; var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { if (k2 === undefined) k2 = k; var desc = Object.getOwnPropertyDescriptor(m, k); if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { desc = { enumerable: true, get: function() { return m[k]; } }; } Object.defineProperty(o, k2, desc); }) : (function(o, m, k, k2) { if (k2 === undefined) k2 = k; o[k2] = m[k]; })); var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { Object.defineProperty(o, "default", { enumerable: true, value: v }); }) : function(o, v) { o["default"] = v; }); var __importStar = (this && this.__importStar) || function (mod) { if (mod && mod.__esModule) return mod; var result = {}; if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k); __setModuleDefault(result, mod); return result; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.Transpiler = void 0; const typescript_1 = __importStar(require("typescript")); const tsquery_1 = require("@phenomnomnominal/tsquery"); const console_1 = require("console"); const indexerReader_1 = require("./indexerReader"); const path = __importStar(require("path")); const utils_1 = require("./utils"); const scryptlib_1 = require("scryptlib"); const os_1 = require("os"); const fs_1 = require("fs"); const types_1 = require("./types"); const typescript_2 = require("typescript"); const snippets_1 = require("./snippets"); /** * @ignore */ const DEFAULT_AST_COMPILE_OPTS = Object.freeze({ ast: true, asm: false, hex: false, debug: false, artifact: false, sourceMap: false, outputToFiles: true, cmdArgs: '--std' }); /** * @ignore */ var DecoratorName; (function (DecoratorName) { DecoratorName["Prop"] = "prop"; DecoratorName["Method"] = "method"; })(DecoratorName || (DecoratorName = {})); /** * @ignore */ class EmittedLine { constructor(prefixTabs = 0, currentCol = 0, codeLines = "", sourceMap = []) { this.prefixTabs = prefixTabs; this.currentCol = currentCol; this.code = codeLines; this.sourceMap = sourceMap; } copy() { return new EmittedLine(this.prefixTabs, this.currentCol, this.code, JSON.parse(JSON.stringify(this.sourceMap))); } findByCol(col) { for (let i = 0; i < this.sourceMap.length; i++) { const sourceMap = this.sourceMap[i]; if (col >= sourceMap[0]) { const nextSourceMap = this.sourceMap[i + 1]; if (nextSourceMap && col <= nextSourceMap[0]) { return sourceMap; } else if (!nextSourceMap) { return sourceMap; } } } return this.sourceMap[0]; } } /** * @ignore */ class EmittedSection { constructor(initialLine) { this.lines = []; this.errors = []; if (initialLine) this.lines.push(initialLine); this.skipNextAppend = false; } static join(...sections) { let completed = new EmittedSection(); sections.forEach(sec => completed.concat(sec)); return completed; } getLastLine() { return this.lines[this.lines.length - 1]; } getCode() { return this.lines.map(l => l.code).join('\n'); } getSourceMap() { return this.lines.map(l => l.sourceMap); } append(code, srcLocation) { var _a; if (this.skipNextAppend) { this.skipNextAppend = false; return this; } let lastLine = this.getLastLine(); let inlineCode = code.replaceAll(/\n/g, ''); let startNewLine = code.startsWith("\n"); if (startNewLine || !lastLine) { let prefixTab = (_a = lastLine === null || lastLine === void 0 ? void 0 : lastLine.prefixTabs) !== null && _a !== void 0 ? _a : 0; lastLine = new EmittedLine(prefixTab, prefixTab * 2, Array(prefixTab * 2).fill(' ').join('') + inlineCode); this.lines.push(lastLine); } else { lastLine.code += inlineCode; } if (srcLocation) { // adjust col to skip prefix spaces let skipSpaceCol = inlineCode.search(/[^ ]/); let adjustCol = lastLine.currentCol + (skipSpaceCol > 0 ? skipSpaceCol : 0); // sourcemap format: [ generatedCodeColumn, sourceIndex, sourceCodeLine, sourceCodeColumn, nameIndex? ] lastLine.sourceMap.push([adjustCol, 0, srcLocation.line, srcLocation.character]); } lastLine.currentCol += inlineCode.length; return this; } appendWith(ctx, updater, increaseTab = false) { if (this.skipNextAppend) { this.skipNextAppend = false; return this; } try { // use the last line of current section as the start line of upcoming section let initialLine = (this.getLastLine() || new EmittedLine()).copy(); if (increaseTab) { initialLine.prefixTabs += 1; } this.concat(updater.call(ctx, new EmittedSection(initialLine)), true); if (increaseTab) { this.getLastLine().prefixTabs -= 1; } } catch (err) { if (err instanceof types_1.TranspileError) { this.errors.push(err); } else { throw err; } } return this; } concat(postSection, lastLineOverlays = false) { if (lastLineOverlays) this.lines.pop(); this.lines = this.lines.concat(postSection.lines); this.errors = this.errors.concat(postSection.errors); return this; } } const InjectedParamTxPreimage = "__scrypt_ts_txPreimage"; const InjectedParamChangeAmountVar = "__scrypt_ts_changeAmount"; const InjectedParamChangeAddressVar = "__scrypt_ts_changeAddress"; const InjectedParamAccessPathPrefix = "__scrypt_ts_accessPathForProp__"; const InjectedPropAccessPathReaderPrefix = "__scrypt_ts_accessPathReaderForProp__"; const InjectedParamPrevoutsVar = "__scrypt_ts_prevouts"; const InjectedCtxVerionProp = "__scrypt_ts_ctx_version"; const InjectedCtxHashPrevoutsProp = "__scrypt_ts_ctx_hashprevouts"; const InjectedCtxHashSequenceProp = "__scrypt_ts_ctx_hashsequence"; const InjectedCtxHashOutputsProp = "__scrypt_ts_ctx_hashoutputs"; const InjectedCtxLocktimeProp = "__scrypt_ts_ctx_locktime"; const InjectedCtxSequenceProp = "__scrypt_ts_ctx_sequence"; const InjectedCtxSigHashTypeProp = "__scrypt_ts_ctx_sighashtype"; const InjectedCtxOutpointTxidProp = "__scrypt_ts_ctx_outpoint_txid"; const InjectedCtxOutpointOutputIndexProp = "__scrypt_ts_ctx_outpoint_outputindex"; const InjectedCtxScriptCodeProp = "__scrypt_ts_ctx_scriptcode"; const InjectedCtxValueProp = "__scrypt_ts_ctx_value"; const InjectedCtxPreimageProp = "__scrypt_ts_ctx_preimage"; const InjectedChangeProp = "__scrypt_ts_change"; const InjectedPrevoutsProp = "__scrypt_ts_prevouts"; const SmartContractBuildinsFuncs = ["buildChangeOutput", "getStateScript", "timeLock", 'buildStateOutput', 'checkSig', 'checkMultiSig', "checkPreimageAdvanced", "checkPreimageSigHashType", 'checkPreimage', 'insertCodeSeparator', ]; /** * @ignore */ class Transpiler { constructor(sourceFile, host, checker, tsRootDir, scryptOutDir, indexer, compilerOptions) { this.scComponents = []; // public publicMethods: ts.MethodDeclaration[] = []; this.methodInfos = new Map(); this.propInfos = new Map(); this._localTypeSymbols = new Map(); this._importedTypeSymbols = new Map(); this._watch = false; this._constructorParametersMap = new Map(); this._srcFile = sourceFile; this._host = host; this._checker = checker; this._tsRootDir = tsRootDir; this._scryptOutDir = scryptOutDir; this._indexer = indexer; this._compilerOptions = compilerOptions; this._watch = compilerOptions.watch || false; this.searchSmartContractComponents(); this.searchTopCtcs(); } get ctxMethods() { return this.getCtxMethodInfos().map(info => info.name); } get _scryptRelativePath() { return this.getRelativePathFromTsRoot(this._srcFile.fileName); } get _scryptFullPath() { return path.join(this._scryptOutDir, this._scryptRelativePath); } get currentContractName() { return this._currentContract.name.getText(); } get currentbaseContractName() { return Transpiler.getBaseContractName(this._currentContract); } get currentbaseContract() { return Transpiler.abstractContractAst.get(this.currentbaseContractName); } get _transformationResultRelativePath() { return (0, utils_1.alterFileExt)(this._scryptRelativePath, "transformer.json"); } get _transformationResultFullPath() { return (0, utils_1.alterFileExt)(this._scryptFullPath, "transformer.json"); } get _sourceMapRelativePath() { return (0, utils_1.alterFileExt)(this._scryptRelativePath, "scrypt.map"); } get _sourceMapFullPath() { return (0, utils_1.alterFileExt)(this._scryptFullPath, "scrypt.map"); } setLocalSymbols(localTypeSymbols) { this._localTypeSymbols = localTypeSymbols; } transform(allmissSym) { // console.log(`transform ${this._srcFile.fileName} to ${this._outFilePath}`) let contractAndLibs = this.scComponents.map(cDef => this.transformClassDeclaration(cDef)); let structs = this.transformTypeLiteralAndInterfaces(); let imports = this.transformImports(allmissSym); let result = EmittedSection.join(imports, structs, ...contractAndLibs); if (this._watch) { setTimeout(() => { this.diagnose(result.errors); }, 500); } else { this.outputScrypt(result); const errors = this.checkTransformedScrypt(result); result.errors.push(...errors); this.diagnose(result.errors); this.outputSourceMapFile(result); this.updateIndex(); this.outputTransformationResult(result); } return result; } isTransformable() { return this.scComponents.length > 0; } getSCComponents() { return this.scComponents; } isFromThirdParty(filepath) { return filepath.endsWith(".d.ts"); } outputScrypt(result) { typescript_1.default.sys.writeFile(this._scryptFullPath, result.getCode()); } outputTransformationResult(result) { let transResult = { success: result.errors.length === 0, errors: result.errors, scryptfile: this._scryptRelativePath, sourceMapFile: this._sourceMapRelativePath, ctxMethods: this.ctxMethods }; typescript_1.default.sys.writeFile(this._transformationResultFullPath, JSON.stringify(transResult, null, 2)); } diagnose(errors) { errors.forEach(error => this.outputDiagnostic(error)); } outputDiagnostic(diag) { console.log(`scrypt-ts ERROR - ${diag.srcRange.fileName}:${diag.srcRange.start.line}:${diag.srcRange.start.character}:${diag.srcRange.end.line}:${diag.srcRange.end.character} - ${diag.message}\n`); } // check transformed scrypt code to collect compile time errors checkTransformedScrypt(result) { if (result.errors.length === 0 && (0, fs_1.existsSync)(this._scryptFullPath)) { try { const tmpDir = (0, fs_1.mkdtempSync)(path.join((0, os_1.tmpdir)(), "scrypt-ts-")); const input = { path: this._scryptFullPath, content: result.getCode() }; const settings = Object.assign({}, DEFAULT_AST_COMPILE_OPTS, { cmdPrefix: (0, scryptlib_1.findCompiler)(), outputDir: tmpDir }); const astResult = (0, scryptlib_1.compile)(input, settings); const errors = this.compileError2transpileError(astResult.errors, result.lines); return errors; } catch (err) { // for the case when scrypt compiler crushed. return [new types_1.TranspileError(`Internal compilation FATAL error raised for auto-generated file: ${this._scryptFullPath} , please report it as a bug to scrypt-ts\n${err}`, { fileName: this._scryptFullPath, start: { line: -1, character: -1 }, end: { line: -1, character: -1 } })]; } } return []; } compileError2transpileError(errors, scryptLines) { const tsSrcLines = this._srcFile.getFullText().split('\n'); return errors.map(error => { var _a, _b, _c, _d, _f; const scryptLine = scryptLines[(((_a = error.position[0]) === null || _a === void 0 ? void 0 : _a.line) || 0) - 1]; const sourcemap = scryptLine ? scryptLine.findByCol(((_b = error.position[0]) === null || _b === void 0 ? void 0 : _b.column) || -1) : undefined; if (sourcemap) { const tsLine = sourcemap[2]; const tsColumn = sourcemap[3]; // code from current column to end of line const tsCode = tsSrcLines[tsLine].slice(tsColumn).replace(/\/\/.*/, "").trim(); return new types_1.TranspileError(`The code '${tsCode}' was successfully transformed but compiled with error: ${error.message}`, { fileName: this._srcFile.fileName, start: { line: tsLine, character: tsColumn }, end: { line: tsLine, character: tsColumn + tsCode.length } }); } else { // when can not find sourcemap for the certain `scryptLine`, use the first sourcemap const sourcemap = (_c = scryptLines[1]) === null || _c === void 0 ? void 0 : _c.sourceMap[0]; return new types_1.TranspileError(`Internal compilation error raised for auto-generated file: ${this._scryptFullPath}:${((_d = error.position[0]) === null || _d === void 0 ? void 0 : _d.line) || -1}:${((_f = error.position[0]) === null || _f === void 0 ? void 0 : _f.column) || -1} , please report it as a bug to scrypt-ts`, { fileName: this._srcFile.fileName, start: { line: sourcemap ? sourcemap[2] : -1, character: sourcemap ? sourcemap[3] : -1 }, end: { line: -1, character: -1 } }); } }); } outputSourceMapFile(result) { typescript_1.default.sys.writeFile(this._sourceMapFullPath, JSON.stringify(result.getSourceMap())); } updateIndex() { const globalSymbols = Array.from(this._localTypeSymbols.entries()) .map(([symbolName, symbol]) => { const symbolFile = this.findDeclarationFile(symbol); let symbolDec = symbol.declarations[0]; symbolDec = symbolDec.name || symbolDec; return { name: symbolName, srcRange: { fileName: symbolFile.fileName, start: symbolFile.getLineAndCharacterOfPosition(symbolDec.getStart()), end: symbolFile.getLineAndCharacterOfPosition(symbolDec.getEnd()) } }; }); this._indexer.addSymbols(globalSymbols, this._scryptRelativePath); } isExtendsSCComponent(node) { if (this.isContract(node)) { if ((0, utils_1.hasModifier)(node, typescript_1.default.SyntaxKind.AbstractKeyword)) { Transpiler.abstractContractAst.set(node.name.getText(), node); // ignore abstract class, then are base contract class return false; } return true; } return this.isLibrary(node); } isContract(node) { if (Array.isArray(node.heritageClauses) && node.heritageClauses.length === 1) { let clause = node.heritageClauses[0]; if (clause.token == typescript_1.default.SyntaxKind.ExtendsKeyword && clause.types.length === 1) { let baseContractName = Transpiler.getBaseContractName(node); return baseContractName === "SmartContract" || Transpiler.abstractContractAst.has(baseContractName); } } return false; } static getBaseContractName(node) { if (Array.isArray(node.heritageClauses) && node.heritageClauses.length === 1) { let clause = node.heritageClauses[0]; if (clause.token == typescript_1.default.SyntaxKind.ExtendsKeyword && clause.types.length === 1) { return clause.types[0].getText(); } } throw new Error(`Can't get base contract for class ${node.name.getText()}`); } isInherited(node) { if (node.heritageClauses.length === 1 && node.heritageClauses[0].token === typescript_1.default.SyntaxKind.ExtendsKeyword) { const baseContractName = Transpiler.getBaseContractName(node); return Transpiler.abstractContractAst.has(baseContractName); } return false; } isTranspilingConstructor(node) { if (!node.parent) { return false; } if (typescript_1.default.isConstructorDeclaration(node.parent)) { return true; } return this.isTranspilingConstructor(node.parent); } isTranspilingBaseContract(node) { const cls = Transpiler.getClassDeclaration(node); if (cls.name.getText() !== this.currentContractName) { return true; } } isLibrary(node) { var _a; if ((node === null || node === void 0 ? void 0 : node.heritageClauses.length) === 1 && (node === null || node === void 0 ? void 0 : node.heritageClauses[0].token) === typescript_1.default.SyntaxKind.ExtendsKeyword) { const parentClassName = (_a = node === null || node === void 0 ? void 0 : node.heritageClauses[0].types[0]) === null || _a === void 0 ? void 0 : _a.getText(); return parentClassName === "SmartContractLib"; } return false; } /** * get relative path starting from `tsRootDir` * @param fullFilePath * @param ext extension of the path * @returns */ getRelativePathFromTsRoot(fullFilePath, ext = 'scrypt') { // ts source file to root dir relative path which will be kept in scrypt output structure. let root2srcRelativePath = path.relative(this._tsRootDir, fullFilePath).replaceAll('\\', '/'); if (root2srcRelativePath.startsWith("src/contracts/")) { root2srcRelativePath = root2srcRelativePath.replace("src/contracts/", ""); } if (root2srcRelativePath.startsWith("../")) { root2srcRelativePath = root2srcRelativePath.replace("../", ""); } const basename = path.basename(root2srcRelativePath, path.extname(root2srcRelativePath)); return path.join(path.dirname(root2srcRelativePath), `${basename}.${ext}`).replaceAll('\\', '/'); } getRelativePathFromArtifacts(fullFilePath, ext = 'scrypt') { // ts source file to root dir relative path which will be kept in scrypt output structure. let relativePath = path.relative(this._scryptOutDir, fullFilePath).replaceAll('\\', '/'); const basename = path.basename(relativePath, path.extname(relativePath)); return path.join(path.dirname(relativePath), `${basename}.${ext}`).replaceAll('\\', '/'); } searchSmartContractComponents() { const scComponentDefs = (0, tsquery_1.tsquery)(this._srcFile, "ClassDeclaration:has(HeritageClause)"); let scComponents = scComponentDefs.filter((cDef) => this.isExtendsSCComponent(cDef)); scComponents.forEach(cDef => (0, console_1.assert)(typescript_1.default.isClassDeclaration(cDef))); this.scComponents = scComponents; } searchTopCtcs() { const declarations = (0, tsquery_1.tsquery)(this._srcFile, "SourceFile > VariableStatement > VariableDeclarationList"); const ctcDeclarations = declarations.filter(d => { if (typescript_1.default.isVariableDeclarationList(d)) { return this.isCtcDeclaration(d.declarations[0]); } }); ctcDeclarations.forEach((d) => { Transpiler.topCtcs.set(`${(0, utils_1.sha1)(this._srcFile.fileName)}:${d.declarations[0].name.getText()}`, this.evalCtcExpression(d.declarations[0].initializer)); }); } getCoordinates(pos) { return pos ? this._srcFile.getLineAndCharacterOfPosition(pos) : undefined; } getRange(node) { return { fileName: this._srcFile.fileName, start: this.getCoordinates(node.getStart()), end: this.getCoordinates(node.getEnd()) }; } getResolvedType(node) { let symbol = this._checker.getSymbolAtLocation(node); return symbol ? this._checker.getTypeOfSymbolAtLocation(symbol, symbol.declarations[0]) : undefined; } findDeclarationFile(symbol) { let node = symbol.declarations[0]; while (node === null || node === void 0 ? void 0 : node.parent) { node = node.parent; } return typescript_1.default.isSourceFile(node) ? node : undefined; } transformProps(section, node) { const category = this.isContract(node) ? 'contract' : 'library'; node.members.forEach(m => { if (m.kind === typescript_1.default.SyntaxKind.PropertyDeclaration) { section.appendWith(this, (memSec) => { return this.transformPropertyDeclaration(m, memSec, category); }); } }); } checkPropsOverride(node, baseContract) { const baseContractProps = baseContract.members.filter(m => Transpiler.isProperty(m)) .map(m => m.name.getText()); node.members.forEach(m => { if (Transpiler.isProperty(m)) { if (baseContractProps.includes(m.name.getText())) { throw new types_1.TranspileError(`Untransformable property: '${m.name.getText()}', already defined in base contract '${baseContract.name.getText()}'`, this.getRange(node)); } } }); } checkMethodsOverride(node, baseContract) { const baseContractMethods = baseContract.members.filter(m => Transpiler.isMethod(m)) .map(m => m.name.getText()); node.members.forEach(m => { if (Transpiler.isMethod(m)) { if (baseContractMethods.includes(m.name.getText())) { throw new types_1.TranspileError(`Untransformable method: '${m.name.getText()}', already defined in base contract '${baseContract.name.getText()}'`, this.getRange(node)); } } }); } transformMethods(section, node) { const category = this.isContract(node) ? 'contract' : 'library'; node.members.forEach(m => { if (m.kind === typescript_1.default.SyntaxKind.MethodDeclaration) { section.appendWith(this, (memSec) => { return this.transformMethodDeclaration(m, memSec, category); }); } }); } static create_ctx_function(ctxAccessInfo, section) { if (ctxAccessInfo.accessVersion) { section.append('\n'); section.append(`this.${InjectedCtxVerionProp} = SigHash.nVersion(${InjectedParamTxPreimage});`); } if (ctxAccessInfo.accessHashPrevouts) { section.append('\n'); section.append(`this.${InjectedCtxHashPrevoutsProp} = SigHash.hashPrevouts(${InjectedParamTxPreimage});`); } if (ctxAccessInfo.accessHashSequence) { section.append('\n'); section.append(`this.${InjectedCtxHashSequenceProp} = SigHash.hashSequence(${InjectedParamTxPreimage});`); } if (ctxAccessInfo.accessHashOutputs) { section.append('\n'); section.append(`this.${InjectedCtxHashOutputsProp} = SigHash.hashOutputs(${InjectedParamTxPreimage});`); } if (ctxAccessInfo.accessLocktime) { section.append('\n'); section.append(`this.${InjectedCtxLocktimeProp} = SigHash.nLocktime(${InjectedParamTxPreimage});`); } if (ctxAccessInfo.accessSequence) { section.append('\n'); section.append(`this.${InjectedCtxSequenceProp} = SigHash.nSequence(${InjectedParamTxPreimage});`); } if (ctxAccessInfo.accessSigHashType) { section.append('\n'); section.append(`this.${InjectedCtxSigHashTypeProp} = SigHash.sigHashType(${InjectedParamTxPreimage});`); } if (ctxAccessInfo.accessOutpoint) { section.append('\n'); section.append(`this.${InjectedCtxOutpointOutputIndexProp} = unpack(SigHash.outpoint(${InjectedParamTxPreimage})[32 :]);`); section.append('\n'); section.append(`this.${InjectedCtxOutpointTxidProp} = SigHash.outpoint(${InjectedParamTxPreimage})[0:32];`); } if (ctxAccessInfo.accessScriptCode) { section.append('\n'); section.append(`this.${InjectedCtxScriptCodeProp} = SigHash.scriptCode(${InjectedParamTxPreimage});`); } if (ctxAccessInfo.accessValue) { section.append('\n'); section.append(`this.${InjectedCtxValueProp} = SigHash.value(${InjectedParamTxPreimage});`); } if (ctxAccessInfo.accessSerialize) { section.append('\n'); section.append(`this.${InjectedCtxPreimageProp} = ${InjectedParamTxPreimage};`); } } transformClassDeclaration(node) { const className = node.name.getText(); this._currentContract = node; // also add it to local type symbols this._localTypeSymbols.set(className, this._checker.getSymbolAtLocation(node.name)); const category = this.isContract(node) ? 'contract' : 'library'; const baseContract = this.currentbaseContract; // Preprocess all methods const section = new EmittedSection(); try { this.initAllPropInfos(); this.initAllMethodInfos(); this.injectScryptStructs(section); section .append('\n') .append(`\n${category} `) .append(`${className} {`, this.getCoordinates(node.name.getStart())) .appendWith(this, (membersSection) => { if (baseContract) { this.checkPropsOverride(node, baseContract); this.transformProps(membersSection, baseContract); } this.transformProps(membersSection, node); this.injectScryptProps(membersSection); this.transformConstructor(membersSection, node, baseContract); if (baseContract) { this.checkMethodsOverride(node, baseContract); this.transformMethods(membersSection, baseContract); } this.transformMethods(membersSection, node); if (this.accessTimelock()) { membersSection.append(`\n${snippets_1.TIME_LOCK_FUNCTION}`); } if (this.accessStateProp()) { membersSection.append(`\n${snippets_1.BUILD_STATE_OUTPUT_FUNCTION}`); } if (this.accessChange()) { membersSection.append(`\n${snippets_1.BUILD_CHANGE_OUTPUT_FUNCTION}`); } if (this.getPublicMethodCount() === 0 && category === 'contract') { throw new types_1.TranspileError("A `SmartContract` should have at least one public `@method`", this.getRange(node.name)); } return membersSection; }, true) .append("\n}"); let methodInfos = Array.from(this.methodInfos, ([key, methodInfo]) => ({ key, methodInfo })) .filter(i => i.key.startsWith(`${className}.`)) .filter(i => i.methodInfo.codeSeparatorCount > 0); if (methodInfos.length > 1) { throw new types_1.TranspileError(`insertCodeSeparator() can only be called by one pulic method`, this.getRange(node)); } } catch (error) { if (error instanceof types_1.TranspileError) { section.errors.push(error); } else { throw error; } } return section; } injectScryptProps(section) { if (this.accessCtx()) { const accessCtxInfo = this.getCtxAccessInfo(); if (accessCtxInfo.accessVersion) { section.append(`\nbytes ${InjectedCtxVerionProp};`); } if (accessCtxInfo.accessHashPrevouts) { section.append(`\nbytes ${InjectedCtxHashPrevoutsProp};`); } if (accessCtxInfo.accessHashSequence) { section.append(`\nbytes ${InjectedCtxHashSequenceProp};`); } if (accessCtxInfo.accessHashOutputs) { section.append(`\nbytes ${InjectedCtxHashOutputsProp};`); } if (accessCtxInfo.accessLocktime) { section.append(`\nint ${InjectedCtxLocktimeProp};`); } if (accessCtxInfo.accessSequence) { section.append(`\nint ${InjectedCtxSequenceProp};`); } if (accessCtxInfo.accessSigHashType) { section.append(`\nSigHashType ${InjectedCtxSigHashTypeProp};`); } if (accessCtxInfo.accessOutpoint) { section.append(`\nbytes ${InjectedCtxOutpointTxidProp};`); section.append(`\nint ${InjectedCtxOutpointOutputIndexProp};`); } if (accessCtxInfo.accessScriptCode) { section.append(`\nbytes ${InjectedCtxScriptCodeProp};`); } if (accessCtxInfo.accessValue) { section.append(`\nint ${InjectedCtxValueProp};`); } if (accessCtxInfo.accessSerialize) { section.append(`\nSigHashPreimage ${InjectedCtxPreimageProp};`); } } if (this.accessChange()) { section.append(`\n__scrypt_ts_Change ${InjectedChangeProp};`); } if (this.accessPrevouts()) { section.append(`\nbytes ${InjectedPrevoutsProp};`); } } injectScryptStructs(section) { if (this.accessChange()) { section.append(snippets_1.CHANGE_STRUCT); } } transformPropertySignature(node, toSection) { return this.transformEnclosingTypeNode(node.type || node, toSection) .append(` ${node.name.getText()}`, this.getCoordinates(node.name.getStart())) .append(';'); } transformPropertyDeclaration(node, toSection, category) { if (!Transpiler.isProperty(node)) return toSection; toSection .append("\n"); if (Transpiler.isStateProperty(node)) { if (category === "library") { const decorator = Transpiler.findDecorator(node, DecoratorName.Prop); throw new types_1.TranspileError(`Untransformable property: '${node.name.getText()}', \`@prop(true)\` is only allowed to be used in \`SmartContract\``, this.getRange(decorator.expression)); } if (Transpiler.isStaticProperty(node)) { throw new types_1.TranspileError(`Untransformable property: '${node.name.getText()}', \`@prop(true)\` cannot be static`, this.getRange(node)); } if ((0, utils_1.hasModifier)(node, typescript_1.default.SyntaxKind.ReadonlyKeyword)) { throw new types_1.TranspileError(`Untransformable property: '${node.name.getText()}', \`@prop(true)\` cannot be readonly`, this.getRange(node)); } toSection .append("@state "); } else if (Transpiler.isStaticProperty(node)) { if (!node.initializer) { throw new types_1.TranspileError(`Untransformable property: '${node.name.getText()}', static property shall be initialized when declared`, this.getRange(node)); } } toSection .appendWith(this, toSec => { return this.transformModifiers(node, toSec); }) .appendWith(this, toSec => { if (!node.type) { throw new types_1.TranspileError(`Untransformable property: '${node.name.getText()}', all \`prop()\` should be typed explicitly`, this.getRange(node)); } return this.transformEnclosingTypeNode(node.type, toSec); }) .append(` ${node.name.getText()}`, this.getCoordinates(node.name.getStart())); if (node.initializer) { if (Transpiler.isStaticProperty(node)) { toSection.append(" = ") .appendWith(this, toSec => { return this.transformExpression(node.initializer, toSec); }) .append(';'); return toSection; } else { throw new types_1.TranspileError(`Untransformable property: '${node.name.getText()}', Non-static properties shall only be initialized in the constructor`, this.getRange(node.initializer)); } } toSection.append(';'); // auto append `VarIntReader` for `SortedItemAccessTraceable` property const propInfo = this.findPropInfo(node.name.getText()); if (propInfo && propInfo.isHashed) { toSection.append(`\nVarIntReader ${InjectedPropAccessPathReaderPrefix}${node.name.getText()};`); } return toSection; } buildConstructorParametersMap(node, baseContract) { function getSuperParameters() { const superStmt = node.body.statements[0]; let callexpr = superStmt.expression; if (callexpr.arguments[0] && typescript_1.default.isSpreadElement(callexpr.arguments[0]) && callexpr.arguments[0].getText().endsWith("arguments")) { return node.parameters; } else { return callexpr.arguments; } } const basector = this.getConstructor(baseContract); if (!basector) { return; } const superParameters = getSuperParameters(); basector.parameters.forEach((parameter, index) => { const superParameter = superParameters[index]; this._constructorParametersMap.set(parameter.name.getText(), superParameter); }); } static accessSetConstructor(statements) { return statements.findIndex(statement => { return (typescript_1.default.isExpressionStatement(statement) && statement.expression.kind === typescript_1.default.SyntaxKind.CallExpression && /^this\.init\(/.test(statement.expression.getText())); }) > -1; } checkSuperStmt(node) { const superStmt = node.body.statements[0]; let callexpr = superStmt.expression; if (!typescript_1.default.isCallExpression(callexpr) || callexpr.expression.getText() !== 'super') { throw new types_1.TranspileError(`Constructors for derived classes must contain a \`super()\` call`, this.getRange(node)); } if (Transpiler.accessSetConstructor(node.body.statements)) { throw new types_1.TranspileError(`Direct subclasses of \`SmartContract\` do not need to call \'this.init()\'.`, this.getRange(node)); } if (callexpr.arguments[0] && typescript_1.default.isSpreadElement(callexpr.arguments[0]) && callexpr.arguments[0].getText().endsWith("arguments")) { return; } if (node.parameters.length !== callexpr.arguments.length) { throw new types_1.TranspileError(`All parameters in the constructor must be passed to the \`super()\` call`, this.getRange(node)); } node.parameters.forEach((p, pIdx) => { const arg = callexpr.arguments[pIdx]; if (arg.getText() !== p.name.getText()) { throw new types_1.TranspileError(`All parameters in the constructor must be passed to the \`super()\` call following their declaration order`, this.getRange(node)); } }); } checkSetConstructorStmt(node) { const superStmt = node.body.statements[0]; let callexpr = superStmt.expression; if (!typescript_1.default.isCallExpression(callexpr) || callexpr.expression.getText() !== 'super') { throw new types_1.TranspileError(`Constructors for derived classes must contain a \`super()\` call`, this.getRange(node)); } if (callexpr.arguments[0] && typescript_1.default.isSpreadElement(callexpr.arguments[0]) && callexpr.arguments[0].getText().endsWith("arguments")) { return; } if (node.parameters.length === callexpr.arguments.length) { return; } const setConstructorStmt = node.body.statements[1]; let setConstructorExpr = setConstructorStmt.expression; if (!typescript_1.default.isCallExpression(setConstructorExpr) || setConstructorExpr.expression.getText() !== 'this.init') { throw new types_1.TranspileError(`Inherited contract classes must contain a \`this.init(...arguments)\` call`, this.getRange(node)); } if (setConstructorExpr.arguments[0] && typescript_1.default.isSpreadElement(setConstructorExpr.arguments[0]) && setConstructorExpr.arguments[0].getText().endsWith("arguments")) { return; } if (node.parameters.length !== setConstructorExpr.arguments.length) { throw new types_1.TranspileError(`All parameters in the constructor must be passed to the \`this.init(...arguments)\` call`, this.getRange(node)); } node.parameters.forEach((p, pIdx) => { const arg = setConstructorExpr.arguments[pIdx]; if (arg.getText() !== p.name.getText()) { throw new types_1.TranspileError(`All parameters in the constructor must be passed to the \`this.init(...arguments)\` call following their declaration order`, this.getRange(node)); } }); } canBeImplicitConstructor(node) { // Test if it can be an implicit constructor. let res = true; const ctorParamNames = []; node.parameters.forEach((p) => { const pText = p.name.getFullText().trim(); ctorParamNames.push(pText); }); node.body.statements.slice(1).forEach((stmt) => { // For each statement in the constr check if its a property init stmt. // If not, the constructor cannot be implicit. const isPropSet = this.queryPropertyInitializedInStmt(stmt); if (!isPropSet) { res = false; return; } // If its a prop init stmt, then also check if the value being set is a // constructor parameter. if (!(0, typescript_1.isExpressionStatement)(stmt) || !(0, typescript_2.isBinaryExpression)(stmt.expression)) { res = false; return; } const exprName = stmt.expression.right.getFullText().trim(); let found = false; for (const ctorParamName of ctorParamNames) { if (ctorParamName == exprName) { found = true; break; } } if (!found) { res = false; return; } }); // Check the order of the ctor params compared to the order of prop declarations. const props = node.parent.members.filter(member => Transpiler.isProperty(member)); props.forEach((p) => { const pName = p.name.getFullText().trim(); const ctorParamName = ctorParamNames.shift(); if (pName != ctorParamName) { res = false; return; } }); // Check if the contract contains any non-props. It can still contain static (readonly) non-props. const nonProps = node.parent.members.filter(member => this.isNonProp(member) && !this.isStaticReadOnlyNonProp(member)); if (nonProps.length > 0) { res = false; } // Also check for VarIntReader properties. if (this.accessHashededProp() || this.accessCtx() || this.accessChange() || this.accessPrevouts()) { res = false; } return res; } transformConstructorBody(section, node) { const allProps = this.allPropertyDeclaration(node.parent); let initializedProps = []; node.body.statements.slice(1).forEach((stmt) => { section .append('\n') .appendWith(this, stmtsSec => { const prop = this.queryPropertyInitializedInStmt(stmt); if (prop) { initializedProps.push(prop); if (!Transpiler.isProperty(prop)) { return stmtsSec; //allow but ignore Non-Prop Property } } return this.transformStatement(stmt, stmtsSec); }, true); }); allProps.forEach(prop => { if (!initializedProps.map(p => p.name.getText()).includes(prop.name.getText())) { // allowed static const prop initialized if (!Transpiler.isStaticProperty(prop)) { throw new types_1.TranspileError(`property \`${prop.name.getText()}\` must be initialized in the constructor`, this.getRange(prop)); } } }); return section; } transformConstructor(section, cls, baseContract) { this.checkConstructor(cls); const node = cls.members.find(m => m.kind === typescript_1.default.SyntaxKind.Constructor); if (node) { if (baseContract) { this.buildConstructorParametersMap(node, baseContract); } section.appendWith(this, (section) => { if (!node.body) { throw new types_1.TranspileError(`Missing function body`, this.getRange(node)); } const tmpSection = new EmittedSection(); tmpSection.lines = section.lines; tmpSection.errors = section.errors; tmpSection .append('\n') .append('constructor', this.getCoordinates(node.getStart())) .append('(') .appendWith(this, psSec => { node.parameters.forEach((p, pIdx) => { psSec.appendWith(this, pSec => { let sec = this.transformParameter(p, pSec); if (pIdx !== node.parameters.length - 1) { sec.append(', '); } return sec; }); }); return psSec; }) .append(') '); tmpSection.append('{') .appendWith(this, (sec) => { if (baseContract) { baseContract.members.forEach(member => { if (member.kind === typescript_1.default.SyntaxKind.Constructor) { this.transformConstructorBody(sec, member); } }); } return this.transformConstructorBody(sec, node); }, true) .append('\n}'); // If the constructor can be implicit, we don't even have to transform it. if (this.canBeImplicitConstructor(node)) { section.errors = tmpSection.errors; return section; } return tmpSection; }); } else { if (this.accessHashededProp() || this.accessCtx() || this.accessChange() || this.accessPrevouts()) { section.append(`\n${snippets_1.EMPTY_CONSTRUCTOR}`); return section; } } } transformMethodDeclaration(node, toSection, category) { let methodDec = Transpiler.findDecorator(node, DecoratorName.Method); if (!methodDec) return toSection; const match = /^method\((.*)?\)$/.exec(methodDec.expression.getText()); let sigHashType = '41'; //SigHash.ALL; switch (match[1]) { case "SigHash.ANYONECANPAY_SINGLE": sigHashType = 'c3'; // SigHash.ANYONECANPAY_SINGLE; break; case "SigHash.ANYONECANPAY_ALL": sigHashType = 'c1'; //SigHash.ANYONECANPAY_ALL; break; case "SigHash.ANYONECANPAY_NONE": sigHashType = 'c2'; //SigHash.ANYONECANPAY_NONE; break; case "SigHash.ALL": sigHashType = '41'; //SigHash.ALL; break; case "SigHash.SINGLE": sigHashType = '43'; //SigHash.SINGLE; break; case "SigHash.NONE": sigHashType = '42'; //SigHash.NONE; break; default: break; } const isPublicMethod = Transpiler.isPublicMethod(node); if (isPublicMethod) { if (category === 'library') { const publicModifier = typescript_1.default.getModifiers(node).find(m => m.kind === typescript_1.default.SyntaxKind.PublicKeyword); throw new types_1.TranspileError(`\`@method\` in \`SmartContractLib\` should not be declared as \`public\``, this.getRange(publicModifier)); } const retStmt = (0, utils_1.findReturnStatement)(node); if (retStmt) { throw new types_1.TranspileError(`public methods cannot contain an explicit return statement`, this.getRange(retStmt)); } if (node.type) { // has return type throw new types_1.TranspileError(`public methods cannot have a return type`, this.getRange(node.type)); } } else { if (!node.type) { throw new types_1.TranspileError(`non-public methods must declare the return type explicitly`, this.getRange(node)); } } if (!node.body) { throw new types_1.TranspileError(`Missing function body`, this.getRange(node)); } const shouldAutoAppendSighashPreimage = this.shouldAutoAppendSighashPreimage(node); const shouldAutoAppendChangeAmount = this.shouldAutoAppendChangeAmount(node); const accessPathProps = this.shouldAutoAppendAccessPaths(node); const shouldAutoAppendPrevouts = this.shouldAutoAppendPrevouts(node); const buildChangeOutputExpression = (0, utils_1.findBuildChangeOutputExpression)(node); if (shouldAutoAppendSighashPreimage && buildChangeOutputExpression !== undefined) { const allowedSighashType = ['c1', '41']; // Only sighash ALL allowed. if (!allowedSighashType.includes(sigHashType)) { throw new types_1.TranspileError(`Can only use sighash ALL or ANYONECANPAY_ALL if using \`this.buildChangeOutput()\``, this.getRange(node)); } } toSection .append('\n') .appendWith(this, toSec => { return this.transformModifiers(node, toSec); }) .append('function ') .append(node.name.getText(), this.getCoordinates(node.name.getStart())) .append('(') .appendWith(this, psSec => { // not allow SmartContract as parameter const inValidParams = node.parameters.find(p => { var _a; return ((_a = p.type) === null || _a === void 0 ? void 0 : _a.getText()) === 'SmartContract'; }); if (inValidParams) { throw new types_1.TranspileError(`Untransformable parameter: '${node.getText()}'`, this.getRange(node)); } node.parameters.forEach((p, pIdx) => { psSec.appendWith(this, pSec => { if (this.isHashedMapOrHashedSet(p.name)) { throw new types_1.TranspileError(`\`HashedMap\`/\`HashedSet\`-typed parameter is not allowed`, this.getRange(p.name)); } let sec = this.transformParameter(p, pSec); if (pIdx !== node.parameters.length - 1) { sec.append(', '); } return sec; }); }); let paramLen = node.parameters.length; if (shouldAutoAppendSighashPreimage) { if (paramLen > 0) { psSec.append(', '); }