diff --git a/README.md b/README.md index dec3eb07..aa5ec7b8 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 c9a7ffa4..fa474e52 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 d6f6636e..d1b87ade 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 b4ebe928..9c02a5da 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 d59564a4..a4f5c035 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 00000000..df805a21 --- /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;