From 7d8c7b119c65d4746c18b4ffebc14e7e6557986c Mon Sep 17 00:00:00 2001 From: Jonathan Rainville Date: Wed, 11 Apr 2018 09:47:55 -0400 Subject: [PATCH 1/5] add warning after compile for files without a compatible compiler --- lib/contracts/compiler.js | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-) diff --git a/lib/contracts/compiler.js b/lib/contracts/compiler.js index c9a7ffa43..fa474e523 100644 --- a/lib/contracts/compiler.js +++ b/lib/contracts/compiler.js @@ -7,9 +7,10 @@ class Compiler { } compile_contracts(contractFiles, cb) { + const self = this; let available_compilers = {}; - let pluginCompilers = this.plugins.getPluginsProperty('compilers', 'compilers'); + let pluginCompilers = self.plugins.getPluginsProperty('compilers', 'compilers'); pluginCompilers.forEach(function (compilerObject) { available_compilers[compilerObject.extension] = compilerObject.cb; }); @@ -18,10 +19,13 @@ class Compiler { async.eachObject(available_compilers, function (extension, compiler, callback) { - // TODO: warn about files it doesn't know how to compile let matchingFiles = contractFiles.filter(function (file) { let fileMatch = file.filename.match(/\.[0-9a-z]+$/); - return (fileMatch && (fileMatch[0] === extension)); + if (fileMatch && (fileMatch[0] === extension)) { + file.compiled = true; + return true; + } + return false; }); compiler.call(compiler, matchingFiles || [], function (err, compileResult) { @@ -30,6 +34,12 @@ class Compiler { }); }, function (err) { + contractFiles.forEach(file => { + if (!file.compiled) { + self.logger.warn(`${file.filename} doesn't have a compatible contract compiler. Maybe a plugin exists for it.`); + } + }); + cb(err, compiledObject); } ); From dc9e6c168bcbb5afcacd748009a6bdc378defa08 Mon Sep 17 00:00:00 2001 From: Jonathan Rainville Date: Thu, 12 Apr 2018 13:24:54 -0400 Subject: [PATCH 2/5] start vyper implementation --- lib/core/engine.js | 3 ++ lib/modules/solidity/index.js | 2 +- lib/modules/vyper/index.js | 75 +++++++++++++++++++++++++++++++++++ 3 files changed, 79 insertions(+), 1 deletion(-) create mode 100644 lib/modules/vyper/index.js diff --git a/lib/core/engine.js b/lib/core/engine.js index b4ebe9283..9c02a5da0 100644 --- a/lib/core/engine.js +++ b/lib/core/engine.js @@ -137,6 +137,9 @@ class Engine { this.registerModule('solidity', { contractDirectories: self.config.contractDirectories }); + this.registerModule('vyper', { + contractDirectories: self.config.contractDirectories + }); this.contractsManager = new ContractsManager({ contractFiles: this.config.contractsFiles, diff --git a/lib/modules/solidity/index.js b/lib/modules/solidity/index.js index d59564a4c..a4f5c035d 100644 --- a/lib/modules/solidity/index.js +++ b/lib/modules/solidity/index.js @@ -49,7 +49,7 @@ class Solidity { }); }, function compileContracts(callback) { - self.logger.info("compiling contracts..."); + self.logger.info("compiling solidity contracts..."); let jsonObj = { language: 'Solidity', sources: input, diff --git a/lib/modules/vyper/index.js b/lib/modules/vyper/index.js new file mode 100644 index 000000000..b18830bb5 --- /dev/null +++ b/lib/modules/vyper/index.js @@ -0,0 +1,75 @@ +let async = require('../../utils/async_extend.js'); +const shelljs = require('shelljs'); + +class Vyper { + + constructor(embark, options) { + this.logger = embark.logger; + this.events = embark.events; + this.contractDirectories = options.contractDirectories; + + console.log('Construct VYPER'); + embark.registerCompiler(".py", this.compile_vyper.bind(this)); + } + + compile_vyper(contractFiles, cb) { + let self = this; + async.waterfall([ + function compileContracts(callback) { + self.logger.info("compiling vyper contracts..."); + async.each(contractFiles, + function(file, fileCb) { + shelljs.exec(`vyper ${file.filename}`, (code, stdout, stderr) => { + console.log('Code', code); + console.log('Stdout', stdout); + console.log('Stderr', stderr); + fileCb(); + }); + }, + function (err) { + process.exit(); // TODO remove me + callback(err); + }); + }, + function createCompiledObject(output, callback) { + let json = output.contracts; + + if (!output || !output.contracts) { + return callback(new Error("error compiling for unknown reasons")); + } + + if (Object.keys(output.contracts).length === 0 && output.sourceList.length > 0) { + return callback(new Error("error compiling. There are sources available but no code could be compiled, likely due to fatal errors in the solidity code").message); + } + + let compiled_object = {}; + + for (let contractFile in json) { + for (let contractName in json[contractFile]) { + let contract = json[contractFile][contractName]; + + const className = contractName; + const filename = contractFile; + + compiled_object[className] = {}; + compiled_object[className].code = contract.evm.bytecode.object; + compiled_object[className].runtimeBytecode = contract.evm.deployedBytecode.object; + compiled_object[className].realRuntimeBytecode = contract.evm.deployedBytecode.object.slice(0, -68); + compiled_object[className].swarmHash = contract.evm.deployedBytecode.object.slice(-68).slice(0, 64); + compiled_object[className].gasEstimates = contract.evm.gasEstimates; + compiled_object[className].functionHashes = contract.evm.methodIdentifiers; + compiled_object[className].abiDefinition = contract.abi; + compiled_object[className].filename = filename; + } + } + + callback(null, compiled_object); + } + ], function (err, result) { + cb(err, result); + }); + } + +} + +module.exports = Vyper; From 17e1c71506959bf5435f43e197557e868c7b0cbd Mon Sep 17 00:00:00 2001 From: Jonathan Rainville Date: Thu, 12 Apr 2018 16:33:01 -0400 Subject: [PATCH 3/5] submit contract for deployment, not working yet --- lib/modules/vyper/index.js | 92 +++++++++++++++++++------------------- 1 file changed, 46 insertions(+), 46 deletions(-) diff --git a/lib/modules/vyper/index.js b/lib/modules/vyper/index.js index b18830bb5..d75fbe8c7 100644 --- a/lib/modules/vyper/index.js +++ b/lib/modules/vyper/index.js @@ -1,5 +1,6 @@ let async = require('../../utils/async_extend.js'); const shelljs = require('shelljs'); +const path = require('path'); class Vyper { @@ -8,7 +9,6 @@ class Vyper { this.events = embark.events; this.contractDirectories = options.contractDirectories; - console.log('Construct VYPER'); embark.registerCompiler(".py", this.compile_vyper.bind(this)); } @@ -17,53 +17,53 @@ class Vyper { async.waterfall([ function compileContracts(callback) { self.logger.info("compiling vyper contracts..."); + const compiled_object = {}; async.each(contractFiles, - function(file, fileCb) { - shelljs.exec(`vyper ${file.filename}`, (code, stdout, stderr) => { - console.log('Code', code); - console.log('Stdout', stdout); - console.log('Stderr', stderr); - fileCb(); + function (file, fileCb) { + const fileNameOnly = path.basename(file.filename); + compiled_object[fileNameOnly] = {}; + async.parallel([ + function getByteCode(paraCb) { + shelljs.exec(`vyper ${file.filename}`, { silent: true }, (code, stdout, stderr) => { + if (stderr) { + return paraCb(stderr); + } + if (code !== 0) { + return paraCb(`Vyper exited with error code ${code}`) + } + if (!stdout) { + return paraCb('Execution returned no bytecode'); + } + compiled_object[fileNameOnly].code = stdout.replace(/\n/g, ''); + paraCb(); }); - }, - function (err) { - process.exit(); // TODO remove me - callback(err); - }); - }, - function createCompiledObject(output, callback) { - let json = output.contracts; - - if (!output || !output.contracts) { - return callback(new Error("error compiling for unknown reasons")); - } - - if (Object.keys(output.contracts).length === 0 && output.sourceList.length > 0) { - return callback(new Error("error compiling. There are sources available but no code could be compiled, likely due to fatal errors in the solidity code").message); - } - - let compiled_object = {}; - - for (let contractFile in json) { - for (let contractName in json[contractFile]) { - let contract = json[contractFile][contractName]; - - const className = contractName; - const filename = contractFile; - - compiled_object[className] = {}; - compiled_object[className].code = contract.evm.bytecode.object; - compiled_object[className].runtimeBytecode = contract.evm.deployedBytecode.object; - compiled_object[className].realRuntimeBytecode = contract.evm.deployedBytecode.object.slice(0, -68); - compiled_object[className].swarmHash = contract.evm.deployedBytecode.object.slice(-68).slice(0, 64); - compiled_object[className].gasEstimates = contract.evm.gasEstimates; - compiled_object[className].functionHashes = contract.evm.methodIdentifiers; - compiled_object[className].abiDefinition = contract.abi; - compiled_object[className].filename = filename; - } - } - - callback(null, compiled_object); + }, + function getABI(paraCb) { + shelljs.exec(`vyper -f json ${file.filename}`, { silent: true }, (code, stdout, stderr) => { + if (stderr) { + return paraCb(stderr); + } + if (code !== 0) { + return paraCb(`Vyper exited with error code ${code}`) + } + if (!stdout) { + return paraCb('Execution returned no ABI'); + } + let ABI = []; + try { + ABI = JSON.parse(stdout.replace(/\n/g, '')); + } catch (e) { + return paraCb('ABI is not valid JSON'); + } + compiled_object[fileNameOnly].abiDefinition = ABI; + paraCb(); + }); + } + ], fileCb); + }, + function (err) { + callback(err, compiled_object); + }); } ], function (err, result) { cb(err, result); From 99c04b405f5c7091be0cf011568e45cc19d92a77 Mon Sep 17 00:00:00 2001 From: Jonathan Rainville Date: Fri, 13 Apr 2018 15:48:19 -0400 Subject: [PATCH 4/5] fix clas name and bytecode --- lib/contracts/deploy.js | 3 ++- lib/modules/vyper/index.js | 19 +++++++++++-------- 2 files changed, 13 insertions(+), 9 deletions(-) diff --git a/lib/contracts/deploy.js b/lib/contracts/deploy.js index d6f6636e2..d1b87ade3 100644 --- a/lib/contracts/deploy.js +++ b/lib/contracts/deploy.js @@ -305,7 +305,8 @@ class Deploy { let contractObject = new self.web3.eth.Contract(contract.abiDefinition); try { - deployObject = contractObject.deploy({arguments: contractParams, data: "0x" + contractCode}); + const dataCode = contractCode.startsWith('0x') ? contractCode : "0x" + contractCode; + deployObject = contractObject.deploy({arguments: contractParams, data: dataCode}); } catch(e) { if (e.message.indexOf('Invalid number of parameters for "undefined"') >= 0) { return next(new Error("attempted to deploy " + contract.className + " without specifying parameters")); diff --git a/lib/modules/vyper/index.js b/lib/modules/vyper/index.js index d75fbe8c7..df805a215 100644 --- a/lib/modules/vyper/index.js +++ b/lib/modules/vyper/index.js @@ -20,31 +20,34 @@ class Vyper { const compiled_object = {}; async.each(contractFiles, function (file, fileCb) { - const fileNameOnly = path.basename(file.filename); - compiled_object[fileNameOnly] = {}; + const className = path.basename(file.filename).split('.')[0]; + compiled_object[className] = {}; async.parallel([ function getByteCode(paraCb) { - shelljs.exec(`vyper ${file.filename}`, { silent: true }, (code, stdout, stderr) => { + shelljs.exec(`vyper ${file.filename}`, {silent: true}, (code, stdout, stderr) => { if (stderr) { return paraCb(stderr); } if (code !== 0) { - return paraCb(`Vyper exited with error code ${code}`) + return paraCb(`Vyper exited with error code ${code}`); } if (!stdout) { return paraCb('Execution returned no bytecode'); } - compiled_object[fileNameOnly].code = stdout.replace(/\n/g, ''); + const byteCode = stdout.replace(/\n/g, ''); + compiled_object[className].runtimeBytecode = byteCode; + compiled_object[className].realRuntimeBytecode = byteCode; + compiled_object[className].code = byteCode; paraCb(); }); }, function getABI(paraCb) { - shelljs.exec(`vyper -f json ${file.filename}`, { silent: true }, (code, stdout, stderr) => { + shelljs.exec(`vyper -f json ${file.filename}`, {silent: true}, (code, stdout, stderr) => { if (stderr) { return paraCb(stderr); } if (code !== 0) { - return paraCb(`Vyper exited with error code ${code}`) + return paraCb(`Vyper exited with error code ${code}`); } if (!stdout) { return paraCb('Execution returned no ABI'); @@ -55,7 +58,7 @@ class Vyper { } catch (e) { return paraCb('ABI is not valid JSON'); } - compiled_object[fileNameOnly].abiDefinition = ABI; + compiled_object[className].abiDefinition = ABI; paraCb(); }); } From d3e9dc75ec064f19621bf62553da7aa4efb39060 Mon Sep 17 00:00:00 2001 From: Jonathan Rainville Date: Fri, 13 Apr 2018 16:06:56 -0400 Subject: [PATCH 5/5] update readme to include Vyper doc --- README.md | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index dec3eb079..aa5ec7b86 100644 --- a/README.md +++ b/README.md @@ -160,13 +160,18 @@ Solidity/Serpent files in the contracts directory will automatically be deployed Libraries and languages available ====== -Embark can build and deploy contracts coded in Solidity. It will make them available on the client side using EmbarkJS and Web3.js. +Embark can build and deploy contracts coded in Solidity and now also in Vyper. It will make them available on the client side using EmbarkJS and Web3.js. Further documentation for these can be found below: -* Smart Contracts: [Solidity](https://solidity.readthedocs.io/en/develop/) and [Serpent](https://github.com/ethereum/wiki/wiki/Serpent) +* Smart Contracts: + * [Solidity](https://solidity.readthedocs.io/en/develop/) + * [Vyper](https://vyper.readthedocs.io/en/latest/index.html) + * [Serpent](https://github.com/ethereum/wiki/wiki/Serpent) * Client Side: [Web3.js](https://github.com/ethereum/wiki/wiki/JavaScript-API) and [EmbarkJS](#embarkjs) +However, to use Vyper, you need to have Vyper installed on you computer beforehand. Meaning that doing `vyper contract.v.py` is possible. + Using Contracts ====== Embark will automatically take care of deployment for you and set all needed JS bindings. For example, the contract below: