Skip to content

Commit 1f821f9

Browse files
committed
support import directive - fixes #8
1 parent fca1a6e commit 1f821f9

File tree

4 files changed

+88
-57
lines changed

4 files changed

+88
-57
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
All notable changes will be documented in this file.
33

44
## v0.0.9
5+
- new: support the `import` directive - #8
56
- fix: `localhost` alias may not be available on some systems - #9
67

78
## v0.0.8

src/handler.js

Lines changed: 64 additions & 56 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@
77
const Web3 = require('web3')
88
const solc = require('solc')
99
const { getRemoteCompiler } = require('./remoteCompiler.js')
10+
const {readFileCallback} = require('./utils.js')
11+
const path = require('path');
1012

1113
/** CONST */
1214
const rexTypeError = /Return argument type (.*) is not implicitly convertible to expected type \(type of first return variable\)/;
@@ -30,25 +32,26 @@ const SCOPE = {
3032
function getBestSolidityVersion(source) {
3133
var rx = /^pragma solidity (\^?[^;]+);$/gm;
3234
let allVersions = source.match(rx).map(e => {
33-
try {
34-
return e.match(/(\d+)\.(\d+)\.(\d+)/).splice(1,3).map(a => parseInt(a))
35-
} catch {}
35+
try {
36+
return e.match(/(\d+)\.(\d+)\.(\d+)/).splice(1, 3).map(a => parseInt(a))
37+
} catch { }
3638
})
37-
let lastVersion = allVersions[allVersions.length-1];
38-
if(!lastVersion){
39+
let lastVersion = allVersions[allVersions.length - 1];
40+
if (!lastVersion) {
3941
return undefined;
4042
}
4143
return `^${lastVersion.join('.')}`;
4244
}
4345

46+
4447
/** CLASS */
4548
class SolidityStatement {
4649

4750
constructor(rawCommand, scope) {
4851
this.rawCommand = rawCommand ? rawCommand.trim() : "";
49-
this.hasNoReturnValue = (rexAssign.test(this.rawCommand))
50-
|| (this.rawCommand.startsWith('delete'))
51-
|| (this.rawCommand.startsWith('assembly'))
52+
this.hasNoReturnValue = (rexAssign.test(this.rawCommand))
53+
|| (this.rawCommand.startsWith('delete'))
54+
|| (this.rawCommand.startsWith('assembly'))
5255
|| (this.rawCommand.startsWith('revert'))
5356
|| (rexTypeDecl.test(this.rawCommand))
5457

@@ -78,7 +81,7 @@ class SolidityStatement {
7881
} else {
7982
this.scope = SCOPE.MAIN;
8083
this.rawCommand = this.fixStatement(this.rawCommand);
81-
if(this.rawCommand===';'){
84+
if (this.rawCommand === ';') {
8285
this.hasNoReturnValue = true;
8386
}
8487
}
@@ -134,34 +137,34 @@ class InteractiveSolidityShell {
134137
this.cache = {
135138
compiler: {} /** compilerVersion:object */
136139
}
137-
140+
138141
this.cache.compiler[this.settings.installedSolidityVersion.startsWith("^") ? this.settings.installedSolidityVersion.substring(1) : this.settings.installedSolidityVersion] = solc;
139142
this.reset()
140143

141144
this.blockchain = new Blockchain(this.settings, this.log)
142145
this.blockchain.connect()
143146
}
144147

145-
loadSession(stmts){
146-
if(!stmts) {
148+
loadSession(stmts) {
149+
if (!stmts) {
147150
this.session.statements = []
148151
} else {
149152
this.session.statements = stmts.map(s => new SolidityStatement(s[0], s[1]));
150153
}
151154
}
152155

153-
dumpSession(){
156+
dumpSession() {
154157
return this.session.statements.map(s => s.toList());
155158
}
156159

157-
setSetting(key, value){
158-
switch(key){
160+
setSetting(key, value) {
161+
switch (key) {
159162
case 'installedSolidityVersion': return;
160163
case 'ganacheArgs':
161-
if(!value) {
164+
if (!value) {
162165
value = [];
163166
}
164-
else if(!Array.isArray(value)){
167+
else if (!Array.isArray(value)) {
165168
value = value.split(' ');
166169
}
167170
break;
@@ -194,10 +197,10 @@ class InteractiveSolidityShell {
194197
const lastVersionPragma = this.session.statements.filter(stm => stm.scope === SCOPE.VERSION_PRAGMA).pop();
195198

196199
/* prepare body and return statement */
197-
var lastStatement = this.session.statements[this.session.statements.length -1] || {}
198-
if(lastStatement.scope !== SCOPE.MAIN || lastStatement.hasNoReturnValue === true){
200+
var lastStatement = this.session.statements[this.session.statements.length - 1] || {}
201+
if (lastStatement.scope !== SCOPE.MAIN || lastStatement.hasNoReturnValue === true) {
199202
/* not a main statement, put everything in the body and use a dummy as returnexpression */
200-
var mainBody = mainStatements;
203+
var mainBody = mainStatements;
201204
lastStatement = new SolidityStatement() // add dummy w/o return value
202205
} else {
203206
var mainBody = mainStatements.slice(0, mainStatements.length - 1)
@@ -218,7 +221,7 @@ contract ${this.settings.templateContractName} {
218221
return ${lastStatement.returnExpression}
219222
}
220223
}`.trim();
221-
if(this.settings.debugShowContract) this.log(ret)
224+
if (this.settings.debugShowContract) this.log(ret)
222225
return ret;
223226
}
224227

@@ -230,7 +233,7 @@ contract ${this.settings.templateContractName} {
230233
/** load remote version - (maybe cache?) */
231234

232235
return new Promise((resolve, reject) => {
233-
if(that.cache.compiler[solidityVersion]){
236+
if (that.cache.compiler[solidityVersion]) {
234237
return resolve(that.cache.compiler[solidityVersion]);
235238
}
236239

@@ -245,15 +248,14 @@ contract ${this.settings.templateContractName} {
245248
return reject(err)
246249
})
247250
});
248-
251+
249252
}
250253

251254
compile(source, cbWarning) {
252255
let solidityVersion = getBestSolidityVersion(source);
253-
254256
return new Promise((resolve, reject) => {
255257

256-
if(!solidityVersion){
258+
if (!solidityVersion) {
257259
return reject(new Error(`No valid solidity version found in source code (e.g. pragma solidity 0.8.10).`));
258260
}
259261
this.loadCachedCompiler(solidityVersion).then(solcSelected => {
@@ -274,31 +276,37 @@ contract ${this.settings.templateContractName} {
274276
},
275277
}
276278
input.settings.outputSelection['*']['*'] = ['abi', 'evm.bytecode']
277-
278-
let ret = JSON.parse(solcSelected.compile(JSON.stringify(input)))
279+
280+
function readFileCallbackLambda(sourcePath) {
281+
return readFileCallback(sourcePath, {basePath: process.cwd(), includePath: [path.join(process.cwd(),"./node_modules/")]});
282+
}
283+
284+
const callbacks = { 'import': readFileCallbackLambda };
285+
286+
let ret = JSON.parse(solcSelected.compile(JSON.stringify(input), callbacks))
279287
if (ret.errors) {
280288
let realErrors = ret.errors.filter(err => err.type !== 'Warning');
281289
if (realErrors.length) {
282290
return reject(realErrors);
283291
}
284292
// print handle warnings
285293
let warnings = ret.errors.filter(err => err.type === 'Warning' && !IGNORE_WARNINGS.some(target => err.message.includes(target)));
286-
if(warnings.length) cbWarning(warnings);
287-
294+
if (warnings.length) cbWarning(warnings);
295+
288296
}
289297
return resolve(ret);
290298
})
291-
.catch(err => {
292-
return reject(err);
293-
});
294-
299+
.catch(err => {
300+
return reject(err);
301+
});
302+
295303

296304
});
297305

298-
299-
300306

301-
307+
308+
309+
302310
}
303311

304312
run(statement) {
@@ -323,8 +331,8 @@ contract ${this.settings.templateContractName} {
323331
})
324332
}).catch(errors => {
325333
// frownie face
326-
327-
if(!Array.isArray(errors)){ //handle single error
334+
335+
if (!Array.isArray(errors)) { //handle single error
328336
this.revert();
329337
return reject(errors);
330338
}
@@ -393,7 +401,7 @@ class Blockchain {
393401
this.web3 = new Web3(this.provider);
394402

395403
this.web3.eth.net.isListening().then().catch(err => {
396-
if(!this.settings.autostartGanache){
404+
if (!this.settings.autostartGanache) {
397405
console.warn("⚠️ ganache autostart is disabled")
398406
return;
399407
}
@@ -429,7 +437,7 @@ class Blockchain {
429437
async deploy(contracts, callback) {
430438
//sort deploy other contracts first
431439
Object.entries(contracts).sort((a, b) => a[1].main ? 10 : -1).forEach(([templateContractName, o]) => {
432-
if(o.evm.bytecode.object.length === 0){
440+
if (o.evm.bytecode.object.length === 0) {
433441
return; //no bytecode, probably an interface
434442
}
435443

@@ -444,22 +452,22 @@ class Blockchain {
444452

445453
this.deployed[templateContractName] = thisContract;
446454
this.getAccounts()
447-
.then(accounts => {
448-
thisContract.accounts = accounts;
449-
let instance = thisContract.proxy.deploy({ data: thisContract.bytecode }).send({ from: accounts[0], gas: 3e6 })
450-
thisContract.instance = instance;
451-
return instance;
452-
})
453-
.then(contract => {
454-
if (thisContract.main) {
455-
contract.methods[thisContract.main]().call({ from: thisContract.accounts[0], gas: 3e6 }, callback);
456-
}
457-
return;
458-
})
459-
.catch(err => {
460-
callback(`💥 ganache not yet ready. Please try again. (👉 ${err} 👈)`)
461-
})
462-
455+
.then(accounts => {
456+
thisContract.accounts = accounts;
457+
let instance = thisContract.proxy.deploy({ data: thisContract.bytecode }).send({ from: accounts[0], gas: 3e6 })
458+
thisContract.instance = instance;
459+
return instance;
460+
})
461+
.then(contract => {
462+
if (thisContract.main) {
463+
contract.methods[thisContract.main]().call({ from: thisContract.accounts[0], gas: 3e6 }, callback);
464+
}
465+
return;
466+
})
467+
.catch(err => {
468+
callback(`💥 ganache not yet ready. Please try again. (👉 ${err} 👈)`)
469+
})
470+
463471
}, this);
464472
}
465473
}

src/remoteCompiler.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,7 @@ function getRemoteCompiler(solidityVersion) {
5656
}
5757

5858

59+
5960
module.exports = {
6061
getRemoteCompiler,
6162
getSolcJsCompilerList

src/utils.js

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@
44
* @license MIT
55
* */
66

7+
const fs = require('fs');
8+
79
function convert(str){
810
switch(str){
911
case '': return undefined;
@@ -34,7 +36,26 @@ function multilineInput(command){
3436
return command;
3537
}
3638

39+
function readFileCallback(sourcePath, options) {
40+
options = options || {};
41+
const prefixes = [options.basePath ? options.basePath : ""].concat(
42+
options.includePath ? options.includePath : []
43+
);
44+
for (const prefix of prefixes) {
45+
const prefixedSourcePath = (prefix ? prefix + '/' : "") + sourcePath;
46+
if (fs.existsSync(prefixedSourcePath)) {
47+
try {
48+
return { 'contents': fs.readFileSync(prefixedSourcePath).toString('utf8') }
49+
} catch (e) {
50+
return { error: 'Error reading ' + prefixedSourcePath + ': ' + e };
51+
}
52+
}
53+
}
54+
return { error: 'File not found inside the base path or any of the include paths.' }
55+
}
56+
3757
module.exports = {
3858
convert,
39-
multilineInput
59+
multilineInput,
60+
readFileCallback
4061
}

0 commit comments

Comments
 (0)