Skip to content

Commit 1bd0b99

Browse files
authored
Merge pull request #363 from embark-framework/features/vyper-plugin
Add Vyper contract support
2 parents 6647c31 + d3e9dc7 commit 1bd0b99

File tree

6 files changed

+104
-7
lines changed

6 files changed

+104
-7
lines changed

README.md

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -160,13 +160,18 @@ Solidity/Serpent files in the contracts directory will automatically be deployed
160160
Libraries and languages available
161161
======
162162

163-
Embark can build and deploy contracts coded in Solidity. It will make them available on the client side using EmbarkJS and Web3.js.
163+
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.
164164

165165
Further documentation for these can be found below:
166166

167-
* Smart Contracts: [Solidity](https://solidity.readthedocs.io/en/develop/) and [Serpent](https://github.com/ethereum/wiki/wiki/Serpent)
167+
* Smart Contracts:
168+
* [Solidity](https://solidity.readthedocs.io/en/develop/)
169+
* [Vyper](https://vyper.readthedocs.io/en/latest/index.html)
170+
* [Serpent](https://github.com/ethereum/wiki/wiki/Serpent)
168171
* Client Side: [Web3.js](https://github.com/ethereum/wiki/wiki/JavaScript-API) and [EmbarkJS](#embarkjs)
169172

173+
However, to use Vyper, you need to have Vyper installed on you computer beforehand. Meaning that doing `vyper contract.v.py` is possible.
174+
170175
Using Contracts
171176
======
172177
Embark will automatically take care of deployment for you and set all needed JS bindings. For example, the contract below:

lib/contracts/compiler.js

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,9 +7,10 @@ class Compiler {
77
}
88

99
compile_contracts(contractFiles, cb) {
10+
const self = this;
1011
let available_compilers = {};
1112

12-
let pluginCompilers = this.plugins.getPluginsProperty('compilers', 'compilers');
13+
let pluginCompilers = self.plugins.getPluginsProperty('compilers', 'compilers');
1314
pluginCompilers.forEach(function (compilerObject) {
1415
available_compilers[compilerObject.extension] = compilerObject.cb;
1516
});
@@ -18,10 +19,13 @@ class Compiler {
1819

1920
async.eachObject(available_compilers,
2021
function (extension, compiler, callback) {
21-
// TODO: warn about files it doesn't know how to compile
2222
let matchingFiles = contractFiles.filter(function (file) {
2323
let fileMatch = file.filename.match(/\.[0-9a-z]+$/);
24-
return (fileMatch && (fileMatch[0] === extension));
24+
if (fileMatch && (fileMatch[0] === extension)) {
25+
file.compiled = true;
26+
return true;
27+
}
28+
return false;
2529
});
2630

2731
compiler.call(compiler, matchingFiles || [], function (err, compileResult) {
@@ -30,6 +34,12 @@ class Compiler {
3034
});
3135
},
3236
function (err) {
37+
contractFiles.forEach(file => {
38+
if (!file.compiled) {
39+
self.logger.warn(`${file.filename} doesn't have a compatible contract compiler. Maybe a plugin exists for it.`);
40+
}
41+
});
42+
3343
cb(err, compiledObject);
3444
}
3545
);

lib/contracts/deploy.js

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -305,7 +305,8 @@ class Deploy {
305305
let contractObject = new self.web3.eth.Contract(contract.abiDefinition);
306306

307307
try {
308-
deployObject = contractObject.deploy({arguments: contractParams, data: "0x" + contractCode});
308+
const dataCode = contractCode.startsWith('0x') ? contractCode : "0x" + contractCode;
309+
deployObject = contractObject.deploy({arguments: contractParams, data: dataCode});
309310
} catch(e) {
310311
if (e.message.indexOf('Invalid number of parameters for "undefined"') >= 0) {
311312
return next(new Error("attempted to deploy " + contract.className + " without specifying parameters"));

lib/core/engine.js

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -137,6 +137,9 @@ class Engine {
137137
this.registerModule('solidity', {
138138
contractDirectories: self.config.contractDirectories
139139
});
140+
this.registerModule('vyper', {
141+
contractDirectories: self.config.contractDirectories
142+
});
140143

141144
this.contractsManager = new ContractsManager({
142145
contractFiles: this.config.contractsFiles,

lib/modules/solidity/index.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,7 @@ class Solidity {
4949
});
5050
},
5151
function compileContracts(callback) {
52-
self.logger.info("compiling contracts...");
52+
self.logger.info("compiling solidity contracts...");
5353
let jsonObj = {
5454
language: 'Solidity',
5555
sources: input,

lib/modules/vyper/index.js

Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
let async = require('../../utils/async_extend.js');
2+
const shelljs = require('shelljs');
3+
const path = require('path');
4+
5+
class Vyper {
6+
7+
constructor(embark, options) {
8+
this.logger = embark.logger;
9+
this.events = embark.events;
10+
this.contractDirectories = options.contractDirectories;
11+
12+
embark.registerCompiler(".py", this.compile_vyper.bind(this));
13+
}
14+
15+
compile_vyper(contractFiles, cb) {
16+
let self = this;
17+
async.waterfall([
18+
function compileContracts(callback) {
19+
self.logger.info("compiling vyper contracts...");
20+
const compiled_object = {};
21+
async.each(contractFiles,
22+
function (file, fileCb) {
23+
const className = path.basename(file.filename).split('.')[0];
24+
compiled_object[className] = {};
25+
async.parallel([
26+
function getByteCode(paraCb) {
27+
shelljs.exec(`vyper ${file.filename}`, {silent: true}, (code, stdout, stderr) => {
28+
if (stderr) {
29+
return paraCb(stderr);
30+
}
31+
if (code !== 0) {
32+
return paraCb(`Vyper exited with error code ${code}`);
33+
}
34+
if (!stdout) {
35+
return paraCb('Execution returned no bytecode');
36+
}
37+
const byteCode = stdout.replace(/\n/g, '');
38+
compiled_object[className].runtimeBytecode = byteCode;
39+
compiled_object[className].realRuntimeBytecode = byteCode;
40+
compiled_object[className].code = byteCode;
41+
paraCb();
42+
});
43+
},
44+
function getABI(paraCb) {
45+
shelljs.exec(`vyper -f json ${file.filename}`, {silent: true}, (code, stdout, stderr) => {
46+
if (stderr) {
47+
return paraCb(stderr);
48+
}
49+
if (code !== 0) {
50+
return paraCb(`Vyper exited with error code ${code}`);
51+
}
52+
if (!stdout) {
53+
return paraCb('Execution returned no ABI');
54+
}
55+
let ABI = [];
56+
try {
57+
ABI = JSON.parse(stdout.replace(/\n/g, ''));
58+
} catch (e) {
59+
return paraCb('ABI is not valid JSON');
60+
}
61+
compiled_object[className].abiDefinition = ABI;
62+
paraCb();
63+
});
64+
}
65+
], fileCb);
66+
},
67+
function (err) {
68+
callback(err, compiled_object);
69+
});
70+
}
71+
], function (err, result) {
72+
cb(err, result);
73+
});
74+
}
75+
76+
}
77+
78+
module.exports = Vyper;

0 commit comments

Comments
 (0)