Skip to content

Commit 82cfe09

Browse files
committed
dynamic compiler selection
v0.0.4
1 parent 5636242 commit 82cfe09

File tree

6 files changed

+127
-44
lines changed

6 files changed

+127
-44
lines changed

CHANGELOG.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,9 @@
11
# Change Log
22
All notable changes will be documented in this file.
33

4+
## v0.0.4
5+
- new: dynamic compiler selection via pragma directive
6+
- changing the solidity version pragma attempts to load the selected compiler version remotely. e.g. type `pragma solidity 0.8.4` to switch to solidity v0.8.4.
47

58
## v0.0.1 - 0.0.3
69

README.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,8 @@ An interactive Solidity shell with lightweight session recording.
2727

2828
### Hints
2929

30+
31+
* **Note**: Type `pragma solidity <version>` to dynamically load a different compiler version.
3032
* **Note**: Sessions can be saved and restored using the `.session` command. Your previous session is always stored and can be loaded via `.session load previous` (not safe when running concurrent shells).
3133
* **Note**: `.reset` completely removes all statements. `.undo` removes the last statement.
3234
* **Note**: See what's been generated under the hood? call `.dump`.

bin/main.js

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,7 @@ vorpal
6161
.mode('repl', 'Enters Solidity Shell Mode')
6262
.delimiter(c.bold('» '))
6363
.init(function (args, cb) {
64-
this.log(`🚀 Entering interactive Solidity shell. '${c.bold('.help')}' and '${c.bold('.exit')}' are your friends.`);
64+
this.log(`🚀 Entering interactive Solidity ${c.bold(shell.settings.installedSolidityVersion)} shell. '${c.bold('.help')}' and '${c.bold('.exit')}' are your friends.`);
6565
return cb();
6666
})
6767
.action(function (input, cb) {
@@ -80,6 +80,7 @@ vorpal
8080
-----
8181
8282
${c.bold('$_')} is a placeholder holding the most recent evaluation result.
83+
${c.bold('pragma solidity <version>')} to change the compiler version.
8384
8485
8586
${c.bold('General:')}
@@ -213,4 +214,5 @@ vorpal
213214
.command("$_")
214215

215216
/** start in repl mode */
216-
vorpal.execSync("repl")
217+
vorpal.execSync("repl")
218+
//vorpal.execSync("1+2") /* debug */

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "solidity-shell",
3-
"version": "0.0.3",
3+
"version": "0.0.4",
44
"description": "",
55
"main": "src/index.js",
66
"bin": {

src/handler.js

Lines changed: 115 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
/** IMPORT */
77
const Web3 = require('web3')
88
const solc = require('solc')
9+
const { solcVersions } = require('./solcVersions.js')
910

1011
/** CONST */
1112
const rexTypeError = /Return argument type (.*) is not implicitly convertible to expected type \(type of first return variable\)/;
@@ -16,8 +17,29 @@ const IGNORE_WARNINGS = [
1617
"Unused local variable."
1718
]
1819

20+
const SCOPE = {
21+
CONTRACT: 1, /* statement in contract scope */
22+
SOURCE_UNIT: 2, /* statement in source unit scope */
23+
MAIN: 4, /* statement in main function scope */
24+
VERSION_PRAGMA: 5 /* statement is a solidity version pragma */
25+
}
26+
1927
/** STATIC FUNC */
2028

29+
function getBestSolidityVersion(source) {
30+
var rx = /^pragma solidity (\^?[^;]+);$/gm;
31+
let allVersions = source.match(rx).map(e => {
32+
try {
33+
return e.match(/(\d+)\.(\d+)\.(\d+)/).splice(1,3).map(a => parseInt(a))
34+
} catch {}
35+
})
36+
let lastVersion = allVersions[allVersions.length-1];
37+
if(!lastVersion){
38+
return undefined;
39+
}
40+
return `^${lastVersion.join('.')}`;
41+
}
42+
2143
/** CLASS */
2244
class SolidityStatement {
2345

@@ -29,22 +51,27 @@ class SolidityStatement {
2951
this.scope = scope
3052
} else {
3153
if (this.rawCommand.startsWith('function ') || this.rawCommand.startsWith('modifier ')) {
32-
this.scope = "contract";
54+
this.scope = SCOPE.CONTRACT;
3355
this.hasNoReturnValue = true;
3456
} else if (this.rawCommand.startsWith('mapping ') || this.rawCommand.startsWith('event ')) {
35-
this.scope = "contract";
57+
this.scope = SCOPE.CONTRACT;
3658
this.hasNoReturnValue = true;
59+
} else if (this.rawCommand.startsWith('pragma solidity ')) {
60+
this.scope = SCOPE.VERSION_PRAGMA;
61+
this.hasNoReturnValue = true;
62+
this.rawCommand = this.fixStatement(this.rawCommand);
3763
} else if (this.rawCommand.startsWith('pragma ')) {
38-
this.scope = "sourceUnit";
64+
this.scope = SCOPE.SOURCE_UNIT;
3965
this.hasNoReturnValue = true;
66+
this.rawCommand = this.fixStatement(this.rawCommand);
4067
} else if (this.rawCommand.startsWith('struct ')) {
41-
this.scope = "sourceUnit";
68+
this.scope = SCOPE.SOURCE_UNIT;
4269
this.hasNoReturnValue = true;
4370
} else if (this.rawCommand.startsWith('contract ')) {
44-
this.scope = "sourceUnit";
71+
this.scope = SCOPE.SOURCE_UNIT;
4572
this.hasNoReturnValue = true;
4673
} else {
47-
this.scope = "main";
74+
this.scope = SCOPE.MAIN;
4875
this.rawCommand = this.fixStatement(this.rawCommand);
4976
if(this.rawCommand===';'){
5077
this.hasNoReturnValue = true;
@@ -95,6 +122,11 @@ class InteractiveSolidityShell {
95122
...defaults, ... (settings || {})
96123
};
97124

125+
this.cache = {
126+
compiler: {} /** compilerVersion:object */
127+
}
128+
129+
this.cache.compiler[this.settings.installedSolidityVersion.startsWith("^") ? this.settings.installedSolidityVersion.substring(1) : this.settings.installedSolidityVersion] = solc;
98130
this.reset()
99131

100132
this.blockchain = new Blockchain(this.settings, this.log)
@@ -119,14 +151,12 @@ class InteractiveSolidityShell {
119151
}
120152

121153
reset() {
122-
//console.log("---REVERT--")
123154
this.session = {
124155
statements: [],
125156
}
126157
}
127158

128159
revert() {
129-
//console.log("---REVERT--")
130160
this.session.statements.pop();
131161
}
132162

@@ -135,23 +165,26 @@ class InteractiveSolidityShell {
135165
}
136166

137167
template() {
138-
const prologue = this.session.statements.filter(stm => stm.scope === "sourceUnit");
139-
const contractState = this.session.statements.filter(stm => stm.scope === "contract");
140-
const mainStatements = this.session.statements.filter(stm => stm.scope === "main");
168+
const prologue = this.session.statements.filter(stm => stm.scope === SCOPE.SOURCE_UNIT);
169+
const contractState = this.session.statements.filter(stm => stm.scope === SCOPE.CONTRACT);
170+
const mainStatements = this.session.statements.filter(stm => stm.scope === SCOPE.MAIN);
141171

172+
/* figure out which compiler version to use */
173+
const lastVersionPragma = this.session.statements.filter(stm => stm.scope === SCOPE.VERSION_PRAGMA).pop();
174+
175+
/* prepare body and return statement */
142176
var lastStatement = this.session.statements[this.session.statements.length -1] || {}
143-
if(lastStatement.scope !== 'main'){
177+
if(lastStatement.scope !== SCOPE.MAIN){
144178
/* not a main statement, put everything in the body and use a dummy as returnexpression */
145179
var mainBody = mainStatements;
146180
lastStatement = new SolidityStatement() // add dummy w/o return value
147181
} else {
148182
var mainBody = mainStatements.slice(0, mainStatements.length - 1)
149183
}
150184

151-
152185
const ret = `
153186
// SPDX-License-Identifier: GPL-2.0-or-later
154-
pragma solidity ${this.settings.installedSolidityVersion};
187+
${lastVersionPragma ? lastVersionPragma.rawCommand : `pragma solidity ${this.settings.installedSolidityVersion};`}
155188
156189
${prologue.join('\n\n')}
157190
@@ -168,36 +201,77 @@ contract ${this.settings.templateContractName} {
168201
return ret;
169202
}
170203

171-
async compile(source, cbWarning) {
172-
let input = {
173-
language: 'Solidity',
174-
sources: {
175-
'': {
176-
content: source,
177-
},
178-
},
179-
settings: {
180-
outputSelection: {
181-
'*': {
182-
//
183-
},
184-
},
185-
},
204+
205+
loadCachedCompiler(solidityVersion) {
206+
solidityVersion = solidityVersion.startsWith("^") ? solidityVersion.substring(1) : solidityVersion; //strip leading ^
207+
/** load remote version - (maybe cache?) */
208+
if(this.cache.compiler[solidityVersion]){
209+
return new Promise((resolve, reject) => {
210+
return resolve(this.cache.compiler[solidityVersion]);
211+
});
186212
}
187-
input.settings.outputSelection['*']['*'] = ['abi', 'evm.bytecode']
188213

189-
let ret = JSON.parse(solc.compile(JSON.stringify(input)))
190-
if (ret.errors) {
191-
let realErrors = ret.errors.filter(err => err.type !== 'Warning');
192-
if (realErrors.length) {
193-
throw realErrors;
194-
}
195-
// print handle warnings
196-
let warnings = ret.errors.filter(err => err.type === 'Warning' && !IGNORE_WARNINGS.some(target => err.message.includes(target)));
197-
if(warnings.length) cbWarning(warnings);
214+
var remoteSolidityVersion = solcVersions.find(
215+
(e) => !e.includes('nightly') && e.includes(`v${solidityVersion}`)
216+
)
198217

199-
}
200-
return ret;
218+
var that = this;
219+
220+
return new Promise((resolve, reject) => {
221+
solc.loadRemoteVersion(remoteSolidityVersion, function (err, solcSnapshot) {
222+
that.cache.compiler[solidityVersion] = solcSnapshot;
223+
return resolve(solcSnapshot)
224+
})
225+
});
226+
227+
}
228+
229+
compile(source, cbWarning) {
230+
231+
let solidityVersion = getBestSolidityVersion(source);
232+
233+
return new Promise((resolve, reject) => {
234+
235+
this.loadCachedCompiler(solidityVersion).then(solcSelected => {
236+
237+
let input = {
238+
language: 'Solidity',
239+
sources: {
240+
'': {
241+
content: source,
242+
},
243+
},
244+
settings: {
245+
outputSelection: {
246+
'*': {
247+
//
248+
},
249+
},
250+
},
251+
}
252+
input.settings.outputSelection['*']['*'] = ['abi', 'evm.bytecode']
253+
254+
let ret = JSON.parse(solcSelected.compile(JSON.stringify(input)))
255+
if (ret.errors) {
256+
let realErrors = ret.errors.filter(err => err.type !== 'Warning');
257+
if (realErrors.length) {
258+
return reject(realErrors);
259+
}
260+
// print handle warnings
261+
let warnings = ret.errors.filter(err => err.type === 'Warning' && !IGNORE_WARNINGS.some(target => err.message.includes(target)));
262+
if(warnings.length) cbWarning(warnings);
263+
264+
}
265+
return resolve(ret);
266+
});
267+
268+
269+
});
270+
271+
272+
273+
274+
201275
}
202276

203277
run(statement) {

src/solcVersions.js

Lines changed: 2 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)