scrypt-ts-transpiler
Version:
```bash npm i npx scryptlib download npm t ```
1,086 lines (1,085 loc) • 174 kB
JavaScript
"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(', ');
}