commit
97fea75484
@ -0,0 +1,18 @@ |
|||||||
|
import { WebsocketPlugin } from '@remixproject/engine-web' |
||||||
|
import * as packageJson from '../../../../../package.json' |
||||||
|
|
||||||
|
const profile = { |
||||||
|
name: 'slither', |
||||||
|
displayName: 'Slither', |
||||||
|
url: 'ws://127.0.0.1:65523', |
||||||
|
methods: ['analyse'], |
||||||
|
description: 'Using Remixd daemon, run slither static analysis', |
||||||
|
kind: 'other', |
||||||
|
version: packageJson.version |
||||||
|
} |
||||||
|
|
||||||
|
export class SlitherHandle extends WebsocketPlugin { |
||||||
|
constructor () { |
||||||
|
super(profile) |
||||||
|
} |
||||||
|
} |
File diff suppressed because one or more lines are too long
@ -0,0 +1,54 @@ |
|||||||
|
const introJs = require('intro.js') |
||||||
|
|
||||||
|
export class WalkthroughService { |
||||||
|
constructor (params) { |
||||||
|
this.params = params |
||||||
|
} |
||||||
|
|
||||||
|
start (params) { |
||||||
|
if (!localStorage.getItem('hadTour_initial')) { |
||||||
|
introJs().setOptions({ |
||||||
|
steps: [{ |
||||||
|
title: 'Welcome to Remix IDE', |
||||||
|
intro: 'Click to launch the Home tab that contains links, tips, and shortcuts..', |
||||||
|
element: document.querySelector('#verticalIconsHomeIcon'), |
||||||
|
tooltipClass: 'bg-light text-dark', |
||||||
|
position: 'right' |
||||||
|
}, |
||||||
|
{ |
||||||
|
element: document.querySelector('#compileIcons'), |
||||||
|
title: 'Solidity Compiler', |
||||||
|
intro: 'Having selected a .sol file in the File Explorers (the icon above), compile it with the Solidity Compiler.', |
||||||
|
tooltipClass: 'bg-light text-dark', |
||||||
|
position: 'right' |
||||||
|
}, |
||||||
|
{ |
||||||
|
title: 'Deploy your contract', |
||||||
|
element: document.querySelector('#runIcons'), |
||||||
|
intro: 'Choose a chain, deploy a contract and play with your functions.', |
||||||
|
tooltipClass: 'bg-light text-dark', |
||||||
|
position: 'right' |
||||||
|
} |
||||||
|
] |
||||||
|
}).onafterchange((targetElement) => { |
||||||
|
const header = document.getElementsByClassName('introjs-tooltip-header')[0] |
||||||
|
if (header) { |
||||||
|
header.classList.add('d-flex') |
||||||
|
header.classList.add('justify-content-between') |
||||||
|
header.classList.add('text-nowrap') |
||||||
|
header.classList.add('pr-0') |
||||||
|
} |
||||||
|
const skipbutton = document.getElementsByClassName('introjs-skipbutton')[0] |
||||||
|
if (skipbutton) { |
||||||
|
skipbutton.classList.add('ml-3') |
||||||
|
skipbutton.classList.add('text-decoration-none') |
||||||
|
skipbutton.id = 'remixTourSkipbtn' |
||||||
|
} |
||||||
|
}).start() |
||||||
|
localStorage.setItem('hadTour_initial', true) |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
startFeatureTour () { |
||||||
|
} |
||||||
|
} |
@ -1,3 +1,4 @@ |
|||||||
export { RemixdClient as Sharedfolder } from './services/remixdClient' |
export { RemixdClient as Sharedfolder } from './services/remixdClient' |
||||||
export { GitClient } from './services/gitClient' |
export { GitClient } from './services/gitClient' |
||||||
export { HardhatClient } from './services/hardhatClient' |
export { HardhatClient } from './services/hardhatClient' |
||||||
|
export { SlitherClient } from './services/slitherClient' |
||||||
|
@ -0,0 +1,162 @@ |
|||||||
|
/* eslint dot-notation: "off" */ |
||||||
|
|
||||||
|
import * as WS from 'ws' // eslint-disable-line
|
||||||
|
import { PluginClient } from '@remixproject/plugin' |
||||||
|
import { existsSync, readFileSync, readdirSync } from 'fs' |
||||||
|
import { OutputStandard } from '../types' // eslint-disable-line
|
||||||
|
const { spawn, execSync } = require('child_process') |
||||||
|
|
||||||
|
export class SlitherClient extends PluginClient { |
||||||
|
methods: Array<string> |
||||||
|
websocket: WS |
||||||
|
currentSharedFolder: string |
||||||
|
|
||||||
|
constructor (private readOnly = false) { |
||||||
|
super() |
||||||
|
this.methods = ['analyse'] |
||||||
|
} |
||||||
|
|
||||||
|
setWebSocket (websocket: WS): void { |
||||||
|
this.websocket = websocket |
||||||
|
} |
||||||
|
|
||||||
|
sharedFolder (currentSharedFolder: string): void { |
||||||
|
this.currentSharedFolder = currentSharedFolder |
||||||
|
} |
||||||
|
|
||||||
|
mapNpmDepsDir (list) { |
||||||
|
const remixNpmDepsPath = `${this.currentSharedFolder}/.deps/npm` |
||||||
|
const localNpmDepsPath = `${this.currentSharedFolder}/node_modules` |
||||||
|
const npmDepsExists = existsSync(remixNpmDepsPath) |
||||||
|
const nodeModulesExists = existsSync(localNpmDepsPath) |
||||||
|
let isLocalDep = false |
||||||
|
let isRemixDep = false |
||||||
|
let allowPathString = '' |
||||||
|
let remapString = '' |
||||||
|
|
||||||
|
for (const e of list) { |
||||||
|
const importPath = e.replace(/import ['"]/g, '').trim() |
||||||
|
const packageName = importPath.split('/')[0] |
||||||
|
if (nodeModulesExists && readdirSync(localNpmDepsPath).includes(packageName)) { |
||||||
|
isLocalDep = true |
||||||
|
remapString += `${packageName}=./node_modules/${packageName} ` |
||||||
|
} else if (npmDepsExists && readdirSync(remixNpmDepsPath).includes(packageName)) { |
||||||
|
isRemixDep = true |
||||||
|
remapString += `${packageName}=./.deps/npm/${packageName} ` |
||||||
|
} |
||||||
|
} |
||||||
|
if (isLocalDep) allowPathString += './node_modules,' |
||||||
|
if (isRemixDep) allowPathString += './.deps/npm,' |
||||||
|
|
||||||
|
return { remapString, allowPathString } |
||||||
|
} |
||||||
|
|
||||||
|
transform (detectors: Record<string, any>[]): OutputStandard[] { |
||||||
|
const standardReport: OutputStandard[] = [] |
||||||
|
for (const e of detectors) { |
||||||
|
const obj = {} as OutputStandard |
||||||
|
obj.description = e.description |
||||||
|
obj.title = e.check |
||||||
|
obj.confidence = e.confidence |
||||||
|
obj.severity = e.impact |
||||||
|
obj.sourceMap = e.elements.map((element) => { |
||||||
|
delete element.source_mapping.filename_used |
||||||
|
delete element.source_mapping.filename_absolute |
||||||
|
return element |
||||||
|
}) |
||||||
|
standardReport.push(obj) |
||||||
|
} |
||||||
|
return standardReport |
||||||
|
} |
||||||
|
|
||||||
|
analyse (filePath: string, compilerConfig: Record<string, any>) { |
||||||
|
return new Promise((resolve, reject) => { |
||||||
|
if (this.readOnly) { |
||||||
|
const errMsg: string = '[Slither Analysis]: Cannot analyse in read-only mode' |
||||||
|
return reject(new Error(errMsg)) |
||||||
|
} |
||||||
|
const options = { cwd: this.currentSharedFolder, shell: true } |
||||||
|
const { currentVersion, optimize, evmVersion } = compilerConfig |
||||||
|
if (currentVersion && currentVersion.includes('+commit')) { |
||||||
|
// Get compiler version with commit id e.g: 0.8.2+commit.661d110
|
||||||
|
const versionString: string = currentVersion.substring(0, currentVersion.indexOf('+commit') + 16) |
||||||
|
console.log('\x1b[32m%s\x1b[0m', `[Slither Analysis]: Compiler version is ${versionString}`) |
||||||
|
let solcOutput: Buffer |
||||||
|
// Check solc current installed version
|
||||||
|
try { |
||||||
|
solcOutput = execSync('solc --version', options) |
||||||
|
} catch (err) { |
||||||
|
console.log(err) |
||||||
|
reject(new Error('Error in running solc command')) |
||||||
|
} |
||||||
|
if (!solcOutput.toString().includes(versionString)) { |
||||||
|
console.log('\x1b[32m%s\x1b[0m', '[Slither Analysis]: Compiler version is different from installed solc version') |
||||||
|
// Get compiler version without commit id e.g: 0.8.2
|
||||||
|
const version: string = versionString.substring(0, versionString.indexOf('+commit')) |
||||||
|
// List solc versions installed using solc-select
|
||||||
|
try { |
||||||
|
const solcSelectInstalledVersions: Buffer = execSync('solc-select versions', options) |
||||||
|
// Check if required version is already installed
|
||||||
|
if (!solcSelectInstalledVersions.toString().includes(version)) { |
||||||
|
console.log('\x1b[32m%s\x1b[0m', `[Slither Analysis]: Installing ${version} using solc-select`) |
||||||
|
// Install required version
|
||||||
|
execSync(`solc-select install ${version}`, options) |
||||||
|
} |
||||||
|
console.log('\x1b[32m%s\x1b[0m', `[Slither Analysis]: Setting ${version} as current solc version using solc-select`) |
||||||
|
// Set solc current version as required version
|
||||||
|
execSync(`solc-select use ${version}`, options) |
||||||
|
} catch (err) { |
||||||
|
console.log(err) |
||||||
|
reject(new Error('Error in running solc-select command')) |
||||||
|
} |
||||||
|
} else console.log('\x1b[32m%s\x1b[0m', '[Slither Analysis]: Compiler version is same as installed solc version') |
||||||
|
} |
||||||
|
// Allow paths and set solc remapping for import URLs
|
||||||
|
const fileContent = readFileSync(`${this.currentSharedFolder}/${filePath}`, 'utf8') |
||||||
|
const importsArr = fileContent.match(/import ['"][^.|..](.+?)['"];/g) |
||||||
|
let allowPaths = ''; let remaps = '' |
||||||
|
if (importsArr?.length) { |
||||||
|
const { remapString, allowPathString } = this.mapNpmDepsDir(importsArr) |
||||||
|
allowPaths = allowPathString |
||||||
|
remaps = remapString.trim() |
||||||
|
} |
||||||
|
const allowPathsOption: string = allowPaths ? `--allow-paths ${allowPaths}` : '' |
||||||
|
const optimizeOption: string = optimize ? ' --optimize ' : '' |
||||||
|
const evmOption: string = evmVersion ? ` --evm-version ${evmVersion}` : '' |
||||||
|
const solcArgs: string = optimizeOption || evmOption || allowPathsOption ? `--solc-args '${allowPathsOption}${optimizeOption}${evmOption}'` : '' |
||||||
|
const solcRemaps = remaps ? `--solc-remaps "${remaps}"` : '' |
||||||
|
|
||||||
|
const outputFile: string = 'remix-slitherReport_' + Math.floor(Date.now() / 1000) + '.json' |
||||||
|
const cmd: string = `slither ${filePath} ${solcArgs} ${solcRemaps} --json ${outputFile}` |
||||||
|
console.log('\x1b[32m%s\x1b[0m', '[Slither Analysis]: Running Slither...') |
||||||
|
// Added `stdio: 'ignore'` as for contract with NPM imports analysis which is exported in 'stderr'
|
||||||
|
// get too big and hangs the process. We process analysis from the report file only
|
||||||
|
const child = spawn(cmd, { cwd: this.currentSharedFolder, shell: true, stdio: 'ignore' }) |
||||||
|
|
||||||
|
const response = {} |
||||||
|
child.on('close', () => { |
||||||
|
const outputFileAbsPath: string = `${this.currentSharedFolder}/${outputFile}` |
||||||
|
// Check if slither report file exists
|
||||||
|
if (existsSync(outputFileAbsPath)) { |
||||||
|
let report = readFileSync(outputFileAbsPath, 'utf8') |
||||||
|
report = JSON.parse(report) |
||||||
|
if (report['success']) { |
||||||
|
response['status'] = true |
||||||
|
if (!report['results'] || !report['results'].detectors || !report['results'].detectors.length) { |
||||||
|
response['count'] = 0 |
||||||
|
} else { |
||||||
|
const { detectors } = report['results'] |
||||||
|
response['count'] = detectors.length |
||||||
|
response['data'] = this.transform(detectors) |
||||||
|
} |
||||||
|
console.log('\x1b[32m%s\x1b[0m', `[Slither Analysis]: Analysis Completed!! ${response['count']} warnings found.`) |
||||||
|
resolve(response) |
||||||
|
} else { |
||||||
|
console.log(report['error']) |
||||||
|
reject(new Error('Error in running Slither Analysis.')) |
||||||
|
} |
||||||
|
} else reject(new Error('Error in generating Slither Analysis Report. Make sure Slither is properly installed.')) |
||||||
|
}) |
||||||
|
}) |
||||||
|
} |
||||||
|
} |
Loading…
Reference in new issue