Merge branch 'master' into fixipfs

pull/1399/head
bunsenstraat 3 years ago committed by GitHub
commit 0352c6fc5a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 3
      apps/remix-ide/src/app.js
  2. 2
      apps/remix-ide/src/app/editor/sourceHighlighter.js
  3. 5
      apps/remix-ide/src/app/files/fileManager.js
  4. 3
      apps/remix-ide/src/app/files/remixDProvider.js
  5. 2
      apps/remix-ide/src/app/files/remixd-handle.js
  6. 18
      apps/remix-ide/src/app/files/slither-handle.js
  7. 2
      apps/remix-ide/src/app/panels/file-panel.js
  8. 1
      apps/remix-ide/src/app/panels/tab-proxy.js
  9. 5
      apps/remix-ide/src/app/tabs/compileTab/compileTab.js
  10. 1
      apps/remix-ide/src/remixEngine.js
  11. 10
      libs/remix-ui/checkbox/src/lib/remix-ui-checkbox.tsx
  12. 2
      libs/remix-ui/file-explorer/src/lib/file-explorer.tsx
  13. 1
      libs/remix-ui/solidity-compiler/src/lib/css/style.css
  14. 5
      libs/remix-ui/solidity-compiler/src/lib/logic/compileTabLogic.ts
  15. 145
      libs/remix-ui/static-analyser/src/lib/remix-ui-static-analyser.tsx
  16. 8
      libs/remixd/src/bin/remixd.ts
  17. 4
      libs/remixd/src/index.ts
  18. 1
      libs/remixd/src/serviceList.ts
  19. 34
      libs/remixd/src/services/remixdClient.ts
  20. 162
      libs/remixd/src/services/slitherClient.ts
  21. 12
      libs/remixd/src/types/index.ts
  22. 3
      libs/remixd/src/websocket.ts

@ -448,7 +448,8 @@ Please make a backup of your contracts and start using http://remix.ethereum.org
test,
filePanel.remixdHandle,
filePanel.gitHandle,
filePanel.hardhatHandle
filePanel.hardhatHandle,
filePanel.slitherHandle
])
if (isElectron()) {

@ -37,7 +37,7 @@ class SourceHighlighter {
this.statementMarker = null
this.fullLineMarker = null
this.source = null
if (lineColumnPos) {
if (lineColumnPos && lineColumnPos.start && lineColumnPos.end) {
this.source = filePath
this.style = style || 'var(--info)'
// if (!this.source) this.source = this._deps.fileManager.currentFile()

@ -155,9 +155,9 @@ class FileManager extends Plugin {
* @param {string} path path of the directory
* @returns {boolean} true if path is a directory.
*/
isDirectory (path) {
async isDirectory (path) {
const provider = this.fileProviderOf(path)
const result = provider.isDirectory(path)
const result = await provider.isDirectory(path)
return result
}
@ -362,7 +362,6 @@ class FileManager extends Plugin {
path = this.limitPluginScope(path)
await this._handleExists(path, `Cannot remove file or directory ${path}`)
const provider = this.fileProviderOf(path)
return await provider.remove(path)
} catch (e) {
throw new Error(e)

@ -38,7 +38,7 @@ module.exports = class RemixDProvider extends FileProvider {
})
this._appManager.on('remixd', 'fileRenamed', (oldPath, newPath) => {
this.event.emit('fileRemoved', oldPath, newPath)
this.event.emit('fileRenamed', oldPath, newPath)
})
this._appManager.on('remixd', 'rootFolderChanged', () => {
@ -141,7 +141,6 @@ module.exports = class RemixDProvider extends FileProvider {
this._appManager.call('remixd', 'remove', { path: unprefixedpath })
.then(result => {
const path = unprefixedpath
delete this.filesContent[path]
resolve(true)
this.init()

@ -43,6 +43,7 @@ export class RemixdHandle extends WebsocketPlugin {
if (super.socket) super.deactivate()
// this.appManager.deactivatePlugin('git') // plugin call doesn't work.. see issue https://github.com/ethereum/remix-plugin/issues/342
if (this.appManager.actives.includes('hardhat')) this.appManager.deactivatePlugin('hardhat')
if (this.appManager.actives.includes('slither')) this.appManager.deactivatePlugin('slither')
this.localhostProvider.close((error) => {
if (error) console.log(error)
})
@ -88,6 +89,7 @@ export class RemixdHandle extends WebsocketPlugin {
this.call('filePanel', 'setWorkspace', { name: LOCALHOST, isLocalhost: true }, true)
})
this.call('manager', 'activatePlugin', 'hardhat')
this.call('manager', 'activatePlugin', 'slither')
}
}
if (this.localhostProvider.isConnected()) {

@ -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)
}
}

@ -9,6 +9,7 @@ import { checkSpecialChars, checkSlash } from '../../lib/helper'
const { RemixdHandle } = require('../files/remixd-handle.js')
const { GitHandle } = require('../files/git-handle.js')
const { HardhatHandle } = require('../files/hardhat-handle.js')
const { SlitherHandle } = require('../files/slither-handle.js')
const globalRegistry = require('../../global/registry')
const examples = require('../editor/examples')
const GistHandler = require('../../lib/gist-handler')
@ -59,6 +60,7 @@ module.exports = class Filepanel extends ViewPlugin {
this.remixdHandle = new RemixdHandle(this._deps.fileProviders.localhost, appManager)
this.gitHandle = new GitHandle()
this.hardhatHandle = new HardhatHandle()
this.slitherHandle = new SlitherHandle()
this.registeredMenuItems = []
this.removedMenuItems = []
this.request = {}

@ -44,7 +44,6 @@ export class TabProxy extends Plugin {
fileManager.events.on('fileRemoved', (name) => {
const workspace = this.fileManager.currentWorkspace()
workspace ? this.removeTab(workspace + '/' + name) : this.removeTab(this.fileManager.mode + '/' + name)
})

@ -7,6 +7,7 @@ const profile = {
name: 'solidity-logic',
displayName: 'Solidity compiler logic',
description: 'Compile solidity contracts - Logic',
methods: ['getCompilerState'],
version: packageJson.version
}
@ -68,6 +69,10 @@ class CompileTab extends Plugin {
this.compiler.set('language', lang)
}
getCompilerState () {
return this.compiler.state
}
/**
* Compile a specific file of the file manager
* @param {string} target the path to the file to compile

@ -10,6 +10,7 @@ export class RemixEngine extends Engine {
setPluginOption ({ name, kind }) {
if (kind === 'provider') return { queueTimeout: 60000 * 2 }
if (name === 'LearnEth') return { queueTimeout: 60000 }
if (name === 'slither') return { queueTimeout: 60000 * 4 } // Requires when a solc version is installed
return { queueTimeout: 10000 }
}

@ -1,4 +1,4 @@
import React from 'react' //eslint-disable-line
import React, { CSSProperties } from 'react' //eslint-disable-line
import './remix-ui-checkbox.css'
/* eslint-disable-next-line */
@ -12,6 +12,8 @@ export interface RemixUiCheckboxProps {
id?: string
itemName?: string
categoryId?: string
visibility?: string
display?: string
}
export const RemixUiCheckbox = ({
@ -23,10 +25,12 @@ export const RemixUiCheckbox = ({
checked,
onChange,
itemName,
categoryId
categoryId,
visibility,
display = 'flex'
}: RemixUiCheckboxProps) => {
return (
<div className="listenOnNetwork_2A0YE0 custom-control custom-checkbox" style={{ display: 'flex', alignItems: 'center' }} onClick={onClick}>
<div className="listenOnNetwork_2A0YE0 custom-control custom-checkbox" style={{ display: display, alignItems: 'center', visibility: visibility } as CSSProperties } onClick={onClick}>
<input
id={id}
type={inputType}

@ -408,7 +408,7 @@ export const FileExplorer = (props: FileExplorerProps) => {
try {
await fileManager.remove(p)
} catch (e) {
const isDir = state.fileManager.isDirectory(p)
const isDir = await state.fileManager.isDirectory(p)
toast(`Failed to remove ${isDir ? 'folder' : 'file'} ${p}.`)
}
}

@ -103,7 +103,6 @@
}
.remixui_container {
margin: 0;
margin-bottom: 2%;
}
.remixui_optimizeContainer {
display: flex;

@ -7,6 +7,7 @@ const profile = {
name: 'solidity-logic',
displayName: 'Solidity compiler logic',
description: 'Compile solidity contracts - Logic',
methods: ['getCompilerState'],
version: packageJson.version
}
export class CompileTab extends Plugin {
@ -60,6 +61,10 @@ export class CompileTab extends Plugin {
this.compiler.set('evmVersion', this.evmVersion)
}
getCompilerState () {
return this.compiler.state
}
/**
* Set the compiler to using Solidity or Yul (default to Solidity)
* @params lang {'Solidity' | 'Yul'} ...

@ -55,10 +55,12 @@ export const RemixUiStaticAnalyser = (props: RemixUiStaticAnalyserProps) => {
return indexOfCategory
}
const [autoRun, setAutoRun] = useState(true)
const [slitherEnabled, setSlitherEnabled] = useState(false)
const [showSlither, setShowSlither] = useState('hidden')
const [categoryIndex, setCategoryIndex] = useState(groupedModuleIndex(groupedModules))
const warningContainer = React.useRef(null)
const [warningState, setWarningState] = useState([])
const [warningState, setWarningState] = useState({})
const [state, dispatch] = useReducer(analysisReducer, initialState)
useEffect(() => {
@ -66,7 +68,7 @@ export const RemixUiStaticAnalyser = (props: RemixUiStaticAnalyserProps) => {
}, [])
useEffect(() => {
setWarningState([])
setWarningState({})
if (autoRun) {
if (state.data !== null) {
run(state.data, state.source, state.file)
@ -78,13 +80,15 @@ export const RemixUiStaticAnalyser = (props: RemixUiStaticAnalyserProps) => {
}, [state])
useEffect(() => {
props.analysisModule.on('filePanel', 'setWorkspace', () => {
props.analysisModule.on('filePanel', 'setWorkspace', (currentWorkspace) => {
// Reset warning state
setWarningState([])
// Reset badge
props.event.trigger('staticAnaysisWarning', [])
// Reset state
dispatch({ type: '', payload: {} })
// Show 'Enable Slither Analysis' checkbox
if (currentWorkspace && currentWorkspace.isLocalhost === true) setShowSlither('visible')
})
return () => { }
}, [props.analysisModule])
@ -103,12 +107,35 @@ export const RemixUiStaticAnalyser = (props: RemixUiStaticAnalyserProps) => {
)
}
const showWarnings = (warningMessage, groupByKey) => {
const resultArray = []
warningMessage.map(x => {
resultArray.push(x)
})
function groupBy (objectArray, property) {
return objectArray.reduce((acc, obj) => {
const key = obj[property]
if (!acc[key]) {
acc[key] = []
}
// Add object to list for given key's value
acc[key].push(obj)
return acc
}, {})
}
const groupedCategory = groupBy(resultArray, groupByKey)
setWarningState(groupedCategory)
}
const run = (lastCompilationResult, lastCompilationSource, currentFile) => {
if (state.data !== null) {
if (lastCompilationResult && categoryIndex.length > 0) {
if (lastCompilationResult && (categoryIndex.length > 0 || slitherEnabled)) {
let warningCount = 0
const warningMessage = []
const warningErrors = []
// Remix Analysis
runner.run(lastCompilationResult, categoryIndex, results => {
results.map((result) => {
let moduleName
@ -119,7 +146,6 @@ export const RemixUiStaticAnalyser = (props: RemixUiStaticAnalyserProps) => {
}
})
})
const warningErrors = []
result.report.map((item) => {
let location: any = {}
let locationString = 'not available'
@ -163,28 +189,73 @@ export const RemixUiStaticAnalyser = (props: RemixUiStaticAnalyserProps) => {
warningMessage.push({ msg, options, hasWarning: true, warningModuleName: moduleName })
})
})
const resultArray = []
warningMessage.map(x => {
resultArray.push(x)
})
function groupBy (objectArray, property) {
return objectArray.reduce((acc, obj) => {
const key = obj[property]
if (!acc[key]) {
acc[key] = []
// Slither Analysis
if (slitherEnabled) {
props.analysisModule.call('solidity-logic', 'getCompilerState').then((compilerState) => {
const { currentVersion, optimize, evmVersion } = compilerState
props.analysisModule.call('terminal', 'log', { type: 'info', value: '[Slither Analysis]: Running...' })
props.analysisModule.call('slither', 'analyse', state.file, { currentVersion, optimize, evmVersion }).then((result) => {
if (result.status) {
props.analysisModule.call('terminal', 'log', { type: 'info', value: `[Slither Analysis]: Analysis Completed!! ${result.count} warnings found.` })
const report = result.data
report.map((item) => {
let location: any = {}
let locationString = 'not available'
let column = 0
let row = 0
let fileName = currentFile
if (item.sourceMap && item.sourceMap.length) {
const fileIndex = Object.keys(lastCompilationResult.sources).indexOf(item.sourceMap[0].source_mapping.filename_relative)
if (fileIndex >= 0) {
location = {
start: item.sourceMap[0].source_mapping.start,
length: item.sourceMap[0].source_mapping.length
}
// Add object to list for given key's value
acc[key].push(obj)
return acc
}, {})
location = props.analysisModule._deps.offsetToLineColumnConverter.offsetToLineColumn(
location,
fileIndex,
lastCompilationSource.sources,
lastCompilationResult.sources
)
row = location.start.line
column = location.start.column
locationString = row + 1 + ':' + column + ':'
fileName = Object.keys(lastCompilationResult.sources)[fileIndex]
}
const groupedCategory = groupBy(resultArray, 'warningModuleName')
setWarningState(groupedCategory)
}
warningCount++
const msg = message(item.title, item.description, item.more, fileName, locationString)
const options = {
type: 'warning',
useSpan: true,
errFile: fileName,
fileName,
errLine: row,
errCol: column,
item: { warning: item.description },
name: item.title,
locationString,
more: item.more,
location: location
}
warningErrors.push(options)
warningMessage.push({ msg, options, hasWarning: true, warningModuleName: 'Slither Analysis' })
})
if (categoryIndex.length > 0) {
showWarnings(warningMessage, 'warningModuleName')
props.event.trigger('staticAnaysisWarning', [warningCount])
}
}).catch((error) => {
console.log('Error found:', error) // This should be removed once testing done
props.analysisModule.call('terminal', 'log', { type: 'error', value: '[Slither Analysis]: Error occured! See remixd console for details.' })
showWarnings(warningMessage, 'warningModuleName')
})
})
} else {
showWarnings(warningMessage, 'warningModuleName')
props.event.trigger('staticAnaysisWarning', [warningCount])
}
})
} else {
if (categoryIndex.length) {
warningContainer.current.innerText = 'No compiled AST available'
@ -220,6 +291,14 @@ export const RemixUiStaticAnalyser = (props: RemixUiStaticAnalyserProps) => {
}
}
const handleSlitherEnabled = () => {
if (slitherEnabled) {
setSlitherEnabled(false)
} else {
setSlitherEnabled(true)
}
}
const handleAutoRun = () => {
if (autoRun) {
setAutoRun(false)
@ -317,7 +396,18 @@ export const RemixUiStaticAnalyser = (props: RemixUiStaticAnalyserProps) => {
label="Autorun"
onChange={() => {}}
/>
<Button buttonText="Run" onClick={() => run(state.data, state.source, state.file)} disabled={state.data === null || categoryIndex.length === 0 }/>
<Button buttonText="Run" onClick={() => run(state.data, state.source, state.file)} disabled={(state.data === null || categoryIndex.length === 0) && !slitherEnabled }/>
</div>
<div className="d-flex" id="enableSlitherAnalysis">
<RemixUiCheckbox
id="enableSlither"
inputType="checkbox"
onClick={handleSlitherEnabled}
checked={slitherEnabled}
label="Enable Slither Analysis"
onChange={() => {}}
visibility = {showSlither}
/>
</div>
</div>
<div id="staticanalysismodules" className="list-group list-group-flush">
@ -330,7 +420,7 @@ export const RemixUiStaticAnalyser = (props: RemixUiStaticAnalyserProps) => {
}
</div>
<div className="mt-2 p-2 d-flex border-top flex-column">
<span>last results for:</span>
<span>Last results for:</span>
<span
className="text-break break-word word-break font-weight-bold"
id="staticAnalysisCurrentFile"
@ -338,6 +428,7 @@ export const RemixUiStaticAnalyser = (props: RemixUiStaticAnalyserProps) => {
{state.file}
</span>
</div>
<br/>
{Object.entries(warningState).length > 0 &&
<div id='staticanalysisresult' >
<div className="mb-4">
@ -345,9 +436,9 @@ export const RemixUiStaticAnalyser = (props: RemixUiStaticAnalyserProps) => {
(Object.entries(warningState).map((element, index) => (
<div key={index}>
<span className="text-dark h6">{element[0]}</span>
{element[1].map((x, i) => (
x.hasWarning ? (
<div id={`staticAnalysisModule${element[1].warningModuleName}`} key={i}>
{element[1]['map']((x, i) => ( // eslint-disable-line dot-notation
x.hasWarning ? ( // eslint-disable-next-line dot-notation
<div id={`staticAnalysisModule${element[1]['warningModuleName']}`} key={i}>
<ErrorRenderer message={x.msg} opt={x.options} warningErrors={ x.warningErrors} editor={props.analysisModule}/>
</div>

@ -25,6 +25,7 @@ async function warnLatestVersion () {
const services = {
git: (readOnly: boolean) => new servicesList.GitClient(readOnly),
hardhat: (readOnly: boolean) => new servicesList.HardhatClient(readOnly),
slither: (readOnly: boolean) => new servicesList.SlitherClient(readOnly),
folder: (readOnly: boolean) => new servicesList.Sharedfolder(readOnly)
}
@ -32,11 +33,12 @@ const services = {
const ports = {
git: 65521,
hardhat: 65522,
slither: 65523,
folder: 65520
}
const killCallBack: Array<Function> = []
function startService<S extends 'git' | 'hardhat' | 'folder'> (service: S, callback: (ws: WS, sharedFolderClient: servicesList.Sharedfolder, error?:Error) => void) {
function startService<S extends 'git' | 'hardhat' | 'slither' | 'folder'> (service: S, callback: (ws: WS, sharedFolderClient: servicesList.Sharedfolder, error?:Error) => void) {
const socket = new WebSocket(ports[service], { remixIdeUrl: program.remixIde }, () => services[service](program.readOnly || false))
socket.start(callback)
killCallBack.push(socket.close.bind(socket))
@ -94,6 +96,10 @@ function errorHandler (error: any, service: string) {
sharedFolderClient.setupNotifications(program.sharedFolder)
sharedFolderClient.sharedFolder(program.sharedFolder)
})
startService('slither', (ws: WS, sharedFolderClient: servicesList.Sharedfolder) => {
sharedFolderClient.setWebSocket(ws)
sharedFolderClient.sharedFolder(program.sharedFolder)
})
// Run hardhat service if a hardhat project is shared as folder
const hardhatConfigFilePath = absolutePath('./', program.sharedFolder) + '/hardhat.config.js'
const isHardhatProject = fs.existsSync(hardhatConfigFilePath)

@ -2,6 +2,7 @@
import { RemixdClient as sharedFolder } from './services/remixdClient'
import { GitClient } from './services/gitClient'
import { HardhatClient } from './services/hardhatClient'
import { SlitherClient } from './services/slitherClient'
import Websocket from './websocket'
import * as utils from './utils'
@ -11,6 +12,7 @@ module.exports = {
services: {
sharedFolder,
GitClient,
HardhatClient
HardhatClient,
SlitherClient
}
}

@ -1,3 +1,4 @@
export { RemixdClient as Sharedfolder } from './services/remixdClient'
export { GitClient } from './services/gitClient'
export { HardhatClient } from './services/hardhatClient'
export { SlitherClient } from './services/slitherClient'

@ -180,12 +180,34 @@ export class RemixdClient extends PluginClient {
if (!fs.existsSync(path)) return reject(new Error('File not found ' + path))
if (!isRealPath(path)) return
// Saving the content of the item{folder} before removing it
const ls = []
try {
const resolveList = (path) => {
if (!this._isFile(path)) {
const list = utils.resolveDirectory(path, this.currentSharedFolder)
Object.keys(list).forEach(itemPath => {
if (list[itemPath].isDirectory) {
resolveList(`${this.currentSharedFolder}/${itemPath}`)
}
ls.push(itemPath)
})
}
}
resolveList(path)
ls.push(args.path)
} catch (e) {
throw new Error(e)
}
return fs.remove(path, (error: Error) => {
if (error) {
console.log(error)
return reject(new Error('Failed to remove file/directory: ' + error))
}
this.emit('fileRemoved', args.path)
for (const file in ls) {
this.emit('fileRemoved', ls[file])
}
resolve(true)
})
})
@ -194,10 +216,17 @@ export class RemixdClient extends PluginClient {
}
}
_isFile (path: string): boolean {
try {
return fs.statSync(path).isFile()
} catch (error) {
throw new Error(error)
}
}
isDirectory (args: SharedFolderArgs): boolean {
try {
const path = utils.absolutePath(args.path, this.currentSharedFolder)
return fs.statSync(path).isDirectory()
} catch (error) {
throw new Error(error)
@ -207,7 +236,6 @@ export class RemixdClient extends PluginClient {
isFile (args: SharedFolderArgs): boolean {
try {
const path = utils.absolutePath(args.path, this.currentSharedFolder)
return fs.statSync(path).isFile()
} catch (error) {
throw new Error(error)

@ -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.'))
})
})
}
}

@ -1,6 +1,18 @@
import * as ServiceList from '../serviceList'
import * as Websocket from 'ws'
export interface OutputStandard {
description: string
title: string
confidence: string
severity: string
sourceMap: any
category?: string
reference?: string
example?: any
[key: string]: any
}
type ServiceListKeys = keyof typeof ServiceList;
export type Service = typeof ServiceList[ServiceListKeys]

@ -19,7 +19,8 @@ export default class WebSocket {
const listeners = {
65520: 'remixd',
65521: 'git',
65522: 'hardhat'
65522: 'hardhat',
65523: 'slither'
}
this.server.on('error', (error: Error) => {

Loading…
Cancel
Save