Merge branch 'mg-compile-btn-text' of github.com:Miladinho/remix-project into mg-compile-btn-text

pull/5370/head
Milad Gueramian 3 years ago
commit 6a05abdfb8
  1. 4
      apps/remix-ide-e2e/src/tests/debugger.test.ts
  2. 2
      apps/remix-ide-e2e/src/tests/solidityUnittests.test.ts
  3. 28
      apps/remix-ide-e2e/src/tests/staticAnalysis.test.ts
  4. 124
      apps/remix-ide-e2e/src/tests/terminal.test.ts
  5. 7
      apps/remix-ide/src/app.js
  6. 62
      apps/remix-ide/src/app/tabs/intelligent-script-executor.ts
  7. 9
      apps/remix-ide/src/app/tabs/web3-provider.js
  8. 10
      apps/remix-ide/src/app/udapp/run-tab.js
  9. 10
      apps/remix-ide/src/blockchain/blockchain.js
  10. 2
      apps/remix-ide/src/remixAppManager.js
  11. 2
      apps/solidity-compiler/src/app/compiler-api.ts
  12. 8
      libs/remix-analyzer/src/solidity-analyzer/index.ts
  13. 13
      libs/remix-analyzer/test/analysis/staticAnalysisIntegration-test-0.4.24.ts
  14. 13
      libs/remix-analyzer/test/analysis/staticAnalysisIntegration-test-0.5.0.ts
  15. 7
      libs/remix-analyzer/test/analysis/staticAnalysisIssues-test-0.4.24.ts
  16. 7
      libs/remix-analyzer/test/analysis/staticAnalysisIssues-test-0.5.0.ts
  17. 35
      libs/remix-core-plugin/src/lib/compiler-artefacts.ts
  18. 1
      libs/remix-core-plugin/src/lib/compiler-metadata.ts
  19. 9
      libs/remix-lib/src/execution/txListener.ts
  20. 7
      libs/remix-lib/src/execution/txRunnerVM.ts
  21. 46
      libs/remix-lib/src/execution/txRunnerWeb3.ts
  22. 51
      libs/remix-simulator/src/methods/blocks.ts
  23. 27
      libs/remix-simulator/src/methods/transactions.ts
  24. 2
      libs/remix-tests/src/compiler.ts
  25. 3
      libs/remix-tests/src/deployer.ts
  26. 4
      libs/remix-ui/checkbox/src/lib/remix-ui-checkbox.tsx
  27. 56
      libs/remix-ui/home-tab/src/lib/remix-ui-home-tab.tsx
  28. 10
      libs/remix-ui/run-tab/src/lib/actions/index.ts
  29. 311
      libs/remix-ui/static-analyser/src/lib/remix-ui-static-analyser.tsx
  30. 2
      libs/remix-ui/terminal/src/lib/remix-ui-terminal.tsx
  31. 59
      libs/remix-ui/workspace/src/lib/actions/index.ts
  32. 4
      libs/remix-ui/workspace/src/lib/contexts/index.ts
  33. 3
      libs/remix-ui/workspace/src/lib/css/remix-ui-workspace.css
  34. 14
      libs/remix-ui/workspace/src/lib/providers/FileSystemProvider.tsx
  35. 39
      libs/remix-ui/workspace/src/lib/remix-ui-workspace.tsx
  36. 5
      libs/remixd/src/services/slitherClient.ts

@ -477,7 +477,7 @@ const localVariable_step717_ABIEncoder = { // eslint-disable-line
const jsGetTrace = `(async () => {
try {
const result = await remix.call('debugger', 'getTrace', '0x16be5c31014a7e1552d136f7ed7bc7788f3bb9e45e31b059df253173f2df31e7')
const result = await remix.call('debugger', 'getTrace', '0x65f0813753462414f9a91f0aabea946188327995f54b893b63a8d7ff186cfca3')
console.log('result ', result)
} catch (e) {
console.log(e.message)
@ -486,7 +486,7 @@ const jsGetTrace = `(async () => {
const jsDebug = `(async () => {
try {
const result = await remix.call('debugger', 'debug', '0x16be5c31014a7e1552d136f7ed7bc7788f3bb9e45e31b059df253173f2df31e7')
const result = await remix.call('debugger', 'debug', '0x65f0813753462414f9a91f0aabea946188327995f54b893b63a8d7ff186cfca3')
console.log('result ', result)
} catch (e) {
console.log(e.message)

@ -132,7 +132,7 @@ module.exports = {
.click('*[data-id="testTabCheckAllTests"]')
.clickElementAtPosition('.singleTestLabel', 1)
.scrollAndClick('*[data-id="testTabRunTestsTabRunAction"]')
.waitForElementContainsText('*[data-id="testTabSolidityUnitTestsOutput"]', 'contract deployment failed after trying twice', 120000)
.waitForElementContainsText('*[data-id="testTabSolidityUnitTestsOutput"]', 'revert Deploy Failed', 120000)
},
'Should fail when parameters are passed to method in test contract #group3': function (browser: NightwatchBrowser) {

@ -30,6 +30,27 @@ module.exports = {
},
'Static Analysis': function (browser: NightwatchBrowser) {
runTests(browser)
},
'run analysis and filter results': function (browser: NightwatchBrowser) {
browser
.clickLaunchIcon('filePanel')
.click('*[data-id="treeViewLitreeViewItemcontracts"]')
.click('*[data-id="treeViewLitreeViewItemcontracts/2_Owner.sol"]')
.clickLaunchIcon('solidity')
.pause(10000)
.clickLaunchIcon('solidityStaticAnalysis')
.waitForElementPresent('#staticanalysisresult .warning', 5000)
.assert.containsText('#verticalIconsKindsolidityStaticAnalysis .remixui_status', '1') // Check warning count
.verify.elementPresent('input[name="showLibWarnings"]')
.verify.elementNotPresent('input[name="showLibWarnings"]:checked')
.verify.elementPresent('label[id="headingshowLibWarnings"]')
.click('label[id="headingshowLibWarnings"]')
.pause(1000)
.assert.containsText('#verticalIconsKindsolidityStaticAnalysis .remixui_status', '382')
.click('label[id="headingshowLibWarnings"]')
.pause(1000)
.assert.containsText('#verticalIconsKindsolidityStaticAnalysis .remixui_status', '1')
.end()
}
}
@ -47,14 +68,12 @@ function runTests (browser: NightwatchBrowser) {
'TooMuchGas.() : Variables have very similar names "test" and "test1".',
'TooMuchGas.() : Variables have very similar names "test" and "test1".'],
'#staticanalysisresult .warning',
browser, function () {
browser.end()
}
browser
)
})
}
function listSelectorContains (textsToFind: string[], selector: string, browser: NightwatchBrowser, callback: VoidFunction) {
function listSelectorContains (textsToFind: string[], selector: string, browser: NightwatchBrowser) {
browser.execute(function (selector) {
const items = document.querySelectorAll(selector)
const ret = []
@ -68,6 +87,5 @@ function listSelectorContains (textsToFind: string[], selector: string, browser:
console.log('testing `' + result.value[k] + '` against `' + textsToFind[k] + '`')
browser.assert.equal(result.value[k].indexOf(textsToFind[k]) !== -1, true)
}
callback()
})
}

@ -190,7 +190,17 @@ module.exports = {
.waitForElementVisible('*[data-id="terminalCli"]')
.click('*[data-id="terminalCli"]')
.sendKeys('*[data-id="terminalCliInput"]', 'remix.')
.assert.visible('*[data-id="autoCompletePopUpAutoCompleteItem"]').end()
.assert.visible('*[data-id="autoCompletePopUpAutoCompleteItem"]')
},
'Should run a script right after compilation #group6': function (browser: NightwatchBrowser) {
browser
.addFile('contracts/storage.sol', { content: scriptAutoExec.contract } )
.addFile('scripts/deploy_storage.js', { content: scriptAutoExec.script } )
.openFile('contracts/storage.sol')
.sendKeys('body', [browser.Keys.CONTROL, browser.Keys.SHIFT, 's'])
.pause(5000)
.journalLastChildIncludes('147')
}
}
@ -484,3 +494,115 @@ contract OwnerTest {
return owner;
}
}`
const scriptAutoExec = {
contract: `// SPDX-License-Identifier: GPL-3.0
pragma solidity >=0.7.0 <0.9.0;
library lib {
function test () public view returns (uint) {
return 147;
}
}
/**
* @title Storage
* @dev Store & retrieve value inr a variable
* @custom:dev-run-script ./scripts/deploy_storage.js
*/
contract Storage {
uint256 number;
/**
* @dev Store valrue in variable
* @param num value to store
*/
function store(uint256 num) public {
number = num;
}
/**
* @dev Return value
* @return value of 'number'
*/
function retrieve() public view returns (uint256){
return number;
}
function getFromLib() public view returns (uint) {
return lib.test();
}
}
`,
script: `
// Right click on the script name and hit "Run" to execute
const { expect } = require("chai");
const { ethers } = require("hardhat");
(async () => {
try {
// function getContractFactoryFromArtifact(artifact: Artifact, signer?: ethers.Signer): Promise<ethers.ContractFactory>;
// function getContractFactoryFromArtifact(artifact: Artifact, factoryOptions: FactoryOptions): Promise<ethers.ContractFactory>;
const metadataLib = JSON.parse(await remix.call('fileManager', 'readFile', 'contracts/artifacts/lib.json'))
console.log('deploying lib:')
const artifactLib = {
contractName: 'Lib',
sourceName: 'contracts/1_Storage.sol',
abi: metadataLib.abi,
bytecode: '0x' + metadataLib.data.bytecode.object,
deployedBytecode: '0x' + metadataLib.data.deployedBytecode.object,
linkReferences: metadataLib.data.bytecode.linkReferences,
deployedLinkReferences: metadataLib.data.deployedBytecode.linkReferences,
}
const optionsLib = {}
const factoryLib = await ethers.getContractFactoryFromArtifact(artifactLib, optionsLib)
const lib = await factoryLib.deploy();
await lib.deployed()
console.log('lib deployed', lib.address)
const metadata = JSON.parse(await remix.call('fileManager', 'readFile', 'contracts/artifacts/Storage.json'))
const artifact = {
contractName: 'Storage',
sourceName: 'contracts/1_Storage.sol',
abi: metadata.abi,
bytecode: '0x' + metadata.data.bytecode.object,
deployedBytecode: '0x' + metadata.data.deployedBytecode.object,
linkReferences: metadata.data.bytecode.linkReferences,
deployedLinkReferences: metadata.data.deployedBytecode.linkReferences,
}
const options = {
libraries: {
'lib': lib.address
}
}
const factory = await ethers.getContractFactoryFromArtifact(artifact, options)
const storage = await factory.deploy();
await storage.deployed()
const storeValue = await storage.store(333);
// wait until the transaction is mined
await storeValue.wait();
console.log((await storage.getFromLib()).toString())
// expect((await storage.getFromLib()).toString()).to.equal('34');
} catch (e) {
console.error(e.message)
}
})()
`
}

@ -5,6 +5,7 @@ import { RemixAppManager } from './remixAppManager'
import { ThemeModule } from './app/tabs/theme-module'
import { NetworkModule } from './app/tabs/network-module'
import { Web3ProviderModule } from './app/tabs/web3-provider'
import { IntelligentScriptExecutor } from './app/tabs/intelligent-script-executor'
import { SidePanel } from './app/components/side-panel'
import { HiddenPanel } from './app/components/hidden-panel'
import { VerticalIcons } from './app/components/vertical-icons'
@ -179,7 +180,8 @@ class AppComponent {
api: offsetToLineColumnConverter,
name: 'offsettolinecolumnconverter'
})
// ----------------- run script after each compilation results -----------
const intelligentScriptExecutor = new IntelligentScriptExecutor()
// -------------------Terminal----------------------------------------
makeUdapp(blockchain, compilersArtefacts, domEl => terminal.logHtml(domEl))
const terminal = new Terminal(
@ -222,6 +224,7 @@ class AppComponent {
contextualListener,
terminal,
web3Provider,
intelligentScriptExecutor,
fetchAndCompile,
dGitProvider,
storagePlugin,
@ -343,7 +346,7 @@ class AppComponent {
await this.appManager.activatePlugin(['settings', 'config'])
await this.appManager.activatePlugin(['hiddenPanel', 'pluginManager', 'contextualListener', 'terminal', 'blockchain', 'fetchAndCompile', 'contentImport', 'gistHandler'])
await this.appManager.activatePlugin(['settings'])
await this.appManager.activatePlugin(['walkthrough','storage'])
await this.appManager.activatePlugin(['walkthrough','storage', 'intelligentScriptExecutor'])
this.appManager.on(
'filePanel',

@ -0,0 +1,62 @@
import { Plugin } from '@remixproject/engine'
import * as packageJson from '../../../../../package.json'
export const profile = {
name: 'intelligentScriptExecutor',
displayName: 'Intelligent Script Executor',
description: 'after each compilation, run the script defined in Natspec.',
methods: [],
version: packageJson.version,
kind: 'none'
}
type listener = (event: KeyboardEvent) => void
export class IntelligentScriptExecutor extends Plugin {
executionListener: listener
targetFileName: string
constructor () {
super(profile)
this.executionListener = async (e) => {
// ctrl+e or command+e
const file = await this.call('fileManager', 'file')
if ((e.metaKey || e.ctrlKey) && e.shiftKey && e.keyCode === 83 && file !== '') {
if (file.endsWith('.sol')) {
e.preventDefault()
this.targetFileName = file
await this.call('solidity', 'compile', file)
} else if (file.endsWith('.js') || file.endsWith('.ts')) {
e.preventDefault()
this.runScript(file, false)
}
}
}
}
async runScript (fileName, clearAllInstances) {
await this.call('terminal', 'log', `running ${fileName} ...`)
const content = await this.call('fileManager', 'readFile', fileName)
if (clearAllInstances) {
await this.call('udapp', 'clearAllInstances')
}
await this.call('scriptRunner', 'execute', content)
}
onActivation () {
window.document.addEventListener('keydown', this.executionListener)
this.on('compilerMetadata', 'artefactsUpdated', async (fileName, contract) => {
if (this.targetFileName === contract.file && contract.object && contract.object.devdoc['custom:dev-run-script']) {
this.targetFileName = null
const file = contract.object.devdoc['custom:dev-run-script']
if (file) this.runScript(file, true)
}
})
}
onDeactivation () {
window.document.removeEventListener('keydown', this.executionListener)
this.off('compilerMetadata', 'artefactsUpdated')
}
}

@ -24,8 +24,15 @@ export class Web3ProviderModule extends Plugin {
return new Promise((resolve, reject) => {
const provider = this.blockchain.web3().currentProvider
// see https://github.com/ethereum/web3.js/pull/1018/files#diff-d25786686c1053b786cc2626dc6e048675050593c0ebaafbf0814e1996f22022R129
provider[provider.sendAsync ? 'sendAsync' : 'send'](payload, (error, message) => {
provider[provider.sendAsync ? 'sendAsync' : 'send'](payload, async (error, message) => {
if (error) return reject(error)
if (payload.method === 'eth_sendTransaction') {
if (payload.params.length && !payload.params[0].to && message.result) {
const receipt = await this.call('blockchain', 'getTransactionReceipt', message.result)
const contractData = await this.call('compilerArtefacts', 'getContractDataFromAddress', receipt.contractAddress)
if (contractData) this.call('udapp', 'addInstance', receipt.contractAddress, contractData.contract.abi, contractData.name)
}
}
resolve(message)
})
})

@ -18,7 +18,7 @@ const profile = {
version: packageJson.version,
permission: true,
events: ['newTransaction'],
methods: ['createVMAccount', 'sendTransaction', 'getAccounts', 'pendingTransactionsCount', 'getSettings', 'setEnvironmentMode']
methods: ['createVMAccount', 'sendTransaction', 'getAccounts', 'pendingTransactionsCount', 'getSettings', 'setEnvironmentMode', 'clearAllInstances', 'addInstance']
}
export class RunTab extends ViewPlugin {
@ -64,6 +64,14 @@ export class RunTab extends ViewPlugin {
}
}
clearAllInstances () {
this.emit('clearAllInstancesReducer')
}
addInstance (address, abi, name) {
this.emit('addInstanceReducer', address, abi, name)
}
createVMAccount (newAccount) {
return this.blockchain.createVMAccount(newAccount)
}

@ -22,7 +22,7 @@ const profile = {
name: 'blockchain',
displayName: 'Blockchain',
description: 'Blockchain - Logic',
methods: [],
methods: ['getCode', 'getTransactionReceipt'],
version: packageJson.version
}
@ -392,6 +392,14 @@ export class Blockchain extends Plugin {
return Object.keys(this.txRunner.pendingTxs).length
}
async getCode(address) {
return await this.web3().eth.getCode(address)
}
async getTransactionReceipt (hash) {
return await this.web3().eth.getTransactionReceipt(hash)
}
/**
* This function send a tx only to javascript VM or testnet, will return an error for the mainnet
* SHOULD BE TAKEN CAREFULLY!

@ -8,7 +8,7 @@ const requiredModules = [ // services + layout views + system views
'manager', 'config', 'compilerArtefacts', 'compilerMetadata', 'contextualListener', 'editor', 'offsetToLineColumnConverter', 'network', 'theme',
'fileManager', 'contentImport', 'blockchain', 'web3Provider', 'scriptRunner', 'fetchAndCompile', 'mainPanel', 'hiddenPanel', 'sidePanel', 'menuicons',
'filePanel', 'terminal', 'settings', 'pluginManager', 'tabs', 'udapp', 'dGitProvider', 'solidity-logic', 'gistHandler', 'layout',
'notification', 'permissionhandler', 'walkthrough', 'storage', 'restorebackupzip', 'link-libraries', 'deploy-libraries']
'notification', 'permissionhandler', 'walkthrough', 'storage', 'restorebackupzip', 'link-libraries', 'deploy-libraries', 'intelligentScriptExecutor']
const dependentModules = ['git', 'hardhat', 'slither'] // module which shouldn't be manually activated (e.g git is activated by remixd)

@ -321,7 +321,7 @@ export const CompilerApiMixin = (Base) => class extends Base {
// Run the compiler instead of trying to save the website
this.data.eventHandlers.onKeyDown = async (e) => {
// ctrl+s or command+s
if ((e.metaKey || e.ctrlKey) && e.keyCode === 83 && this.currentFile !== '') {
if ((e.metaKey || e.ctrlKey) && !e.shiftKey && e.keyCode === 83 && this.currentFile !== '') {
e.preventDefault()
this.compileTabLogic.runCompiler(await this.getAppParameter('hardhat-compilation'))
}

@ -15,13 +15,13 @@ export default class staticAnalysisRunner {
* @param toRun module indexes (compiled from remix IDE)
* @param callback callback
*/
run (compilationResult: CompilationResult, toRun: number[], callback: ((reports: AnalysisReport[]) => void)): void {
run (compilationResult: CompilationResult, toRun: number[]): AnalysisReport[] {
const modules: ModuleObj[] = toRun.map((i) => {
const Module = this.modules()[i]
const m = new Module()
return { name: m.name, mod: m }
})
this.runWithModuleList(compilationResult, modules, callback)
return this.runWithModuleList(compilationResult, modules)
}
/**
@ -30,7 +30,7 @@ export default class staticAnalysisRunner {
* @param modules analysis module
* @param callback callback
*/
runWithModuleList (compilationResult: CompilationResult, modules: ModuleObj[], callback: ((reports: AnalysisReport[]) => void)): void {
runWithModuleList (compilationResult: CompilationResult, modules: ModuleObj[]): AnalysisReport[] {
let reports: AnalysisReport[] = []
// Also provide convenience analysis via the AST walker.
const walker = new AstWalker()
@ -64,7 +64,7 @@ export default class staticAnalysisRunner {
}
return { name: item.name, report: report }
}))
callback(reports)
return reports
}
/**

@ -814,12 +814,11 @@ test('Integration test forLoopIteratesOverDynamicArray module', function (t: tes
function runModuleOnFiles (Module: any, t: test.Test, cb: ((fname: string, report: AnalysisReportObj[]) => void)): void {
const statRunner: StatRunner = new StatRunner()
testFiles.forEach((fileName: string) => {
statRunner.runWithModuleList(compilationResults[fileName], [{ name: new Module().name, mod: new Module() }], (reports: AnalysisReport[]) => {
let report: AnalysisReportObj[] = reports[0].report
if (report.some((x: AnalysisReportObj) => x.warning.includes('INTERNAL ERROR'))) {
t.comment('Error while executing Module: ' + JSON.stringify(report))
}
cb(fileName, report)
})
const reports = statRunner.runWithModuleList(compilationResults[fileName], [{ name: new Module().name, mod: new Module() }])
let report: AnalysisReportObj[] = reports[0].report
if (report.some((x: AnalysisReportObj) => x.warning.includes('INTERNAL ERROR'))) {
t.comment('Error while executing Module: ' + JSON.stringify(report))
}
cb(fileName, report)
})
}

@ -816,12 +816,11 @@ test('Integration test forLoopIteratesOverDynamicArray module', function (t: tes
function runModuleOnFiles (Module: any, t: test.Test, cb: ((fname: string, report: AnalysisReportObj[]) => void)): void {
const statRunner: StatRunner = new StatRunner()
testFiles.forEach((fileName: string) => {
statRunner.runWithModuleList(compilationResults[fileName], [{ name: new Module().name, mod: new Module() }], (reports: AnalysisReport[]) => {
let report: AnalysisReportObj[] = reports[0].report
if (report.some((x: AnalysisReportObj) => x['warning'].includes('INTERNAL ERROR'))) {
t.comment('Error while executing Module: ' + JSON.stringify(report))
}
cb(fileName, report)
})
const reports = statRunner.runWithModuleList(compilationResults[fileName], [{ name: new Module().name, mod: new Module() }])
let report: AnalysisReportObj[] = reports[0].report
if (report.some((x: AnalysisReportObj) => x['warning'].includes('INTERNAL ERROR'))) {
t.comment('Error while executing Module: ' + JSON.stringify(report))
}
cb(fileName, report)
})
}

@ -31,10 +31,9 @@ test('staticAnalysisIssues.functionParameterPassingError', function (t) {
const statRunner: StatRunner = new StatRunner()
t.doesNotThrow(() => {
statRunner.runWithModuleList(res, [{ name: new Module().name, mod: new Module() }], (reports: AnalysisReport[]) => {})
statRunner.runWithModuleList(res, [{ name: new Module().name, mod: new Module()}])
}, 'Analysis should not throw')
statRunner.runWithModuleList(res, [{ name: new Module().name, mod: new Module() }], (reports: AnalysisReport[]) => {
t.ok(!reports.some((mod: AnalysisReport) => mod.report.some((rep: AnalysisReportObj) => rep.warning.includes('INTERNAL ERROR')), 'Should not have internal errors'))
})
const reports = statRunner.runWithModuleList(res, [{ name: new Module().name, mod: new Module()}])
t.ok(!reports.some((mod: AnalysisReport) => mod.report.some((rep: AnalysisReportObj) => rep.warning.includes('INTERNAL ERROR')), 'Should not have internal errors'))
})

@ -31,10 +31,9 @@ test('staticAnalysisIssues.functionParameterPassingError', function (t) {
const statRunner: StatRunner = new StatRunner()
t.doesNotThrow(() => {
statRunner.runWithModuleList(res, [{ name: new Module().name, mod: new Module() }], (reports: AnalysisReport[]) => {})
statRunner.runWithModuleList(res, [{ name: new Module().name, mod: new Module() }])
}, 'Analysis should not throw')
statRunner.runWithModuleList(res, [{ name: new Module().name, mod: new Module() }], (reports: AnalysisReport[]) => {
t.ok(!reports.some((mod: AnalysisReport) => mod.report.some((rep: AnalysisReportObj) => rep.warning.includes('INTERNAL ERROR')), 'Should not have internal errors'))
})
const reports = statRunner.runWithModuleList(res, [{ name: new Module().name, mod: new Module() }])
t.ok(!reports.some((mod: AnalysisReport) => mod.report.some((rep: AnalysisReportObj) => rep.warning.includes('INTERNAL ERROR')), 'Should not have internal errors'))
})

@ -1,10 +1,11 @@
'use strict'
import { Plugin } from '@remixproject/engine'
import { util } from '@remix-project/remix-lib'
import { CompilerAbstract } from '@remix-project/remix-solidity'
const profile = {
name: 'compilerArtefacts',
methods: ['get', 'addResolvedContract', 'getCompilerAbstract', 'getAllContractDatas', 'getLastCompilationResult', 'getArtefactsByContractName'],
methods: ['get', 'addResolvedContract', 'getCompilerAbstract', 'getAllContractDatas', 'getLastCompilationResult', 'getArtefactsByContractName', 'getContractDataFromAddress'],
events: [],
version: '0.0.1'
}
@ -72,15 +73,27 @@ export class CompilerArtefacts extends Plugin {
* @returns compilatin output
*/
getAllContractDatas () {
return this.filterAllContractDatas(() => true)
}
/**
* filter compilation output for contracts compiled during a session of Remix IDE
* @returns compilatin output
*/
filterAllContractDatas (filter) {
const contractsData = {}
Object.keys(this.compilersArtefactsPerFile).map((targetFile) => {
const contracts = this.compilersArtefactsPerFile[targetFile].getContracts()
Object.keys(contracts).map((file) => { contractsData[file] = contracts[file] })
Object.keys(contracts).map((file) => {
if (filter(file, contracts[file])) contractsData[file] = contracts[file]
})
})
// making sure we save last compilation result in there
if (this.compilersArtefacts.__last) {
const contracts = this.compilersArtefacts.__last.getContracts()
Object.keys(contracts).map((file) => { contractsData[file] = contracts[file] })
Object.keys(contracts).map((file) => {
if (filter(file, contracts[file])) contractsData[file] = contracts[file]
})
}
return contractsData
}
@ -182,4 +195,20 @@ export class CompilerArtefacts extends Plugin {
get (key) {
return this.compilersArtefacts[key]
}
async getContractDataFromAddress (address) {
const code = await this.call('blockchain', 'getCode', address)
let found
this.filterAllContractDatas((file, contractsData) => {
for (const name of Object.keys(contractsData)) {
const contract = contractsData[name]
if (util.compareByteCode(code, '0x' + contract.evm.deployedBytecode.object)) {
found = { name, contract }
return true
}
}
return true
})
return found
}
}

@ -104,6 +104,7 @@ export class CompilerMetadata extends Plugin {
abi: contract.object.abi
}
await this.call('fileManager', 'writeFile', fileName, JSON.stringify(data, null, '\t'))
this.emit('artefactsUpdated', fileName, contract)
}
_syncContext (contract, metadata) {

@ -60,7 +60,7 @@ export class TxListener {
// in VM mode
// in web3 mode && listen remix txs only
if (!this._isListening) return // we don't listen
if (this._loopId && this.executionContext.getProvider() !== 'vm') return // we seems to already listen on a "web3" network
if (this._loopId) return // we seems to already listen on a "web3" network
let returnValue
let execResult
@ -95,7 +95,7 @@ export class TxListener {
// in VM mode
// in web3 mode && listen remix txs only
if (!this._isListening) return // we don't listen
if (this._loopId && this.executionContext.getProvider() !== 'vm') return // we seems to already listen on a "web3" network
if (this._loopId) return // we seems to already listen on a "web3" network
this.executionContext.web3().eth.getTransaction(txResult.transactionHash, async (error, tx) => {
if (error) return console.log(error)
@ -133,7 +133,7 @@ export class TxListener {
*/
init () {
this.blocks = []
this.lastBlock = null
this.lastBlock = -1
}
/**
@ -170,8 +170,7 @@ export class TxListener {
this.executionContext.web3().eth.getBlockNumber((error, blockNumber) => {
if (this._loopId === null) return
if (error) return console.log(error)
if (currentLoopId === this._loopId && (!this.lastBlock || blockNumber > this.lastBlock)) {
if (!this.lastBlock) this.lastBlock = blockNumber - 1
if (currentLoopId === this._loopId && blockNumber > this.lastBlock) {
let current = this.lastBlock + 1
this.lastBlock = blockNumber
while (blockNumber >= current) {

@ -56,7 +56,12 @@ export class TxRunnerVM {
runInVm (from, to, data, value, gasLimit, useCall, timestamp, callback) {
const self = this
const account = self.vmaccounts[from]
let account
if (!from && useCall && Object.keys(self.vmaccounts).length) {
from = Object.keys(self.vmaccounts)[0]
account = self.vmaccounts[from]
} else account = self.vmaccounts[from]
if (!account) {
return callback('Invalid account selected')
}

@ -101,15 +101,15 @@ export class TxRunnerWeb3 {
// // @todo(#378) this should be removed when https://github.com/WalletConnect/walletconnect-monorepo/issues/334 is fixed
callback(new Error('Gas estimation failed because of an unknown internal error. This may indicated that the transaction will fail.'))
}
gasEstimationForceSend(err, () => {
// callback is called whenever no error
tx['gas'] = !gasEstimation ? gasLimit : gasEstimation
this._api.detectNetwork((err, network) => {
if (err) {
console.log(err)
return
}
this._api.detectNetwork((errNetWork, network) => {
if (errNetWork) {
console.log(errNetWork)
return
}
err = network.name === 'VM' ? null : err // just send the tx if "VM"
gasEstimationForceSend(err, () => {
// callback is called whenever no error
tx['gas'] = !gasEstimation ? gasLimit : gasEstimation
if (this._api.config.getUnpersistedProperty('doNotShowTransactionConfirmationAgain')) {
return this._executeTx(tx, network, null, this._api, promptCb, callback)
@ -120,22 +120,22 @@ export class TxRunnerWeb3 {
}, (error) => {
callback(error)
})
})
}, () => {
const blockGasLimit = this.currentblockGasLimit()
// NOTE: estimateGas very likely will return a large limit if execution of the code failed
// we want to be able to run the code in order to debug and find the cause for the failure
if (err) return callback(err)
}, () => {
const blockGasLimit = this.currentblockGasLimit()
// NOTE: estimateGas very likely will return a large limit if execution of the code failed
// we want to be able to run the code in order to debug and find the cause for the failure
if (err) return callback(err)
let warnEstimation = ' An important gas estimation might also be the sign of a problem in the contract code. Please check loops and be sure you did not sent value to a non payable function (that\'s also the reason of strong gas estimation). '
warnEstimation += ' ' + err
let warnEstimation = ' An important gas estimation might also be the sign of a problem in the contract code. Please check loops and be sure you did not sent value to a non payable function (that\'s also the reason of strong gas estimation). '
warnEstimation += ' ' + err
if (gasEstimation > gasLimit) {
return callback('Gas required exceeds limit: ' + gasLimit + '. ' + warnEstimation)
}
if (gasEstimation > blockGasLimit) {
return callback('Gas required exceeds block gas limit: ' + gasLimit + '. ' + warnEstimation)
}
if (gasEstimation > gasLimit) {
return callback('Gas required exceeds limit: ' + gasLimit + '. ' + warnEstimation)
}
if (gasEstimation > blockGasLimit) {
return callback('Gas required exceeds block gas limit: ' + gasLimit + '. ' + warnEstimation)
}
})
})
})
}

@ -1,6 +1,9 @@
import Web3 from 'web3'
export class Blocks {
vmContext
coinbase: string
TX_INDEX = '0x0' // currently there's always only 1 tx per block, so the transaction index will always be 0x0
constructor (vmContext, _options) {
this.vmContext = vmContext
const options = _options || {}
@ -37,6 +40,28 @@ export class Blocks {
return cb(new Error('block not found'))
}
console.log(block.transactions)
const transactions = block.transactions.map((t) => {
const hash = '0x' + t.hash().toString('hex')
const tx = this.vmContext.txByHash[hash]
const receipt = this.vmContext.currentVm.web3vm.txsReceipt[hash]
if (receipt) {
return {
blockHash: '0x' + block.hash().toString('hex'),
blockNumber: '0x' + block.header.number.toString('hex'),
from: receipt.from,
gas: Web3.utils.toHex(receipt.gas),
chainId: '0xd05',
gasPrice: '0x4a817c800', // 20000000000
hash: receipt.transactionHash,
input: receipt.input,
nonce: '0x' + tx.nonce.toString('hex'),
transactionIndex: this.TX_INDEX,
value: receipt.value === '0x' ? '0x0' : receipt.value,
to: receipt.to ? receipt.to : null
}
}
})
const b = {
baseFeePerGas: '0x01',
number: this.toHex(block.header.number),
@ -55,7 +80,7 @@ export class Blocks {
gasLimit: this.toHex(block.header.gasLimit),
gasUsed: this.toHex(block.header.gasUsed),
timestamp: this.toHex(block.header.timestamp),
transactions: block.transactions.map((t) => '0x' + t.hash().toString('hex')),
transactions,
uncles: []
}
cb(null, b)
@ -70,6 +95,28 @@ export class Blocks {
eth_getBlockByHash (payload, cb) {
const block = this.vmContext.blocks[payload.params[0]]
console.log(block.transactions)
const transactions = block.transactions.map((t) => {
const hash = '0x' + t.hash().toString('hex')
const tx = this.vmContext.txByHash[hash]
const receipt = this.vmContext.currentVm.web3vm.txsReceipt[hash]
if (receipt) {
return {
blockHash: '0x' + block.hash().toString('hex'),
blockNumber: '0x' + block.header.number.toString('hex'),
from: receipt.from,
gas: Web3.utils.toHex(receipt.gas),
chainId: '0xd05',
gasPrice: '0x4a817c800', // 20000000000
hash: receipt.transactionHash,
input: receipt.input,
nonce: '0x' + tx.nonce.toString('hex'),
transactionIndex: this.TX_INDEX,
value: receipt.value === '0x' ? '0x0' : receipt.value,
to: receipt.to ? receipt.to : null
}
}
})
const b = {
baseFeePerGas: '0x01',
number: this.toHex(block.header.number),
@ -88,7 +135,7 @@ export class Blocks {
gasLimit: this.toHex(block.header.gasLimit),
gasUsed: this.toHex(block.header.gasUsed),
timestamp: this.toHex(block.header.timestamp),
transactions: block.transactions.map((t) => '0x' + t.hash().toString('hex')),
transactions,
uncles: []
}

@ -2,6 +2,7 @@ import Web3 from 'web3'
import { toChecksumAddress, BN, Address } from 'ethereumjs-util'
import { processTx } from './txProcess'
import { execution } from '@remix-project/remix-lib'
import { ethers } from 'ethers'
const TxRunnerVM = execution.TxRunnerVM
const TxRunner = execution.TxRunner
@ -122,7 +123,31 @@ export class Transactions {
}
eth_estimateGas (payload, cb) {
cb(null, 10000000 * 8)
// from might be lowercased address (web3)
if (payload.params && payload.params.length > 0 && payload.params[0].from) {
payload.params[0].from = toChecksumAddress(payload.params[0].from)
}
if (payload.params && payload.params.length > 0 && payload.params[0].to) {
payload.params[0].to = toChecksumAddress(payload.params[0].to)
}
payload.params[0].gas = 10000000 * 10
processTx(this.txRunnerInstance, payload, true, (error, result) => {
if (error) return cb(error)
if (result.result.status === '0x0') {
try {
const msg = result.result.execResult.returnValue
const abiCoder = new ethers.utils.AbiCoder()
const reason = abiCoder.decode(['string'], msg.slice(4))[0]
return cb('revert ' + reason)
} catch (e) {
return cb(e.message)
}
}
const gasUsed = result.result.execResult.gasUsed.toNumber()
cb(null, Math.ceil(gasUsed + (15 * gasUsed) / 100))
})
}
eth_getCode (payload, cb) {

@ -183,7 +183,7 @@ export function compileContractSources (sources: SrcIfc, newCompConfig: any, imp
async.waterfall([
(next) => {
if (!deepequal(UTRunner.compilerConfig, newCompConfig)) {
if (!compiler || !deepequal(UTRunner.compilerConfig, newCompConfig)) {
UTRunner.compilerConfig = newCompConfig
const { currentCompilerUrl, evmVersion, optimize, runs, usingWorker } = newCompConfig
compiler = new RemixCompiler(importFileCb)

@ -79,6 +79,9 @@ export function deployAll (compileResult: compilationInterface, web3: Web3, test
console.error(err)
callback(err)
})
}).catch((err) => {
console.error(err)
callback(err)
})
}

@ -12,6 +12,7 @@ export interface RemixUiCheckboxProps {
id?: string
itemName?: string
categoryId?: string
title?: string
visibility?: string
display?: string
}
@ -26,11 +27,12 @@ export const RemixUiCheckbox = ({
onChange,
itemName,
categoryId,
title,
visibility,
display = 'flex'
}: RemixUiCheckboxProps) => {
return (
<div className="listenOnNetwork_2A0YE0 custom-control custom-checkbox" style={{ display: display, alignItems: 'center', visibility: visibility } as CSSProperties } onClick={onClick}>
<div className="listenOnNetwork_2A0YE0 custom-control custom-checkbox" title={title} style={{ display: display, alignItems: 'center', visibility: visibility } as CSSProperties } onClick={onClick}>
<input
id={id}
type={inputType}

@ -1,7 +1,6 @@
import React, { useState, useRef, useEffect, useReducer } from 'react' // eslint-disable-line
import './remix-ui-home-tab.css'
import JSZip from 'jszip'
import { ModalDialog } from '@remix-ui/modal-dialog' // eslint-disable-line
import { Toaster } from '@remix-ui/toaster' // eslint-disable-line
import PluginButton from './components/pluginButton' // eslint-disable-line
@ -176,53 +175,6 @@ export const RemixUiHomeTab = (props: RemixUiHomeTabProps) => {
const startPluginManager = async () => {
plugin.verticalIcons.select('pluginManager')
}
const saveAs = (blob, name) => {
const node = document.createElement('a')
node.download = name
node.rel = 'noopener'
node.href = URL.createObjectURL(blob)
setTimeout(function () { URL.revokeObjectURL(node.href) }, 4E4) // 40s
setTimeout(function () {
try {
node.dispatchEvent(new MouseEvent('click'))
} catch (e) {
const evt = document.createEvent('MouseEvents')
evt.initMouseEvent('click', true, true, window, 0, 0, 0, 80,
20, false, false, false, false, 0, null)
node.dispatchEvent(evt)
}
}, 0) // 40s
}
const downloadFiles = async () => {
try {
plugin.call('notification', 'toast', 'preparing files for download, please wait..')
const zip = new JSZip()
zip.file("readme.txt", "This is a Remix backup file.\nThis zip should be used by the restore backup tool in Remix.\nThe .workspaces directory contains your workspaces.")
const browserProvider = fileManager.getProvider('browser')
await browserProvider.copyFolderToJson('/', ({ path, content }) => {
zip.file(path, content)
})
zip.generateAsync({ type: 'blob' }).then(function (blob) {
const today = new Date()
const date = today.getFullYear() + '-' + (today.getMonth() + 1) + '-' + today.getDate()
const time = today.getHours() + 'h' + today.getMinutes() + 'min'
saveAs(blob, `remix-backup-at-${time}-${date}.zip`)
_paq.push(['trackEvent', 'Backup', 'download', 'home'])
}).catch((e) => {
_paq.push(['trackEvent', 'Backup', 'error', e.message])
plugin.call('notification', 'toast', e.message)
})
} catch (e) {
plugin.call('notification', 'toast', e.message)
}
}
const restoreBackupZip = async () => {
await plugin.appManager.activatePlugin(['restorebackupzip'])
await plugin.call('mainPanel', 'showContent', 'restorebackupzip')
plugin.verticalIcons.select('restorebackupzip')
_paq.push(['trackEvent', 'pluginManager', 'userActivate', 'restorebackupzip'])
}
const showFullMessage = (title: string, loadItem: string, examples: Array<string>) => {
setState(prevState => {
@ -336,14 +288,6 @@ export const RemixUiHomeTab = (props: RemixUiHomeTabProps) => {
<i className="mr-1 far fa-hdd"></i>
<label className="ml-1 remixui_home_text" onClick={() => connectToLocalhost()}>Connect to Localhost</label>
</p>
<p className="mb-1">
<i className="mr-1 far fa-download"></i>
<label className="ml-1 remixui_home_text" onClick={() => downloadFiles()}>Download Backup</label>
</p>
<p className="mb-1">
<i className="mr-1 far fa-upload"></i>
<label className="ml-1 remixui_home_text" onClick={() => restoreBackupZip()}>Restore Backup</label>
</p>
<p className="mt-3 mb-0"><label>LOAD FROM:</label></p>
<div className="btn-group">
<button className="btn mr-1 btn-secondary" data-id="landingPageImportFromGistButton" onClick={() => importFromGist()}>Gist</button>

@ -83,6 +83,14 @@ const setupEvents = () => {
setExecutionContext(env)
})
plugin.on('udapp', 'clearAllInstancesReducer', () => {
dispatch(clearAllInstances())
})
plugin.on('udapp', 'addInstanceReducer', (address, abi, name) => {
addInstance({ abi, address, name })
})
plugin.on('filePanel', 'setWorkspace', () => {
dispatch(resetUdapp())
resetAndInit()
@ -549,7 +557,7 @@ export const updateTxFeeContent = (content: string) => {
dispatch(setTxFeeContent(content))
}
const addInstance = (instance: { contractData?: ContractData, address: string, name: string, abi?: any, decodedResponse?: Record<number, any> }) => {
export const addInstance = (instance: { contractData?: ContractData, address: string, name: string, abi?: any, decodedResponse?: Record<number, any> }) => {
instance.decodedResponse = {}
dispatch(addNewInstance(instance))
}

@ -1,4 +1,4 @@
import React, { useEffect, useState, useReducer } from 'react' // eslint-disable-line
import React, { useEffect, useState, useReducer, useRef } from 'react' // eslint-disable-line
import Button from './Button/StaticAnalyserButton' // eslint-disable-line
import { util } from '@remix-project/remix-lib'
import _ from 'lodash'
@ -64,10 +64,12 @@ export const RemixUiStaticAnalyser = (props: RemixUiStaticAnalyserProps) => {
const [autoRun, setAutoRun] = useState(true)
const [slitherEnabled, setSlitherEnabled] = useState(false)
const [showSlither, setShowSlither] = useState(false)
let [showLibsWarning, setShowLibsWarning] = useState(false) // eslint-disable-line prefer-const
const [categoryIndex, setCategoryIndex] = useState(groupedModuleIndex(groupedModules))
const warningContainer = React.useRef(null)
const [warningState, setWarningState] = useState({})
const warningContainer = useRef(null)
const allWarnings = useRef({})
const [state, dispatch] = useReducer(analysisReducer, initialState)
useEffect(() => {
@ -76,9 +78,12 @@ export const RemixUiStaticAnalyser = (props: RemixUiStaticAnalyserProps) => {
useEffect(() => {
setWarningState({})
const runAnalysis = async () => {
await run(state.data, state.source, state.file)
}
if (autoRun) {
if (state.data !== null) {
run(state.data, state.source, state.file)
runAnalysis().catch(console.error);
}
} else {
props.event.trigger('staticAnaysisWarning', [])
@ -131,6 +136,30 @@ export const RemixUiStaticAnalyser = (props: RemixUiStaticAnalyserProps) => {
)
}
const filterWarnings = () => {
let newWarningState = {}
let newWarningCount = 0
if (showLibsWarning) {
for (const category in allWarnings.current)
newWarningCount = newWarningCount + allWarnings.current[category].length
newWarningState = allWarnings.current
}
else {
for (const category in allWarnings.current) {
const warnings = allWarnings.current[category]
newWarningState[category] = []
for (const warning of warnings) {
if (!warning.options.isLibrary) {
newWarningCount++
newWarningState[category].push(warning)
}
}
}
}
props.event.trigger('staticAnaysisWarning', [newWarningCount])
setWarningState(newWarningState)
}
const showWarnings = (warningMessage, groupByKey) => {
const resultArray = []
warningMessage.map(x => {
@ -149,143 +178,149 @@ export const RemixUiStaticAnalyser = (props: RemixUiStaticAnalyserProps) => {
}
const groupedCategory = groupBy(resultArray, groupByKey)
setWarningState(groupedCategory)
allWarnings.current = groupedCategory
filterWarnings()
}
const run = (lastCompilationResult, lastCompilationSource, currentFile) => {
const run = async (lastCompilationResult, lastCompilationSource, currentFile) => {
if (state.data !== null) {
if (lastCompilationResult && (categoryIndex.length > 0 || slitherEnabled)) {
let warningCount = 0
const warningMessage = []
const warningErrors = []
// Remix Analysis
_paq.push(['trackEvent', 'solidityStaticAnalyzer', 'analyzeWithRemixAnalyzer'])
runner.run(lastCompilationResult, categoryIndex, results => {
results.map((result) => {
let moduleName
Object.keys(groupedModules).map(key => {
groupedModules[key].forEach(el => {
if (el.name === result.name) {
moduleName = groupedModules[key][0].categoryDisplayName
}
})
})
result.report.map((item) => {
let location: any = {}
let locationString = 'not available'
let column = 0
let row = 0
let fileName = currentFile
if (item.location) {
const split = item.location.split(':')
const file = split[2]
location = {
start: parseInt(split[0]),
length: parseInt(split[1])
}
location = props.analysisModule._deps.offsetToLineColumnConverter.offsetToLineColumn(
location,
parseInt(file),
lastCompilationSource.sources,
lastCompilationResult.sources
)
row = location.start.line
column = location.start.column
locationString = row + 1 + ':' + column + ':'
fileName = Object.keys(lastCompilationResult.sources)[file]
const results = runner.run(lastCompilationResult, categoryIndex)
for (const result of results) {
let moduleName
Object.keys(groupedModules).map(key => {
groupedModules[key].forEach(el => {
if (el.name === result.name) {
moduleName = groupedModules[key][0].categoryDisplayName
}
warningCount++
const msg = message(result.name, item.warning, item.more, fileName, locationString)
const options = {
type: 'warning',
useSpan: true,
errFile: fileName,
fileName,
errLine: row,
errCol: column,
item: item,
name: result.name,
locationString,
more: item.more,
location: location
}
warningErrors.push(options)
warningMessage.push({ msg, options, hasWarning: true, warningModuleName: moduleName })
})
})
// Slither Analysis
if (slitherEnabled) {
props.analysisModule.call('solidity', 'getCompilerState').then((compilerState) => {
const { currentVersion, optimize, evmVersion } = compilerState
props.analysisModule.call('terminal', 'log', { type: 'info', value: '[Slither Analysis]: Running...' })
_paq.push(['trackEvent', 'solidityStaticAnalyzer', 'analyzeWithSlither'])
props.analysisModule.call('slither', 'analyse', state.file, { currentVersion, optimize, evmVersion }).then(async (result) => {
if (result.status) {
props.analysisModule.call('terminal', 'log', { type: 'info', value: `[Slither Analysis]: Analysis Completed!! ${result.count} warnings found.` })
const report = result.data
for (const item of report) {
let location: any = {}
let locationString = 'not available'
let column = 0
let row = 0
let fileName = currentFile
for (const item of result.report) {
let location: any = {}
let locationString = 'not available'
let column = 0
let row = 0
let fileName = currentFile
let isLibrary = false
if (item.sourceMap && item.sourceMap.length) {
let path = item.sourceMap[0].source_mapping.filename_relative
let fileIndex = Object.keys(lastCompilationResult.sources).indexOf(path)
if (fileIndex === -1) {
path = await props.analysisModule.call('fileManager', 'getUrlFromPath', path)
fileIndex = Object.keys(lastCompilationResult.sources).indexOf(path.file)
}
if (fileIndex >= 0) {
location = {
start: item.sourceMap[0].source_mapping.start,
length: item.sourceMap[0].source_mapping.length
}
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]
}
}
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
if (item.location) {
const split = item.location.split(':')
const file = split[2]
location = {
start: parseInt(split[0]),
length: parseInt(split[1])
}
location = props.analysisModule._deps.offsetToLineColumnConverter.offsetToLineColumn(
location,
parseInt(file),
lastCompilationSource.sources,
lastCompilationResult.sources
)
row = location.start.line
column = location.start.column
locationString = row + 1 + ':' + column + ':'
fileName = Object.keys(lastCompilationResult.sources)[file]
}
if(fileName !== currentFile) {
const {file, provider} = await props.analysisModule.call('fileManager', 'getPathFromUrl', fileName)
if (file.startsWith('.deps') || (provider.type === 'localhost' && file.startsWith('localhost/node_modules'))) isLibrary = true
}
const msg = message(result.name, item.warning, item.more, fileName, locationString)
const options = {
type: 'warning',
useSpan: true,
errFile: fileName,
fileName,
isLibrary,
errLine: row,
errCol: column,
item: item,
name: result.name,
locationString,
more: item.more,
location: location
}
warningErrors.push(options)
warningMessage.push({ msg, options, hasWarning: true, warningModuleName: moduleName })
}
}
// Slither Analysis
if (slitherEnabled) {
try {
const compilerState = await props.analysisModule.call('solidity', 'getCompilerState')
const { currentVersion, optimize, evmVersion } = compilerState
await props.analysisModule.call('terminal', 'log', { type: 'info', value: '[Slither Analysis]: Running...' })
_paq.push(['trackEvent', 'solidityStaticAnalyzer', 'analyzeWithSlither'])
const result = await props.analysisModule.call('slither', 'analyse', state.file, { currentVersion, optimize, evmVersion })
if (result.status) {
props.analysisModule.call('terminal', 'log', { type: 'info', value: `[Slither Analysis]: Analysis Completed!! ${result.count} warnings found.` })
const report = result.data
for (const item of report) {
let location: any = {}
let locationString = 'not available'
let column = 0
let row = 0
let fileName = currentFile
let isLibrary = false
if (item.sourceMap && item.sourceMap.length) {
let path = item.sourceMap[0].source_mapping.filename_relative
let fileIndex = Object.keys(lastCompilationResult.sources).indexOf(path)
if (fileIndex === -1) {
path = await props.analysisModule.call('fileManager', 'getUrlFromPath', path)
fileIndex = Object.keys(lastCompilationResult.sources).indexOf(path.file)
}
if (fileIndex >= 0) {
location = {
start: item.sourceMap[0].source_mapping.start,
length: item.sourceMap[0].source_mapping.length
}
warningErrors.push(options)
warningMessage.push({ msg, options, hasWarning: true, warningModuleName: 'Slither Analysis' })
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]
}
showWarnings(warningMessage, 'warningModuleName')
props.event.trigger('staticAnaysisWarning', [warningCount])
}
}).catch(() => {
props.analysisModule.call('terminal', 'log', { type: 'error', value: '[Slither Analysis]: Error occured! See remixd console for details.' })
showWarnings(warningMessage, 'warningModuleName')
})
})
} else {
if(fileName !== currentFile) {
const {file, provider} = await props.analysisModule.call('fileManager', 'getPathFromUrl', fileName)
if (file.startsWith('.deps') || (provider.type === 'localhost' && file.startsWith('localhost/node_modules'))) isLibrary = true
}
const msg = message(item.title, item.description, item.more, fileName, locationString)
const options = {
type: 'warning',
useSpan: true,
errFile: fileName,
fileName,
isLibrary,
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' })
}
showWarnings(warningMessage, 'warningModuleName')
}
} catch(error) {
props.analysisModule.call('terminal', 'log', { type: 'error', value: '[Slither Analysis]: Error occured! See remixd console for details.' })
showWarnings(warningMessage, 'warningModuleName')
props.event.trigger('staticAnaysisWarning', [warningCount])
}
})
} else showWarnings(warningMessage, 'warningModuleName')
} else {
if (categoryIndex.length) {
warningContainer.current.innerText = 'No compiled AST available'
@ -346,6 +381,17 @@ export const RemixUiStaticAnalyser = (props: RemixUiStaticAnalyserProps) => {
}
}
const handleShowLibsWarning = () => {
if (showLibsWarning) {
showLibsWarning = false
setShowLibsWarning(false)
} else {
showLibsWarning = true
setShowLibsWarning(true)
}
filterWarnings()
}
const categoryItem = (categoryId, item, i) => {
return (
<div className="form-check" key={i}>
@ -409,6 +455,7 @@ export const RemixUiStaticAnalyser = (props: RemixUiStaticAnalyserProps) => {
<RemixUiCheckbox
id="checkAllEntries"
inputType="checkbox"
title="Select all Remix analysis modules"
checked={Object.values(groupedModules).map((value: any) => {
return (value.map(x => {
return x._index.toString()
@ -421,12 +468,13 @@ export const RemixUiStaticAnalyser = (props: RemixUiStaticAnalyserProps) => {
<RemixUiCheckbox
id="autorunstaticanalysis"
inputType="checkbox"
title="Run static analysis after the compilation"
onClick={handleAutoRun}
checked={autoRun}
label="Autorun"
onChange={() => {}}
/>
<Button buttonText="Run" onClick={() => run(state.data, state.source, state.file)} disabled={(state.data === null || categoryIndex.length === 0) && !slitherEnabled }/>
<Button buttonText="Run" onClick={async () => await run(state.data, state.source, state.file)} disabled={(state.data === null || categoryIndex.length === 0) && !slitherEnabled }/>
</div>
{ showSlither &&
<div className="d-flex mt-2" id="enableSlitherAnalysis">
@ -469,14 +517,25 @@ export const RemixUiStaticAnalyser = (props: RemixUiStaticAnalyserProps) => {
{state.file}
</span>
</div>
<br/>
{Object.entries(warningState).length > 0 &&
<div id='staticanalysisresult' >
<RemixUiCheckbox
id="showLibWarnings"
name="showLibWarnings"
categoryId="showLibWarnings"
title="when checked, the results are also displayed for external contract libraries"
inputType="checkbox"
checked={showLibsWarning}
label="Display all results"
onClick={handleShowLibsWarning}
onChange={() => {}}
/>
<br/>
<div className="mb-4">
{
(Object.entries(warningState).map((element, index) => (
<div key={index}>
<span className="text-dark h6">{element[0]}</span>
{element[1]['length'] > 0 ? <span className="text-dark h6">{element[0]}</span> : null}
{element[1]['map']((x, i) => ( // eslint-disable-line dot-notation
x.hasWarning ? ( // eslint-disable-next-line dot-notation
<div data-id={`staticAnalysisModule${x.warningModuleName}${i}`} id={`staticAnalysisModule${x.warningModuleName}${i}`} key={i}>

@ -462,7 +462,7 @@ export const RemixUiTerminal = (props: RemixUiTerminalProps) => {
title="If checked Remix will listen on all transactions mined in the current environment and not only transactions created by you"
htmlFor="listenNetworkCheck"
>
listen on network
listen on all transactions
</label>
</div>
<div className="remix_ui_terminal_search d-flex align-items-center h-100">

@ -5,13 +5,14 @@ import { customAction } from '@remixproject/plugin-api/lib/file-system/file-pane
import { displayNotification, displayPopUp, fetchDirectoryError, fetchDirectoryRequest, fetchDirectorySuccess, focusElement, fsInitializationCompleted, hidePopUp, removeInputFieldSuccess, setCurrentWorkspace, setExpandPath, setMode, setWorkspaces } from './payload'
import { listenOnPluginEvents, listenOnProviderEvents } from './events'
import { createWorkspaceTemplate, getWorkspaces, loadWorkspacePreset, setPlugin } from './workspace'
import { QueryParams } from '@remix-project/remix-lib'
import JSZip from 'jszip'
export * from './events'
export * from './workspace'
import { QueryParams } from '@remix-project/remix-lib'
const queryParams = new QueryParams()
const _paq = window._paq = window._paq || []
let plugin, dispatch: React.Dispatch<any>
@ -269,6 +270,40 @@ export const handleExpandPath = (paths: string[]) => {
dispatch(setExpandPath(paths))
}
export const handleDownloadFiles = async () => {
try {
plugin.call('notification', 'toast', 'preparing files for download, please wait..')
const zip = new JSZip()
zip.file("readme.txt", "This is a Remix backup file.\nThis zip should be used by the restore backup tool in Remix.\nThe .workspaces directory contains your workspaces.")
const browserProvider = plugin.fileManager.getProvider('browser')
await browserProvider.copyFolderToJson('/', ({ path, content }) => {
zip.file(path, content)
})
zip.generateAsync({ type: 'blob' }).then(function (blob) {
const today = new Date()
const date = today.getFullYear() + '-' + (today.getMonth() + 1) + '-' + today.getDate()
const time = today.getHours() + 'h' + today.getMinutes() + 'min'
saveAs(blob, `remix-backup-at-${time}-${date}.zip`)
_paq.push(['trackEvent', 'Backup', 'download', 'home'])
}).catch((e) => {
_paq.push(['trackEvent', 'Backup', 'error', e.message])
plugin.call('notification', 'toast', e.message)
})
} catch (e) {
plugin.call('notification', 'toast', e.message)
}
}
export const restoreBackupZip = async () => {
await plugin.appManager.activatePlugin(['restorebackupzip'])
await plugin.call('mainPanel', 'showContent', 'restorebackupzip')
plugin.verticalIcons.select('restorebackupzip')
_paq.push(['trackEvent', 'pluginManager', 'userActivate', 'restorebackupzip'])
}
const packageGistFiles = async (directory) => {
const workspaceProvider = plugin.fileProviders.workspace
const isFile = await workspaceProvider.isFile(directory)
@ -344,3 +379,23 @@ const getOriginalFiles = async (id) => {
const data = await res.json()
return data.files || []
}
const saveAs = (blob, name) => {
const node = document.createElement('a')
node.download = name
node.rel = 'noopener'
node.href = URL.createObjectURL(blob)
setTimeout(function () { URL.revokeObjectURL(node.href) }, 4E4) // 40s
setTimeout(function () {
try {
node.dispatchEvent(new MouseEvent('click'))
} catch (e) {
const evt = document.createEvent('MouseEvents')
evt.initMouseEvent('click', true, true, window, 0, 0, 0, 80,
20, false, false, false, false, 0, null)
node.dispatchEvent(evt)
}
}, 0) // 40s
}

@ -27,5 +27,7 @@ export const FileSystemContext = createContext<{
dispatchRunScript: (path: string) => Promise<void>,
dispatchEmitContextMenuEvent: (cmd: customAction) => Promise<void>,
dispatchHandleClickFile: (path: string, type: 'file' | 'folder' | 'gist') => Promise<void>
dispatchHandleExpandPath: (paths: string[]) => Promise<void>
dispatchHandleExpandPath: (paths: string[]) => Promise<void>,
dispatchHandleDownloadFiles: () => Promise<void>,
dispatchHandleRestoreBackup: () => Promise<void>
}>(null)

@ -58,4 +58,7 @@
.remixui_menuicon {
padding-right : 10px;
}
.remixui_menuicon:hover {
transform: scale(1.3);
}

@ -5,7 +5,7 @@ import { Toaster } from '@remix-ui/toaster' // eslint-disable-line
// eslint-disable-next-line @typescript-eslint/no-unused-vars
import { FileSystemContext } from '../contexts'
import { browserReducer, browserInitialState } from '../reducers/workspace'
import { initWorkspace, fetchDirectory, removeInputField, deleteWorkspace, clearPopUp, publishToGist, createNewFile, setFocusElement, createNewFolder, deletePath, renamePath, copyFile, copyFolder, runScript, emitContextMenuEvent, handleClickFile, handleExpandPath, addInputField, createWorkspace, fetchWorkspaceDirectory, renameWorkspace, switchToWorkspace, uploadFile } from '../actions'
import { initWorkspace, fetchDirectory, removeInputField, deleteWorkspace, clearPopUp, publishToGist, createNewFile, setFocusElement, createNewFolder, deletePath, renamePath, copyFile, copyFolder, runScript, emitContextMenuEvent, handleClickFile, handleExpandPath, addInputField, createWorkspace, fetchWorkspaceDirectory, renameWorkspace, switchToWorkspace, uploadFile, handleDownloadFiles, restoreBackupZip } from '../actions'
import { Modal, WorkspaceProps } from '../types'
// eslint-disable-next-line @typescript-eslint/no-unused-vars
import { Workspace } from '../remix-ui-workspace'
@ -115,6 +115,14 @@ export const FileSystemProvider = (props: WorkspaceProps) => {
await handleExpandPath(paths)
}
const dispatchHandleDownloadFiles = async () => {
await handleDownloadFiles()
}
const dispatchHandleRestoreBackup = async () => {
await restoreBackupZip()
}
useEffect(() => {
dispatchInitWorkspace()
}, [])
@ -214,7 +222,9 @@ export const FileSystemProvider = (props: WorkspaceProps) => {
dispatchRunScript,
dispatchEmitContextMenuEvent,
dispatchHandleClickFile,
dispatchHandleExpandPath
dispatchHandleExpandPath,
dispatchHandleDownloadFiles,
dispatchHandleRestoreBackup
}
return (
<FileSystemContext.Provider value={value}>

@ -50,6 +50,22 @@ export function Workspace () {
global.modal('Delete Current Workspace', 'Are you sure to delete the current workspace?', 'OK', onFinishDeleteWorkspace, '')
}
const downloadWorkspaces = async () => {
try {
await global.dispatchHandleDownloadFiles()
} catch (e) {
console.error(e)
}
}
const restoreBackup = async () => {
try {
await global.dispatchHandleRestoreBackup()
} catch (e) {
console.error(e)
}
}
const onFinishRenameWorkspace = async () => {
if (workspaceRenameInput.current === undefined) return
// @ts-ignore: Object is possibly 'null'.
@ -156,9 +172,30 @@ export function Workspace () {
e.stopPropagation()
deleteCurrentWorkspace()
}}
className='fas fa-trash'
className='fas fa-trash remixui_menuicon'
title='Delete'>
</span>
<span
hidden={currentWorkspace === NO_WORKSPACE}
id='workspacesDownload'
data-id='workspacesDownload'
onClick={(e) => {
e.stopPropagation()
downloadWorkspaces()
}}
className='far fa-download remixui_menuicon'
title='Download Workspaces'>
</span>
<span
id='workspacesRestore'
data-id='workspacesRestore'
onClick={(e) => {
e.stopPropagation()
restoreBackup()
}}
className='far fa-upload remixui_menuicon'
title='Restore Workspaces Backup'>
</span>
</span>
<select id="workspacesSelect" value={currentWorkspace} data-id="workspacesSelect" onChange={(e) => switchWorkspace(e.target.value)} className="form-control custom-select">
{

@ -135,7 +135,7 @@ export class SlitherClient extends PluginClient {
}
const solcRemaps = remaps ? `--solc-remaps "${remaps}"` : ''
const outputFile: string = 'remix-slitherReport_' + Math.floor(Date.now() / 1000) + '.json'
const outputFile = 'remix-slither-report.json'
const cmd = `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'
@ -149,9 +149,6 @@ export class SlitherClient extends PluginClient {
if (existsSync(outputFileAbsPath)) {
let report = readFileSync(outputFileAbsPath, 'utf8')
report = JSON.parse(report)
unlink(outputFileAbsPath, (err) => {
if (err) console.log(err)
})
if (report['success']) {
response['status'] = true
if (!report['results'] || !report['results'].detectors || !report['results'].detectors.length) {

Loading…
Cancel
Save