UNPKG

10.9 kBJavaScriptView Raw
1#!/usr/bin/env node
2"use strict";
3var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
4 if (k2 === undefined) k2 = k;
5 var desc = Object.getOwnPropertyDescriptor(m, k);
6 if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
7 desc = { enumerable: true, get: function() { return m[k]; } };
8 }
9 Object.defineProperty(o, k2, desc);
10}) : (function(o, m, k, k2) {
11 if (k2 === undefined) k2 = k;
12 o[k2] = m[k];
13}));
14var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
15 Object.defineProperty(o, "default", { enumerable: true, value: v });
16}) : function(o, v) {
17 o["default"] = v;
18});
19var __importStar = (this && this.__importStar) || function (mod) {
20 if (mod && mod.__esModule) return mod;
21 var result = {};
22 if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
23 __setModuleDefault(result, mod);
24 return result;
25};
26var __importDefault = (this && this.__importDefault) || function (mod) {
27 return (mod && mod.__esModule) ? mod : { "default": mod };
28};
29Object.defineProperty(exports, "__esModule", { value: true });
30const commander = __importStar(require("commander"));
31const fs = __importStar(require("fs"));
32const os = __importStar(require("os"));
33const path = __importStar(require("path"));
34const index_1 = __importDefault(require("./index"));
35const smtchecker_1 = __importDefault(require("./smtchecker"));
36const smtsolver_1 = __importDefault(require("./smtsolver"));
37// hold on to any exception handlers that existed prior to this script running, we'll be adding them back at the end
38const originalUncaughtExceptionListeners = process.listeners('uncaughtException');
39// FIXME: remove annoying exception catcher of Emscripten
40// see https://github.com/chriseth/browser-solidity/issues/167
41process.removeAllListeners('uncaughtException');
42const program = new commander.Command();
43const commanderParseInt = function (value) {
44 const parsedValue = parseInt(value, 10);
45 if (isNaN(parsedValue)) {
46 throw new commander.InvalidArgumentError('Not a valid integer.');
47 }
48 return parsedValue;
49};
50program.name('solcjs');
51program.version(index_1.default.version());
52program
53 .option('--version', 'Show version and exit.')
54 .option('--optimize', 'Enable bytecode optimizer.', false)
55 .option('--optimize-runs <optimize-runs>', 'The number of runs specifies roughly how often each opcode of the deployed code will be executed across the lifetime of the contract. ' +
56 'Lower values will optimize more for initial deployment cost, higher values will optimize more for high-frequency usage.', commanderParseInt)
57 .option('--bin', 'Binary of the contracts in hex.')
58 .option('--abi', 'ABI of the contracts.')
59 .option('--standard-json', 'Turn on Standard JSON Input / Output mode.')
60 .option('--base-path <path>', 'Root of the project source tree. ' +
61 'The import callback will attempt to interpret all import paths as relative to this directory.')
62 .option('--include-path <path...>', 'Extra source directories available to the import callback. ' +
63 'When using a package manager to install libraries, use this option to specify directories where packages are installed. ' +
64 'Can be used multiple times to provide multiple locations.')
65 .option('-o, --output-dir <output-directory>', 'Output directory for the contracts.')
66 .option('-p, --pretty-json', 'Pretty-print all JSON output.', false)
67 .option('-v, --verbose', 'More detailed console output.', false);
68program.parse(process.argv);
69const options = program.opts();
70const files = program.args;
71const destination = options.outputDir || '.';
72function abort(msg) {
73 console.error(msg || 'Error occurred');
74 process.exit(1);
75}
76function readFileCallback(sourcePath) {
77 const prefixes = [options.basePath ? options.basePath : ''].concat(options.includePath ? options.includePath : []);
78 for (const prefix of prefixes) {
79 const prefixedSourcePath = (prefix ? prefix + '/' : '') + sourcePath;
80 if (fs.existsSync(prefixedSourcePath)) {
81 try {
82 return { contents: fs.readFileSync(prefixedSourcePath).toString('utf8') };
83 }
84 catch (e) {
85 return { error: 'Error reading ' + prefixedSourcePath + ': ' + e };
86 }
87 }
88 }
89 return { error: 'File not found inside the base path or any of the include paths.' };
90}
91function withUnixPathSeparators(filePath) {
92 // On UNIX-like systems forward slashes in paths are just a part of the file name.
93 if (os.platform() !== 'win32') {
94 return filePath;
95 }
96 return filePath.replace(/\\/g, '/');
97}
98function makeSourcePathRelativeIfPossible(sourcePath) {
99 const absoluteBasePath = (options.basePath ? path.resolve(options.basePath) : path.resolve('.'));
100 const absoluteIncludePaths = (options.includePath
101 ? options.includePath.map((prefix) => { return path.resolve(prefix); })
102 : []);
103 // Compared to base path stripping logic in solc this is much simpler because path.resolve()
104 // handles symlinks correctly (does not resolve them except in work dir) and strips .. segments
105 // from paths going beyond root (e.g. `/../../a/b/c` -> `/a/b/c/`). It's simpler also because it
106 // ignores less important corner cases: drive letters are not stripped from absolute paths on
107 // Windows and UNC paths are not handled in a special way (at least on Linux). Finally, it has
108 // very little test coverage so there might be more differences that we are just not aware of.
109 const absoluteSourcePath = path.resolve(sourcePath);
110 for (const absolutePrefix of [absoluteBasePath].concat(absoluteIncludePaths)) {
111 const relativeSourcePath = path.relative(absolutePrefix, absoluteSourcePath);
112 if (!relativeSourcePath.startsWith('../')) {
113 return withUnixPathSeparators(relativeSourcePath);
114 }
115 }
116 // File is not located inside base path or include paths so use its absolute path.
117 return withUnixPathSeparators(absoluteSourcePath);
118}
119function toFormattedJson(input) {
120 return JSON.stringify(input, null, program.prettyJson ? 4 : 0);
121}
122function reformatJsonIfRequested(inputJson) {
123 return (program.prettyJson ? toFormattedJson(JSON.parse(inputJson)) : inputJson);
124}
125let callbacks;
126if (options.basePath || !options.standardJson) {
127 callbacks = { import: readFileCallback };
128}
129if (options.standardJson) {
130 const input = fs.readFileSync(process.stdin.fd).toString('utf8');
131 if (program.verbose) {
132 console.log('>>> Compiling:\n' + reformatJsonIfRequested(input) + '\n');
133 }
134 let output = reformatJsonIfRequested(index_1.default.compile(input, callbacks));
135 try {
136 if (smtsolver_1.default.availableSolvers.length === 0) {
137 console.log('>>> Cannot retry compilation with SMT because there are no SMT solvers available.');
138 }
139 else {
140 const inputJSON = smtchecker_1.default.handleSMTQueries(JSON.parse(input), JSON.parse(output), smtsolver_1.default.smtSolver, smtsolver_1.default.availableSolvers[0]);
141 if (inputJSON) {
142 if (program.verbose) {
143 console.log('>>> Retrying compilation with SMT:\n' + toFormattedJson(inputJSON) + '\n');
144 }
145 output = reformatJsonIfRequested(index_1.default.compile(JSON.stringify(inputJSON), callbacks));
146 }
147 }
148 }
149 catch (e) {
150 const addError = {
151 component: 'general',
152 formattedMessage: e.toString(),
153 message: e.toString(),
154 type: 'Warning'
155 };
156 const outputJSON = JSON.parse(output);
157 if (!outputJSON.errors) {
158 outputJSON.errors = [];
159 }
160 outputJSON.errors.push(addError);
161 output = toFormattedJson(outputJSON);
162 }
163 if (program.verbose) {
164 console.log('>>> Compilation result:');
165 }
166 console.log(output);
167 process.exit(0);
168}
169else if (files.length === 0) {
170 console.error('Must provide a file');
171 process.exit(1);
172}
173if (!(options.bin || options.abi)) {
174 abort('Invalid option selected, must specify either --bin or --abi');
175}
176if (!options.basePath && options.includePath && options.includePath.length > 0) {
177 abort('--include-path option requires a non-empty base path.');
178}
179if (options.includePath) {
180 for (const includePath of options.includePath) {
181 if (!includePath) {
182 abort('Empty values are not allowed in --include-path.');
183 }
184 }
185}
186const sources = {};
187for (let i = 0; i < files.length; i++) {
188 try {
189 sources[makeSourcePathRelativeIfPossible(files[i])] = {
190 content: fs.readFileSync(files[i]).toString()
191 };
192 }
193 catch (e) {
194 abort('Error reading ' + files[i] + ': ' + e);
195 }
196}
197const cliInput = {
198 language: 'Solidity',
199 settings: {
200 optimizer: {
201 enabled: options.optimize,
202 runs: options.optimizeRuns
203 },
204 outputSelection: {
205 '*': {
206 '*': ['abi', 'evm.bytecode']
207 }
208 }
209 },
210 sources: sources
211};
212if (program.verbose) {
213 console.log('>>> Compiling:\n' + toFormattedJson(cliInput) + '\n');
214}
215const output = JSON.parse(index_1.default.compile(JSON.stringify(cliInput), callbacks));
216let hasError = false;
217if (!output) {
218 abort('No output from compiler');
219}
220else if (output.errors) {
221 for (const error in output.errors) {
222 const message = output.errors[error];
223 if (message.severity === 'warning') {
224 console.log(message.formattedMessage);
225 }
226 else {
227 console.error(message.formattedMessage);
228 hasError = true;
229 }
230 }
231}
232fs.mkdirSync(destination, { recursive: true });
233function writeFile(file, content) {
234 file = path.join(destination, file);
235 fs.writeFile(file, content, function (err) {
236 if (err) {
237 console.error('Failed to write ' + file + ': ' + err);
238 }
239 });
240}
241for (const fileName in output.contracts) {
242 for (const contractName in output.contracts[fileName]) {
243 let contractFileName = fileName + ':' + contractName;
244 contractFileName = contractFileName.replace(/[:./\\]/g, '_');
245 if (options.bin) {
246 writeFile(contractFileName + '.bin', output.contracts[fileName][contractName].evm.bytecode.object);
247 }
248 if (options.abi) {
249 writeFile(contractFileName + '.abi', toFormattedJson(output.contracts[fileName][contractName].abi));
250 }
251 }
252}
253// Put back original exception handlers.
254originalUncaughtExceptionListeners.forEach(function (listener) {
255 process.addListener('uncaughtException', listener);
256});
257if (hasError) {
258 process.exit(1);
259}