parent
17185a4600
commit
32c56da63c
@ -0,0 +1,13 @@ |
|||||||
|
import { ElectronPlugin } from '@remixproject/engine-electron'; |
||||||
|
|
||||||
|
export class SlitherHandleDesktop extends ElectronPlugin { |
||||||
|
constructor() { |
||||||
|
super({ |
||||||
|
displayName: 'slither', |
||||||
|
name: 'slither', |
||||||
|
description: 'electron slither', |
||||||
|
methods: ['analyse'] |
||||||
|
}) |
||||||
|
this.methods = ['analyse'] |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,36 @@ |
|||||||
|
import { ElectronBasePluginClient } from "@remixproject/plugin-electron"; |
||||||
|
import { Profile } from "@remixproject/plugin-utils"; |
||||||
|
|
||||||
|
export class ElectronBasePluginRemixdClient extends ElectronBasePluginClient { |
||||||
|
log: (...message: any) => void |
||||||
|
error: (...message: any) => void |
||||||
|
|
||||||
|
currentSharedFolder: string = '' |
||||||
|
constructor(webContentsId: number, profile: Profile) { |
||||||
|
super(webContentsId, profile); |
||||||
|
this.log = (...message: any) => { |
||||||
|
for(const m of message) { |
||||||
|
this.call('terminal', 'log', { |
||||||
|
type: 'log', |
||||||
|
value: m |
||||||
|
}) |
||||||
|
} |
||||||
|
} |
||||||
|
this.error = (...message: any) => { |
||||||
|
for(const m of message) { |
||||||
|
this.call('terminal', 'log', { |
||||||
|
type: 'error', |
||||||
|
value: m |
||||||
|
}) |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
|
||||||
|
this.onload(async () => { |
||||||
|
this.on('fs' as any, 'workingDirChanged', async (path: string) => { |
||||||
|
this.currentSharedFolder = path |
||||||
|
}) |
||||||
|
this.currentSharedFolder = await this.call('fs' as any, 'getWorkingDir') |
||||||
|
}) |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,193 @@ |
|||||||
|
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.')) |
||||||
|
} |
||||||
|
}) |
||||||
|
}) |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,24 @@ |
|||||||
|
import * as pathModule from 'path' |
||||||
|
/** |
||||||
|
* returns the absolute path of the given @arg path |
||||||
|
* |
||||||
|
* @param {String} path - relative path (Unix style which is the one used by Remix IDE) |
||||||
|
* @param {String} sharedFolder - absolute shared path. platform dependent representation. |
||||||
|
* @return {String} platform dependent absolute path (/home/user1/.../... for unix, c:\user\...\... for windows) |
||||||
|
*/ |
||||||
|
function absolutePath (path: string, sharedFolder:string): string { |
||||||
|
path = normalizePath(path) |
||||||
|
path = pathModule.resolve(sharedFolder, path) |
||||||
|
return path |
||||||
|
} |
||||||
|
function normalizePath (path) { |
||||||
|
if (path === '/') path = './' |
||||||
|
if (process.platform === 'win32') { |
||||||
|
return path.replace(/\//g, '\\') |
||||||
|
} |
||||||
|
return path |
||||||
|
} |
||||||
|
|
||||||
|
export { absolutePath } |
||||||
|
|
||||||
|
|
@ -0,0 +1,34 @@ |
|||||||
|
import { Profile } from "@remixproject/plugin-utils"; |
||||||
|
import { ElectronBasePlugin, ElectronBasePluginClient } from "@remixproject/plugin-electron" |
||||||
|
|
||||||
|
import { ElectronBasePluginRemixdClient } from "../lib/remixd" |
||||||
|
import { SlitherClientMixin } from "../lib/slither"; |
||||||
|
const profile: Profile = { |
||||||
|
name: 'slither', |
||||||
|
displayName: 'electron slither', |
||||||
|
description: 'electron slither', |
||||||
|
} |
||||||
|
|
||||||
|
export class SlitherPlugin extends ElectronBasePlugin { |
||||||
|
clients: any [] |
||||||
|
constructor() { |
||||||
|
super(profile, clientProfile, SlitherClientMixin(SlitherPluginClient)) |
||||||
|
this.methods = [...super.methods] |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
const clientProfile: Profile = { |
||||||
|
name: 'slither', |
||||||
|
displayName: 'electron slither', |
||||||
|
description: 'electron slither', |
||||||
|
methods: ['analyse'] |
||||||
|
} |
||||||
|
|
||||||
|
|
||||||
|
class SlitherPluginClient extends ElectronBasePluginRemixdClient { |
||||||
|
constructor(webContentsId: number, profile: Profile) { |
||||||
|
super(webContentsId, profile); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
|
@ -0,0 +1,95 @@ |
|||||||
|
import {NightwatchBrowser} from 'nightwatch' |
||||||
|
import { ChildProcess, spawn } from 'child_process' |
||||||
|
import { homedir } from 'os' |
||||||
|
const tests = { |
||||||
|
before: function (browser: NightwatchBrowser, done: VoidFunction) { |
||||||
|
done() |
||||||
|
}, |
||||||
|
open: function (browser: NightwatchBrowser) { |
||||||
|
browser.waitForElementVisible('*[data-id="openFolderButton"]', 10000).click('*[data-id="openFolderButton"]') |
||||||
|
}, |
||||||
|
|
||||||
|
'open default template': function (browser: NightwatchBrowser) { |
||||||
|
browser |
||||||
|
.waitForElementVisible('*[data-id="remixIdeIconPanel"]', 10000) |
||||||
|
.waitForElementVisible('button[data-id="landingPageImportFromTemplate"]') |
||||||
|
.click('button[data-id="landingPageImportFromTemplate"]') |
||||||
|
.waitForElementVisible('*[data-id="modalDialogCustomPromptTextCreate"]') |
||||||
|
.waitForElementPresent('[data-id="fileSystemModalDialogModalFooter-react"] .modal-ok') |
||||||
|
.click('[data-id="fileSystemModalDialogModalFooter-react"] .modal-ok') |
||||||
|
.pause(3000) |
||||||
|
.windowHandles(function (result) { |
||||||
|
console.log(result.value) |
||||||
|
browser.switchWindow(result.value[1]) |
||||||
|
.waitForElementVisible('*[data-id="treeViewLitreeViewItemtests"]') |
||||||
|
.click('*[data-id="treeViewLitreeViewItemtests"]') |
||||||
|
.waitForElementVisible('*[data-id="treeViewLitreeViewItemcontracts"]') |
||||||
|
.click('*[data-id="treeViewLitreeViewItemcontracts"]') |
||||||
|
.waitForElementVisible('[data-id="treeViewLitreeViewItemcontracts/1_Storage.sol"]') |
||||||
|
.openFile('contracts/1_Storage.sol') |
||||||
|
.waitForElementVisible('*[id="editorView"]', 10000) |
||||||
|
.getEditorValue((content) => { |
||||||
|
browser.assert.ok(content.includes('function retrieve() public view returns (uint256){')) |
||||||
|
}) |
||||||
|
}) |
||||||
|
}, |
||||||
|
'Should install slither #group6': function (browser: NightwatchBrowser) { |
||||||
|
browser.perform(async (done) => { |
||||||
|
await installSlither() |
||||||
|
done() |
||||||
|
}) |
||||||
|
}, |
||||||
|
'run slither': function (browser: NightwatchBrowser) { |
||||||
|
browser |
||||||
|
.click('[data-id="verticalIconsKindpluginManager"]') |
||||||
|
.scrollAndClick('[data-id="pluginManagerComponentActivateButtonsolidityStaticAnalysis"]') |
||||||
|
.clickLaunchIcon('solidity').click('*[data-id="compilerContainerCompileBtn"]') |
||||||
|
.pause(1000) |
||||||
|
.clickLaunchIcon('solidityStaticAnalysis') |
||||||
|
.useXpath() |
||||||
|
.click('//*[@id="staticAnalysisRunBtn"]') |
||||||
|
.waitForElementPresent('//*[@id="staticanalysisresult"]', 5000) |
||||||
|
.waitForElementVisible({ |
||||||
|
selector: "//*[@data-id='nolibslitherwarnings'][contains(text(), '1')]", |
||||||
|
locateStrategy: 'xpath', |
||||||
|
timeout: 5000 |
||||||
|
}) |
||||||
|
.waitForElementVisible({ |
||||||
|
selector: "//div[@data-id='block']/span[contains(text(), '1 warnings found.')]", |
||||||
|
locateStrategy: 'xpath', |
||||||
|
timeout: 5000 |
||||||
|
}) |
||||||
|
}, |
||||||
|
|
||||||
|
after: function (browser: NightwatchBrowser) { |
||||||
|
browser.end() |
||||||
|
}, |
||||||
|
} |
||||||
|
|
||||||
|
async function installSlither(): Promise<void> { |
||||||
|
console.log('installSlither', process.cwd()) |
||||||
|
try { |
||||||
|
const server = spawn('node', ['../../dist/libs/remixd/src/scripts/installSlither.js'], { cwd: process.cwd(), shell: true, detached: true }) |
||||||
|
return new Promise((resolve, reject) => { |
||||||
|
server.stdout.on('data', function (data) { |
||||||
|
console.log(data.toString()) |
||||||
|
if ( |
||||||
|
data.toString().includes("Slither is ready to use") |
||||||
|
) { |
||||||
|
console.log('resolving') |
||||||
|
resolve() |
||||||
|
} |
||||||
|
}) |
||||||
|
server.stderr.on('err', function (data) { |
||||||
|
console.log(data.toString()) |
||||||
|
reject(data.toString()) |
||||||
|
}) |
||||||
|
}) |
||||||
|
} catch (e) { |
||||||
|
console.log(e) |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
module.exports = { |
||||||
|
...process.platform.startsWith('linux')?tests:{} |
||||||
|
} |
Loading…
Reference in new issue