2020-02-07 07:04:18 +04:00
|
|
|
import fs from 'fs';
|
|
|
|
import path from 'path';
|
|
|
|
import tmp from 'tmp';
|
|
|
|
|
|
|
|
import { logger } from './logger';
|
|
|
|
|
|
|
|
const { spawn } = require('child_process');
|
|
|
|
const dataDir = process.env['DATA_DIR'] || path.join(__dirname, 'tmp');
|
|
|
|
|
|
|
|
const JOB_TIMEOUT = 10000;
|
|
|
|
|
|
|
|
export class CompilerError extends Error {
|
|
|
|
constructor(message: string) {
|
|
|
|
super(message);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
export class LigoCompiler {
|
|
|
|
private ligoCmd = process.env['LIGO_CMD'] || [
|
|
|
|
'docker',
|
|
|
|
'run',
|
|
|
|
'-t',
|
|
|
|
'--rm',
|
|
|
|
'-v',
|
|
|
|
`${dataDir}:${dataDir}`,
|
|
|
|
'-w',
|
|
|
|
dataDir,
|
|
|
|
'ligolang/ligo:next'
|
|
|
|
];
|
|
|
|
|
|
|
|
private execPromise(cmd: string | string[], args: string[]): Promise<string> {
|
|
|
|
let command: string[] = [];
|
|
|
|
if (Array.isArray(cmd)) {
|
|
|
|
command = cmd;
|
|
|
|
} else {
|
|
|
|
command = cmd.split(' ');
|
|
|
|
}
|
|
|
|
|
|
|
|
let program = command[0];
|
|
|
|
const argument = [...command.slice(1), ...args];
|
|
|
|
|
|
|
|
return new Promise((resolve, reject) => {
|
|
|
|
try {
|
|
|
|
const result = spawn(program, argument, { shell: false, cwd: dataDir });
|
|
|
|
let finalResult = '';
|
|
|
|
let finalError = '';
|
|
|
|
|
|
|
|
result.stdout.on('data', (data: Buffer) => {
|
|
|
|
finalResult += data.toString();
|
|
|
|
});
|
|
|
|
|
|
|
|
result.stderr.on('data', (data: Buffer) => {
|
|
|
|
finalError += data.toString();
|
|
|
|
});
|
|
|
|
|
|
|
|
result.on('close', (code: any) => {
|
|
|
|
if (code === 0) {
|
|
|
|
resolve(finalResult);
|
|
|
|
} else {
|
|
|
|
reject(new CompilerError(finalError));
|
|
|
|
}
|
|
|
|
});
|
|
|
|
} catch (ex) {
|
|
|
|
logger.error(`Unexpected compiler error ${ex}`);
|
|
|
|
reject(ex);
|
|
|
|
}
|
|
|
|
|
|
|
|
setTimeout(() => {
|
|
|
|
reject(new Error(`command: ${cmd} Timed out after ${JOB_TIMEOUT} ms`));
|
|
|
|
}, JOB_TIMEOUT);
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
private createTemporaryFile(fileContent: string) {
|
|
|
|
return new Promise<{ name: string; remove: () => void }>(
|
|
|
|
(resolve, reject) => {
|
|
|
|
tmp.file(
|
|
|
|
{ dir: dataDir, postfix: '.ligo' },
|
|
|
|
(err, name, fd, remove) => {
|
|
|
|
if (err) {
|
|
|
|
reject(err);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
fs.write(fd, Buffer.from(fileContent), err => {
|
|
|
|
if (err) {
|
|
|
|
reject(err);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
resolve({
|
|
|
|
name,
|
|
|
|
remove: () => {
|
|
|
|
try {
|
|
|
|
remove();
|
|
|
|
} catch (ex) {
|
|
|
|
logger.error(`Unable to remove file ${name}`);
|
|
|
|
}
|
|
|
|
const ppFile = name.replace('.ligo', '.pp.ligo');
|
|
|
|
try {
|
|
|
|
if (fs.existsSync(ppFile)) {
|
|
|
|
fs.unlinkSync(ppFile);
|
|
|
|
}
|
|
|
|
} catch (ex) {
|
|
|
|
logger.error(`Unable to remove file ${ppFile}`);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
});
|
|
|
|
});
|
|
|
|
}
|
|
|
|
);
|
|
|
|
}
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
async compileContract(
|
|
|
|
syntax: string,
|
|
|
|
code: string,
|
|
|
|
entrypoint: string,
|
|
|
|
format: string
|
|
|
|
) {
|
|
|
|
const { name, remove } = await this.createTemporaryFile(code);
|
2020-02-28 02:20:23 +04:00
|
|
|
|
2020-02-07 07:04:18 +04:00
|
|
|
try {
|
|
|
|
const result = await this.execPromise(this.ligoCmd, [
|
|
|
|
'compile-contract',
|
|
|
|
'--michelson-format',
|
|
|
|
format,
|
|
|
|
'-s',
|
|
|
|
syntax,
|
|
|
|
name,
|
|
|
|
entrypoint
|
|
|
|
]);
|
|
|
|
return result;
|
|
|
|
} finally {
|
|
|
|
remove();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
async compileExpression(syntax: string, expression: string, format: string) {
|
|
|
|
const result = await this.execPromise(this.ligoCmd, [
|
|
|
|
'compile-expression',
|
|
|
|
'--michelson-format',
|
|
|
|
format,
|
|
|
|
syntax,
|
|
|
|
expression
|
|
|
|
]);
|
|
|
|
|
|
|
|
return result;
|
|
|
|
}
|
|
|
|
|
2020-02-28 02:20:23 +04:00
|
|
|
async compileStorage(
|
|
|
|
syntax: string,
|
|
|
|
code: string,
|
|
|
|
entrypoint: string,
|
|
|
|
format: string,
|
|
|
|
storage: string
|
|
|
|
) {
|
|
|
|
const { name, remove } = await this.createTemporaryFile(code);
|
|
|
|
|
|
|
|
try {
|
|
|
|
const result = await this.execPromise(this.ligoCmd, [
|
|
|
|
'compile-storage',
|
|
|
|
'--michelson-format',
|
|
|
|
format,
|
|
|
|
'-s',
|
|
|
|
syntax,
|
|
|
|
name,
|
|
|
|
entrypoint,
|
|
|
|
storage
|
|
|
|
]);
|
|
|
|
|
|
|
|
return result;
|
|
|
|
} finally {
|
|
|
|
remove();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-02-07 07:04:18 +04:00
|
|
|
async dryRun(
|
|
|
|
syntax: string,
|
|
|
|
code: string,
|
|
|
|
entrypoint: string,
|
|
|
|
parameter: string,
|
|
|
|
storage: string
|
|
|
|
) {
|
|
|
|
const { name, remove } = await this.createTemporaryFile(code);
|
|
|
|
try {
|
|
|
|
const result = await this.execPromise(this.ligoCmd, [
|
|
|
|
'dry-run',
|
|
|
|
'-s',
|
|
|
|
syntax,
|
|
|
|
name,
|
|
|
|
entrypoint,
|
|
|
|
parameter,
|
|
|
|
storage
|
|
|
|
]);
|
|
|
|
return result;
|
|
|
|
} finally {
|
|
|
|
remove();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
async evaluateValue(syntax: string, code: string, entrypoint: string) {
|
|
|
|
const { name, remove } = await this.createTemporaryFile(code);
|
|
|
|
try {
|
|
|
|
const result = await this.execPromise(this.ligoCmd, [
|
|
|
|
'evaluate-value',
|
|
|
|
'-s',
|
|
|
|
syntax,
|
|
|
|
name,
|
|
|
|
entrypoint
|
|
|
|
]);
|
|
|
|
return result;
|
|
|
|
} finally {
|
|
|
|
remove();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
async runFunction(
|
|
|
|
syntax: string,
|
|
|
|
code: string,
|
|
|
|
entrypoint: string,
|
|
|
|
parameter: string
|
|
|
|
) {
|
|
|
|
const { name, remove } = await this.createTemporaryFile(code);
|
|
|
|
try {
|
|
|
|
const result = await this.execPromise(this.ligoCmd, [
|
|
|
|
'run-function',
|
|
|
|
'-s',
|
|
|
|
syntax,
|
|
|
|
name,
|
|
|
|
entrypoint,
|
|
|
|
parameter
|
|
|
|
]);
|
|
|
|
return result;
|
|
|
|
} finally {
|
|
|
|
remove();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|