UNPKG

@neo-one/smart-contract-compiler

Version:

NEO•ONE TypeScript smart contract compiler.

470 lines (468 loc) 20.7 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.BaseScriptBuilder = void 0; const client_common_1 = require("@neo-one/client-common"); const ts_utils_1 = require("@neo-one/ts-utils"); const utils_1 = require("@neo-one/utils"); const bn_js_1 = require("bn.js"); const source_map_1 = require("source-map"); const DiagnosticCode_1 = require("../../DiagnosticCode"); const DiagnosticMessage_1 = require("../../DiagnosticMessage"); const declaration_1 = require("../declaration"); const expression_1 = require("../expression"); const file_1 = require("../file"); const pc_1 = require("../pc"); const statement_1 = require("../statement"); const JumpTable_1 = require("./JumpTable"); const resolveJumps_1 = require("./resolveJumps"); const compilers = [declaration_1.declarations, expression_1.expressions, file_1.files, statement_1.statements]; class BaseScriptBuilder { constructor(context, helpers, sourceFile, contractInfo, linked = {}, allHelpers = []) { this.context = context; this.helpers = helpers; this.sourceFile = sourceFile; this.contractInfo = contractInfo; this.linked = linked; this.allHelpers = allHelpers; this.jumpTable = new JumpTable_1.JumpTable(); this.mutableBytecode = []; this.mutablePC = 0; this.jumpTablePC = new pc_1.DeferredProgramCounter(); this.mutableProcessedByteCode = []; this.mutableCurrentTags = []; this.nodes = new Map(); this.mutableModuleMap = {}; this.mutableReverseModuleMap = {}; this.mutableExportMap = {}; this.mutableNextModuleIndex = 0; this.mutableCurrentModuleIndex = 0; this.mutableFeatures = { storage: false, dynamicInvoke: false }; this.compilers = compilers .reduce((acc, kindCompilers) => acc.concat(kindCompilers), []) .reduce((acc, kindCompilerClass) => { const kindCompiler = new kindCompilerClass(); if (acc[kindCompiler.kind] !== undefined) { throw new Error(`Found duplicate compiler for kind ${kindCompiler.kind}`); } acc[kindCompiler.kind] = kindCompiler; return acc; }, {}); } get scope() { if (this.mutableCurrentScope === undefined) { throw new Error('Scope has not been set'); } return this.mutableCurrentScope; } get moduleIndex() { return this.mutableCurrentModuleIndex; } process() { const sourceFile = this.sourceFile; const { bytecode } = this.capture(() => { const sourceFilePath = ts_utils_1.tsUtils.file.getFilePath(sourceFile); this.mutableModuleMap[sourceFilePath] = this.mutableNextModuleIndex; this.mutableReverseModuleMap[this.mutableNextModuleIndex] = sourceFilePath; this.mutableCurrentModuleIndex = this.mutableNextModuleIndex; this.mutableNextModuleIndex += 1; this.mutableCurrentScope = this.createScope(sourceFile, 0, undefined); this.nodes.set(sourceFile, 0); const options = {}; this.mutableCurrentScope.emit(this, sourceFile, options, (innerOptions) => { this.emitHelper(sourceFile, this.pushValueOptions(innerOptions), this.helpers.createGlobalObject); this.emitOp(sourceFile, 'DUP'); this.scope.setGlobal(this, sourceFile, this.pushValueOptions(innerOptions)); this.emitOp(sourceFile, 'DUP'); this.emitHelper(sourceFile, this.pushValueOptions(innerOptions), this.helpers.addEmptyModule); this.allHelpers.forEach((helper) => { if (helper.needsGlobal) { this.emitOp(sourceFile, 'DUP'); } helper.emitGlobal(this, sourceFile, innerOptions); }); this.emitOp(sourceFile, 'DROP'); this.visit(sourceFile, innerOptions); const contractInfo = this.contractInfo; if (contractInfo !== undefined) { this.emitHelper(contractInfo.smartContract, innerOptions, this.helpers.invokeSmartContract({ contractInfo, })); } this.scope.getGlobal(this, sourceFile, options); this.allHelpers.forEach((helper) => { if (helper.needsGlobalOut) { this.emitOp(sourceFile, 'DUP'); } helper.emitGlobalOut(this, sourceFile, innerOptions); }); this.emitOp(sourceFile, 'DROP'); }); }); this.mutableProcessedByteCode = bytecode; } getFinalResult(sourceMaps) { this.withProgramCounter((programCounter) => { this.emitJmp(this.sourceFile, 'JMP', programCounter.getLast()); this.jumpTablePC.setPC(programCounter.getCurrent()); this.jumpTable.emitTable(this, this.sourceFile); }); this.emitBytecode(this.mutableProcessedByteCode); const bytecode = resolveJumps_1.resolveJumps(this.mutableBytecode); let pc = 0; const sourceMapGenerator = new source_map_1.SourceMapGenerator(); const addedFiles = new Set(); const mutableTagToLength = {}; const buffers = bytecode.map(([node, tags, value], idx) => { let finalValue; if (value instanceof pc_1.Jump) { let jumpPCBuffer = Buffer.alloc(2, 0); const offsetPC = new bn_js_1.BN(value.pc.getPC()).sub(new bn_js_1.BN(pc)); const jumpPC = offsetPC.toTwos(16); try { if (jumpPC.fromTwos(16).toNumber() !== value.pc.getPC() - pc) { throw new Error(`Something went wrong, expected 2's complement of ${value.pc.getPC() - pc}, found: ${jumpPC .fromTwos(16) .toNumber()}`); } jumpPCBuffer = jumpPC.toArrayLike(Buffer, 'le', 2); } catch (_a) { this.context.reportError(node, DiagnosticCode_1.DiagnosticCode.SomethingWentWrong, DiagnosticMessage_1.DiagnosticMessage.CompilationFailedPleaseReport); } const byteCodeBuffer = client_common_1.ByteBuffer[client_common_1.Op[value.op]]; if (byteCodeBuffer === undefined) { throw new Error('Something went wrong, could not find bytecode buffer'); } finalValue = Buffer.concat([byteCodeBuffer, jumpPCBuffer]); } else if (value instanceof pc_1.Line) { const currentLine = new bn_js_1.BN(idx + 1); const byteCodeBuffer = client_common_1.ByteBuffer[client_common_1.Op.PUSHBYTES4]; finalValue = Buffer.concat([byteCodeBuffer, currentLine.toArrayLike(Buffer, 'le', 4)]); } else { finalValue = value; } const sourceFile = ts_utils_1.tsUtils.node.getSourceFile(node); const filePath = ts_utils_1.tsUtils.file.getFilePath(sourceFile); const { line, character } = sourceFile.getLineAndCharacterOfPosition(node.getStart()); sourceMapGenerator.addMapping({ generated: { line: idx + 1, column: 0 }, original: { line: line + 1, column: character }, source: filePath, }); if (!addedFiles.has(filePath)) { addedFiles.add(filePath); sourceMapGenerator.setSourceContent(filePath, node.getSourceFile().getFullText()); } const tag = tags[0]; if (tag !== undefined) { const currentLength = mutableTagToLength[tag]; mutableTagToLength[tag] = currentLength === undefined ? finalValue.length : currentLength + finalValue.length; } pc += finalValue.length; return finalValue; }); const sourceMap = (async () => { await Promise.all(Object.entries(sourceMaps).map(async ([filePath, srcMap]) => { await source_map_1.SourceMapConsumer.with(srcMap, undefined, async (consumer) => { sourceMapGenerator.applySourceMap(consumer, filePath); }); })); return sourceMapGenerator.toJSON(); })(); return { code: Buffer.concat(buffers), sourceMap, features: this.mutableFeatures, }; } visit(node, options) { const compiler = this.compilers[node.kind]; if (compiler === undefined) { this.context.reportUnsupported(node); } else { compiler.visitNode(this, node, options); } } withScope(node, options, func) { let index = this.nodes.get(node); if (index === undefined) { index = 0; } else { index += 1; } this.nodes.set(node, index); const currentScope = this.mutableCurrentScope; this.mutableCurrentScope = this.createScope(node, index, currentScope); this.mutableCurrentScope.emit(this, node, options, func); this.mutableCurrentScope = currentScope; } withProgramCounter(func) { const pc = new pc_1.ProgramCounterHelper(() => this.mutablePC); func(pc); pc.setLast(); } emitOp(node, code, buffer) { if (((code === 'APPCALL' || code === 'TAILCALL') && buffer !== undefined && buffer.equals(Buffer.alloc(20, 0))) || code === 'CALL_ED') { this.mutableFeatures = Object.assign(Object.assign({}, this.mutableFeatures), { dynamicInvoke: true }); } const bytecode = client_common_1.Op[code]; if (bytecode === undefined) { throw new client_common_1.UnknownOpError(code); } this.emitOpByte(node, bytecode, buffer); } emitPushInt(node, valueIn) { const value = new bn_js_1.BN(valueIn); if (value.eq(client_common_1.utils.NEGATIVE_ONE)) { this.emitOp(node, 'PUSHM1'); } else if (value.eq(client_common_1.utils.ZERO)) { this.emitPush(node, client_common_1.utils.toSignedBuffer(value)); } else if (value.gt(client_common_1.utils.ZERO) && value.lt(client_common_1.utils.SIXTEEN)) { this.emitOpByte(node, client_common_1.Op.PUSH1 - 1 + value.toNumber()); } else { this.emitPush(node, client_common_1.utils.toSignedBuffer(value)); } } emitPushBoolean(node, value) { this.emitOp(node, value ? 'PUSH1' : 'PUSH0'); } emitPushString(node, value) { this.emitPush(node, this.toBuffer(value)); } emitPushBuffer(node, value) { this.emitPush(node, value); } emitJmp(node, code, pc) { this.emitJump(node, new pc_1.Jmp(code, pc)); } emitHelper(node, options, helper) { const prevTags = this.mutableCurrentTags; this.mutableCurrentTags = [helper.constructor.name]; helper.emit(this, node, options); this.mutableCurrentTags = prevTags; } emitBytecode(bytecode) { const pc = this.mutablePC; bytecode.forEach(([node, tags, code]) => { if (code instanceof pc_1.Call) { this.emitJump(node, code, tags); } else if (code instanceof pc_1.Jmp) { this.emitJump(node, code.plus(pc), tags); } else { if (code instanceof pc_1.Jump) { throw new Error('Something went wrong.'); } if (code instanceof pc_1.Line) { this.emitLineRaw(node, code, tags); } else { this.emitRaw(node, code, tags); } } }); } emitCall(node) { this.emitJump(node, new pc_1.Call(this.jumpTablePC)); } emitSysCall(node, name) { if (name === 'Neo.Storage.Put' || name === 'Neo.Storage.Delete') { this.mutableFeatures = Object.assign(Object.assign({}, this.mutableFeatures), { storage: true }); } const sysCallBuffer = Buffer.allocUnsafe(4); sysCallBuffer.writeUInt32LE(client_common_1.toSysCallHash(client_common_1.assertSysCall(name)), 0); const writer = new client_common_1.BinaryWriter(); writer.writeVarBytesLE(sysCallBuffer); this.emitOp(node, 'SYSCALL', writer.toBuffer()); } emitLine(node) { this.emitLineRaw(node, new pc_1.Line()); } isCurrentSmartContract(node) { if (this.contractInfo === undefined) { return false; } const symbol = this.context.analysis.getSymbol(node); if (symbol === undefined) { return false; } const symbols = this.context.analysis.getSymbolAndAllInheritedSymbols(this.contractInfo.smartContract); if (symbols.some((smartContractSymbol) => smartContractSymbol === symbol)) { return true; } const typeSymbol = this.context.analysis.getTypeSymbol(node); return typeSymbol !== undefined && symbols.some((smartContractSymbol) => smartContractSymbol === typeSymbol); } loadModule(sourceFile) { const options = {}; let moduleIndex = this.mutableModuleMap[ts_utils_1.tsUtils.file.getFilePath(sourceFile)]; if (moduleIndex === undefined) { moduleIndex = this.mutableNextModuleIndex; this.mutableNextModuleIndex += 1; this.mutableModuleMap[ts_utils_1.tsUtils.file.getFilePath(sourceFile)] = moduleIndex; this.mutableReverseModuleMap[moduleIndex] = ts_utils_1.tsUtils.file.getFilePath(sourceFile); const currentScope = this.mutableCurrentScope; this.mutableCurrentScope = this.createScope(sourceFile, 0, undefined); const currentModuleIndex = this.mutableCurrentModuleIndex; this.mutableCurrentModuleIndex = moduleIndex; this.scope.getGlobal(this, sourceFile, this.pushValueOptions(options)); this.emitOp(sourceFile, 'DUP'); this.emitHelper(sourceFile, this.pushValueOptions(options), this.helpers.addEmptyModule); this.mutableCurrentScope.emit(this, sourceFile, options, (innerOptions) => { this.scope.setGlobal(this, sourceFile, options); this.visit(sourceFile, innerOptions); }); this.mutableCurrentScope = currentScope; this.mutableCurrentModuleIndex = currentModuleIndex; } this.scope.getGlobal(this, sourceFile, this.pushValueOptions(options)); this.emitHelper(sourceFile, this.pushValueOptions(options), this.helpers.getModule({ moduleIndex })); } capture(func) { const originalCapturedBytecode = this.mutableCapturedBytecode; this.mutableCapturedBytecode = []; const originalPC = this.mutablePC; this.mutablePC = 0; func(); const capturedBytecode = this.mutableCapturedBytecode; this.mutableCapturedBytecode = originalCapturedBytecode; const capturedLength = this.mutablePC; this.mutablePC = originalPC; return { length: capturedLength, bytecode: capturedBytecode }; } getLinkedScriptHash(node, filePath, smartContractClass) { const reportError = () => { this.context.reportError(node, DiagnosticCode_1.DiagnosticCode.InvalidLinkedSmartContract, DiagnosticMessage_1.DiagnosticMessage.InvalidLinkedSmartContractMissing, smartContractClass); }; const fileLinked = this.linked[filePath]; if (fileLinked === undefined) { reportError(); return undefined; } const address = fileLinked[smartContractClass]; if (address === undefined) { reportError(); return undefined; } return client_common_1.crypto.addressToScriptHash({ addressVersion: client_common_1.common.NEO_ADDRESS_VERSION, address, }); } pushValueOptions(options) { return Object.assign(Object.assign({}, options), { pushValue: true }); } noPushValueOptions(options) { return Object.assign(Object.assign({}, options), { pushValue: false }); } setValueOptions(options) { return Object.assign(Object.assign({}, options), { setValue: true }); } noSetValueOptions(options) { return Object.assign(Object.assign({}, options), { setValue: false }); } noValueOptions(options) { return Object.assign(Object.assign({}, options), { pushValue: false, setValue: false }); } breakPCOptions(options, pc) { return Object.assign(Object.assign({}, options), { breakPC: pc }); } continuePCOptions(options, pc) { return Object.assign(Object.assign({}, options), { continuePC: pc }); } catchPCOptions(options, pc) { return Object.assign(Object.assign({}, options), { catchPC: pc }); } noCatchPCOptions(options) { return Object.assign(Object.assign({}, options), { catchPC: undefined }); } finallyPCOptions(options, pc) { return Object.assign(Object.assign({}, options), { finallyPC: pc }); } handleSuperConstructOptions(options, handleSuperConstruct) { return Object.assign(Object.assign({}, options), { handleSuperConstruct }); } castOptions(options, cast) { return Object.assign(Object.assign({}, options), { cast }); } noCastOptions(options) { return Object.assign(Object.assign({}, options), { cast: undefined }); } superClassOptions(options, superClass) { return Object.assign(Object.assign({}, options), { superClass }); } noSuperClassOptions(options) { return Object.assign(Object.assign({}, options), { superClass: undefined }); } hasExport(sourceFile, name) { const exported = this.mutableExportMap[ts_utils_1.tsUtils.file.getFilePath(sourceFile)]; return exported !== undefined && exported.has(name); } addExport(name) { const filePath = utils_1.utils.nullthrows(this.mutableReverseModuleMap[this.mutableCurrentModuleIndex]); let fileExports = this.mutableExportMap[filePath]; if (fileExports === undefined) { this.mutableExportMap[filePath] = fileExports = new Set(); } fileExports.add(name); } toBuffer(value) { return Buffer.from(value, 'utf8'); } emitPush(node, value) { if (value.length <= client_common_1.Op.PUSHBYTES75) { this.emitOpByte(node, value.length, value); } else if (value.length < 0x100) { this.emitOp(node, 'PUSHDATA1', new client_common_1.ScriptBuilder().emitUInt8(value.length).emit(value).build()); } else if (value.length < 0x10000) { this.emitOp(node, 'PUSHDATA2', new client_common_1.ScriptBuilder().emitUInt16LE(value.length).emit(value).build()); } else if (value.length < 0x100000000) { this.emitOp(node, 'PUSHDATA4', new client_common_1.ScriptBuilder().emitUInt32LE(value.length).emit(value).build()); } else { throw new Error('Value too large.'); } } emitOpByte(node, byteCode, buffer) { const byteCodeBuffer = client_common_1.ByteBuffer[byteCode]; let value = byteCodeBuffer; if (buffer !== undefined) { value = Buffer.concat([byteCodeBuffer, buffer]); } this.emitRaw(node, value); } emitRaw(node, value, tags = this.mutableCurrentTags) { this.push(node, tags, value); this.mutablePC += value.length; } emitJump(node, jump, tags = this.mutableCurrentTags) { this.push(node, tags, jump); this.mutablePC += 3; } emitLineRaw(node, line, tags = this.mutableCurrentTags) { this.push(node, tags, line); this.mutablePC += 5; } push(node, tags, value) { if (this.mutableCapturedBytecode !== undefined) { this.mutableCapturedBytecode.push([node, tags, value]); } else { this.mutableBytecode.push([node, tags, value]); } } } exports.BaseScriptBuilder = BaseScriptBuilder; //# sourceMappingURL=BaseScriptBuilder.js.map