From 1e59b58887bc36b9c0e86259174e95302c1f9161 Mon Sep 17 00:00:00 2001 From: Jonathan Rainville Date: Fri, 28 Jun 2019 14:02:27 -0400 Subject: [PATCH] feat(@embark/solc): add embark-solc to monorepo --- packages/embark-solc/.npmrc | 5 + packages/embark-solc/README.md | 29 +++ packages/embark-solc/package.json | 68 +++++++ packages/embark-solc/src/index.js | 28 +++ packages/embark-solc/src/lib/Compiler.js | 221 +++++++++++++++++++++++ packages/embark-solc/tsconfig.json | 4 + packages/embark-solc/tslint.json | 3 + yarn.lock | 2 +- 8 files changed, 359 insertions(+), 1 deletion(-) create mode 100644 packages/embark-solc/.npmrc create mode 100644 packages/embark-solc/README.md create mode 100644 packages/embark-solc/package.json create mode 100644 packages/embark-solc/src/index.js create mode 100644 packages/embark-solc/src/lib/Compiler.js create mode 100644 packages/embark-solc/tsconfig.json create mode 100644 packages/embark-solc/tslint.json diff --git a/packages/embark-solc/.npmrc b/packages/embark-solc/.npmrc new file mode 100644 index 000000000..6cc4313d2 --- /dev/null +++ b/packages/embark-solc/.npmrc @@ -0,0 +1,5 @@ +engine-strict = true +package-lock = false +save-exact = true +scripts-prepend-node-path = true + diff --git a/packages/embark-solc/README.md b/packages/embark-solc/README.md new file mode 100644 index 000000000..001fb8f92 --- /dev/null +++ b/packages/embark-solc/README.md @@ -0,0 +1,29 @@ +Embark-Solc +====== + +Plugin for [Embark](https://github.com/embark-framework/embark) to compile contracts using solc + +## Installation + + +In your embark dapp directory: + +```npm install embark-solc --save``` + +then add embark-solc to the plugins section in `embark.json`: + +```Json + "plugins": { + "embark-solc": { + "outputBinary": false + } + } +``` + +- `outputBinary` can be specified to generate a .bin file that contains the binary of the contracts in hex. Default value is `false`. + +## Requirements + +- [Embark](https://www.npmjs.com/package/embark) 4.0.0 or higher +- [Solc](https://github.com/ethereum/solidity/releases) installed and available globally on your machine (h) + diff --git a/packages/embark-solc/package.json b/packages/embark-solc/package.json new file mode 100644 index 000000000..79be83d0d --- /dev/null +++ b/packages/embark-solc/package.json @@ -0,0 +1,68 @@ +{ + "name": "embark-solc", + "version": "4.1.0-beta.4", + "author": "Richard Ramos", + "contributors": [], + "description": "Solc plugin for Embark (uses command line)", + "homepage": "https://github.com/embark-framework/embark/tree/master/packages/embark-solc#readme", + "bugs": "https://github.com/embark-framework/embark/issues", + "keywords": [ + "blockchain", + "dapps", + "ethereum", + "ipfs", + "serverless", + "solc", + "solidity" + ], + "files": [ + "dist" + ], + "license": "MIT", + "repository": { + "directory": "packages/embark-solc", + "type": "git", + "url": "https://github.com/embark-framework/embark.git" + }, + "main": "./dist/index.js", + "scripts": { + "build": "cross-env BABEL_ENV=node babel src --copy-files --extensions \".ts\" --out-dir dist --root-mode upward --source-maps", + "ci": "npm run qa", + "clean": "npm run reset", + "lint": "npm-run-all lint:*", + "lint:js": "eslint src/", + "// lint:ts": "tslint -c tslint.json \"src/**/*.ts\"", + "package": "npm pack", + "// qa": "npm-run-all lint typecheck build package", + "qa": "npm-run-all lint build package", + "reset": "npx rimraf dist embark-*.tgz package", + "start": "npm run watch", + "// typecheck": "tsc", + "watch": "run-p watch:*", + "watch:build": "npm run build -- --verbose --watch", + "// watch:typecheck": "npm run typecheck -- --preserveWatchOutput --watch" + }, + "eslintConfig": { + "extends": "../../.eslintrc.json" + }, + "dependencies": { + "async": "^2.6.0", + "semver": "^5.6.0", + "shelljs": "^0.8.1" + }, + "devDependencies": { + "@babel/cli": "7.2.3", + "@babel/core": "7.2.2", + "cross-env": "5.2.0", + "eslint": "5.7.0", + "npm-run-all": "4.1.5", + "rimraf": "2.6.3", + "tslint": "5.16.0", + "typescript": "3.4.5" + }, + "engines": { + "node": ">=8.12.0 <12.0.0", + "npm": ">=6.4.1", + "yarn": ">=1.12.3" + } +} diff --git a/packages/embark-solc/src/index.js b/packages/embark-solc/src/index.js new file mode 100644 index 000000000..f7f978499 --- /dev/null +++ b/packages/embark-solc/src/index.js @@ -0,0 +1,28 @@ +/*global require, module*/ +const Compiler = require("./lib/Compiler"); +const semver = require('semver'); + +module.exports = (embark) => { + if (embark.config.embarkConfig.versions.solc) { + embark.registerCompiler('.sol', (contractFiles, options, cb) => { + if (!contractFiles || !contractFiles.length) { + return cb(); + } + Compiler.getSolcVersion(embark.logger, (err, version) => { + if (err) { + embark.logger.error(err); + embark.logger.error("Error getting solc's version. Will default back to Embark's compiler"); + return cb(null, false); + } + if (semver.lt(version, embark.config.embarkConfig.versions.solc)) { + embark.logger.warn(`Current version of solc lower than version in embark.json`); + embark.logger.warn(`Current: ${version} | Wanted: ${embark.config.embarkConfig.versions.solc}`); + embark.logger.warn('Will default back to Embark\'s compiler'); + return cb(null, false); + } + Compiler.compileSolc(embark, contractFiles, embark.config.contractDirectories, options, cb); + }); + }); + + } +}; diff --git a/packages/embark-solc/src/lib/Compiler.js b/packages/embark-solc/src/lib/Compiler.js new file mode 100644 index 000000000..7d2a7ee44 --- /dev/null +++ b/packages/embark-solc/src/lib/Compiler.js @@ -0,0 +1,221 @@ +const async = require('async'); +const shelljs = require('shelljs'); +const fs = require('fs'); +const path = require('path'); + +function compileSolcContract(logger, compileSettings, allowedDirectories, callback) { + const command = `solc --standard-json --allow-paths ${allowedDirectories.join(',')}`; + + shelljs.ShellString(JSON.stringify(compileSettings)).exec(command, {silent: true}, (code, stdout, stderr) => { + if (stderr) { + logger.warn(stderr); + } + + if (code !== 0) { + return callback(`solc exited with error code ${code}`); + } + + if (!stdout) { + return callback('solc execution returned nothing'); + } + + callback(null, stdout.replace(/\n/g, '')); + }); +} + +function getSolcVersion(logger, callback) { + shelljs.exec('solc --version', {silent: true}, (code, stdout, stderr) => { + if (stderr) { + logger.warn(stderr); + } + + if (code !== 0) { + return callback(`solc exited with error code ${code}`); + } + + if (!stdout) { + return callback('solc execution returned nothing'); + } + + const result = stdout.match(/(\d+.\d+.\d+)/); + callback(null, result[1]); + }); +} + +function compileSolc(embark, contractFiles, contractDirectories, options, callback) { + if (!contractFiles || !contractFiles.length) { + return callback(); + } + + const logger = embark.logger; + const outputBinary = embark.pluginConfig.outputBinary; + const outputDir = embark.config.buildDir + embark.config.contractDirectories[0]; + const solcConfig = embark.config.embarkConfig.options.solc; + + let allowedDirectories = []; + const remappings = []; + const compilationSettings = { + language: 'Solidity', + sources: {}, + settings: { + optimizer: { + enabled: solcConfig['optimize'], + runs: solcConfig['optimize-runs'] + }, + remappings, + outputSelection: { + '*': { + '': ['ast'], + '*': [ + 'abi', + 'devdoc', + 'evm.bytecode', + 'evm.deployedBytecode', + 'evm.gasEstimates', + 'evm.legacyAssembly', + 'evm.methodIdentifiers', + 'metadata', + 'userdoc' + ] + } + } + } + }; + + async.waterfall([ + function checkSolc(next) { + const solc = shelljs.which('solc'); + if (!solc) { + logger.error('solc is not installed on your machine'); + logger.info('You can install it by following the instructions on: http://solidity.readthedocs.io/en/latest/installing-solidity.html'); + return next('Compiler not installed'); + } + logger.info("compiling solidity contracts with command line solc..."); + next(); + }, + + function getContentAndRemappings(next) { + async.each(contractFiles, (file, eachCb) => { + file.prepareForCompilation(options.isCoverage).then((content) => { + // add contract directory and all it's recusrive import direcotries to allowed directories + let dir = path.dirname(file.path); + if (!allowedDirectories.includes(dir)) allowedDirectories.push(dir); + + file.importRemappings.forEach((importRemapping) => { + dir = path.dirname(importRemapping.target); + if (!allowedDirectories.includes(dir)) allowedDirectories.push(dir); + + const remapping = `${importRemapping.prefix}=${importRemapping.target}`; + if (!remappings.includes(remapping)) { + remappings.push(remapping); + } + }); + + // TODO change this to Embark's utils function once embark-solc is in the mono-repo + compilationSettings.sources[file.path.replace(/\\/g, '/')] = { + content: content.replace(/\r\n/g, '\n') + }; + + eachCb(); + }).catch(eachCb); + }, next); + }, + + function compile(next) { + compileSolcContract(logger, compilationSettings, allowedDirectories, (err, compileString) => { + if (err) { + return next(err); + } + let json; + try { + json = JSON.parse(compileString); + } catch (e) { + logger.error(e.message || e); + return callback(`Compiling returned an unreadable result`); + } + + embark.events.emit('contracts:compiled:solc', json); + + const contracts = json.contracts; + + // Check for errors + if (json.errors) { + let isError = false; + json.errors.forEach(error => { + if (error.severity === 'error') { + isError = true; + logger.error(error.formattedMessage); + } else { + logger.warn(error.formattedMessage); + } + logger.debug(error.message); // Print more error information in debug + }); + if (isError) { + return next(`Error while compiling`); + } + } + next(null, contracts); + }); + }, + + function populateCompiledObject(contracts, next) { + const compiledObject = {}; + for (let contractFile in contracts) { + for (let contractName in contracts[contractFile]) { + let contract = contracts[contractFile][contractName]; + let filename = contractFile; + for (let directory of contractDirectories) { + let match = new RegExp("^" + directory); + filename = filename.replace(match, ''); + } + + let className = contractName; + const contractFileMatch = className.match(/.sol:(.*)/); + if (contractFileMatch) { + className = contractFileMatch[1]; + } + + compiledObject[className] = {}; + compiledObject[className].code = contract.evm.bytecode.object; + compiledObject[className].linkReferences = contract.evm.bytecode.linkReferences; + compiledObject[className].runtimeBytecode = contract.evm.deployedBytecode.object; + compiledObject[className].realRuntimeBytecode = contract.evm.deployedBytecode.object.slice(0, -68); + compiledObject[className].swarmHash = contract.evm.deployedBytecode.object.slice(-68).slice(0, 64); + compiledObject[className].gasEstimates = contract.evm.gasEstimates; + compiledObject[className].functionHashes = contract.evm.methodIdentifiers; + compiledObject[className].abiDefinition = contract.abi; + compiledObject[className].userdoc = contract.userdoc; + compiledObject[className].filename = filename; + const normalized = path.normalize(filename); + const origContract = contractFiles.find(contractFile => normalized.includes(path.normalize(contractFile.originalPath))); + if (origContract) { + compiledObject[className].originalFilename = path.normalize(origContract.originalPath); + } + } + } + + next(null, compiledObject); + } + + ], (err, compiledObject) => { + callback(err, compiledObject); + + if (outputBinary) { + embark.events.once("outputDone", () => { + async.eachOf(compiledObject, (contract, className, eachCb) => { + fs.writeFile(path.join(outputDir, className + ".bin"), compiledObject[className].code, eachCb); + }, (err) => { + if (err) { + logger.error("Error writing binary file", err.message || err); + } + }); + }); + } + }); +} + +module.exports = { + compileSolc, + compileSolcContract, + getSolcVersion +}; diff --git a/packages/embark-solc/tsconfig.json b/packages/embark-solc/tsconfig.json new file mode 100644 index 000000000..52d43eaaa --- /dev/null +++ b/packages/embark-solc/tsconfig.json @@ -0,0 +1,4 @@ +{ + "extends": "../../tsconfig.json", + "include": ["src/**/*"] +} diff --git a/packages/embark-solc/tslint.json b/packages/embark-solc/tslint.json new file mode 100644 index 000000000..0946f2096 --- /dev/null +++ b/packages/embark-solc/tslint.json @@ -0,0 +1,3 @@ +{ + "extends": "../../tslint.json" +} diff --git a/yarn.lock b/yarn.lock index 37e5f1f5e..08ea0dbff 100644 --- a/yarn.lock +++ b/yarn.lock @@ -16698,7 +16698,7 @@ shelljs@0.5.3: resolved "https://registry.yarnpkg.com/shelljs/-/shelljs-0.5.3.tgz#c54982b996c76ef0c1e6b59fbdc5825f5b713113" integrity sha1-xUmCuZbHbvDB5rWfvcWCX1txMRM= -shelljs@^0.8.2: +shelljs@^0.8.1, shelljs@^0.8.2: version "0.8.3" resolved "https://registry.yarnpkg.com/shelljs/-/shelljs-0.8.3.tgz#a7f3319520ebf09ee81275b2368adb286659b097" integrity sha512-fc0BKlAWiLpwZljmOvAOTE/gXawtCoNrP5oaY7KIaQbbyHeQVg01pSEuEGvGh3HEdBU4baCD7wQBwADmM/7f7A==