Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

New Command: Fetch interface declaration from etherscan.io #19

Merged
merged 6 commits into from
Aug 5, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,12 @@
# Change Log
All notable changes will be documented in this file.

## v0.2.0
- new: new command to fetch & load interface declaration from etherscan.io #19

![shell-fetch-interface](https://user-images.githubusercontent.com/2865694/183062446-c952b308-9fc7-49f9-8308-3eac09ca3b4a.gif)


## v0.1.2

- fix: support require(), type, abstract, library
Expand Down
4 changes: 4 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,10 @@ BNB
.help ... this help :)
.exit ... exit the shell

Source:
.fetch
interface <address> <name> [chain=mainnet] ... fetch and load an interface declaration from an ABI spec on etherscan.io

Blockchain:
.chain
restart ... restart the blockchain service
Expand Down
347 changes: 193 additions & 154 deletions bin/main.js

Large diffs are not rendered by default.

3,665 changes: 2,937 additions & 728 deletions package-lock.json

Large diffs are not rendered by default.

3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "solidity-shell",
"version": "0.1.2",
"version": "0.2.0",
"description": "An interactive Solidity shell with lightweight session recording and remote compiler support",
"main": "src/index.js",
"bin": {
Expand All @@ -26,6 +26,7 @@
],
"license": "MIT",
"dependencies": {
"abi-to-sol": "^0.6.5",
"ganache": "^7.0.4",
"minimist": "^1.2.5",
"readline-sync": "^1.4.10",
Expand Down
32 changes: 16 additions & 16 deletions src/blockchain.js
Original file line number Diff line number Diff line change
Expand Up @@ -56,14 +56,14 @@ class AbsBlockchainBase {
})
});
}

methodCall(cmd, args) {
return new Promise((resolve, reject) => {
let func = this.web3.eth[cmd];
if(func === undefined) {
if (func === undefined) {
return reject(" 🧨 Unsupported Method");
}
if(typeof func === "function"){
if (typeof func === "function") {
func((err, result) => {
if (err) return reject(new Error(err));
return resolve(result);
Expand All @@ -74,21 +74,21 @@ class AbsBlockchainBase {
});
}

rpcCall(method, params){
rpcCall(method, params) {
return new Promise((resolve, reject) => {
let payload = {
"jsonrpc":"2.0",
"method":method,
"params":params === undefined ? [] : params,
"id":1
"jsonrpc": "2.0",
"method": method,
"params": params === undefined ? [] : params,
"id": 1
}
this.provider.send(payload, (error, result) => {
if(error)
if (error)
return reject(error);
return resolve(result);
});
});

}

async deploy(contracts, callback) {
Expand Down Expand Up @@ -147,7 +147,7 @@ class BuiltinGanacheBlockchain extends AbsBlockchainBase {
const defaultOptions = {
logging: { quiet: true },
};
this.options = {...defaultOptions, ...shell.settings.ganacheOptions};
this.options = { ...defaultOptions, ...shell.settings.ganacheOptions };
}

connect() {
Expand All @@ -165,14 +165,14 @@ class BuiltinGanacheBlockchain extends AbsBlockchainBase {
this.web3 = new Web3(this.provider);
});


}

startService() {
if (this.provider !== undefined) {
return this.provider;
}

this.provider = ganache.provider(this.options);
}
stopService() {
Expand Down Expand Up @@ -222,9 +222,9 @@ class ExternalProcessBlockchain extends AbsBlockchainBase {
return this.proc;
}
this.log("ℹ️ ganache-mgr: starting temp. ganache instance ...\n »");

this.proc = require('child_process').spawn(this.shell.settings.ganacheCmd, this.shell.settings.ganacheArgs);
this.proc.on('error', function(err) {
this.proc.on('error', function (err) {
console.error(`
🧨 Unable to launch blockchain serivce: ➜ ℹ️ ${err}

Expand All @@ -236,7 +236,7 @@ class ExternalProcessBlockchain extends AbsBlockchainBase {

stopService() {
this.log("💀 ganache-mgr: stopping temp. ganache instance");
if(this.proc) {
if (this.proc) {
this.proc.kill('SIGINT');
this.proc = undefined;
}
Expand Down
35 changes: 29 additions & 6 deletions src/compiler/remoteCompiler.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,23 +5,24 @@
* */
/** IMPORT */
const { solcVersions } = require('./autogenerated/solcVersions.js')
const { generateSolidity } = require('abi-to-sol')
const request = require('request');

function normalizeSolcVersion(version) {
return version.replace('soljson-', '').replace('.js', '');
}

function getSolcJsCompilerList(options){
function getSolcJsCompilerList(options) {
options = options || {};
return new Promise((resolve, reject) => {
request.get('https://solc-bin.ethereum.org/bin/list.json', (err, res, body) => {
if(err){
if (err) {
return reject(err)
}else{
} else {
let data = JSON.parse(body);
let releases = Object.values(data.releases)

if(options.nightly){
if (options.nightly) {
releases = Array.from(new Set([...releases, ...data.builds.map(b => b.path)]));
}
return resolve(releases.map(normalizeSolcVersion))
Expand All @@ -38,7 +39,7 @@ function getRemoteCompiler(solidityVersion) {
(e) => !e.includes('nightly') && e.includes(`v${solidityVersion}`)
)

if (remoteSolidityVersion) {
if (remoteSolidityVersion) {
return resolve(remoteSolidityVersion);
}
//download remote compiler list and check again.
Expand All @@ -55,10 +56,32 @@ function getRemoteCompiler(solidityVersion) {
});
}

//.import interface 0x40cfee8d71d67108db46f772b7e2cd55813bf2fb test2
function getRemoteInterfaceFromEtherscan(address, name, chain) {
return new Promise((resolve, reject) => {

let provider = `https://api${(!chain || chain == "mainnet") ? "" : `-${chain}`}.etherscan.io`
let url = `${provider}/api?module=contract&action=getabi&address=${address}`;
request.get(url, (err, res, body) => {
if (err) {
return reject(err)
} else {
let data = JSON.parse(body);
if (!data.status || data.status != "1" || !data.result) {
return reject(data)
}
let abi = JSON.parse(data.result);
let src = generateSolidity({ name: name, solidityVersion: "0.8.9", abi });
src = src.substring(src.indexOf("\n\n") + 2, src.indexOf("// THIS FILE WAS AUTOGENERATED FROM"));
return resolve(src)
}
})
});
}

module.exports = {
getRemoteCompiler,
getSolcJsCompilerList
getSolcJsCompilerList,
getRemoteInterfaceFromEtherscan
}

6 changes: 4 additions & 2 deletions src/compiler/utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,10 @@ const fs = require('fs');

function readFileCallback(sourcePath, options) {
options = options || {};
if(sourcePath.startsWith("https://") && options.allowHttp){
if (sourcePath.startsWith("https://") && options.allowHttp) {
//allow https! imports; not yet implemented
const res = require('sync-request')('GET', sourcePath); //@todo: this is super buggy and might freeze the app. needs async/promises.
return { contents: res.getBody('utf8')};
return { contents: res.getBody('utf8') };
}
else {
const prefixes = [options.basePath ? options.basePath : ""].concat(
Expand All @@ -31,6 +31,8 @@ function readFileCallback(sourcePath, options) {
return { error: 'File not found inside the base path or any of the include paths.' }
}



module.exports = {
readFileCallback
}
39 changes: 19 additions & 20 deletions src/handler.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,10 @@
* */
/** IMPORT */
const path = require('path');
const Web3 = require('web3');
const solc = require('solc');
const { getRemoteCompiler } = require('./compiler/remoteCompiler.js');
const { readFileCallback } = require('./compiler/utils.js');
const { ExternalProcessBlockchain, ExternalUrlBlockchain ,BuiltinGanacheBlockchain } = require('./blockchain.js');
const { ExternalProcessBlockchain, ExternalUrlBlockchain, BuiltinGanacheBlockchain } = require('./blockchain.js');


/** CONST */
Expand Down Expand Up @@ -153,13 +152,13 @@ class InteractiveSolidityShell {
}

initBlockchain() {
if(this.blockchain){
if (this.blockchain) {
this.blockchain.stopService();
}

if(!this.settings.blockchainProvider || this.settings.blockchainProvider === "internal"){
if (!this.settings.blockchainProvider || this.settings.blockchainProvider === "internal") {
this.blockchain = new BuiltinGanacheBlockchain(this);
} else if(this.settings.blockchainProvider.startsWith("https://") || this.settings.blockchainProvider.startsWith("http://")) {
} else if (this.settings.blockchainProvider.startsWith("https://") || this.settings.blockchainProvider.startsWith("http://")) {
this.blockchain = new ExternalUrlBlockchain(this, this.settings.blockchainProvider);
} else if (this.settings.blockchainProvider.length > 0) {
this.settings.ganacheCmd = this.settings.blockchainProvider;
Expand Down Expand Up @@ -306,12 +305,12 @@ contract ${this.settings.templateContractName} {
const callbacks = {
'import': (sourcePath) => readFileCallback(
sourcePath, {
basePath: process.cwd(),
includePath: [
path.join(process.cwd(), "node_modules")
],
allowHttp: this.settings.resolveHttpImports
}
basePath: process.cwd(),
includePath: [
path.join(process.cwd(), "node_modules")
],
allowHttp: this.settings.resolveHttpImports
}
)
};

Expand Down Expand Up @@ -368,7 +367,7 @@ contract ${this.settings.templateContractName} {
}
let retType = ""
let matches = lastTypeError.message.match(rexTypeErrorReturnArgumentX);
if(matches){
if (matches) {
//console.log("2nd pass - detect return type")
retType = matches[1].trim();
if (retType.startsWith('int_const -')) {
Expand All @@ -381,28 +380,28 @@ contract ${this.settings.templateContractName} {
let fragments = retType.split(' '); //address[] storage pointer
fragments.pop() // pop 'pointer'
console.log(fragments)
if (fragments[1] == "storage"){
if (fragments[1] == "storage") {
fragments[1] = "memory";
}
retType = fragments.join(' ');
}
} else if(lastTypeError.message.includes(TYPE_ERROR_DETECT_RETURNS)) {
} else if (lastTypeError.message.includes(TYPE_ERROR_DETECT_RETURNS)) {
console.error("WARNING: cannot auto-resolve type for complex function yet ://\n If this is a function call, try unpacking the function return values into local variables explicitly!\n e.g. `(uint a, address b, address c) = myContract.doSomething(1,2,3);`")
// lets give it a low-effort try to resolve return types. this will not always work.
let rexFunctionName = new RegExp(`([a-zA-Z0-9_\\.]+)\\s*\\(.*?\\)`);
let matchedFunctionNames = statement.rawCommand.match(rexFunctionName);
if(matchedFunctionNames.length >= 1 ){
if (matchedFunctionNames.length >= 1) {
let funcNameParts = matchedFunctionNames[1].split(".");
let funcName = funcNameParts[funcNameParts.length-1]; //get last
let funcName = funcNameParts[funcNameParts.length - 1]; //get last
let rexReturns = new RegExp(`function ${funcName}\\s*\\(.* returns\\s*\\(([^\\)]+)\\)`)

let returnDecl = sourceCode.match(rexReturns);
if(returnDecl.length >1){
if (returnDecl.length > 1) {
retType = returnDecl[1];
}
}

if(retType === ""){
if (retType === "") {
this.revert();
return reject(errors);
}
Expand All @@ -411,7 +410,7 @@ contract ${this.settings.templateContractName} {
this.revert();
return reject(errors);
}

this.session.statements[this.session.statements.length - 1].returnType = retType;

//try again!
Expand Down
2 changes: 1 addition & 1 deletion src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
* @author github.com/tintinweb
* @license MIT
* */
const {InteractiveSolidityShell} = require('./handler');
const { InteractiveSolidityShell } = require('./handler');

module.exports = {
InteractiveSolidityShell
Expand Down