diff --git a/README.md b/README.md index dec3eb0792..aa5ec7b869 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: diff --git a/lib/contracts/compiler.js b/lib/contracts/compiler.js index c9a7ffa433..fa474e5232 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); } ); diff --git a/lib/contracts/deploy.js b/lib/contracts/deploy.js index d6f6636e29..d1b87ade36 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/core/engine.js b/lib/core/engine.js index b4ebe92830..9c02a5da0a 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 d59564a4c3..a4f5c035d5 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 0000000000..df805a2155 --- /dev/null +++ b/lib/modules/vyper/index.js @@ -0,0 +1,78 @@ +let async = require('../../utils/async_extend.js'); +const shelljs = require('shelljs'); +const path = require('path'); + +class Vyper { + + constructor(embark, options) { + this.logger = embark.logger; + this.events = embark.events; + this.contractDirectories = options.contractDirectories; + + 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..."); + const compiled_object = {}; + async.each(contractFiles, + function (file, fileCb) { + 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) => { + if (stderr) { + return paraCb(stderr); + } + if (code !== 0) { + return paraCb(`Vyper exited with error code ${code}`); + } + if (!stdout) { + return paraCb('Execution returned no bytecode'); + } + 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) => { + 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[className].abiDefinition = ABI; + paraCb(); + }); + } + ], fileCb); + }, + function (err) { + callback(err, compiled_object); + }); + } + ], function (err, result) { + cb(err, result); + }); + } + +} + +module.exports = Vyper;