1 | #!/usr/bin/env node
|
2 | "use strict";
|
3 | var __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 | }));
|
14 | var __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 | });
|
19 | var __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 | };
|
26 | var __importDefault = (this && this.__importDefault) || function (mod) {
|
27 | return (mod && mod.__esModule) ? mod : { "default": mod };
|
28 | };
|
29 | Object.defineProperty(exports, "__esModule", { value: true });
|
30 | const commander = __importStar(require("commander"));
|
31 | const fs = __importStar(require("fs"));
|
32 | const os = __importStar(require("os"));
|
33 | const path = __importStar(require("path"));
|
34 | const index_1 = __importDefault(require("./index"));
|
35 | const smtchecker_1 = __importDefault(require("./smtchecker"));
|
36 | const smtsolver_1 = __importDefault(require("./smtsolver"));
|
37 |
|
38 | const originalUncaughtExceptionListeners = process.listeners('uncaughtException');
|
39 |
|
40 |
|
41 | process.removeAllListeners('uncaughtException');
|
42 | const program = new commander.Command();
|
43 | const 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 | };
|
50 | program.name('solcjs');
|
51 | program.version(index_1.default.version());
|
52 | program
|
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);
|
68 | program.parse(process.argv);
|
69 | const options = program.opts();
|
70 | const files = program.args;
|
71 | const destination = options.outputDir || '.';
|
72 | function abort(msg) {
|
73 | console.error(msg || 'Error occurred');
|
74 | process.exit(1);
|
75 | }
|
76 | function 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 | }
|
91 | function withUnixPathSeparators(filePath) {
|
92 |
|
93 | if (os.platform() !== 'win32') {
|
94 | return filePath;
|
95 | }
|
96 | return filePath.replace(/\\/g, '/');
|
97 | }
|
98 | function 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 |
|
104 |
|
105 |
|
106 |
|
107 |
|
108 |
|
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 |
|
117 | return withUnixPathSeparators(absoluteSourcePath);
|
118 | }
|
119 | function toFormattedJson(input) {
|
120 | return JSON.stringify(input, null, program.prettyJson ? 4 : 0);
|
121 | }
|
122 | function reformatJsonIfRequested(inputJson) {
|
123 | return (program.prettyJson ? toFormattedJson(JSON.parse(inputJson)) : inputJson);
|
124 | }
|
125 | let callbacks;
|
126 | if (options.basePath || !options.standardJson) {
|
127 | callbacks = { import: readFileCallback };
|
128 | }
|
129 | if (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 | }
|
169 | else if (files.length === 0) {
|
170 | console.error('Must provide a file');
|
171 | process.exit(1);
|
172 | }
|
173 | if (!(options.bin || options.abi)) {
|
174 | abort('Invalid option selected, must specify either --bin or --abi');
|
175 | }
|
176 | if (!options.basePath && options.includePath && options.includePath.length > 0) {
|
177 | abort('--include-path option requires a non-empty base path.');
|
178 | }
|
179 | if (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 | }
|
186 | const sources = {};
|
187 | for (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 | }
|
197 | const 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 | };
|
212 | if (program.verbose) {
|
213 | console.log('>>> Compiling:\n' + toFormattedJson(cliInput) + '\n');
|
214 | }
|
215 | const output = JSON.parse(index_1.default.compile(JSON.stringify(cliInput), callbacks));
|
216 | let hasError = false;
|
217 | if (!output) {
|
218 | abort('No output from compiler');
|
219 | }
|
220 | else 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 | }
|
232 | fs.mkdirSync(destination, { recursive: true });
|
233 | function 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 | }
|
241 | for (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 |
|
254 | originalUncaughtExceptionListeners.forEach(function (listener) {
|
255 | process.addListener('uncaughtException', listener);
|
256 | });
|
257 | if (hasError) {
|
258 | process.exit(1);
|
259 | }
|