add plugins

pull/4964/head
Your Name 7 months ago
parent 2314a3a32e
commit ebbdf6b5ea
  1. 138
      apps/remix-ide/src/app.js
  2. 4
      apps/remix-ide/src/app/panels/file-panel.js
  3. 13
      apps/remix-ide/src/app/plugins/electron/foundryPlugin.ts
  4. 13
      apps/remix-ide/src/app/plugins/electron/hardhatPlugin.ts
  5. 6
      apps/remixdesktop/src/engine.ts
  6. 207
      apps/remixdesktop/src/lib/foundry.ts
  7. 1
      apps/remixdesktop/src/lib/remixd.ts
  8. 193
      apps/remixdesktop/src/lib/slither.ts
  9. 2
      apps/remixdesktop/src/lib/utils.ts
  10. 246
      apps/remixdesktop/src/plugins/foundryPlugin.ts
  11. 219
      apps/remixdesktop/src/plugins/hardhatPlugin.ts
  12. 175
      apps/remixdesktop/src/plugins/slitherPlugin.ts
  13. 2
      apps/remixdesktop/src/preload.ts

@ -1,45 +1,45 @@
'use strict'
import {RunTab, makeUdapp} from './app/udapp'
import {RemixEngine} from './remixEngine'
import {RemixAppManager} from './remixAppManager'
import {ThemeModule} from './app/tabs/theme-module'
import {LocaleModule} from './app/tabs/locale-module'
import {NetworkModule} from './app/tabs/network-module'
import {Web3ProviderModule} from './app/tabs/web3-provider'
import {CompileAndRun} from './app/tabs/compile-and-run'
import {PluginStateLogger} from './app/tabs/state-logger'
import {SidePanel} from './app/components/side-panel'
import {StatusBar} from './app/components/status-bar'
import {HiddenPanel} from './app/components/hidden-panel'
import {PinnedPanel} from './app/components/pinned-panel'
import {VerticalIcons} from './app/components/vertical-icons'
import {LandingPage} from './app/ui/landing-page/landing-page'
import {MainPanel} from './app/components/main-panel'
import {PermissionHandlerPlugin} from './app/plugins/permission-handler-plugin'
import {AstWalker} from '@remix-project/remix-astwalker'
import {LinkLibraries, DeployLibraries, OpenZeppelinProxy} from '@remix-project/core-plugin'
import {CodeParser} from './app/plugins/parser/code-parser'
import {SolidityScript} from './app/plugins/solidity-script'
import {WalkthroughService} from './walkthroughService'
import {OffsetToLineColumnConverter, CompilerMetadata, CompilerArtefacts, FetchAndCompile, CompilerImports, GistHandler} from '@remix-project/core-plugin'
import {Registry} from '@remix-project/remix-lib'
import {ConfigPlugin} from './app/plugins/config'
import {StoragePlugin} from './app/plugins/storage'
import {Layout} from './app/panels/layout'
import {NotificationPlugin} from './app/plugins/notification'
import {Blockchain} from './blockchain/blockchain'
import {MergeVMProvider, LondonVMProvider, BerlinVMProvider, ShanghaiVMProvider, CancunVMProvider} from './app/providers/vm-provider'
import {MainnetForkVMProvider} from './app/providers/mainnet-vm-fork-provider'
import {SepoliaForkVMProvider} from './app/providers/sepolia-vm-fork-provider'
import {GoerliForkVMProvider} from './app/providers/goerli-vm-fork-provider'
import {CustomForkVMProvider} from './app/providers/custom-vm-fork-provider'
import {HardhatProvider} from './app/providers/hardhat-provider'
import {GanacheProvider} from './app/providers/ganache-provider'
import {FoundryProvider} from './app/providers/foundry-provider'
import {ExternalHttpProvider} from './app/providers/external-http-provider'
import { RunTab, makeUdapp } from './app/udapp'
import { RemixEngine } from './remixEngine'
import { RemixAppManager } from './remixAppManager'
import { ThemeModule } from './app/tabs/theme-module'
import { LocaleModule } from './app/tabs/locale-module'
import { NetworkModule } from './app/tabs/network-module'
import { Web3ProviderModule } from './app/tabs/web3-provider'
import { CompileAndRun } from './app/tabs/compile-and-run'
import { PluginStateLogger } from './app/tabs/state-logger'
import { SidePanel } from './app/components/side-panel'
import { StatusBar } from './app/components/status-bar'
import { HiddenPanel } from './app/components/hidden-panel'
import { PinnedPanel } from './app/components/pinned-panel'
import { VerticalIcons } from './app/components/vertical-icons'
import { LandingPage } from './app/ui/landing-page/landing-page'
import { MainPanel } from './app/components/main-panel'
import { PermissionHandlerPlugin } from './app/plugins/permission-handler-plugin'
import { AstWalker } from '@remix-project/remix-astwalker'
import { LinkLibraries, DeployLibraries, OpenZeppelinProxy } from '@remix-project/core-plugin'
import { CodeParser } from './app/plugins/parser/code-parser'
import { SolidityScript } from './app/plugins/solidity-script'
import { WalkthroughService } from './walkthroughService'
import { OffsetToLineColumnConverter, CompilerMetadata, CompilerArtefacts, FetchAndCompile, CompilerImports, GistHandler } from '@remix-project/core-plugin'
import { Registry } from '@remix-project/remix-lib'
import { ConfigPlugin } from './app/plugins/config'
import { StoragePlugin } from './app/plugins/storage'
import { Layout } from './app/panels/layout'
import { NotificationPlugin } from './app/plugins/notification'
import { Blockchain } from './blockchain/blockchain'
import { MergeVMProvider, LondonVMProvider, BerlinVMProvider, ShanghaiVMProvider, CancunVMProvider } from './app/providers/vm-provider'
import { MainnetForkVMProvider } from './app/providers/mainnet-vm-fork-provider'
import { SepoliaForkVMProvider } from './app/providers/sepolia-vm-fork-provider'
import { GoerliForkVMProvider } from './app/providers/goerli-vm-fork-provider'
import { CustomForkVMProvider } from './app/providers/custom-vm-fork-provider'
import { HardhatProvider } from './app/providers/hardhat-provider'
import { GanacheProvider } from './app/providers/ganache-provider'
import { FoundryProvider } from './app/providers/foundry-provider'
import { ExternalHttpProvider } from './app/providers/external-http-provider'
import { FileDecorator } from './app/plugins/file-decorator'
import { CodeFormat } from './app/plugins/code-format'
import { SolidityUmlGen } from './app/plugins/solidity-umlgen'
@ -58,7 +58,12 @@ import { compilerLoaderPlugin, compilerLoaderPluginDesktop } from './app/plugins
import { appUpdaterPlugin } from './app/plugins/electron/appUpdaterPlugin'
import { SlitherHandleDesktop } from './app/plugins/electron/slitherPlugin'
import { SlitherHandle } from './app/files/slither-handle'
import {SolCoder} from './app/plugins/solcoderAI'
import { FoundryHandle } from './app/files/foundry-handle'
import { FoundryHandleDesktop } from './app/plugins/electron/foundryPlugin'
import { HardhatHandle } from './app/files/hardhat-handle'
import { HardhatHandleDesktop } from './app/plugins/electron/hardhatPlugin'
import { SolCoder } from './app/plugins/solcoderAI'
const isElectron = require('is-electron')
@ -75,6 +80,7 @@ const Config = require('./config')
const FileManager = require('./app/files/fileManager')
import FileProvider from "./app/files/fileProvider"
import { appPlatformTypes } from '@remix-ui/app'
const DGitProvider = require('./app/files/dgitProvider')
const WorkspaceFileProvider = require('./app/files/workspaceFileProvider')
@ -83,19 +89,19 @@ const PluginManagerComponent = require('./app/components/plugin-manager-componen
const CompileTab = require('./app/tabs/compile-tab')
const SettingsTab = require('./app/tabs/settings-tab')
const AnalysisTab = require('./app/tabs/analysis-tab')
const {DebuggerTab} = require('./app/tabs/debugger-tab')
const { DebuggerTab } = require('./app/tabs/debugger-tab')
const TestTab = require('./app/tabs/test-tab')
const FilePanel = require('./app/panels/file-panel')
const Editor = require('./app/editor/editor')
const Terminal = require('./app/panels/terminal')
const {TabProxy} = require('./app/panels/tab-proxy.js')
const { TabProxy } = require('./app/panels/tab-proxy.js')
export class platformApi {
get name () {
get name() {
return isElectron() ? appPlatformTypes.desktop : appPlatformTypes.web
}
isDesktop () {
isDesktop() {
return isElectron()
}
}
@ -115,7 +121,7 @@ class AppComponent {
// load app config
const config = new Config(configStorage)
Registry.getInstance().put({api: config, name: 'config'})
Registry.getInstance().put({ api: config, name: 'config' })
// load file system
this._components.filesProviders = {}
@ -196,12 +202,12 @@ class AppComponent {
this.themeModule = new ThemeModule()
// ----------------- locale service ---------------------------------
this.localeModule = new LocaleModule()
Registry.getInstance().put({api: this.themeModule, name: 'themeModule'})
Registry.getInstance().put({api: this.localeModule, name: 'localeModule'})
Registry.getInstance().put({ api: this.themeModule, name: 'themeModule' })
Registry.getInstance().put({ api: this.localeModule, name: 'localeModule' })
// ----------------- editor service ----------------------------
const editor = new Editor() // wrapper around ace editor
Registry.getInstance().put({api: editor, name: 'editor'})
Registry.getInstance().put({ api: editor, name: 'editor' })
editor.event.register('requiringToSaveCurrentfile', (currentFile) => {
fileManager.saveCurrentFile()
if (currentFile.endsWith('.circom')) this.appManager.activatePlugin(['circuit-compiler'])
@ -209,7 +215,7 @@ class AppComponent {
// ----------------- fileManager service ----------------------------
const fileManager = new FileManager(editor, appManager)
Registry.getInstance().put({api: fileManager, name: 'filemanager'})
Registry.getInstance().put({ api: fileManager, name: 'filemanager' })
// ----------------- dGit provider ---------------------------------
const dGitProvider = new DGitProvider()
@ -288,7 +294,7 @@ class AppComponent {
// -------------------Terminal----------------------------------------
makeUdapp(blockchain, compilersArtefacts, (domEl) => terminal.logHtml(domEl))
const terminal = new Terminal(
{appManager, blockchain},
{ appManager, blockchain },
{
getPosition: (event) => {
const limitUp = 36
@ -382,16 +388,24 @@ class AppComponent {
this.engine.register([appUpdater])
}
const compilerloader = isElectron()? new compilerLoaderPluginDesktop(): new compilerLoaderPlugin()
const compilerloader = isElectron() ? new compilerLoaderPluginDesktop() : new compilerLoaderPlugin()
this.engine.register([compilerloader])
// slither analyzer plugin (remixd / desktop)
const slitherPlugin = isElectron() ? new SlitherHandleDesktop() : new SlitherHandle()
this.engine.register([slitherPlugin])
//foundry plugin
const foundryPlugin = isElectron() ? new FoundryHandleDesktop() : new FoundryHandle()
this.engine.register([foundryPlugin])
// hardhat plugin
const hardhatPlugin = isElectron() ? new HardhatHandleDesktop() : new HardhatHandle()
this.engine.register([hardhatPlugin])
// LAYOUT & SYSTEM VIEWS
const appPanel = new MainPanel()
Registry.getInstance().put({api: this.mainview, name: 'mainview'})
Registry.getInstance().put({ api: this.mainview, name: 'mainview' })
const tabProxy = new TabProxy(fileManager, editor)
this.engine.register([appPanel, tabProxy])
@ -443,8 +457,6 @@ class AppComponent {
analysis,
test,
filePanel.remixdHandle,
filePanel.hardhatHandle,
filePanel.foundryHandle,
filePanel.truffleHandle,
linkLibraries,
deployLibraries,
@ -453,10 +465,10 @@ class AppComponent {
])
this.layout.panels = {
tabs: {plugin: tabProxy, active: true},
editor: {plugin: editor, active: true},
main: {plugin: appPanel, active: false},
terminal: {plugin: terminal, active: true, minimized: false}
tabs: { plugin: tabProxy, active: true },
editor: { plugin: editor, active: true },
main: { plugin: appPanel, active: false },
terminal: { plugin: terminal, active: true, minimized: false }
}
}
@ -469,7 +481,7 @@ class AppComponent {
} catch (e) {
console.log("couldn't register iframe plugins", e.message)
}
if (isElectron()){
if (isElectron()) {
await this.appManager.activatePlugin(['fs'])
}
await this.appManager.activatePlugin(['layout'])
@ -511,8 +523,8 @@ class AppComponent {
await this.appManager.activatePlugin(['walkthrough', 'storage', 'search', 'compileAndRun', 'recorder'])
await this.appManager.activatePlugin(['solidity-script', 'remix-templates'])
if (isElectron()){
await this.appManager.activatePlugin(['isogit', 'electronconfig', 'electronTemplates', 'xterm', 'ripgrep', 'appUpdater', 'slither'])
if (isElectron()) {
await this.appManager.activatePlugin(['isogit', 'electronconfig', 'electronTemplates', 'xterm', 'ripgrep', 'appUpdater', 'slither', 'foundry', 'hardhat'])
}
this.appManager.on(

@ -6,8 +6,6 @@ import { FileSystemProvider } from '@remix-ui/workspace' // eslint-disable-line
import {Registry} from '@remix-project/remix-lib'
import { RemixdHandle } from '../plugins/remixd-handle'
import {PluginViewWrapper} from '@remix-ui/helper'
const { HardhatHandle } = require('../files/hardhat-handle.js')
const { FoundryHandle } = require('../files/foundry-handle.js')
const { TruffleHandle } = require('../files/truffle-handle.js')
/*
@ -68,8 +66,6 @@ module.exports = class Filepanel extends ViewPlugin {
this.el.setAttribute('id', 'fileExplorerView')
this.remixdHandle = new RemixdHandle(this.fileProviders.localhost, appManager)
this.hardhatHandle = new HardhatHandle()
this.foundryHandle = new FoundryHandle()
this.truffleHandle = new TruffleHandle()
this.contentImport = contentImport
this.workspaces = []

@ -0,0 +1,13 @@
import { ElectronPlugin } from '@remixproject/engine-electron';
export class FoundryHandleDesktop extends ElectronPlugin {
constructor() {
super({
displayName: 'foundry',
name: 'foundry',
description: 'electron foundry',
methods: ['sync', 'compile']
})
this.methods = ['sync', 'compile']
}
}

@ -0,0 +1,13 @@
import { ElectronPlugin } from '@remixproject/engine-electron';
export class HardhatHandleDesktop extends ElectronPlugin {
constructor() {
super({
displayName: 'hardhat',
name: 'hardhat',
description: 'electron hardhat',
methods: ['sync', 'compile']
})
this.methods = ['sync', 'compile']
}
}

@ -11,6 +11,8 @@ import { RipgrepPlugin } from './plugins/ripgrepPlugin';
import { CompilerLoaderPlugin } from './plugins/compilerLoader';
import { SlitherPlugin } from './plugins/slitherPlugin';
import { AppUpdaterPlugin } from './plugins/appUpdater';
import { FoundryPlugin } from './plugins/foundryPlugin';
import { HardhatPlugin } from './plugins/hardhatPlugin';
const engine = new Engine()
const appManager = new PluginManager()
@ -23,6 +25,8 @@ const ripgrepPlugin = new RipgrepPlugin()
const compilerLoaderPlugin = new CompilerLoaderPlugin()
const slitherPlugin = new SlitherPlugin()
const appUpdaterPlugin = new AppUpdaterPlugin()
const foundryPlugin = new FoundryPlugin()
const hardhatPlugin = new HardhatPlugin()
engine.register(appManager)
engine.register(fsPlugin)
@ -33,7 +37,9 @@ engine.register(templatesPlugin)
engine.register(ripgrepPlugin)
engine.register(compilerLoaderPlugin)
engine.register(slitherPlugin)
engine.register(foundryPlugin)
engine.register(appUpdaterPlugin)
engine.register(hardhatPlugin)
appManager.activatePlugin('electronconfig')
appManager.activatePlugin('fs')

@ -1,207 +0,0 @@
import { spawn } from 'child_process'
import chokidar from 'chokidar'
import fs from 'fs'
import { basename, join } from 'path'
import * as utils from './utils'
export const FoundryClientMixin = (Base) => class extends Base {
currentSharedFolder: string
watcher: chokidar.FSWatcher
warnlog: boolean
buildPath: string
cachePath: string
logTimeout: NodeJS.Timeout
processingTimeout: NodeJS.Timeout
startListening() {
if (fs.existsSync(this.buildPath) && fs.existsSync(this.cachePath)) {
this.listenOnFoundryCompilation()
} else {
this.listenOnFoundryFolder()
}
}
listenOnFoundryFolder() {
console.log('Foundry out folder doesn\'t exist... waiting for the compilation.')
try {
if (this.watcher) this.watcher.close()
this.watcher = chokidar.watch(this.currentSharedFolder, { depth: 1, ignorePermissionErrors: true, ignoreInitial: true })
// watch for new folders
this.watcher.on('addDir', () => {
if (fs.existsSync(this.buildPath) && fs.existsSync(this.cachePath)) {
this.listenOnFoundryCompilation()
}
})
} catch (e) {
console.log(e)
}
}
compile() {
return new Promise((resolve, reject) => {
if (this.readOnly) {
const errMsg = '[Foundry Compilation]: Cannot compile in read-only mode'
return reject(new Error(errMsg))
}
const cmd = `forge build`
const options = { cwd: this.currentSharedFolder, shell: true }
const child = spawn(cmd, options)
let result = ''
let error = ''
child.stdout.on('data', (data) => {
const msg = `[Foundry Compilation]: ${data.toString()}`
console.log('\x1b[32m%s\x1b[0m', msg)
result += msg + '\n'
})
child.stderr.on('data', (err) => {
error += `[Foundry Compilation]: ${err.toString()} \n`
})
child.on('close', () => {
if (error && result) resolve(error + result)
else if (error) reject(error)
else resolve(result)
})
})
}
checkPath() {
if (!fs.existsSync(this.buildPath) || !fs.existsSync(this.cachePath)) {
this.listenOnFoundryFolder()
return false
}
if (!fs.existsSync(join(this.cachePath, 'solidity-files-cache.json'))) return false
return true
}
private async processArtifact() {
if (!this.checkPath()) return
const folderFiles = await fs.promises.readdir(this.buildPath) // "out" folder
try {
const cache = JSON.parse(await fs.promises.readFile(join(this.cachePath, 'solidity-files-cache.json'), { encoding: 'utf-8' }))
// name of folders are file names
for (const file of folderFiles) {
const path = join(this.buildPath, file) // out/Counter.sol/
const compilationResult = {
input: {},
output: {
contracts: {},
sources: {}
},
inputSources: { sources: {}, target: '' },
solcVersion: null,
compilationTarget: null
}
compilationResult.inputSources.target = file
await this.readContract(path, compilationResult, cache)
this.emit('compilationFinished', compilationResult.compilationTarget, { sources: compilationResult.input }, 'soljson', compilationResult.output, compilationResult.solcVersion)
}
clearTimeout(this.logTimeout)
this.logTimeout = setTimeout(() => {
// @ts-ignore
this.call('terminal', 'log', { type: 'log', value: `receiving compilation result from Foundry. Select a file to populate the contract interaction interface.` })
console.log('Syncing compilation result from Foundry')
}, 1000)
} catch (e) {
console.log(e)
}
}
async triggerProcessArtifact() {
// prevent multiple calls
clearTimeout(this.processingTimeout)
this.processingTimeout = setTimeout(async () => await this.processArtifact(), 1000)
}
listenOnFoundryCompilation() {
try {
if (this.watcher) this.watcher.close()
this.watcher = chokidar.watch(this.cachePath, { depth: 0, ignorePermissionErrors: true, ignoreInitial: true })
this.watcher.on('change', async () => await this.triggerProcessArtifact())
this.watcher.on('add', async () => await this.triggerProcessArtifact())
// process the artifact on activation
this.triggerProcessArtifact()
} catch (e) {
console.log(e)
}
}
async readContract(contractFolder, compilationResultPart, cache) {
const files = await fs.promises.readdir(contractFolder)
for (const file of files) {
const path = join(contractFolder, file)
const content = await fs.promises.readFile(path, { encoding: 'utf-8' })
compilationResultPart.inputSources.sources[file] = { content }
await this.feedContractArtifactFile(file, content, compilationResultPart, cache)
}
}
async feedContractArtifactFile(path, content, compilationResultPart, cache) {
const contentJSON = JSON.parse(content)
const contractName = basename(path).replace('.json', '')
let sourcePath = ''
if (contentJSON?.metadata?.settings?.compilationTarget) {
for (const key in contentJSON.metadata.settings.compilationTarget) {
if (contentJSON.metadata.settings.compilationTarget[key] === contractName) {
sourcePath = key
break
}
}
}
if (!sourcePath) return
const currentCache = cache.files[sourcePath]
if (!currentCache.artifacts[contractName]) return
// extract source and version
const metadata = contentJSON.metadata
if (metadata.compiler && metadata.compiler.version) {
compilationResultPart.solcVersion = metadata.compiler.version
} else {
compilationResultPart.solcVersion = ''
console.log('\x1b[32m%s\x1b[0m', 'compiler version not found, please update Foundry to the latest version.')
}
if (metadata.sources) {
for (const path in metadata.sources) {
const absPath = utils.absolutePath(path, this.currentSharedFolder)
try {
const content = await fs.promises.readFile(absPath, { encoding: 'utf-8' })
compilationResultPart.input[path] = { content }
} catch (e) {
compilationResultPart.input[path] = { content: '' }
}
}
} else {
console.log('\x1b[32m%s\x1b[0m', 'sources input not found, please update Foundry to the latest version.')
}
compilationResultPart.compilationTarget = sourcePath
// extract data
if (!compilationResultPart.output['sources'][sourcePath]) compilationResultPart.output['sources'][sourcePath] = {}
compilationResultPart.output['sources'][sourcePath] = {
ast: contentJSON['ast'],
id: contentJSON['id']
}
if (!compilationResultPart.output['contracts'][sourcePath]) compilationResultPart.output['contracts'][sourcePath] = {}
contentJSON.bytecode.object = contentJSON.bytecode.object.replace('0x', '')
contentJSON.deployedBytecode.object = contentJSON.deployedBytecode.object.replace('0x', '')
compilationResultPart.output['contracts'][sourcePath][contractName] = {
abi: contentJSON.abi,
evm: {
bytecode: contentJSON.bytecode,
deployedBytecode: contentJSON.deployedBytecode,
methodIdentifiers: contentJSON.methodIdentifiers
}
}
}
async sync() {
console.log('syncing Foundry with Remix...')
this.processArtifact()
}
}

@ -28,6 +28,7 @@ export class ElectronBasePluginRemixdClient extends ElectronBasePluginClient {
this.onload(async () => {
this.on('fs' as any, 'workingDirChanged', async (path: string) => {
console.log('workingDirChanged base remixd', path)
this.currentSharedFolder = path
})
this.currentSharedFolder = await this.call('fs' as any, 'getWorkingDir')

@ -1,193 +0,0 @@
import { existsSync, readFileSync, readdirSync, unlinkSync } from 'fs'
import * as utils from './utils'
const { spawn, execSync } = require('child_process') // eslint-disable-line
export interface OutputStandard {
description: string
title: string
confidence: string
severity: string
sourceMap: any
category?: string
reference?: string
example?: any
[key: string]: any
}
export const SlitherClientMixin = (Base) => class extends Base {
methods: Array<string>
currentSharedFolder: string
constructor(...args: any[]) {
super(...args); // Ensure the parent constructor is called
}
log(...message: any) {
if (this.log) {
this.log(...message)
} else {
console.log(...message)
}
}
error(...message: any) {
if (this.error) {
this.error(...message)
} else {
console.error(...message)
}
}
mapNpmDepsDir(list) {
const remixNpmDepsPath = utils.absolutePath('.deps/npm', this.currentSharedFolder)
const localNpmDepsPath = utils.absolutePath('node_modules', this.currentSharedFolder)
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) => {
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)
this.log(`[Slither Analysis]: Compiler version is ${versionString}`)
let solcOutput: Buffer
// Check solc current installed version
try {
solcOutput = execSync('solc --version', options)
} catch (err) {
this.error(err)
reject(new Error('Error in running solc command'))
}
if (!solcOutput.toString().includes(versionString)) {
this.log('[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)) {
this.log(`[Slither Analysis]: Installing ${version} using solc-select`)
// Install required version
execSync(`solc-select install ${version}`, options)
}
this.log(`[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) {
this.error(err)
reject(new Error('Error in running solc-select command'))
}
} else this.log('[Slither Analysis]: Compiler version is same as installed solc version')
}
// Allow paths and set solc remapping for import URLs
const fileContent = readFileSync(utils.absolutePath(filePath, this.currentSharedFolder), 'utf8')
const importsArr = fileContent.match(/import ['"][^.|..](.+?)['"];/g)
let remaps = ''
if (importsArr?.length) {
const { remapString } = this.mapNpmDepsDir(importsArr)
remaps = remapString.trim()
}
const optimizeOption: string = optimize ? '--optimize' : ''
const evmOption: string = evmVersion ? `--evm-version ${evmVersion}` : ''
let solcArgs = ''
if (optimizeOption) {
solcArgs += optimizeOption + ' '
}
if (evmOption) {
if (!solcArgs.endsWith(' ')) solcArgs += ' '
solcArgs += evmOption
}
if (solcArgs) {
solcArgs = `--solc-args "${solcArgs.trimStart()}"`
}
const solcRemaps = remaps ? `--solc-remaps "${remaps}"` : ''
const outputFile = 'remix-slither-report.json'
try {
// We don't keep the previous analysis
const outputFilePath = utils.absolutePath(outputFile, this.currentSharedFolder)
if (existsSync(outputFilePath)) unlinkSync(outputFilePath)
} catch (e) {
this.error('unable to remove the output file')
this.error(e.message)
}
const cmd = `slither ${filePath} ${solcArgs} ${solcRemaps} --json ${outputFile}`
this.log('[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 = utils.absolutePath(outputFile, this.currentSharedFolder)
// 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)
}
resolve(response)
} else {
this.log(report['error'])
reject(new Error('Error in running Slither Analysis.'))
}
} else {
this.error('Error in generating Slither Analysis Report. Make sure Slither is properly installed.')
reject(new Error('Error in generating Slither Analysis Report. Make sure Slither is properly installed.'))
}
})
})
}
}

@ -19,6 +19,6 @@ function normalizePath (path) {
return path
}
export { absolutePath }
export { absolutePath, normalizePath }

@ -1,35 +1,247 @@
import { Profile } from "@remixproject/plugin-utils";
import { ElectronBasePlugin, ElectronBasePluginClient } from "@remixproject/plugin-electron"
import chokidar from 'chokidar'
import { ElectronBasePluginRemixdClient } from "../lib/remixd"
import fs from 'fs'
import * as utils from '../lib/utils'
import { FoundryClientMixin } from "../lib/foundry";
import { basename, join } from "path";
import { spawn } from "child_process";
const profile: Profile = {
name: 'slither',
displayName: 'electron slither',
description: 'electron slither',
name: 'foundry',
displayName: 'electron foundry',
description: 'electron foundry',
}
export class FoundryPlugin extends ElectronBasePlugin {
clients: any []
constructor() {
super(profile, clientProfile, FoundryClientMixin(FoundryPluginClient))
this.methods = [...super.methods]
}
clients: any[]
constructor() {
super(profile, clientProfile, FoundryPluginClient)
this.methods = [...super.methods]
}
}
const clientProfile: Profile = {
name: 'foundry',
displayName: 'electron foundry',
description: 'electron foundry',
methods: ['sync', 'compile']
name: 'foundry',
displayName: 'electron foundry',
description: 'electron foundry',
methods: ['sync', 'compile']
}
class FoundryPluginClient extends ElectronBasePluginRemixdClient {
constructor(webContentsId: number, profile: Profile) {
super(webContentsId, profile);
}
watcher: chokidar.FSWatcher
warnlog: boolean
buildPath: string
cachePath: string
logTimeout: NodeJS.Timeout
processingTimeout: NodeJS.Timeout
async onActivation(): Promise<void> {
console.log('Foundry plugin activated')
this.call('terminal', 'log', { type: 'log', value: 'Foundry plugin activated' })
this.startListening()
this.on('fs' as any, 'workingDirChanged', async (path: string) => {
console.log('workingDirChanged foundry', path)
this.currentSharedFolder = path
this.startListening()
})
this.currentSharedFolder = await this.call('fs' as any, 'getWorkingDir')
}
startListening() {
this.buildPath = utils.absolutePath('out', this.currentSharedFolder)
this.cachePath = utils.absolutePath('cache', this.currentSharedFolder)
console.log('Foundry plugin checking for', this.buildPath, this.cachePath)
if (fs.existsSync(this.buildPath) && fs.existsSync(this.cachePath)) {
this.listenOnFoundryCompilation()
} else {
this.listenOnFoundryFolder()
}
}
listenOnFoundryFolder() {
console.log('Foundry out folder doesn\'t exist... waiting for the compilation.')
try {
if (this.watcher) this.watcher.close()
this.watcher = chokidar.watch(this.currentSharedFolder, { depth: 1, ignorePermissionErrors: true, ignoreInitial: true })
// watch for new folders
this.watcher.on('addDir', (path: string) => {
console.log('add dir foundry', path)
if (fs.existsSync(this.buildPath) && fs.existsSync(this.cachePath)) {
this.listenOnFoundryCompilation()
}
})
} catch (e) {
console.log(e)
}
}
compile() {
return new Promise((resolve, reject) => {
const cmd = `forge build`
const options = { cwd: this.currentSharedFolder, shell: true }
const child = spawn(cmd, options)
let result = ''
let error = ''
child.stdout.on('data', (data) => {
const msg = `[Foundry Compilation]: ${data.toString()}`
console.log('\x1b[32m%s\x1b[0m', msg)
result += msg + '\n'
})
child.stderr.on('data', (err) => {
error += `[Foundry Compilation]: ${err.toString()} \n`
})
child.on('close', () => {
if (error && result) resolve(error + result)
else if (error) reject(error)
else resolve(result)
})
})
}
checkPath() {
if (!fs.existsSync(this.buildPath) || !fs.existsSync(this.cachePath)) {
this.listenOnFoundryFolder()
return false
}
if (!fs.existsSync(join(this.cachePath, 'solidity-files-cache.json'))) return false
return true
}
private async processArtifact() {
if (!this.checkPath()) return
const folderFiles = await fs.promises.readdir(this.buildPath) // "out" folder
try {
const cache = JSON.parse(await fs.promises.readFile(join(this.cachePath, 'solidity-files-cache.json'), { encoding: 'utf-8' }))
// name of folders are file names
for (const file of folderFiles) {
const path = join(this.buildPath, file) // out/Counter.sol/
const compilationResult = {
input: {},
output: {
contracts: {},
sources: {}
},
inputSources: { sources: {}, target: '' },
solcVersion: null,
compilationTarget: null
}
compilationResult.inputSources.target = file
await this.readContract(path, compilationResult, cache)
this.emit('compilationFinished', compilationResult.compilationTarget, { sources: compilationResult.input }, 'soljson', compilationResult.output, compilationResult.solcVersion)
}
clearTimeout(this.logTimeout)
this.logTimeout = setTimeout(() => {
// @ts-ignore
this.call('terminal', 'log', { type: 'log', value: `receiving compilation result from Foundry. Select a file to populate the contract interaction interface.` })
console.log('Syncing compilation result from Foundry')
}, 1000)
} catch (e) {
console.log(e)
}
}
async triggerProcessArtifact() {
// prevent multiple calls
clearTimeout(this.processingTimeout)
this.processingTimeout = setTimeout(async () => await this.processArtifact(), 1000)
}
listenOnFoundryCompilation() {
try {
console.log('Foundry out folder exists... processing the artifact.')
if (this.watcher) this.watcher.close()
this.watcher = chokidar.watch(this.cachePath, { depth: 0, ignorePermissionErrors: true, ignoreInitial: true })
this.watcher.on('change', async () => await this.triggerProcessArtifact())
this.watcher.on('add', async () => await this.triggerProcessArtifact())
// process the artifact on activation
this.triggerProcessArtifact()
} catch (e) {
console.log(e)
}
}
async readContract(contractFolder, compilationResultPart, cache) {
const files = await fs.promises.readdir(contractFolder)
for (const file of files) {
const path = join(contractFolder, file)
const content = await fs.promises.readFile(path, { encoding: 'utf-8' })
compilationResultPart.inputSources.sources[file] = { content }
await this.feedContractArtifactFile(file, content, compilationResultPart, cache)
}
}
async feedContractArtifactFile(path, content, compilationResultPart, cache) {
const contentJSON = JSON.parse(content)
const contractName = basename(path).replace('.json', '')
let sourcePath = ''
if (contentJSON?.metadata?.settings?.compilationTarget) {
for (const key in contentJSON.metadata.settings.compilationTarget) {
if (contentJSON.metadata.settings.compilationTarget[key] === contractName) {
sourcePath = key
break
}
}
}
if (!sourcePath) return
const currentCache = cache.files[sourcePath]
if (!currentCache.artifacts[contractName]) return
// extract source and version
const metadata = contentJSON.metadata
if (metadata.compiler && metadata.compiler.version) {
compilationResultPart.solcVersion = metadata.compiler.version
} else {
compilationResultPart.solcVersion = ''
console.log('\x1b[32m%s\x1b[0m', 'compiler version not found, please update Foundry to the latest version.')
}
if (metadata.sources) {
for (const path in metadata.sources) {
const absPath = utils.absolutePath(path, this.currentSharedFolder)
try {
const content = await fs.promises.readFile(absPath, { encoding: 'utf-8' })
compilationResultPart.input[path] = { content }
} catch (e) {
compilationResultPart.input[path] = { content: '' }
}
}
} else {
console.log('\x1b[32m%s\x1b[0m', 'sources input not found, please update Foundry to the latest version.')
}
compilationResultPart.compilationTarget = sourcePath
// extract data
if (!compilationResultPart.output['sources'][sourcePath]) compilationResultPart.output['sources'][sourcePath] = {}
compilationResultPart.output['sources'][sourcePath] = {
ast: contentJSON['ast'],
id: contentJSON['id']
}
if (!compilationResultPart.output['contracts'][sourcePath]) compilationResultPart.output['contracts'][sourcePath] = {}
contentJSON.bytecode.object = contentJSON.bytecode.object.replace('0x', '')
contentJSON.deployedBytecode.object = contentJSON.deployedBytecode.object.replace('0x', '')
compilationResultPart.output['contracts'][sourcePath][contractName] = {
abi: contentJSON.abi,
evm: {
bytecode: contentJSON.bytecode,
deployedBytecode: contentJSON.deployedBytecode,
methodIdentifiers: contentJSON.methodIdentifiers
}
}
}
async sync() {
console.log('syncing Foundry with Remix...')
this.processArtifact()
}
}

@ -0,0 +1,219 @@
import { Profile } from "@remixproject/plugin-utils";
import { ElectronBasePlugin, ElectronBasePluginClient } from "@remixproject/plugin-electron"
import chokidar from 'chokidar'
import { ElectronBasePluginRemixdClient } from "../lib/remixd"
import fs from 'fs'
import * as utils from '../lib/utils'
import { basename, join } from "path";
import { spawn } from "child_process";
const profile: Profile = {
name: 'hardhat',
displayName: 'electron slither',
description: 'electron slither',
}
export class HardhatPlugin extends ElectronBasePlugin {
clients: any[]
constructor() {
super(profile, clientProfile, HardhatPluginClient)
this.methods = [...super.methods]
}
}
const clientProfile: Profile = {
name: 'hardhat',
displayName: 'electron hardhat',
description: 'electron hardhat',
methods: ['sync', 'compile']
}
class HardhatPluginClient extends ElectronBasePluginRemixdClient {
watcher: chokidar.FSWatcher
warnlog: boolean
buildPath: string
cachePath: string
logTimeout: NodeJS.Timeout
processingTimeout: NodeJS.Timeout
async onActivation(): Promise<void> {
console.log('Hardhat plugin activated')
this.call('terminal', 'log', { type: 'log', value: 'Hardhat plugin activated' })
this.startListening()
this.on('fs' as any, 'workingDirChanged', async (path: string) => {
console.log('workingDirChanged hardhat', path)
this.currentSharedFolder = path
this.startListening()
})
this.currentSharedFolder = await this.call('fs' as any, 'getWorkingDir')
}
startListening() {
this.buildPath = utils.absolutePath('artifacts/contracts', this.currentSharedFolder)
if (fs.existsSync(this.buildPath)) {
this.listenOnHardhatCompilation()
} else {
console.log('If you are using Hardhat, run `npx hardhat compile` or run the compilation with `Enable Hardhat Compilation` checked from the Remix IDE.')
this.listenOnHardHatFolder()
}
}
compile(configPath: string) {
return new Promise((resolve, reject) => {
const cmd = `npx hardhat compile --config ${utils.normalizePath(configPath)}`
const options = { cwd: this.currentSharedFolder, shell: true }
const child = spawn(cmd, options)
let result = ''
let error = ''
child.stdout.on('data', (data) => {
const msg = `[Hardhat Compilation]: ${data.toString()}`
console.log('\x1b[32m%s\x1b[0m', msg)
result += msg + '\n'
})
child.stderr.on('data', (err) => {
error += `[Hardhat Compilation]: ${err.toString()} \n`
})
child.on('close', () => {
if (error && result) resolve(error + result)
else if (error) reject(error)
else resolve(result)
})
})
}
checkPath() {
if (!fs.existsSync(this.buildPath)) {
this.listenOnHardHatFolder()
return false
}
return true
}
private async processArtifact() {
console.log('processing artifact')
if (!this.checkPath()) return
// resolving the files
const folderFiles = await fs.promises.readdir(this.buildPath)
const targetsSynced = []
// name of folders are file names
for (const file of folderFiles) { // ["artifacts/contracts/Greeter.sol/"]
const contractFilePath = join(this.buildPath, file)
const stat = await fs.promises.stat(contractFilePath)
if (!stat.isDirectory()) continue
const files = await fs.promises.readdir(contractFilePath)
const compilationResult = {
input: {},
output: {
contracts: {},
sources: {}
},
solcVersion: null,
target: null
}
for (const file of files) {
if (file.endsWith('.dbg.json')) { // "artifacts/contracts/Greeter.sol/Greeter.dbg.json"
const stdFile = file.replace('.dbg.json', '.json')
const contentStd = await fs.promises.readFile(join(contractFilePath, stdFile), { encoding: 'utf-8' })
const contentDbg = await fs.promises.readFile(join(contractFilePath, file), { encoding: 'utf-8' })
const jsonDbg = JSON.parse(contentDbg)
const jsonStd = JSON.parse(contentStd)
compilationResult.target = jsonStd.sourceName
targetsSynced.push(compilationResult.target)
const path = join(contractFilePath, jsonDbg.buildInfo)
const content = await fs.promises.readFile(path, { encoding: 'utf-8' })
await this.feedContractArtifactFile(content, compilationResult)
}
if (compilationResult.target) {
// we are only interested in the contracts that are in the target of the compilation
compilationResult.output = {
...compilationResult.output,
contracts: { [compilationResult.target]: compilationResult.output.contracts[compilationResult.target] }
}
this.emit('compilationFinished', compilationResult.target, { sources: compilationResult.input }, 'soljson', compilationResult.output, compilationResult.solcVersion)
}
}
}
clearTimeout(this.logTimeout)
this.logTimeout = setTimeout(() => {
this.call('terminal', 'log', { value: 'receiving compilation result from Hardhat. Select a file to populate the contract interaction interface.', type: 'log' })
if (targetsSynced.length) {
console.log(`Processing artifacts for files: ${[...new Set(targetsSynced)].join(', ')}`)
// @ts-ignore
this.call('terminal', 'log', { type: 'log', value: `synced with Hardhat: ${[...new Set(targetsSynced)].join(', ')}` })
} else {
console.log('No artifacts to process')
// @ts-ignore
this.call('terminal', 'log', { type: 'log', value: 'No artifacts from Hardhat to process' })
}
}, 1000)
}
listenOnHardHatFolder() {
console.log('Hardhat artifacts folder doesn\'t exist... waiting for the compilation.')
try {
if (this.watcher) this.watcher.close()
this.watcher = chokidar.watch(this.currentSharedFolder, { depth: 2, ignorePermissionErrors: true, ignoreInitial: true })
// watch for new folders
this.watcher.on('addDir', (path: string) => {
console.log('add dir hardhat', path)
if (fs.existsSync(this.buildPath)) {
this.listenOnHardhatCompilation()
}
})
} catch (e) {
console.log('listenOnHardHatFolder', e)
}
}
async triggerProcessArtifact() {
console.log('triggerProcessArtifact')
// prevent multiple calls
clearTimeout(this.processingTimeout)
this.processingTimeout = setTimeout(async () => await this.processArtifact(), 1000)
}
listenOnHardhatCompilation() {
try {
console.log('listening on Hardhat compilation...', this.buildPath)
if (this.watcher) this.watcher.close()
this.watcher = chokidar.watch(this.buildPath, { depth: 1, ignorePermissionErrors: true, ignoreInitial: true })
this.watcher.on('change', async () => await this.triggerProcessArtifact())
this.watcher.on('add', async () => await this.triggerProcessArtifact())
this.watcher.on('unlink', async () => await this.triggerProcessArtifact())
// process the artifact on activation
this.processArtifact()
} catch (e) {
console.log('listenOnHardhatCompilation', e)
}
}
async sync() {
console.log('syncing from Hardhat')
this.processArtifact()
}
async feedContractArtifactFile(artifactContent, compilationResultPart) {
const contentJSON = JSON.parse(artifactContent)
compilationResultPart.solcVersion = contentJSON.solcVersion
for (const file in contentJSON.input.sources) {
const source = contentJSON.input.sources[file]
const absPath = join(this.currentSharedFolder, file)
if (fs.existsSync(absPath)) { // if not that is a lib
const contentOnDisk = await fs.promises.readFile(absPath, { encoding: 'utf-8' })
if (contentOnDisk === source.content) {
compilationResultPart.input[file] = source
compilationResultPart.output['sources'][file] = contentJSON.output.sources[file]
compilationResultPart.output['contracts'][file] = contentJSON.output.contracts[file]
if (contentJSON.output.errors && contentJSON.output.errors.length) {
compilationResultPart.output['errors'] = contentJSON.output.errors.filter(error => error.sourceLocation.file === file)
}
}
}
}
}
}

@ -2,7 +2,22 @@ import { Profile } from "@remixproject/plugin-utils";
import { ElectronBasePlugin, ElectronBasePluginClient } from "@remixproject/plugin-electron"
import { ElectronBasePluginRemixdClient } from "../lib/remixd"
import { SlitherClientMixin } from "../lib/slither";
import * as utils from '../lib/utils'
import { existsSync, readdirSync, readFileSync, unlinkSync } from "fs-extra";
export interface OutputStandard {
description: string
title: string
confidence: string
severity: string
sourceMap: any
category?: string
reference?: string
example?: any
[key: string]: any
}
const { spawn, execSync } = require('child_process') // eslint-disable-line
const profile: Profile = {
name: 'slither',
displayName: 'electron slither',
@ -10,9 +25,9 @@ const profile: Profile = {
}
export class SlitherPlugin extends ElectronBasePlugin {
clients: any []
clients: any[]
constructor() {
super(profile, clientProfile, SlitherClientMixin(SlitherPluginClient))
super(profile, clientProfile, SlitherPluginClient)
this.methods = [...super.methods]
}
}
@ -24,10 +39,158 @@ const clientProfile: Profile = {
methods: ['analyse']
}
class SlitherPluginClient extends ElectronBasePluginRemixdClient {
constructor(webContentsId: number, profile: Profile) {
super(webContentsId, profile);
mapNpmDepsDir(list) {
const remixNpmDepsPath = utils.absolutePath('.deps/npm', this.currentSharedFolder)
const localNpmDepsPath = utils.absolutePath('node_modules', this.currentSharedFolder)
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) => {
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)
this.log(`[Slither Analysis]: Compiler version is ${versionString}`)
let solcOutput: Buffer
// Check solc current installed version
try {
solcOutput = execSync('solc --version', options)
} catch (err) {
this.error(err)
reject(new Error('Error in running solc command'))
}
if (!solcOutput.toString().includes(versionString)) {
this.log('[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)) {
this.log(`[Slither Analysis]: Installing ${version} using solc-select`)
// Install required version
execSync(`solc-select install ${version}`, options)
}
this.log(`[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) {
this.error(err)
reject(new Error('Error in running solc-select command'))
}
} else this.log('[Slither Analysis]: Compiler version is same as installed solc version')
}
// Allow paths and set solc remapping for import URLs
const fileContent = readFileSync(utils.absolutePath(filePath, this.currentSharedFolder), 'utf8')
const importsArr = fileContent.match(/import ['"][^.|..](.+?)['"];/g)
let remaps = ''
if (importsArr?.length) {
const { remapString } = this.mapNpmDepsDir(importsArr)
remaps = remapString.trim()
}
const optimizeOption: string = optimize ? '--optimize' : ''
const evmOption: string = evmVersion ? `--evm-version ${evmVersion}` : ''
let solcArgs = ''
if (optimizeOption) {
solcArgs += optimizeOption + ' '
}
if (evmOption) {
if (!solcArgs.endsWith(' ')) solcArgs += ' '
solcArgs += evmOption
}
if (solcArgs) {
solcArgs = `--solc-args "${solcArgs.trimStart()}"`
}
const solcRemaps = remaps ? `--solc-remaps "${remaps}"` : ''
const outputFile = 'remix-slither-report.json'
try {
// We don't keep the previous analysis
const outputFilePath = utils.absolutePath(outputFile, this.currentSharedFolder)
if (existsSync(outputFilePath)) unlinkSync(outputFilePath)
} catch (e) {
this.error('unable to remove the output file')
this.error(e.message)
}
const cmd = `slither ${filePath} ${solcArgs} ${solcRemaps} --json ${outputFile}`
this.log('[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 = utils.absolutePath(outputFile, this.currentSharedFolder)
// 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)
}
resolve(response)
} else {
this.log(report['error'])
reject(new Error('Error in running Slither Analysis.'))
}
} else {
this.error('Error in generating Slither Analysis Report. Make sure Slither is properly installed.')
reject(new Error('Error in generating Slither Analysis Report. Make sure Slither is properly installed.'))
}
})
})
}
}

@ -6,7 +6,7 @@ console.log('preload.ts', new Date().toLocaleTimeString())
/* preload script needs statically defined API for each plugin */
const exposedPLugins = ['fs', 'git', 'xterm', 'isogit', 'electronconfig', 'electronTemplates', 'ripgrep', 'compilerloader', 'appUpdater', 'slither']
const exposedPLugins = ['fs', 'git', 'xterm', 'isogit', 'electronconfig', 'electronTemplates', 'ripgrep', 'compilerloader', 'appUpdater', 'slither', 'foundry', 'hardhat']
let webContentsId: number | undefined

Loading…
Cancel
Save