Merge branch 'master' into react-plugin-manager

pull/1344/head
Joe Izang 3 years ago committed by GitHub
commit 1f46c590a9
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 2
      apps/remix-ide-e2e/src/commands/getModalBody.ts
  2. 13
      apps/remix-ide-e2e/src/commands/verifyContracts.ts
  3. 2
      apps/remix-ide-e2e/src/helpers/init.ts
  4. 2
      apps/remix-ide-e2e/src/tests/ballot.test.ts
  5. 2
      apps/remix-ide-e2e/src/tests/ballot_0_4_11.spec.ts
  6. 4
      apps/remix-ide-e2e/src/tests/debugger.spec.ts
  7. 2
      apps/remix-ide-e2e/src/tests/editor.spec.ts
  8. 10
      apps/remix-ide-e2e/src/tests/gist.spec.ts
  9. 1
      apps/remix-ide-e2e/src/tests/pluginManager.spec.ts
  10. 31
      apps/remix-ide-e2e/src/tests/publishContract.test.ts
  11. 3
      apps/remix-ide-e2e/src/tests/solidityUnittests.spec.ts
  12. 49
      apps/remix-ide-e2e/src/tests/url.spec.ts
  13. 7
      apps/remix-ide-e2e/src/tests/usingWebWorker.test.ts
  14. 1
      apps/remix-ide-e2e/src/tests/verticalIconsPanel.spec.ts
  15. 4
      apps/remix-ide/ci/publishIpfs
  16. 60
      apps/remix-ide/src/app.js
  17. 46
      apps/remix-ide/src/app/compiler/compiler-abstract.js
  18. 169
      apps/remix-ide/src/app/compiler/compiler-imports.js
  19. 21
      apps/remix-ide/src/app/compiler/compiler-input.js
  20. 34
      apps/remix-ide/src/app/components/vertical-icons.js
  21. 2
      apps/remix-ide/src/app/editor/sourceHighlighter.js
  22. 148
      apps/remix-ide/src/app/files/compiler-metadata.js
  23. 19
      apps/remix-ide/src/app/files/dgitProvider.js
  24. 702
      apps/remix-ide/src/app/files/file-explorer.js
  25. 33
      apps/remix-ide/src/app/files/fileManager.js
  26. 4
      apps/remix-ide/src/app/files/fileProvider.js
  27. 3
      apps/remix-ide/src/app/files/remixDProvider.js
  28. 18
      apps/remix-ide/src/app/files/remixd-handle.js
  29. 18
      apps/remix-ide/src/app/files/slither-handle.js
  30. 48
      apps/remix-ide/src/app/panels/file-panel.js
  31. 1
      apps/remix-ide/src/app/panels/tab-proxy.js
  32. 2
      apps/remix-ide/src/app/tabs/analysis-tab.js
  33. 375
      apps/remix-ide/src/app/tabs/compile-tab.js
  34. 578
      apps/remix-ide/src/app/tabs/compileTab/compilerContainer.js
  35. 2
      apps/remix-ide/src/app/tabs/hardhat-provider.js
  36. 7
      apps/remix-ide/src/app/tabs/runTab/contractDropdown.js
  37. 2
      apps/remix-ide/src/app/tabs/runTab/model/dropdownlogic.js
  38. 7
      apps/remix-ide/src/app/tabs/runTab/recorder.js
  39. 8
      apps/remix-ide/src/app/tabs/runTab/settings.js
  40. 43
      apps/remix-ide/src/app/tabs/settings-tab.js
  41. 7
      apps/remix-ide/src/app/tabs/test-tab.js
  42. 88
      apps/remix-ide/src/app/ui/confirmDialog.js
  43. 2
      apps/remix-ide/src/app/ui/copy-to-clipboard.js
  44. 28
      apps/remix-ide/src/app/ui/landing-page/landing-page.js
  45. 7
      apps/remix-ide/src/app/ui/sendTxCallbacks.js
  46. BIN
      apps/remix-ide/src/assets/img/optimismLogo.webp
  47. 8
      apps/remix-ide/src/assets/js/editor/darkTheme.js
  48. 10
      apps/remix-ide/src/assets/js/intro.min.js
  49. 10
      apps/remix-ide/src/blockchain/execution-context.js
  50. 7
      apps/remix-ide/src/index.html
  51. 4
      apps/remix-ide/src/lib/cmdInterpreterAPI.js
  52. 2
      apps/remix-ide/src/lib/publishOnIpfs.js
  53. 1
      apps/remix-ide/src/remixEngine.js
  54. 54
      apps/remix-ide/src/walkthroughService.js
  55. 15
      libs/remix-analyzer/package.json
  56. 15
      libs/remix-astwalker/package.json
  57. 1
      libs/remix-core-plugin/.eslintrc
  58. 3
      libs/remix-core-plugin/README.md
  59. 12
      libs/remix-core-plugin/package.json
  60. 5
      libs/remix-core-plugin/src/index.ts
  61. 11
      libs/remix-core-plugin/src/lib/compiler-artefacts.ts
  62. 180
      libs/remix-core-plugin/src/lib/compiler-content-imports.ts
  63. 28
      libs/remix-core-plugin/src/lib/compiler-fetch-and-compile.ts
  64. 145
      libs/remix-core-plugin/src/lib/compiler-metadata.ts
  65. 77
      libs/remix-core-plugin/src/lib/offset-line-to-column-converter.ts
  66. 10
      libs/remix-core-plugin/tsconfig.json
  67. 12
      libs/remix-core-plugin/tsconfig.lib.json
  68. 15
      libs/remix-debug/package.json
  69. 8
      libs/remix-debug/src/code/codeManager.ts
  70. 2
      libs/remix-debug/src/code/codeUtils.ts
  71. 4
      libs/remix-debug/src/debugger/VmDebugger.ts
  72. 2
      libs/remix-debug/src/eventManager.ts
  73. 2
      libs/remix-debug/src/solidity-decoder/types/util.ts
  74. 4
      libs/remix-debug/src/trace/traceManager.ts
  75. 2
      libs/remix-debug/test/decoder/vmCall.ts
  76. 2
      libs/remix-debug/test/vmCall.ts
  77. 11
      libs/remix-lib/package.json
  78. 2
      libs/remix-lib/src/eventManager.ts
  79. 6
      libs/remix-lib/src/execution/forkAt.ts
  80. 32
      libs/remix-lib/src/execution/txExecution.ts
  81. 2
      libs/remix-lib/src/execution/txRunnerVM.ts
  82. 18
      libs/remix-lib/src/execution/txRunnerWeb3.ts
  83. 17
      libs/remix-lib/src/init.ts
  84. 15
      libs/remix-simulator/package.json
  85. 2
      libs/remix-simulator/src/vm-context.ts
  86. 15
      libs/remix-solidity/package.json
  87. 48
      libs/remix-solidity/src/compiler/compiler-abstract.ts
  88. 4
      libs/remix-solidity/src/compiler/compiler-helpers.ts
  89. 0
      libs/remix-solidity/src/compiler/compiler-utils.ts
  90. 3
      libs/remix-solidity/src/index.ts
  91. 2
      libs/remix-solidity/src/lib/eventManager.ts
  92. 19
      libs/remix-tests/package.json
  93. 10
      libs/remix-ui/checkbox/src/lib/remix-ui-checkbox.tsx
  94. 35
      libs/remix-ui/clipboard/src/lib/copy-to-clipboard/copy-to-clipboard.tsx
  95. 36
      libs/remix-ui/debugger-ui/src/lib/vm-debugger/assembly-items.tsx
  96. 46
      libs/remix-ui/debugger-ui/src/lib/vm-debugger/solidity-state.tsx
  97. 8
      libs/remix-ui/debugger-ui/src/reducers/assembly-items.ts
  98. 70
      libs/remix-ui/debugger-ui/src/utils/solidityTypeFormatter.ts
  99. 5
      libs/remix-ui/file-explorer/src/lib/file-explorer-context-menu.tsx
  100. 64
      libs/remix-ui/file-explorer/src/lib/file-explorer.tsx
  101. Some files were not shown because too many files have changed in this diff Show More

@ -3,7 +3,7 @@ import EventEmitter from 'events'
class GetModalBody extends EventEmitter {
command (this: NightwatchBrowser, callback: (value: string, cb: VoidFunction) => void) {
this.api.waitForElementPresent('.modal-body')
this.api.waitForElementVisible('[data-id="modalDialogModalBody"]')
.getText('#modal-dialog', (result) => {
console.log(result)
const value = typeof result.value === 'string' ? result.value : null

@ -22,12 +22,15 @@ function verifyContracts (browser: NightwatchBrowser, compiledContractNames: str
if (opts.version) {
browser
.click('*[data-id="compilation-details"]')
.waitForElementVisible('*[data-id="treeViewDivcompiler"]')
.waitForElementVisible('*[data-id="remixui_treeviewitem_metadata"]')
.pause(2000)
.click('*[data-id="treeViewDivcompiler"]')
.waitForElementVisible('*[data-id="treeViewLicompiler/version"]')
.assert.containsText('*[data-id="treeViewLicompiler/version"]', `version:\n ${opts.version}`)
.modalFooterCancelClick()
.click('*[data-id="remixui_treeviewitem_metadata"]')
.waitForElementVisible('*[data-id="treeViewDivtreeViewItemcompiler"]')
.pause(2000)
.click('*[data-id="treeViewDivtreeViewItemcompiler"]')
.waitForElementVisible('*[data-id="treeViewLiversion"]')
.assert.containsText('*[data-id="treeViewLiversion"]', `${opts.version}`)
.click('[data-id="workspacesModalDialog-modal-footer-ok-react"]')
.perform(() => {
done()
callback()

@ -7,6 +7,8 @@ export default function (browser: NightwatchBrowser, callback: VoidFunction, url
.url(url || 'http://127.0.0.1:8080')
.pause(5000)
.switchBrowserTab(0)
.waitForElementVisible('[id="remixTourSkipbtn"]')
.click('[id="remixTourSkipbtn"]')
.fullscreenWindow(() => {
if (preloadPlugins) {
initModules(browser, () => {

@ -49,7 +49,7 @@ module.exports = {
'Debug Ballot / delegate': function (browser: NightwatchBrowser) {
browser.pause(500)
.click('*[data-id="txLoggerDebugButton0xf88bc0ac0761f78d8c883b32550c68dadcdb095595c30e1a1b7c583e5e958dcb"]')
.debugTransaction(1)
.waitForElementVisible('*[data-id="buttonNavigatorJumpPreviousBreakpoint"]')
.click('*[data-id="buttonNavigatorJumpPreviousBreakpoint"]')
.pause(2000)

@ -45,7 +45,7 @@ module.exports = {
'Debug Ballot / delegate': function (browser: NightwatchBrowser) {
browser.pause(500)
.click('*[data-id="txLoggerDebugButton0xf88bc0ac0761f78d8c883b32550c68dadcdb095595c30e1a1b7c583e5e958dcb"]')
.debugTransaction(1)
.pause(2000)
.waitForElementVisible('*[data-id="buttonNavigatorJumpPreviousBreakpoint"]')
.click('*[data-id="buttonNavigatorJumpPreviousBreakpoint"]')

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

@ -36,7 +36,7 @@ module.exports = {
.click('*[class="ace_content"]')
.sendKeys('*[class="ace_text-input"]', 'error')
.pause(2000)
.waitForElementVisible('.ace_error', 60000)
.waitForElementVisible('.ace_error', 120000)
.checkAnnotations('error', 28)
.clickLaunchIcon('udapp')
.checkAnnotationsNotPresent('error')

@ -3,7 +3,7 @@ import { NightwatchBrowser } from 'nightwatch'
import init from '../helpers/init'
const testData = {
validGistId: '1859c97c6e1efc91047d725d5225888e',
validGistId: '02a847917a6a7ecaf4a7e0d4e68715bf',
invalidGistId: '6368b389f9302v32902msk2402'
}
// 99266d6da54cc12f37f11586e8171546c7700d67
@ -20,7 +20,7 @@ module.exports = {
- switch to a file in the new gist
*/
console.log('token', process.env.gist_token)
const gistid = 'c15ed7c182a7991ea0a4dea1544fa254'
const gistid = '17ac9315bc065a3d95cf8dc1b28d71f8'
browser
.refresh()
.pause(10000)
@ -125,9 +125,9 @@ module.exports = {
.waitForElementVisible('*[data-id="modalDialogCustomPromptText"]')
.setValue('*[data-id="modalDialogCustomPromptText"]', testData.validGistId)
.modalFooterOKClick()
.openFile(`gist-${testData.validGistId}/ApplicationRegistry`)
.waitForElementVisible(`div[title='default_workspace/gist-${testData.validGistId}/ApplicationRegistry']`)
.assert.containsText(`div[title='default_workspace/gist-${testData.validGistId}/ApplicationRegistry'] > span`, 'ApplicationRegistry')
.openFile(`gist-${testData.validGistId}/README.txt`)
.waitForElementVisible(`div[title='default_workspace/gist-${testData.validGistId}/README.txt']`)
.assert.containsText(`div[title='default_workspace/gist-${testData.validGistId}/README.txt'] > span`, 'README.txt')
.end()
}
}

@ -114,6 +114,7 @@ module.exports = {
.setValue('*[data-id="localPluginUrl"]', testData.pluginUrl)
.click('*[data-id="localPluginRadioButtoniframe"]')
.click('*[data-id="localPluginRadioButtonsidePanel"]')
.click('*[data-id="pluginManagerLocalPluginModalDialogModalDialogModalFooter-react"]')
.click('*[data-id="pluginManagerLocalPluginModalDialog-modal-footer-ok-react')
// .modalFooterOKClick()

@ -20,24 +20,32 @@ module.exports = {
.verifyContracts(['Ballot'])
.click('#publishOnIpfs')
.pause(8000)
.getModalBody((value, done) => {
if (value.indexOf('Metadata of "ballot" was published successfully.') === -1) browser.assert.fail('ipfs deploy failed', '', '')
if (value.indexOf('ipfs://') === -1) browser.assert.fail('ipfs deploy failed', '', '')
done()
.waitForElementVisible('[data-id="publishToStorageModalDialogModalBody-react"]', 60000)
.getText('[data-id="publishToStorageModalDialogModalBody-react"]', (result) => {
const value = <string>(result.value)
browser.perform((done) => {
if (value.indexOf('Metadata of "ballot" was published successfully.') === -1) browser.assert.fail('ipfs deploy failed')
done()
})
})
.modalFooterOKClick()
.click('[data-id="publishToStorage-modal-footer-ok-react"]')
},
'Publish on Swarm': '' + function (browser: NightwatchBrowser) {
browser
.click('#publishOnSwarm')
.pause(8000)
.getModalBody((value, done) => {
if (value.indexOf('Metadata of "ballot" was successfully.') === -1) browser.assert.fail('swarm deploy failed', '', '')
if (value.indexOf('bzz') === -1) browser.assert.fail('swarm deploy failed', '', '')
done()
.getText('[data-id="publishToStorageModalDialogModalBody-react"]', (result) => {
const value = <string>(result.value)
browser.perform((done) => {
if (value.indexOf('Metadata of "ballot" was published successfully.') === -1) browser.assert.fail('swarm deploy failed')
if (value.indexOf('bzz') === -1) browser.assert.fail('swarm deploy failed')
done()
})
})
.modalFooterOKClick()
.click('[data-id="publishToStorage-modal-footer-ok-react"]')
},
'Should publish contract metadata to ipfs on deploy': function (browser: NightwatchBrowser) {
@ -48,10 +56,11 @@ module.exports = {
.clickLaunchIcon('udapp')
.waitForElementPresent('*[data-id="contractDropdownIpfsCheckbox"]')
.click('*[data-id="contractDropdownIpfsCheckbox"]')
.waitForElementVisible('*[data-id="Deploy - transact (not payable)"]')
.click('*[data-id="Deploy - transact (not payable)"]')
.pause(8000)
.getModalBody((value, done) => {
if (value.indexOf('Metadata of "storage" was published successfully.') === -1) browser.assert.fail('ipfs deploy failed', '', '')
if (value.indexOf('Metadata of "storage" was published successfully.') === -1) browser.assert.fail('ipfs deploy failed')
done()
})
.modalFooterOKClick()

@ -84,8 +84,7 @@ module.exports = {
.scrollAndClick('*[data-id="testTabRunTestsTabRunAction"]')
.pause(2000)
.click('*[data-id="testTabRunTestsTabStopAction"]')
.waitForElementContainsText('*[data-id="testTabRunTestsTabStopAction"]', 'Stopping', 60000)
.waitForElementContainsText('*[data-id="testTabSolidityUnitTestsOutput"]', '/tests/ks2b_test.sol', 120000)
.waitForElementContainsText('*[data-id="testTabSolidityUnitTestsOutput"]', '/tests/ks2b_test.sol', 200000)
.notContainsText('*[data-id="testTabSolidityUnitTestsOutput"]', '/tests/4_Ballot_test.sol')
.notContainsText('*[data-id="testTabSolidityUnitTestsOutput"]', '/tests/simple_storage_test.sol')
.waitForElementContainsText('*[data-id="testTabTestsExecutionStopped"]', 'The test execution has been stopped', 60000)

@ -17,7 +17,7 @@ module.exports = {
return sources
},
'Should load the code from URL params': function (browser: NightwatchBrowser) {
'Should load the code from URL params (code param)': function (browser: NightwatchBrowser) {
browser
.pause(5000)
.url('http://127.0.0.1:8080/#optimize=true&runs=300&evmVersion=istanbul&version=soljson-v0.7.4+commit.3f05b770.js&code=cHJhZ21hIHNvbGlkaXR5ID49MC42LjAgPDAuNy4wOwoKaW1wb3J0ICJodHRwczovL2dpdGh1Yi5jb20vT3BlblplcHBlbGluL29wZW56ZXBwZWxpbi1jb250cmFjdHMvYmxvYi9tYXN0ZXIvY29udHJhY3RzL2FjY2Vzcy9Pd25hYmxlLnNvbCI7Cgpjb250cmFjdCBHZXRQYWlkIGlzIE93bmFibGUgewogIGZ1bmN0aW9uIHdpdGhkcmF3KCkgZXh0ZXJuYWwgb25seU93bmVyIHsKICB9Cn0')
@ -31,6 +31,43 @@ module.exports = {
})
},
'Should load the code from URL params (url param)': function (browser: NightwatchBrowser) {
browser
.pause(5000)
.url('http://127.0.0.1:8080/#optimize=true&runs=300&evmVersion=istanbul&version=soljson-v0.7.4+commit.3f05b770.js&url=https://github.com/ethereum/remix-project/blob/master/apps/remix-ide/contracts/app/solidity/mode.sol')
.refresh() // we do one reload for making sure we already have the default workspace
.pause(5000)
.currentWorkspaceIs('code-sample')
.getEditorValue((content) => {
browser.assert.ok(content.indexOf(
'proposals.length = _numProposals;') !== -1,
'url has not been loaded')
})
},
'Should load the code from URL & code params': function (browser: NightwatchBrowser) {
browser
.pause(5000)
.url('http://127.0.0.1:8080/#optimize=true&runs=300&evmVersion=istanbul&version=soljson-v0.7.4+commit.3f05b770.js&url=https://github.com/ethereum/remix-project/blob/master/apps/remix-ide/contracts/app/solidity/mode.sol&code=cHJhZ21hIHNvbGlkaXR5ID49MC42LjAgPDAuNy4wOwoKaW1wb3J0ICJodHRwczovL2dpdGh1Yi5jb20vT3BlblplcHBlbGluL29wZW56ZXBwZWxpbi1jb250cmFjdHMvYmxvYi9tYXN0ZXIvY29udHJhY3RzL2FjY2Vzcy9Pd25hYmxlLnNvbCI7Cgpjb250cmFjdCBHZXRQYWlkIGlzIE93bmFibGUgewogIGZ1bmN0aW9uIHdpdGhkcmF3KCkgZXh0ZXJuYWwgb25seU93bmVyIHsKICB9Cn0')
.refresh() // we do one reload for making sure we already have the default workspace
.pause(5000)
.currentWorkspaceIs('code-sample')
.getEditorValue((content) => {
browser.assert.ok(content.indexOf(
'proposals.length = _numProposals;') !== -1,
'code has not been loaded')
})
.openFile('contract-76747f6e19.sol')
.openFile('ethereum')
.openFile('ethereum/remix-project')
.openFile('ethereum/remix-project/apps')
.openFile('ethereum/remix-project/apps/remix-ide')
.openFile('ethereum/remix-project/apps/remix-ide/contracts')
.openFile('ethereum/remix-project/apps/remix-ide/contracts/app')
.openFile('ethereum/remix-project/apps/remix-ide/contracts/app/solidity')
.openFile('ethereum/remix-project/apps/remix-ide/contracts/app/solidity/mode.sol')
},
'Should load using URL compiler params': function (browser: NightwatchBrowser) {
browser
.pause(5000)
@ -38,22 +75,22 @@ module.exports = {
.refresh()
.pause(5000)
.clickLaunchIcon('solidity')
.assert.containsText('#versionSelector option[selected="selected"]', '0.7.4+commit.3f05b770')
.assert.containsText('#evmVersionSelector option[selected="selected"]', 'istanbul')
.assert.containsText('#versionSelector option[data-id="selected"]', '0.7.4+commit.3f05b770')
.assert.containsText('#evmVersionSelector option[data-id="selected"]', 'istanbul')
.verify.elementPresent('#optimize:checked')
.verify.attributeEquals('#runs', 'value', '300')
},
'Should load using compiler from link passed in remix URL': function (browser: NightwatchBrowser) {
browser
.url('http://127.0.0.1:8080/#version=https://solidity-blog.s3.eu-central-1.amazonaws.com/data/08preview/soljson.js')
.url('http://127.0.0.1:8080/#version=https://solidity-blog.s3.eu-central-1.amazonaws.com/data/08preview/soljson.js&optimize=false')
.refresh()
.pause(5000)
.clickLaunchIcon('solidity')
.pause(5000)
.assert.containsText('#versionSelector option[selected="selected"]', 'custom')
.assert.containsText('#versionSelector option[data-id="selected"]', 'custom')
// default values
.assert.containsText('#evmVersionSelector option[selected="selected"]', 'default')
.assert.containsText('#evmVersionSelector option[data-id="selected"]', 'default')
.verify.elementPresent('#optimize')
.assert.elementNotPresent('#optimize:checked')
.verify.elementPresent('#runs:disabled')

@ -32,11 +32,8 @@ module.exports = {
.clickLaunchIcon('filePanel')
.addFile('basic.sol', sources[0]['basic.sol'])
.clickLaunchIcon('solidity')
.execute(function () {
const elem = document.getElementById('nightlies') as HTMLInputElement
elem.checked = true
})
.waitForElementVisible('[data-id="compilerNightliesBuild"]')
.click('[data-id="compilerNightliesBuild"]')
.noWorkerErrorFor('soljson-v0.3.4+commit.7dab8902.js')
.noWorkerErrorFor('soljson-v0.6.5+commit.f956cc89.js')
.noWorkerErrorFor('soljson-v0.6.8-nightly.2020.5.14+commit.a6d0067b.js')

@ -22,6 +22,7 @@ module.exports = {
'Checks vertical icons panel contex menu deactivate': function (browser: NightwatchBrowser) {
browser.waitForElementVisible('div[data-id="remixIdeIconPanel"]', 10000)
.waitForElementVisible('*[data-id="verticalIconsKinddebugger"]', 7000)
.pause(5000)
.rightClick('[data-id="verticalIconsKinddebugger"]')
.click('*[id="menuitemdeactivate"]')
.click('*[data-id="verticalIconsKindsettings"]')

@ -6,13 +6,13 @@ console.log('current folder', process.cwd())
const folder = process.cwd() + '/temp_publish_docker';
(async () => {
const host = 'ipfs.komputing.org' // ethdev berlin ipfs node
const host = 'ipfs.remixproject.org'
const ipfs = IpfsHttpClient({ host, port: 443, protocol: 'https' })
try {
let result = await ipfs.add(globSource(folder, { recursive: true}), { pin: false })
const hash = result.cid.toString()
console.log('ipfs://' + hash)
console.log('https://ipfsgw.komputing.org/ipfs/' + hash)
console.log('https://ipfs.remixproject.org/ipfs/' + hash)
console.log('https://gateway.ipfs.io/ipfs/' + hash)
} catch (e) {
console.log(e)

@ -7,6 +7,7 @@ import PanelsResize from './lib/panels-resize'
import { RemixEngine } from './remixEngine'
import { RemixAppManager } from './remixAppManager'
import { FramingService } from './framingService'
import { WalkthroughService } from './walkthroughService'
import { MainView } from './app/panels/main-view'
import { ThemeModule } from './app/tabs/theme-module'
import { NetworkModule } from './app/tabs/network-module'
@ -16,7 +17,8 @@ import { HiddenPanel } from './app/components/hidden-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 FetchAndCompile from './app/compiler/compiler-sourceVerifier-fetchAndCompile'
import { OffsetToLineColumnConverter, CompilerMetadata, CompilerArtefacts, FetchAndCompile, CompilerImports } from '@remix-project/core-plugin'
import migrateFileSystem from './migrateFileSystem'
@ -25,7 +27,7 @@ const csjs = require('csjs-inject')
const yo = require('yo-yo')
const remixLib = require('@remix-project/remix-lib')
const registry = require('./global/registry')
const { OffsetToLineColumnConverter } = require('./lib/offsetToLineColumnConverter')
const QueryParams = require('./lib/query-params')
const Storage = remixLib.Storage
const RemixDProvider = require('./app/files/remixDProvider')
@ -38,13 +40,10 @@ const FileProvider = require('./app/files/fileProvider')
const DGitProvider = require('./app/files/dgitProvider')
const WorkspaceFileProvider = require('./app/files/workspaceFileProvider')
const toolTip = require('./app/ui/tooltip')
const CompilerMetadata = require('./app/files/compiler-metadata')
const CompilerImport = require('./app/compiler/compiler-imports')
const Blockchain = require('./blockchain/blockchain.js')
const PluginManagerComponent = require('./app/components/plugin-manager-component')
const CompilersArtefacts = require('./app/compiler/compiler-artefacts')
const CompileTab = require('./app/tabs/compile-tab')
const SettingsTab = require('./app/tabs/settings-tab')
@ -114,7 +113,11 @@ const css = csjs`
fill: var(--secondary);
}
.centered svg polygon {
fill: var(--secondary);
fill : var(--secondary);
}
.onboarding {
color : var(--text-info);
background-color : var(--info);
}
.matomoBtn {
width : 100px;
@ -128,11 +131,11 @@ class App {
self._components = {}
self._view = {}
self._view.splashScreen = yo`
<div class=${css.centered}>
${basicLogo()}
<div class="info-secondary" style="text-align:center">
REMIX IDE
</div>
<div class=${css.centered}>
${basicLogo()}
<div class="info-secondary" style="text-align:center">
REMIX IDE
</div>
</div>
`
document.body.appendChild(self._view.splashScreen)
@ -262,14 +265,14 @@ Please make a backup of your contracts and start using http://remix.ethereum.org
const dGitProvider = new DGitProvider()
// ----------------- import content service ------------------------
const contentImport = new CompilerImport(fileManager)
const contentImport = new CompilerImports()
const blockchain = new Blockchain(registry.get('config').api)
// ----------------- compilation metadata generation service ---------
const compilerMetadataGenerator = new CompilerMetadata(blockchain, fileManager, registry.get('config').api)
const compilerMetadataGenerator = new CompilerMetadata()
// ----------------- compilation result service (can keep track of compilation results) ----------------------------
const compilersArtefacts = new CompilersArtefacts() // store all the compilation results (key represent a compiler name)
const compilersArtefacts = new CompilerArtefacts() // store all the compilation results (key represent a compiler name)
registry.put({ api: compilersArtefacts, name: 'compilersartefacts' })
// service which fetch contract artifacts from sourve-verify, put artifacts in remix and compile it
@ -335,7 +338,7 @@ Please make a backup of your contracts and start using http://remix.ethereum.org
const hiddenPanel = new HiddenPanel()
const pluginManagerComponent = new PluginManagerComponent(appManager, engine)
const filePanel = new FilePanel(appManager)
const landingPage = new LandingPage(appManager, menuicons, fileManager, filePanel)
const landingPage = new LandingPage(appManager, menuicons, fileManager, filePanel, contentImport)
const settings = new SettingsTab(
registry.get('config').api,
editor,
@ -358,6 +361,9 @@ Please make a backup of your contracts and start using http://remix.ethereum.org
settings
])
const queryParams = new QueryParams()
const params = queryParams.get()
const onAcceptMatomo = () => {
_paq.push(['forgetUserOptOut'])
// @TODO remove next line when https://github.com/matomo-org/matomo/commit/9e10a150585522ca30ecdd275007a882a70c6df5 is used
@ -365,12 +371,21 @@ Please make a backup of your contracts and start using http://remix.ethereum.org
settings.updateMatomoAnalyticsChoice(true)
const el = document.getElementById('modal-dialog')
el.parentElement.removeChild(el)
startWalkthroughService()
}
const onDeclineMatomo = () => {
settings.updateMatomoAnalyticsChoice(false)
_paq.push(['optUserOut'])
const el = document.getElementById('modal-dialog')
el.parentElement.removeChild(el)
startWalkthroughService()
}
const startWalkthroughService = () => {
const walkthroughService = new WalkthroughService(localStorage)
if (!params.code && !params.url && !params.minimizeterminal && !params.gist && !params.minimizesidepanel) {
walkthroughService.start()
}
}
// Ask to opt in to Matomo for remix, remix-alpha and remix-beta
@ -404,6 +419,8 @@ Please make a backup of your contracts and start using http://remix.ethereum.org
fn: null
}
)
} else {
startWalkthroughService()
}
// CONTENT VIEWS & DEFAULT PLUGINS
@ -445,7 +462,8 @@ Please make a backup of your contracts and start using http://remix.ethereum.org
test,
filePanel.remixdHandle,
filePanel.gitHandle,
filePanel.hardhatHandle
filePanel.hardhatHandle,
filePanel.slitherHandle
])
if (isElectron()) {
@ -458,14 +476,12 @@ Please make a backup of your contracts and start using http://remix.ethereum.org
console.log('couldn\'t register iframe plugins', e.message)
}
await appManager.activatePlugin(['contentImport', 'theme', 'editor', 'fileManager', 'compilerMetadata', 'compilerArtefacts', 'network', 'web3Provider', 'offsetToLineColumnConverter'])
await appManager.activatePlugin(['theme', 'editor', 'fileManager', 'compilerMetadata', 'compilerArtefacts', 'network', 'web3Provider', 'offsetToLineColumnConverter'])
await appManager.activatePlugin(['mainPanel', 'menuicons', 'tabs'])
await appManager.activatePlugin(['sidePanel']) // activating host plugin separately
await appManager.activatePlugin(['home'])
await appManager.activatePlugin(['hiddenPanel', 'pluginManager', 'filePanel', 'settings', 'contextualListener', 'terminal', 'fetchAndCompile'])
const queryParams = new QueryParams()
const params = queryParams.get()
await appManager.activatePlugin(['settings'])
await appManager.activatePlugin(['hiddenPanel', 'pluginManager', 'filePanel', 'contextualListener', 'terminal', 'fetchAndCompile', 'contentImport'])
// Set workspace after initial activation
if (Array.isArray(workspace)) {
@ -497,7 +513,7 @@ Please make a backup of your contracts and start using http://remix.ethereum.org
// Load and start the service who manager layout and frame
const framingService = new FramingService(sidePanel, menuicons, mainview, this._components.resizeFeature)
framingService.start(params)
if (params.embed) framingService.embed()
framingService.start(params)
}

@ -1,46 +0,0 @@
'use strict'
var remixLib = require('@remix-project/remix-lib')
var txHelper = remixLib.execution.txHelper
module.exports = class CompilerAbstract {
constructor (languageversion, data, source) {
this.languageversion = languageversion
this.data = data
this.source = source // source code
}
getContracts () {
return this.data.contracts
}
getContract (name) {
return txHelper.getContract(name, this.data.contracts)
}
visitContracts (calllback) {
return txHelper.visitContracts(this.data.contracts, calllback)
}
getData () {
return this.data
}
getAsts () {
return this.data.sources // ast
}
getSourceName (fileIndex) {
if (this.data && this.data.sources) {
return Object.keys(this.data.sources)[fileIndex]
} else if (Object.keys(this.source.sources).length === 1) {
// if we don't have ast, we return the only one filename present.
const sourcesArray = Object.keys(this.source.sources)
return sourcesArray[0]
}
return null
}
getSourceCode () {
return this.source
}
}

@ -1,169 +0,0 @@
'use strict'
import { Plugin } from '@remixproject/engine'
import * as packageJson from '../../../../../package.json'
import { RemixURLResolver } from '@remix-project/remix-url-resolver'
const remixTests = require('@remix-project/remix-tests')
const globalRegistry = require('../../global/registry')
const addTooltip = require('../ui/tooltip')
const async = require('async')
const profile = {
name: 'contentImport',
displayName: 'content import',
version: packageJson.version,
methods: ['resolve', 'resolveAndSave', 'isExternalUrl']
}
module.exports = class CompilerImports extends Plugin {
constructor (fileManager) {
super(profile)
this.fileManager = fileManager
// const token = await this.call('settings', 'getGithubAccessToken')
const token = globalRegistry.get('config').api.get('settings/gist-access-token') // TODO replace with the plugin call above https://github.com/ethereum/remix-ide/issues/2288
const protocol = window.location.protocol
this.urlResolver = new RemixURLResolver(token, protocol)
this.previouslyHandled = {} // cache import so we don't make the request at each compilation.
}
isRelativeImport (url) {
return /^([^/]+)/.exec(url)
}
isExternalUrl (url) {
const handlers = this.urlResolver.getHandlers()
return handlers.some(handler => handler.match(url))
}
/**
* resolve the content of @arg url. This only resolves external URLs.
*
* @param {String} url - external URL of the content. can be basically anything like raw HTTP, ipfs URL, github address etc...
* @returns {Promise} - { content, cleanUrl, type, url }
*/
resolve (url) {
return new Promise((resolve, reject) => {
this.import(url, null, (error, content, cleanUrl, type, url) => {
if (error) return reject(error)
resolve({ content, cleanUrl, type, url })
})
})
}
async import (url, force, loadingCb, cb) {
if (typeof force !== 'boolean') {
const temp = loadingCb
loadingCb = force
cb = temp
force = false
}
if (!loadingCb) loadingCb = () => {}
if (!cb) cb = () => {}
var self = this
if (force) delete this.previouslyHandled[url]
var imported = this.previouslyHandled[url]
if (imported) {
return cb(null, imported.content, imported.cleanUrl, imported.type, url)
}
let resolved
try {
resolved = await this.urlResolver.resolve(url)
const { content, cleanUrl, type } = resolved
self.previouslyHandled[url] = {
content,
cleanUrl,
type
}
cb(null, content, cleanUrl, type, url)
} catch (e) {
return cb(new Error('not found ' + url))
}
}
importExternal (url, targetPath, cb) {
this.import(url,
// TODO: move to an event that is generated, the UI shouldn't be here
(loadingMsg) => { addTooltip(loadingMsg) },
(error, content, cleanUrl, type, url) => {
if (error) return cb(error)
if (this.fileManager) {
const provider = this.fileManager.currentFileProvider()
const path = targetPath || type + '/' + cleanUrl
if (provider) provider.addExternal('.deps/' + path, content, url)
}
cb(null, content)
})
}
/**
* import the content of @arg url.
* first look in the browser localstorage (browser explorer) or locahost explorer. if the url start with `browser/*` or `localhost/*`
* then check if the @arg url is located in the localhost, in the node_modules or installed_contracts folder
* then check if the @arg url match any external url
*
* @param {String} url - URL of the content. can be basically anything like file located in the browser explorer, in the localhost explorer, raw HTTP, github address etc...
* @param {String} targetPath - (optional) internal path where the content should be saved to
* @returns {Promise} - string content
*/
resolveAndSave (url, targetPath) {
return new Promise((resolve, reject) => {
if (url.indexOf('remix_tests.sol') !== -1) resolve(remixTests.assertLibCode)
if (!this.fileManager) {
// fallback to just resolving the file, it won't be saved in file manager
return this.importExternal(url, targetPath, (error, content) => {
if (error) return reject(error)
resolve(content)
})
}
var provider = this.fileManager.fileProviderOf(url)
if (provider) {
if (provider.type === 'localhost' && !provider.isConnected()) {
return reject(new Error(`file provider ${provider.type} not available while trying to resolve ${url}`))
}
provider.exists(url).then(exist => {
/*
if the path is absolute and the file does not exist, we can stop here
Doesn't make sense to try to resolve "localhost/node_modules/localhost/node_modules/<path>" and we'll end in an infinite loop.
*/
if (!exist && url.startsWith('browser/')) return reject(new Error(`not found ${url}`))
if (!exist && url.startsWith('localhost/')) return reject(new Error(`not found ${url}`))
if (exist) {
return provider.get(url, (error, content) => {
if (error) return reject(error)
resolve(content)
})
}
// try to resolve localhost modules (aka truffle imports) - e.g from the node_modules folder
const localhostProvider = this.fileManager.getProvider('localhost')
if (localhostProvider.isConnected()) {
var splitted = /([^/]+)\/(.*)$/g.exec(url)
return async.tryEach([
(cb) => { this.resolveAndSave('localhost/installed_contracts/' + url).then((result) => cb(null, result)).catch((error) => cb(error.message)) },
(cb) => { if (!splitted) { cb('URL not parseable: ' + url) } else { this.resolveAndSave('localhost/installed_contracts/' + splitted[1] + '/contracts/' + splitted[2]).then((result) => cb(null, result)).catch((error) => cb(error.message)) } },
(cb) => { this.resolveAndSave('localhost/node_modules/' + url).then((result) => cb(null, result)).catch((error) => cb(error.message)) },
(cb) => { if (!splitted) { cb('URL not parseable: ' + url) } else { this.resolveAndSave('localhost/node_modules/' + splitted[1] + '/contracts/' + splitted[2]).then((result) => cb(null, result)).catch((error) => cb(error.message)) } }],
(error, result) => {
if (error) {
return this.importExternal(url, targetPath, (error, content) => {
if (error) return reject(error)
resolve(content)
})
}
resolve(result)
})
}
this.importExternal(url, targetPath, (error, content) => {
if (error) return reject(error)
resolve(content)
})
}).catch(error => {
return reject(error)
})
}
})
}
}

@ -1,21 +0,0 @@
'use strict'
module.exports = (sources, opts) => {
return JSON.stringify({
language: 'Solidity',
sources: sources,
settings: {
optimizer: {
enabled: opts.optimize === true || opts.optimize === 1,
runs: 200
},
libraries: opts.libraries,
outputSelection: {
'*': {
'': ['ast'],
'*': ['abi', 'metadata', 'devdoc', 'userdoc', 'evm.legacyAssembly', 'evm.bytecode', 'evm.deployedBytecode', 'evm.methodIdentifiers', 'evm.gasEstimates']
}
}
}
})
}

@ -69,12 +69,14 @@ export class VerticalIcons extends Plugin {
title = title.replace(/^\w/, c => c.toUpperCase())
this.icons[name] = yo`
<div
class="${css.icon}"
class="${css.icon} m-2"
onclick="${() => { this.toggle(name) }}"
plugin="${name}"
title="${title}"
oncontextmenu="${(e) => this.itemContextMenu(e, name, documentation)}"
data-id="verticalIconsKind${name}">
data-id="verticalIconsKind${name}"
id="verticalIconsKind${name}"
>
<img class="image" src="${icon}" alt="${name}" />
</div>`
this.iconKind[kind || 'none'].appendChild(this.icons[name])
@ -249,13 +251,14 @@ export class VerticalIcons extends Plugin {
render () {
const home = yo`
<div
class="${css.homeIcon}"
class="m-1 mt-2 ${css.homeIcon}"
onclick="${async () => {
await this.appManager.activatePlugin('home')
this.call('tabs', 'focus', 'home')
}}"
plugin="home" title="Home"
data-id="verticalIconsHomeIcon"
id="verticalIconsHomeIcon"
>
${basicLogo()}
</div>
@ -270,16 +273,18 @@ export class VerticalIcons extends Plugin {
this.iconKind.settings = yo`<div id='settingsIcons' data-id="verticalIconsSettingsIcons"></div>`
this.view = yo`
<div class=${css.icons}>
${home}
${this.iconKind.fileexplorer}
${this.iconKind.compiler}
${this.iconKind.udapp}
${this.iconKind.testing}
${this.iconKind.analysis}
${this.iconKind.debugging}
${this.iconKind.none}
${this.iconKind.settings}
<div class="h-100">
<div class=${css.icons}>
${home}
${this.iconKind.fileexplorer}
${this.iconKind.compiler}
${this.iconKind.udapp}
${this.iconKind.testing}
${this.iconKind.analysis}
${this.iconKind.debugging}
${this.iconKind.none}
${this.iconKind.settings}
</div>
</div>
`
return this.view
@ -292,7 +297,6 @@ const css = csjs`
width: 42px;
height: 42px;
margin-bottom: 20px;
margin-left: -5px;
cursor: pointer;
}
.homeIcon svg path {
@ -302,8 +306,6 @@ const css = csjs`
fill: var(--primary);
}
.icons {
margin-left: 10px;
margin-top: 15px;
}
.icon {
cursor: pointer;

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

@ -1,148 +0,0 @@
'use strict'
import { Plugin } from '@remixproject/engine'
import * as packageJson from '../../../../../package.json'
import { joinPath } from '../../lib/helper'
var CompilerAbstract = require('../compiler/compiler-abstract')
const profile = {
name: 'compilerMetadata',
methods: ['deployMetadataOf'],
events: [],
version: packageJson.version
}
class CompilerMetadata extends Plugin {
constructor (blockchain, fileManager, config) {
super(profile)
this.blockchain = blockchain
this.fileManager = fileManager
this.config = config
this.networks = ['VM:-', 'main:1', 'ropsten:3', 'rinkeby:4', 'kovan:42', 'görli:5', 'Custom']
this.innerPath = 'artifacts'
}
_JSONFileName (path, contractName) {
return joinPath(path, this.innerPath, contractName + '.json')
}
_MetadataFileName (path, contractName) {
return joinPath(path, this.innerPath, contractName + '_metadata.json')
}
onActivation () {
var self = this
this.on('solidity', 'compilationFinished', (file, source, languageVersion, data) => {
if (!self.config.get('settings/generate-contract-metadata')) return
const compiler = new CompilerAbstract(languageVersion, data, source)
var provider = self.fileManager.fileProviderOf(source.target)
var path = self.fileManager.extractPathOf(source.target)
if (provider) {
compiler.visitContracts((contract) => {
if (contract.file !== source.target) return
var fileName = self._JSONFileName(path, contract.name)
var metadataFileName = self._MetadataFileName(path, contract.name)
provider.get(fileName, (error, content) => {
if (!error) {
content = content || '{}'
var metadata
try {
metadata = JSON.parse(content)
} catch (e) {
console.log(e)
}
var deploy = metadata.deploy || {}
self.networks.forEach((network) => {
deploy[network] = self._syncContext(contract, deploy[network] || {})
})
let parsedMetadata
try {
parsedMetadata = JSON.parse(contract.object.metadata)
} catch (e) {
console.log(e)
}
if (parsedMetadata) provider.set(metadataFileName, JSON.stringify(parsedMetadata, null, '\t'))
var data = {
deploy,
data: {
bytecode: contract.object.evm.bytecode,
deployedBytecode: contract.object.evm.deployedBytecode,
gasEstimates: contract.object.evm.gasEstimates,
methodIdentifiers: contract.object.evm.methodIdentifiers
},
abi: contract.object.abi
}
provider.set(fileName, JSON.stringify(data, null, '\t'))
}
})
})
}
})
}
_syncContext (contract, metadata) {
var linkReferences = metadata.linkReferences
var autoDeployLib = metadata.autoDeployLib
if (!linkReferences) linkReferences = {}
if (autoDeployLib === undefined) autoDeployLib = true
for (var libFile in contract.object.evm.bytecode.linkReferences) {
if (!linkReferences[libFile]) linkReferences[libFile] = {}
for (var lib in contract.object.evm.bytecode.linkReferences[libFile]) {
if (!linkReferences[libFile][lib]) {
linkReferences[libFile][lib] = '<address>'
}
}
}
metadata.linkReferences = linkReferences
metadata.autoDeployLib = autoDeployLib
return metadata
}
// TODO: is only called by dropdownLogic and can be moved there
deployMetadataOf (contractName, fileLocation) {
return new Promise((resolve, reject) => {
var provider
let path
if (fileLocation) {
provider = this.fileManager.fileProviderOf(fileLocation)
path = fileLocation.split('/')
path.pop()
path = path.join('/')
} else {
provider = this.fileManager.currentFileProvider()
path = this.fileManager.currentPath()
}
if (provider) {
this.blockchain.detectNetwork((err, { id, name } = {}) => {
if (err) {
console.log(err)
reject(err)
} else {
var fileName = this._JSONFileName(path, contractName)
provider.get(fileName, (error, content) => {
if (error) return reject(error)
if (!content) return resolve()
try {
var metadata = JSON.parse(content)
metadata = metadata.deploy || {}
return resolve(metadata[name + ':' + id] || metadata[name] || metadata[id] || metadata[name.toLowerCase() + ':' + id] || metadata[name.toLowerCase()])
} catch (e) {
reject(e.message)
}
})
}
})
} else {
reject(new Error(`Please select the folder in the file explorer where the metadata of ${contractName} can be found`))
}
})
}
}
module.exports = CompilerMetadata

@ -27,10 +27,10 @@ class DGitProvider extends Plugin {
constructor () {
super(profile)
this.ipfsconfig = {
host: 'ipfs.komputing.org',
host: 'ipfs.remixproject.org',
port: 443,
protocol: 'https',
ipfsurl: 'https://ipfsgw.komputing.org/ipfs/'
ipfsurl: 'https://ipfs.remixproject.org/ipfs/'
}
this.globalIPFSConfig = {
host: 'ipfs.io',
@ -206,7 +206,20 @@ class DGitProvider extends Plugin {
const commits = await this.log({ ref: 'HEAD' })
ob = {
ref: commits[0].oid,
message: commits[0].commit.message
message: commits[0].commit.message,
commits: JSON.stringify(commits.map((commit) => {
return {
oid: commit.oid,
commit: {
parent: commit.commit?.parent,
tree: commit.commit?.tree,
message: commit.commit?.message,
committer: {
timestamp: commit.commit?.committer?.timestamp
}
}
}
}))
}
} catch (e) {
ob = {

@ -1,702 +0,0 @@
/* global FileReader */
/* global fetch */
const async = require('async')
const Gists = require('gists')
const modalDialogCustom = require('../ui/modal-dialog-custom')
const tooltip = require('../ui/tooltip')
const QueryParams = require('../../lib/query-params')
const helper = require('../../lib/helper')
const yo = require('yo-yo')
const Treeview = require('../ui/TreeView')
const modalDialog = require('../ui/modaldialog')
const EventManager = require('events')
const contextMenu = require('../ui/contextMenu')
const css = require('./styles/file-explorer-styles')
const globalRegistry = require('../../global/registry')
const queryParams = new QueryParams()
let MENU_HANDLE
function fileExplorer (localRegistry, files, menuItems, plugin) {
var self = this
this.events = new EventManager()
// file provider backend
this.files = files
// element currently focused on
this.focusElement = null
// path currently focused on
this.focusPath = null
const allItems =
[
{
action: 'createNewFile',
title: 'Create New File',
icon: 'fas fa-plus-circle'
},
{
action: 'publishToGist',
title: 'Publish all [browser] explorer files to a github gist',
icon: 'fab fa-github'
},
{
action: 'uploadFile',
title: 'Add Local file to the Browser Storage Explorer',
icon: 'far fa-folder-open'
},
{
action: 'updateGist',
title: 'Update the current [gist] explorer',
icon: 'fab fa-github'
}
]
// menu items
this.menuItems = allItems.filter(
(item) => {
if (menuItems) return menuItems.find((name) => { return name === item.action })
}
)
self._components = {}
self._components.registry = localRegistry || globalRegistry
self._deps = {
config: self._components.registry.get('config').api,
editor: self._components.registry.get('editor').api,
fileManager: self._components.registry.get('filemanager').api
}
self.events.register('focus', function (path) {
self._deps.fileManager.open(path)
})
self._components.registry.put({ api: self, name: `fileexplorer/${self.files.type}` })
// warn if file changed outside of Remix
function remixdDialog () {
return yo`<div>This file has been changed outside of Remix IDE.</div>`
}
this.files.event.register('fileExternallyChanged', (path, file) => {
if (self._deps.config.get('currentFile') === path && self._deps.editor.currentContent() && self._deps.editor.currentContent() !== file.content) {
if (this.files.isReadOnly(path)) return self._deps.editor.setText(file.content)
modalDialog(path + ' changed', remixdDialog(),
{
label: 'Replace by the new content',
fn: () => {
self._deps.editor.setText(file.content)
}
},
{
label: 'Keep the content displayed in Remix',
fn: () => {}
}
)
}
})
// register to event of the file provider
files.event.on('fileRemoved', fileRemoved)
files.event.on('fileRenamed', fileRenamed)
files.event.on('fileRenamedError', fileRenamedError)
files.event.on('fileAdded', fileAdded)
files.event.on('folderAdded', folderAdded)
function fileRenamedError (error) {
modalDialogCustom.alert(error)
}
function fileAdded (filepath) {
self.ensureRoot(() => {
const folderpath = filepath.split('/').slice(0, -1).join('/')
const currentTree = self.treeView.nodeAt(folderpath)
if (!self.treeView.isExpanded(folderpath)) self.treeView.expand(folderpath)
if (currentTree) {
self.files.resolveDirectory(folderpath, (error, fileTree) => {
if (error) console.error(error)
if (!fileTree) return
fileTree = normalize(folderpath, fileTree)
self.treeView.updateNodeFromJSON(folderpath, fileTree, true)
self.focusElement = self.treeView.labelAt(self.focusPath)
// TODO: here we update the selected file (it applicable)
// cause we are refreshing the interface of the whole directory when there's a new file.
if (self.focusElement && !self.focusElement.classList.contains('bg-secondary')) {
self.focusElement.classList.add('bg-secondary')
}
})
}
})
}
function extractNameFromKey (key) {
const keyPath = key.split('/')
return keyPath[keyPath.length - 1]
}
function folderAdded (folderpath) {
self.ensureRoot(() => {
folderpath = folderpath.split('/').slice(0, -1).join('/')
self.files.resolveDirectory(folderpath, (error, fileTree) => {
if (error) console.error(error)
if (!fileTree) return
fileTree = normalize(folderpath, fileTree)
self.treeView.updateNodeFromJSON(folderpath, fileTree, true)
if (!self.treeView.isExpanded(folderpath)) self.treeView.expand(folderpath)
})
})
}
function fileRemoved (filepath) {
const label = self.treeView.labelAt(filepath)
filepath = filepath.split('/').slice(0, -1).join('/')
if (label && label.parentElement) {
label.parentElement.removeChild(label)
}
self.updatePath(filepath)
}
function fileRenamed (oldName, newName, isFolder) {
fileRemoved(oldName)
fileAdded(newName)
}
// make interface and register to nodeClick, leafClick
self.treeView = new Treeview({
extractData: function extractData (value, tree, key) {
var newValue = {}
// var isReadOnly = false
var isFile = false
Object.keys(value).filter(function keep (x) {
if (x === '/content') isFile = true
if (x[0] !== '/') return true
}).forEach(function (x) { newValue[x] = value[x] })
return {
path: (tree || {}).path ? tree.path + '/' + key : key,
children: isFile ? undefined
: value instanceof Array ? value.map((item, index) => ({
key: index, value: item
})) : value instanceof Object ? Object.keys(value).map(subkey => ({
key: subkey, value: value[subkey]
})) : undefined
}
},
formatSelf: function formatSelf (key, data, li) {
const isRoot = data.path === self.files.type
const isFolder = !!data.children
return yo`
<div class="${css.items}">
<span
title="${data.path}"
class="${css.label} ${!isRoot ? !isFolder ? css.leaf : css.folder : ''}"
data-path="${data.path}"
style="${isRoot ? 'font-weight:bold;' : ''}"
onkeydown=${editModeOff}
onblur=${editModeOff}
>
${key.split('/').pop()}
</span>
${isRoot ? self.renderMenuItems() : ''}
</div>
`
}
})
/**
* Extracts first two folders as a subpath from the path.
**/
function extractExternalFolder (path) {
const firstSlIndex = path.indexOf('/', 1)
if (firstSlIndex === -1) return ''
const secondSlIndex = path.indexOf('/', firstSlIndex + 1)
if (secondSlIndex === -1) return ''
return path.substring(0, secondSlIndex)
}
self.treeView.event.register('nodeRightClick', function (key, data, label, event) {
if (self.files.readonly) return
if (key === self.files.type) return
MENU_HANDLE && MENU_HANDLE.hide(null, true)
const actions = {}
const provider = self._deps.fileManager.fileProviderOf(key)
actions['Create File'] = () => self.createNewFile(key)
actions['Create Folder'] = () => self.createNewFolder(key)
// @todo(#2386) not fully implemented. Readd later when fixed
if (provider.isExternalFolder(key)) {
/* actions['Discard changes'] = () => {
modalDialogCustom.confirm(
'Discard changes',
'Are you sure you want to discard all your changes?',
() => { self.files.discardChanges(key) },
() => {}
)
} */
} else {
const folderPath = extractExternalFolder(key)
actions.Rename = () => {
if (self.files.isReadOnly(key)) { return tooltip('cannot rename folder. ' + self.files.type + ' is a read only explorer') }
var name = label.querySelector('span[data-path="' + key + '"]')
if (name) editModeOn(name)
}
actions.Delete = () => {
if (self.files.isReadOnly(key)) { return tooltip('cannot delete folder. ' + self.files.type + ' is a read only explorer') }
const currentFoldername = extractNameFromKey(key)
modalDialogCustom.confirm('Confirm to delete folder', `Are you sure you want to delete ${currentFoldername} folder?`,
async () => {
const fileManager = self._deps.fileManager
const removeFolder = await fileManager.remove(key)
if (!removeFolder) {
tooltip(`failed to remove ${key}. Make sure the directory is empty before removing it.`)
}
}, () => {})
}
if (folderPath === 'browser/gists') {
actions['Push changes to gist'] = () => {
const id = key.substr(key.lastIndexOf('/') + 1, key.length - 1)
modalDialogCustom.confirm(
'Push back to Gist',
'Are you sure you want to push all your changes back to Gist?',
() => { self.toGist(id) },
() => {}
)
}
}
}
MENU_HANDLE = contextMenu(event, actions)
})
self.treeView.event.register('leafRightClick', function (key, data, label, event) {
if (key === self.files.type) return
MENU_HANDLE && MENU_HANDLE.hide(null, true)
const actions = {}
const provider = self._deps.fileManager.fileProviderOf(key)
if (!provider.isExternalFolder(key)) {
actions['Create Folder'] = () => self.createNewFolder(self._deps.fileManager.extractPathOf(key))
actions.Rename = () => {
if (self.files.isReadOnly(key)) { return tooltip('cannot rename file. ' + self.files.type + ' is a read only explorer') }
var name = label.querySelector('span[data-path="' + key + '"]')
if (name) editModeOn(name)
}
actions.Delete = () => {
if (self.files.isReadOnly(key)) { return tooltip('cannot delete file. ' + self.files.type + ' is a read only explorer') }
const currentFilename = extractNameFromKey(key)
modalDialogCustom.confirm(
'Delete file', `Are you sure you want to delete ${currentFilename} file?`,
async () => {
const fileManager = self._deps.fileManager
const removeFile = await fileManager.remove(key)
if (!removeFile) {
tooltip(`Failed to remove file ${key}.`)
}
},
() => {}
)
}
if (key.endsWith('.js')) {
actions.Run = async () => {
provider.get(key, (error, content) => {
if (error) return console.log(error)
plugin.call('scriptRunner', 'execute', content)
})
}
}
}
MENU_HANDLE = contextMenu(event, actions)
})
self.treeView.event.register('leafClick', function (key, data, label) {
self.events.trigger('focus', [key])
})
self.treeView.event.register('nodeClick', function (path, childrenContainer) {
if (!childrenContainer) return
if (childrenContainer.style.display === 'none') return
self.updatePath(path)
})
// register to main app, trigger when the current file in the editor changed
self._deps.fileManager.events.on('currentFileChanged', (newFile) => {
const provider = self._deps.fileManager.fileProviderOf(newFile)
if (self.focusElement && self.focusPath !== newFile) {
self.focusElement.classList.remove('bg-secondary')
self.focusElement = null
self.focusPath = null
}
if (provider && (provider.type === files.type)) {
self.focusElement = self.treeView.labelAt(newFile)
if (self.focusElement) {
self.focusElement.classList.add('bg-secondary')
self.focusPath = newFile
}
}
})
self._deps.fileManager.events.on('noFileSelected', () => {
if (self.focusElement) {
self.focusElement.classList.remove('bg-secondary')
self.focusElement = null
self.focusPath = null
}
})
var textUnderEdit = null
function selectElementContents (el) {
var range = document.createRange()
range.selectNodeContents(el)
var sel = window.getSelection()
sel.removeAllRanges()
sel.addRange(range)
}
function editModeOn (label) {
textUnderEdit = label.innerText
label.setAttribute('contenteditable', true)
label.classList.add('bg-light')
label.focus()
selectElementContents(label)
}
function editModeOff (event) {
const label = this
const isFolder = label.className.indexOf('folder') !== -1
function rename () {
var newPath = label.dataset.path
newPath = newPath.split('/')
newPath[newPath.length - 1] = label.innerText
newPath = newPath.join('/')
if (label.innerText === '') {
modalDialogCustom.alert('File name cannot be empty')
label.innerText = textUnderEdit
} else if (helper.checkSpecialChars(label.innerText)) {
modalDialogCustom.alert('Special characters are not allowed')
label.innerText = textUnderEdit
} else {
files.exists(newPath, (error, exist) => {
if (error) return modalDialogCustom.alert('Unexpected error while renaming: ' + error)
if (!exist) {
files.rename(label.dataset.path, newPath, isFolder)
} else {
modalDialogCustom.alert('File already exists.')
label.innerText = textUnderEdit
}
})
}
}
if (event.which === 13) event.preventDefault()
if ((event.type === 'blur' || event.which === 13) && label.getAttribute('contenteditable')) {
var save = textUnderEdit !== label.innerText
if (save) {
modalDialogCustom.confirm(
'Confirm to rename a ' + (isFolder ? 'folder' : 'file'),
'Are you sure you want to rename ' + textUnderEdit + '?',
() => { rename() },
() => { label.innerText = textUnderEdit }
)
}
label.removeAttribute('contenteditable')
label.classList.remove('bg-light')
}
}
}
fileExplorer.prototype.updatePath = function (path) {
this.files.resolveDirectory(path, (error, fileTree) => {
if (error) console.error(error)
if (!fileTree) return
var newTree = normalize(path, fileTree)
this.treeView.updateNodeFromJSON(path, newTree, true)
})
}
fileExplorer.prototype.hide = function () {
if (this.container) this.container.style.display = 'none'
}
fileExplorer.prototype.show = function () {
if (this.container) this.container.style.display = 'block'
}
fileExplorer.prototype.init = function () {
this.container = yo`<div></div>`
return this.container
}
fileExplorer.prototype.publishToGist = function () {
modalDialogCustom.confirm(
'Create a public gist',
'Are you sure you want to publish all your files in browser directory anonymously as a public gist on github.com? Note: this will not include directories.',
() => { this.toGist() }
)
}
fileExplorer.prototype.uploadFile = function (event) {
// TODO The file explorer is merely a view on the current state of
// the files module. Please ask the user here if they want to overwrite
// a file and then just use `files.add`. The file explorer will
// pick that up via the 'fileAdded' event from the files module.
const self = this
;[...event.target.files].forEach((file) => {
const files = this.files
function loadFile () {
var fileReader = new FileReader()
fileReader.onload = async function (event) {
if (helper.checkSpecialChars(file.name)) {
modalDialogCustom.alert('Special characters are not allowed')
return
}
var success = await files.set(name, event.target.result)
if (!success) {
modalDialogCustom.alert('Failed to create file ' + name)
} else {
self.events.trigger('focus', [name])
}
}
fileReader.readAsText(file)
}
var name = files.type + '/' + file.name
files.exists(name, (error, exist) => {
if (error) console.log(error)
if (!exist) {
loadFile()
} else {
modalDialogCustom.confirm('Confirm overwrite', `The file ${name} already exists! Would you like to overwrite it?`, () => { loadFile() })
}
})
})
}
fileExplorer.prototype.toGist = function (id) {
const proccedResult = function (error, data) {
if (error) {
modalDialogCustom.alert('Failed to manage gist: ' + error)
console.log('Failed to manage gist: ' + error)
} else {
if (data.html_url) {
modalDialogCustom.confirm('Gist is ready', `The gist is at ${data.html_url}. Would you like to open it in a new window?`, () => {
window.open(data.html_url, '_blank')
})
} else {
modalDialogCustom.alert(data.message + ' ' + data.documentation_url + ' ' + JSON.stringify(data.errors, null, '\t'))
}
}
}
/**
* This function is to get the original content of given gist
* @params id is the gist id to fetch
*/
async function getOriginalFiles (id) {
if (!id) {
return []
}
const url = `https://api.github.com/gists/${id}`
const res = await fetch(url)
const data = await res.json()
return data.files || []
}
// If 'id' is not defined, it is not a gist update but a creation so we have to take the files from the browser explorer.
const folder = id ? '/gists/' + id : '/'
this.packageFiles(this.files, folder, (error, packaged) => {
if (error) {
console.log(error)
modalDialogCustom.alert('Failed to create gist: ' + error.message)
} else {
// check for token
var tokenAccess = this._deps.config.get('settings/gist-access-token')
if (!tokenAccess) {
modalDialogCustom.alert(
'Remix requires an access token (which includes gists creation permission). Please go to the settings tab to create one.'
)
} else {
const description = 'Created using remix-ide: Realtime Ethereum Contract Compiler and Runtime. \n Load this file by pasting this gists URL or ID at https://remix.ethereum.org/#version=' +
queryParams.get().version + '&optimize=' + queryParams.get().optimize + '&runs=' + queryParams.get().runs + '&gist='
const gists = new Gists({ token: tokenAccess })
if (id) {
const originalFileList = getOriginalFiles(id)
// Telling the GIST API to remove files
const updatedFileList = Object.keys(packaged)
const allItems = Object.keys(originalFileList)
.filter(fileName => updatedFileList.indexOf(fileName) === -1)
.reduce((acc, deleteFileName) => ({
...acc,
[deleteFileName]: null
}), originalFileList)
// adding new files
updatedFileList.forEach((file) => {
const _items = file.split('/')
const _fileName = _items[_items.length - 1]
allItems[_fileName] = packaged[file]
})
tooltip('Saving gist (' + id + ') ...')
gists.edit({
description: description,
public: true,
files: allItems,
id: id
}, (error, result) => {
proccedResult(error, result)
if (!error) {
for (const key in allItems) {
if (allItems[key] === null) delete allItems[key]
}
}
})
} else {
// id is not existing, need to create a new gist
tooltip('Creating a new gist ...')
gists.create({
description: description,
public: true,
files: packaged
}, (error, result) => {
proccedResult(error, result)
})
}
}
}
})
}
// return all the files, except the temporary/readonly ones..
fileExplorer.prototype.packageFiles = function (filesProvider, directory, callback) {
const ret = {}
filesProvider.resolveDirectory(directory, (error, files) => {
if (error) callback(error)
else {
async.eachSeries(Object.keys(files), (path, cb) => {
if (filesProvider.isDirectory(path)) {
cb()
} else {
filesProvider.get(path, (error, content) => {
if (error) return cb(error)
if (/^\s+$/.test(content) || !content.length) {
content = '// this line is added to create a gist. Empty file is not allowed.'
}
ret[path] = { content }
cb()
})
}
}, (error) => {
callback(error, ret)
})
}
})
}
fileExplorer.prototype.createNewFile = function (parentFolder = '/') {
const self = this
modalDialogCustom.prompt('Create new file', 'File Name (e.g Untitled.sol)', 'Untitled.sol', (input) => {
if (!input) input = 'New file'
helper.createNonClashingName(parentFolder + '/' + input, self.files, async (error, newName) => {
if (error) return tooltip('Failed to create file ' + newName + ' ' + error)
const fileManager = self._deps.fileManager
const createFile = await fileManager.writeFile(newName, '')
if (!createFile) {
tooltip('Failed to create file ' + newName)
} else {
await fileManager.open(newName)
if (newName.includes('_test.sol')) {
self.events.trigger('newTestFileCreated', [newName])
}
}
})
}, null, true)
}
fileExplorer.prototype.createNewFolder = function (parentFolder) {
const self = this
modalDialogCustom.prompt('Create new folder', '', 'New folder', (input) => {
if (!input) {
return tooltip('Failed to create folder. The name can not be empty')
}
const currentPath = !parentFolder ? self._deps.fileManager.currentPath() : parentFolder
let newName = currentPath ? currentPath + '/' + input : self.files.type + '/' + input
newName = newName + '/'
self.files.exists(newName, (error, exist) => {
if (error) return tooltip('Unexpected error while creating folder: ' + error)
if (!exist) {
self.files.set(newName, '')
} else {
tooltip('Folder already exists.', () => {})
}
})
}, null, true)
}
fileExplorer.prototype.renderMenuItems = function () {
let items = ''
if (this.menuItems) {
items = this.menuItems.map(({ action, title, icon }) => {
if (action === 'uploadFile') {
return yo`
<label
id=${action}
data-id="fileExplorerUploadFile${action}"
class="${icon} mb-0 ${css.newFile}"
title="${title}"
>
<input id="fileUpload" data-id="fileExplorerFileUpload" type="file" onchange=${(event) => {
event.stopPropagation()
this.uploadFile(event)
}} multiple />
</label>
`
} else {
return yo`
<span
id=${action}
data-id="fileExplorerNewFile${action}"
onclick=${(event) => { event.stopPropagation(); this[action]() }}
class="newFile ${icon} ${css.newFile}"
title=${title}
>
</span>
`
}
})
}
return yo`<span class=" ${css.menu}">${items}</span>`
}
fileExplorer.prototype.ensureRoot = function (cb) {
cb = cb || (() => {})
var self = this
if (self.element) return cb()
const root = {}
root[this.files.type] = {}
var element = self.treeView.render(root, false)
element.classList.add(css.fileexplorer)
element.events = self.events
element.api = self.api
self.container.appendChild(element)
self.element = element
if (cb) cb()
self.treeView.expand(self.files.type)
}
function normalize (path, filesList) {
var prefix = path.split('/')[0]
var newList = {}
Object.keys(filesList).forEach(key => {
newList[prefix + '/' + key] = filesList[key].isDirectory ? {} : { '/content': true }
})
return newList
}
module.exports = fileExplorer

@ -22,7 +22,7 @@ const profile = {
icon: 'assets/img/fileManager.webp',
permission: true,
version: packageJson.version,
methods: ['file', 'exists', 'open', 'writeFile', 'readFile', 'copyFile', 'copyDir', 'rename', 'mkdir', 'readdir', 'remove', 'getCurrentFile', 'getFile', 'getFolder', 'setFile', 'switchFile', 'refresh'],
methods: ['file', 'exists', 'open', 'writeFile', 'readFile', 'copyFile', 'copyDir', 'rename', 'mkdir', 'readdir', 'remove', 'getCurrentFile', 'getFile', 'getFolder', 'setFile', 'switchFile', 'refresh', 'getProviderOf', 'getProviderByName'],
kind: 'file-system'
}
const errorMsg = {
@ -155,9 +155,9 @@ class FileManager extends Plugin {
* @param {string} path path of the directory
* @returns {boolean} true if path is a directory.
*/
isDirectory (path) {
async isDirectory (path) {
const provider = this.fileProviderOf(path)
const result = provider.isDirectory(path)
const result = await provider.isDirectory(path)
return result
}
@ -362,7 +362,6 @@ class FileManager extends Plugin {
path = this.limitPluginScope(path)
await this._handleExists(path, `Cannot remove file or directory ${path}`)
const provider = this.fileProviderOf(path)
return await provider.remove(path)
} catch (e) {
throw new Error(e)
@ -597,6 +596,32 @@ class FileManager extends Plugin {
}
}
/**
* Async API method getProviderOf
* @param {string} file
*
*/
async getProviderOf (file) {
const cancall = await this.askUserPermission('getProviderByName')
if (cancall) {
return file ? this.fileProviderOf(file) : this.currentFileProvider()
}
}
/**
* Async API method getProviderByName
* @param {string} name
*
*/
async getProviderByName (name) {
const cancall = await this.askUserPermission('getProviderByName')
if (cancall) {
return this.getProvider(name)
}
}
getProvider (name) {
return this._deps.filesProviders[name]
}

@ -1,6 +1,6 @@
'use strict'
const CompilerImport = require('../compiler/compiler-imports')
import { CompilerImports } from '@remix-project/core-plugin'
const EventManager = require('events')
const modalDialogCustom = require('../ui/modal-dialog-custom')
const tooltip = require('../ui/tooltip')
@ -44,7 +44,7 @@ class FileProvider {
discardChanges (path) {
this.remove(path)
const compilerImport = new CompilerImport()
const compilerImport = new CompilerImports()
this.providerExternalsStorage.keys().map(value => {
if (value.indexOf(path) === 0) {
compilerImport.import(

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

@ -43,6 +43,7 @@ export class RemixdHandle extends WebsocketPlugin {
if (super.socket) super.deactivate()
// this.appManager.deactivatePlugin('git') // plugin call doesn't work.. see issue https://github.com/ethereum/remix-plugin/issues/342
if (this.appManager.actives.includes('hardhat')) this.appManager.deactivatePlugin('hardhat')
if (this.appManager.actives.includes('slither')) this.appManager.deactivatePlugin('slither')
this.localhostProvider.close((error) => {
if (error) console.log(error)
})
@ -88,6 +89,7 @@ export class RemixdHandle extends WebsocketPlugin {
this.call('filePanel', 'setWorkspace', { name: LOCALHOST, isLocalhost: true }, true)
})
this.call('manager', 'activatePlugin', 'hardhat')
this.call('manager', 'activatePlugin', 'slither')
}
}
if (this.localhostProvider.isConnected()) {
@ -134,23 +136,25 @@ export class RemixdHandle extends WebsocketPlugin {
}
function remixdDialog () {
const commandText = 'remixd -s path-to-the-shared-folder --remix-ide remix-ide-instance-URL'
const commandText = 'remixd -s <path-to-the-shared-folder> -u <remix-ide-instance-URL>'
return yo`
<div class=${css.dialog}>
<div class=${css.dialogParagraph}>
Access your local file system from Remix IDE using <a target="_blank" href="https://www.npmjs.com/package/@remix-project/remixd">Remixd NPM package</a>.<br/><br/>
Remixd needs to be running in the background to load the files in localhost workspace. For more info, please check the <a target="_blank" href="https://remix-ide.readthedocs.io/en/latest/remixd.html">Remixd tutorial</a>.
</div>
<div class=${css.dialogParagraph}>If you are just looking for the remixd command, here it is:
<div class=${css.dialogParagraph}>
If you are just looking for the remixd command, here it is:
<br><br><b>${commandText}</b>
<span class="">${copyToClipboard(() => commandText)}</span>
</div>
<div class=${css.dialogParagraph}>When connected, a session will be started between <em>${window.location.origin}</em> and your local file system at <i>ws://127.0.0.1:65520</i>
and the shared folder will be in the File Explorers workspace named "localhost".
<br/>Note, if the shared folder is a Hardhat project, an additional Hardhat websocket plugin will be listening at <i>ws://127.0.0.1:65522</i>
<div class=${css.dialogParagraph}>
When connected, a session will be started between <em>${window.location.origin}</em> and your local file system at <i>ws://127.0.0.1:65520</i>.
The shared folder will be in the "File Explorers" workspace named "localhost".
<br/>Read more about other <a target="_blank" href="https://remix-ide.readthedocs.io/en/latest/remixd.html#ports-usage">Remixd ports usage</a>
</div>
<div class=${css.dialogParagraph}>Please make sure your system is secured enough and ports 65520, 65522 are not opened nor forwarded.
This feature is still in Alpha, so we recommend to keep a copy of the shared folder.
<div class=${css.dialogParagraph}>
This feature is still in Alpha. We recommend to keep a backup of the shared folder.
</div>
<div class=${css.dialogParagraph}>
<h6 class="text-danger">

@ -0,0 +1,18 @@
import { WebsocketPlugin } from '@remixproject/engine-web'
import * as packageJson from '../../../../../package.json'
const profile = {
name: 'slither',
displayName: 'Slither',
url: 'ws://127.0.0.1:65523',
methods: ['analyse'],
description: 'Using Remixd daemon, run slither static analysis',
kind: 'other',
version: packageJson.version
}
export class SlitherHandle extends WebsocketPlugin {
constructor () {
super(profile)
}
}

@ -9,6 +9,7 @@ import { checkSpecialChars, checkSlash } from '../../lib/helper'
const { RemixdHandle } = require('../files/remixd-handle.js')
const { GitHandle } = require('../files/git-handle.js')
const { HardhatHandle } = require('../files/hardhat-handle.js')
const { SlitherHandle } = require('../files/slither-handle.js')
const globalRegistry = require('../../global/registry')
const examples = require('../editor/examples')
const GistHandler = require('../../lib/gist-handler')
@ -34,7 +35,7 @@ const modalDialogCustom = require('../ui/modal-dialog-custom')
const profile = {
name: 'filePanel',
displayName: 'File explorers',
methods: ['createNewFile', 'uploadFile', 'getCurrentWorkspace', 'getWorkspaces', 'createWorkspace', 'setWorkspace'],
methods: ['createNewFile', 'uploadFile', 'getCurrentWorkspace', 'getWorkspaces', 'createWorkspace', 'setWorkspace', 'registerContextMenuItem'],
events: ['setWorkspace', 'renameWorkspace', 'deleteWorkspace', 'createWorkspace'],
icon: 'assets/img/fileManager.webp',
description: ' - ',
@ -59,10 +60,13 @@ module.exports = class Filepanel extends ViewPlugin {
this.remixdHandle = new RemixdHandle(this._deps.fileProviders.localhost, appManager)
this.gitHandle = new GitHandle()
this.hardhatHandle = new HardhatHandle()
this.slitherHandle = new SlitherHandle()
this.registeredMenuItems = []
this.removedMenuItems = []
this.request = {}
this.workspaces = []
this.initialWorkspace = null
this.appManager = appManager
}
render () {
@ -88,6 +92,7 @@ module.exports = class Filepanel extends ViewPlugin {
request={this.request}
workspaces={this.workspaces}
registeredMenuItems={this.registeredMenuItems}
removedMenuItems={this.removedMenuItems}
initialWorkspace={this.initialWorkspace}
/>
, this.el)
@ -101,8 +106,22 @@ module.exports = class Filepanel extends ViewPlugin {
if (!item) throw new Error('Invalid register context menu argument')
if (!item.name || !item.id) throw new Error('Item name and id is mandatory')
if (!item.type && !item.path && !item.extension && !item.pattern) throw new Error('Invalid file matching criteria provided')
if (this.registeredMenuItems.filter((o) => {
return o.id === item.id && o.name === item.name
}).length) throw new Error(`Action ${item.name} already exists on ${item.id}`)
this.registeredMenuItems = [...this.registeredMenuItems, item]
this.removedMenuItems = this.removedMenuItems.filter(menuItem => item.id !== menuItem.id)
this.renderComponent()
}
removePluginActions (plugin) {
this.registeredMenuItems = this.registeredMenuItems.filter((item) => {
if (item.id !== plugin.name || item.sticky === true) return true
else {
this.removedMenuItems.push(item)
return false
}
})
this.renderComponent()
}
@ -148,21 +167,34 @@ module.exports = class Filepanel extends ViewPlugin {
}
if (loadedFromGist) return
if (params.code) {
if (params.code || params.url) {
try {
await this.processCreateWorkspace('code-sample')
this._deps.fileProviders.workspace.setWorkspace('code-sample')
var hash = bufferToHex(keccakFromString(params.code))
const fileName = 'contract-' + hash.replace('0x', '').substring(0, 10) + '.sol'
const path = fileName
await this._deps.fileProviders.workspace.set(path, atob(params.code))
let path = ''
let content = ''
if (params.code) {
var hash = bufferToHex(keccakFromString(params.code))
path = 'contract-' + hash.replace('0x', '').substring(0, 10) + '.sol'
content = atob(params.code)
await this._deps.fileProviders.workspace.set(path, content)
}
if (params.url) {
const data = await this.call('contentImport', 'resolve', params.url)
path = data.cleanUrl
content = data.content
await this._deps.fileProviders.workspace.set(path, content)
}
this.initialWorkspace = 'code-sample'
await this._deps.fileManager.openFile(fileName)
await this._deps.fileManager.openFile(path)
} catch (e) {
console.error(e)
}
return
}
const self = this
this.appManager.on('manager', 'pluginDeactivated', self.removePluginActions.bind(this))
// insert example contracts if there are no files to show
return new Promise((resolve, reject) => {
this._deps.fileProviders.browser.resolveDirectory('/', async (error, filesList) => {

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

@ -50,9 +50,7 @@ class AnalysisTab extends ViewPlugin {
renderComponent () {
ReactDOM.render(
<RemixUiStaticAnalyser
analysisRunner={this.runner}
registry={this.registry}
staticanalysis={this.staticanalysis}
analysisModule={this}
event={this.event}
/>,

@ -1,28 +1,18 @@
/* global */
import React from 'react' // eslint-disable-line
import ReactDOM from 'react-dom'
import { SolidityCompiler, CompileTab as CompileTabLogic, parseContracts } from '@remix-ui/solidity-compiler' // eslint-disable-line
import { compile } from '@remix-project/remix-solidity'
import { ViewPlugin } from '@remixproject/engine-web'
import * as packageJson from '../../../../../package.json'
import publishToStorage from '../../publishToStorage'
import { compile } from '../compiler/compiler-helpers'
const EventEmitter = require('events')
const $ = require('jquery')
const yo = require('yo-yo')
const copy = require('copy-text-to-clipboard')
var QueryParams = require('../../lib/query-params')
const TreeView = require('../ui/TreeView')
const modalDialog = require('../ui/modaldialog')
const copyToClipboard = require('../ui/copy-to-clipboard')
const modalDialogCustom = require('../ui/modal-dialog-custom')
const parseContracts = require('./compileTab/contractParser')
const addTooltip = require('../ui/tooltip')
const Renderer = require('../ui/renderer')
const globalRegistry = require('../../global/registry')
var css = require('./styles/compile-tab-styles')
const CompileTabLogic = require('./compileTab/compileTab.js')
const CompilerContainer = require('./compileTab/compilerContainer.js')
const profile = {
name: 'solidity',
displayName: 'Solidity compiler',
@ -33,8 +23,7 @@ const profile = {
location: 'sidePanel',
documentation: 'https://remix-ide.readthedocs.io/en/latest/solidity_editor.html',
version: packageJson.version,
methods: ['getCompilationResult', 'compile', 'compileWithParameters', 'setCompilerConfig']
methods: ['getCompilationResult', 'compile', 'compileWithParameters', 'setCompilerConfig', 'compileFile']
}
// EditorApi:
@ -57,11 +46,9 @@ class CompileTab extends ViewPlugin {
// dependencies
this.editor = editor
this.config = config
this.renderer = new Renderer(this)
this.fileManager = fileManager
this.contractsDetails = {}
this.data = {
contractsDetails: {},
eventHandlers: {},
loading: false
}
@ -71,30 +58,32 @@ class CompileTab extends ViewPlugin {
this.editor,
this.config,
this.fileProvider,
this.contentImport
this.contentImport,
this.setCompileErrors.bind(this)
)
}
onActivationInternal () {
this.compiler = this.compileTabLogic.compiler
this.compileTabLogic.init()
this.compilerContainer = new CompilerContainer(
this.compileTabLogic,
this.editor,
this.config,
this.queryParams
)
this.contractMap = {}
this.isHardHatProject = false
this.compileErrors = {}
this.compiledFileName = ''
this.selectedVersion = ''
this.configurationSettings = null
this.el = document.createElement('div')
this.el.setAttribute('id', 'compileTabView')
}
resetResults () {
if (this._view.errorContainer) {
this._view.errorContainer.innerHTML = ''
}
this.compilerContainer.currentFile = ''
this.data.contractsDetails = {}
yo.update(this._view.contractSelection, this.contractSelection())
this.currentFile = ''
this.contractsDetails = {}
this.emit('statusChanged', { key: 'none' })
this.renderComponent()
}
setCompileErrors (data) {
this.compileErrors = data
this.renderComponent()
}
/************
@ -102,13 +91,6 @@ class CompileTab extends ViewPlugin {
*/
listenToEvents () {
this.on('filePanel', 'setWorkspace', (workspace) => {
this.compileTabLogic.isHardhatProject().then((result) => {
if (result && workspace.isLocalhost) this.compilerContainer.hardhatCompilation.style.display = 'flex'
else this.compilerContainer.hardhatCompilation.style.display = 'none'
})
})
this.data.eventHandlers.onContentChanged = () => {
this.emit('statusChanged', { key: 'edited', title: 'the content has changed, needs recompilation', type: 'info' })
}
@ -127,9 +109,6 @@ class CompileTab extends ViewPlugin {
this.compiler.event.register('compilerLoaded', this.data.eventHandlers.onCompilerLoaded)
this.data.eventHandlers.onStartingCompilation = () => {
if (this._view.errorContainer) {
this._view.errorContainer.innerHTML = ''
}
this.emit('statusChanged', { key: 'loading', title: 'compiling...', type: 'info' })
}
@ -137,23 +116,32 @@ class CompileTab extends ViewPlugin {
this.call('editor', 'clearAnnotations')
}
this.on('filePanel', 'setWorkspace', () => this.resetResults())
this.on('filePanel', 'setWorkspace', (workspace) => {
this.compileTabLogic.isHardhatProject().then((result) => {
if (result && workspace.isLocalhost) this.isHardHatProject = true
else this.isHardHatProject = false
this.renderComponent()
})
this.resetResults()
})
this.compileTabLogic.event.on('startingCompilation', this.data.eventHandlers.onStartingCompilation)
this.compileTabLogic.event.on('removeAnnotations', this.data.eventHandlers.onRemoveAnnotations)
this.data.eventHandlers.onCurrentFileChanged = (name) => {
this.compilerContainer.currentFile = name
this.currentFile = name
this.renderComponent()
}
this.fileManager.events.on('currentFileChanged', this.data.eventHandlers.onCurrentFileChanged)
this.data.eventHandlers.onNoFileSelected = () => {
this.compilerContainer.currentFile = ''
this.currentFile = ''
this.renderComponent()
}
this.fileManager.events.on('noFileSelected', this.data.eventHandlers.onNoFileSelected)
this.data.eventHandlers.onCompilationFinished = (success, data, source) => {
this._view.errorContainer.appendChild(yo`<span data-id="compilationFinishedWith_${this.getCurrentVersion()}"></span>`)
this.setCompileErrors(data)
if (success) {
// forwarding the event to the appManager infra
this.emit('compilationFinished', source.target, source, 'soljson', data)
@ -165,9 +153,9 @@ class CompileTab extends ViewPlugin {
})
} else this.emit('statusChanged', { key: 'succeed', title: 'compilation successful', type: 'success' })
// Store the contracts
this.data.contractsDetails = {}
this.contractsDetails = {}
this.compiler.visitContracts((contract) => {
this.data.contractsDetails[contract.name] = parseContracts(
this.contractsDetails[contract.name] = parseContracts(
contract.name,
contract.object,
this.compiler.getSource(contract.file)
@ -178,37 +166,9 @@ class CompileTab extends ViewPlugin {
this.emit('statusChanged', { key: count, title: `compilation failed with ${count} error${count.length > 1 ? 's' : ''}`, type: 'error' })
}
// Update contract Selection
const contractMap = {}
if (success) this.compiler.visitContracts((contract) => { contractMap[contract.name] = contract })
const contractSelection = this.contractSelection(contractMap)
yo.update(this._view.contractSelection, contractSelection)
if (data.error) {
this.renderer.error(
data.error.formattedMessage || data.error,
this._view.errorContainer,
{ type: data.error.severity || 'error', errorType: data.error.type }
)
if (data.error.mode === 'panic') {
return modalDialogCustom.alert(yo`
<div><i class="fas fa-exclamation-circle ${css.panicError}" aria-hidden="true"></i>
The compiler returned with the following internal error: <br> <b>${data.error.formattedMessage}.<br>
The compiler might be in a non-sane state, please be careful and do not use further compilation data to deploy to mainnet.
It is heavily recommended to use another browser not affected by this issue (Firefox is known to not be affected).</b><br>
Please join <a href="https://gitter.im/ethereum/remix" target="blank" >remix gitter channel</a> for more information.</div>`)
}
}
if (data.errors && data.errors.length) {
data.errors.forEach((err) => {
if (this.config.get('hideWarnings')) {
if (err.severity !== 'warning') {
this.renderer.error(err.formattedMessage, this._view.errorContainer, { type: err.severity, errorType: err.type })
}
} else {
this.renderer.error(err.formattedMessage, this._view.errorContainer, { type: err.severity, errorType: err.type })
}
})
}
this.contractMap = {}
if (success) this.compiler.visitContracts((contract) => { this.contractMap[contract.name] = contract })
this.renderComponent()
}
this.compiler.event.register('compilationFinished', this.data.eventHandlers.onCompilationFinished)
@ -226,11 +186,19 @@ class CompileTab extends ViewPlugin {
// ctrl+s or command+s
if ((e.metaKey || e.ctrlKey) && e.keyCode === 83) {
e.preventDefault()
this.compileTabLogic.runCompiler(this.compilerContainer.hhCompilation)
this.compileTabLogic.runCompiler(this.hhCompilation)
}
})
}
setHardHatCompilation (value) {
this.hhCompilation = value
}
setSelectedVersion (version) {
this.selectedVersion = version
}
getCompilationResult () {
return this.compileTabLogic.compiler.state.lastCompilationResult
}
@ -254,20 +222,15 @@ class CompileTab extends ViewPlugin {
* @param {object} settings {evmVersion, optimize, runs, version, language}
*/
async compileWithParameters (compilationTargets, settings) {
settings.version = settings.version || this.compilerContainer.data.selectedVersion
settings.version = settings.version || this.selectedVersion
const res = await compile(compilationTargets, settings)
return res
}
// This function is used for passing the compiler remix-tests
getCurrentVersion () {
return this.compilerContainer.data.selectedVersion
}
// This function is used for passing the compiler configuration to 'remix-tests'
getCurrentCompilerConfig () {
return {
currentVersion: this.compilerContainer.data.selectedVersion,
currentVersion: this.selectedVersion,
evmVersion: this.compileTabLogic.evmVersion,
optimize: this.compileTabLogic.optimize,
runs: this.compileTabLogic.runs
@ -280,96 +243,10 @@ class CompileTab extends ViewPlugin {
* @param {object} settings {evmVersion, optimize, runs, version, language}
*/
setCompilerConfig (settings) {
return new Promise((resolve, reject) => {
addTooltip(yo`<div><b>${this.currentRequest.from}</b> is updating the <b>Solidity compiler configuration</b>.<pre class="text-left">${JSON.stringify(settings, null, '\t')}</pre></div>`)
this.compilerContainer.setConfiguration(settings)
// @todo(#2875) should use loading compiler return value to check whether the compiler is loaded instead of "setInterval"
let timeout = 0
const id = setInterval(() => {
timeout++
console.log(this.data.loading)
if (!this.data.loading || timeout > 10) {
resolve()
clearInterval(id)
}
}, 200)
})
}
/*********
* SUB-COMPONENTS
*/
/**
* Section to select the compiled contract
* @param {string[]} contractList Names of the compiled contracts
*/
contractSelection (contractMap) {
// Return the file name of a path: ex "browser/ballot.sol" -> "ballot.sol"
const getFileName = (path) => {
const part = path.split('/')
return part[part.length - 1]
}
const contractList = contractMap ? Object.keys(contractMap).map((key) => ({
name: key,
file: getFileName(contractMap[key].file)
})) : []
const selectEl = yo`
<select
onchange="${e => this.selectContract(e.target.value)}"
data-id="compiledContracts" id="compiledContracts" class="custom-select"
>
${contractList.map(({ name, file }) => yo`<option value="${name}">${name} (${file})</option>`)}
</select>
`
// define swarm logo
const result = contractList.length
? yo`<section class="${css.compilerSection} pt-3">
<!-- Select Compiler Version -->
<div class="mb-3">
<label class="${css.compilerLabel} form-check-label" for="compiledContracts">Contract</label>
${selectEl}
</div>
<article class="mt-2 pb-0">
<button id="publishOnSwarm" class="btn btn-secondary btn-block" title="Publish on Swarm" onclick="${() => { publishToStorage('swarm', this.fileProvider, this.fileManager, this.data.contractsDetails[this.selectedContract]) }}">
<span>Publish on Swarm</span>
<img id="swarmLogo" class="${css.storageLogo} ml-2" src="assets/img/swarm.webp">
</button>
<button id="publishOnIpfs" class="btn btn-secondary btn-block" title="Publish on Ipfs" onclick="${() => { publishToStorage('ipfs', this.fileProvider, this.fileManager, this.data.contractsDetails[this.selectedContract]) }}">
<span>Publish on Ipfs</span>
<img id="ipfsLogo" class="${css.storageLogo} ml-2" src="assets/img/ipfs.webp">
</button>
<button data-id="compilation-details" class="btn btn-secondary btn-block" title="Display Contract Details" onclick="${() => { this.details() }}">
Compilation Details
</button>
<!-- Copy to Clipboard -->
<div class="${css.contractHelperButtons}">
<div class="input-group">
<div class="btn-group" role="group" aria-label="Copy to Clipboard">
<button class="btn ${css.copyButton}" title="Copy ABI to clipboard" onclick="${() => { this.copyABI() }}">
<i class="${css.copyIcon} far fa-copy" aria-hidden="true"></i>
<span>ABI</span>
</button>
<button class="btn ${css.copyButton}" title="Copy Bytecode to clipboard" onclick="${() => { this.copyBytecode() }}">
<i class="${css.copyIcon} far fa-copy" aria-hidden="true"></i>
<span>Bytecode</span>
</button>
</div>
</div>
</div>
</div>
</section>`
: yo`<section class="${css.container} clearfix"><article class="px-2 mt-2 pb-0 d-flex">
<span class="mt-2 mx-3 w-100 alert alert-warning" role="alert">No Contract Compiled Yet</span>
</article></section>`
if (contractList.length) {
this.selectedContract = selectEl.value
} else {
delete this.selectedContract
}
return result
this.configurationSettings = settings
this.renderComponent()
// @todo(#2875) should use loading compiler return value to check whether the compiler is loaded instead of "setInterval"
addTooltip(yo`<div><b>${this.currentRequest.from}</b> is updating the <b>Solidity compiler configuration</b>.<pre class="text-left">${JSON.stringify(settings, null, '\t')}</pre></div>`)
}
// TODO : Add success alert when compilation succeed
@ -390,128 +267,40 @@ class CompileTab extends ViewPlugin {
this.selectedContract = contractName
}
details () {
const help = {
Assembly: 'Assembly opcodes describing the contract including corresponding solidity source code',
Opcodes: 'Assembly opcodes describing the contract',
'Runtime Bytecode': 'Bytecode storing the state and being executed during normal contract call',
bytecode: 'Bytecode being executed during contract creation',
functionHashes: 'List of declared function and their corresponding hash',
gasEstimates: 'Gas estimation for each function call',
metadata: 'Contains all informations related to the compilation',
metadataHash: 'Hash representing all metadata information',
abi: 'ABI: describing all the functions (input/output params, scope, ...)',
name: 'Name of the compiled contract',
swarmLocation: 'Swarm url where all metadata information can be found (contract needs to be published first)',
web3Deploy: 'Copy/paste this code to any JavaScript/Web3 console to deploy this contract'
}
if (!this.selectedContract) throw new Error('No contract compiled yet')
const contractProperties = this.data.contractsDetails[this.selectedContract]
const log = yo`<div class="${css.detailsJSON}"></div>`
Object.keys(contractProperties).map(propertyName => {
const copyDetails = yo`<span class="${css.copyDetails}">${copyToClipboard(() => contractProperties[propertyName])}</span>`
const questionMark = yo`<span class="${css.questionMark}"><i title="${help[propertyName]}" class="fas fa-question-circle" aria-hidden="true"></i></span>`
log.appendChild(yo`<div class=${css.log}>
<div class="${css.key}">${propertyName} ${copyDetails} ${questionMark}</div>
${this.insertValue(contractProperties, propertyName)}
</div>`)
})
modalDialog(this.selectedContract, log, { label: '' }, { label: 'Close' })
}
insertValue (details, propertyName) {
var node
if (propertyName === 'web3Deploy' || propertyName === 'name' || propertyName === 'Assembly') {
node = yo`<pre>${details[propertyName]}</pre>`
} else if (propertyName === 'abi' || propertyName === 'metadata') {
const treeView = new TreeView({
extractData: function (item, parent, key) {
var ret = {}
if (item instanceof Array) {
ret.children = item.map((item, index) => ({ key: index, value: item }))
ret.self = ''
} else if (item instanceof Object) {
ret.children = Object.keys(item).map((key) => ({ key: key, value: item[key] }))
ret.self = ''
} else {
ret.self = item
ret.children = []
}
return ret
}
})
if (details[propertyName] !== '') {
try {
node = yo`
<div>
${treeView.render(typeof details[propertyName] === 'object' ? details[propertyName] : JSON.parse(details[propertyName]))}
</div>` // catch in case the parsing fails.
} catch (e) {
node = yo`<div>Unable to display "${propertyName}": ${e.message}</div>`
}
} else {
node = yo`<div> - </div>`
}
} else {
node = yo`<div>${JSON.stringify(details[propertyName], null, 4)}</div>`
}
return yo`<pre class="${css.value}">${node || ''}</pre>`
}
getContractProperty (property) {
if (!this.selectedContract) throw new Error('No contract compiled yet')
const contractProperties = this.data.contractsDetails[this.selectedContract]
return contractProperties[property] || null
}
copyContractProperty (property) {
let content = this.getContractProperty(property)
if (!content) {
addTooltip('No content available for ' + property)
return
}
try {
if (typeof content !== 'string') {
content = JSON.stringify(content, null, '\t')
}
} catch (e) {}
copy(content)
addTooltip('Copied value to clipboard')
}
copyABI () {
this.copyContractProperty('abi')
}
copyBytecode () {
this.copyContractProperty('bytecode')
render () {
this.renderComponent()
return this.el
}
render () {
if (this._view.el) return this._view.el
this.onActivationInternal()
this._view.errorContainer = yo`<div class="${css.errorBlobs} p-4" data-id="compiledErrors" ></div>`
this._view.contractSelection = this.contractSelection()
this._view.compilerContainer = this.compilerContainer.render()
this.compilerContainer.activate()
this._view.el = yo`
<div id="compileTabView">
${this._view.compilerContainer}
${this._view.contractSelection}
${this._view.errorContainer}
</div>`
return this._view.el
renderComponent () {
ReactDOM.render(
<SolidityCompiler plugin={this}/>
, this.el)
}
onActivation () {
this.call('manager', 'activatePlugin', 'solidity-logic')
this.listenToEvents()
this.call('filePanel', 'registerContextMenuItem', {
id: 'solidity',
name: 'compileFile',
label: 'Compile',
type: [],
extension: ['.sol'],
path: [],
pattern: []
})
}
compileFile (event) {
if (event.path.length > 0) {
this.compileTabLogic.compileFile(event.path[0])
}
}
onDeactivation () {
this.compilerContainer.deactivate()
this.editor.event.unregister('contentChanged')
this.editor.event.unregister('sessionSwitched')
this.editor.event.unregister('contentChanged', this.data.eventHandlers.onContentChanged)
this.compiler.event.unregister('loadingCompiler', this.data.eventHandlers.onLoadingCompiler)
this.compiler.event.unregister('compilerLoaded', this.data.eventHandlers.onCompilerLoaded)

@ -1,578 +0,0 @@
import toaster from '../../ui/tooltip'
import { canUseWorker, baseURLBin, baseURLWasm, urlFromVersion, pathToURL, promisedMiniXhr } from '../../compiler/compiler-utils'
const yo = require('yo-yo')
const helper = require('../../../lib/helper')
const addTooltip = require('../../ui/tooltip')
const semver = require('semver')
const modalDialogCustom = require('../../ui/modal-dialog-custom')
const css = require('../styles/compile-tab-styles')
class CompilerContainer {
constructor (compileTabLogic, editor, config, queryParams) {
this._view = {}
this.compileTabLogic = compileTabLogic
this.editor = editor
this.config = config
this.queryParams = queryParams
this.hhCompilation = false
this.data = {
hideWarnings: config.get('hideWarnings') || false,
autoCompile: config.get('autoCompile'),
compileTimeout: null,
timeout: 300,
allversions: null,
selectedVersion: null,
defaultVersion: 'soljson-v0.8.4+commit.c7e474f2.js' // this default version is defined: in makeMockCompiler (for browser test)
}
}
/**
* Update the compilation button with the name of the current file
*/
set currentFile (name = '') {
if (name && name !== '') {
this._setCompilerVersionFromPragma(name)
}
if (!this._view.compilationButton) return
const button = this.compilationButton(name.split('/').pop())
this._disableCompileBtn(!name || (name && !this.isSolFileSelected(name)))
yo.update(this._view.compilationButton, button)
}
isSolFileSelected (currentFile = '') {
if (!currentFile) currentFile = this.config.get('currentFile')
if (!currentFile) return false
const extention = currentFile.substr(currentFile.length - 3, currentFile.length)
return extention.toLowerCase() === 'sol' || extention.toLowerCase() === 'yul'
}
deactivate () {
// deactivate editor listeners
this.editor.event.unregister('contentChanged')
this.editor.event.unregister('sessionSwitched')
}
activate () {
this.currentFile = this.config.get('currentFile')
this.listenToEvents()
}
listenToEvents () {
this.editor.event.register('sessionSwitched', () => {
if (!this._view.compileIcon) return
this.scheduleCompilation()
})
this.compileTabLogic.event.on('startingCompilation', () => {
if (!this._view.compileIcon) return
this._view.compileIcon.setAttribute('title', 'compiling...')
this._view.compileIcon.classList.remove(`${css.bouncingIcon}`)
this._view.compileIcon.classList.add(`${css.spinningIcon}`)
})
this.compileTabLogic.compiler.event.register('compilationDuration', (speed) => {
if (!this._view.warnCompilationSlow) return
if (speed > 1000) {
const msg = `Last compilation took ${speed}ms. We suggest to turn off autocompilation.`
this._view.warnCompilationSlow.setAttribute('title', msg)
this._view.warnCompilationSlow.style.visibility = 'visible'
} else {
this._view.warnCompilationSlow.style.visibility = 'hidden'
}
})
this.editor.event.register('contentChanged', () => {
if (!this._view.compileIcon) return
this.scheduleCompilation()
this._view.compileIcon.classList.add(`${css.bouncingIcon}`) // @TODO: compileView tab
})
this.compileTabLogic.compiler.event.register('loadingCompiler', () => {
if (!this._view.compileIcon) return
this._disableCompileBtn(true)
this._view.compileIcon.setAttribute('title', 'compiler is loading, please wait a few moments.')
this._view.compileIcon.classList.add(`${css.spinningIcon}`)
this._view.warnCompilationSlow.style.visibility = 'hidden'
this._updateLanguageSelector()
})
this.compileTabLogic.compiler.event.register('compilerLoaded', () => {
if (!this._view.compileIcon) return
this._disableCompileBtn(false)
this._view.compileIcon.setAttribute('title', '')
this._view.compileIcon.classList.remove(`${css.spinningIcon}`)
if (this.data.autoCompile) this.compileIfAutoCompileOn()
})
this.compileTabLogic.compiler.event.register('compilationFinished', (success, data, source) => {
if (!this._view.compileIcon) return
this._view.compileIcon.setAttribute('title', 'idle')
this._view.compileIcon.classList.remove(`${css.spinningIcon}`)
this._view.compileIcon.classList.remove(`${css.bouncingIcon}`)
})
}
/**************
* SUBCOMPONENT
*/
compilationButton (name = '') {
const displayed = name || '<no file selected>'
const disabled = name && this.isSolFileSelected() ? '' : 'disabled'
return yo`
<button id="compileBtn" data-id="compilerContainerCompileBtn" class="btn btn-primary btn-block ${disabled} mt-3" title="Compile" onclick="${this.compile.bind(this)}">
<span>${this._view.compileIcon} Compile ${displayed}</span>
</button>
`
}
_disableCompileBtn (shouldDisable) {
const btn = document.getElementById('compileBtn')
if (!btn) return
if (shouldDisable) {
btn.classList.add('disabled')
} else if (this.isSolFileSelected()) {
btn.classList.remove('disabled')
}
}
// Load solc compiler version according to pragma in contract file
_setCompilerVersionFromPragma (filename) {
if (!this.data.allversions) return
this.compileTabLogic.fileManager.readFile(filename).then(data => {
const pragmaArr = data.match(/(pragma solidity (.+?);)/g)
if (pragmaArr && pragmaArr.length === 1) {
const pragmaStr = pragmaArr[0].replace('pragma solidity', '').trim()
const pragma = pragmaStr.substring(0, pragmaStr.length - 1)
const releasedVersions = this.data.allversions.filter(obj => !obj.prerelease).map(obj => obj.version)
const allVersions = this.data.allversions.map(obj => this._retrieveVersion(obj.version))
const currentCompilerName = this._retrieveVersion(this._view.versionSelector.selectedOptions[0].label)
// contains only numbers part, for example '0.4.22'
const pureVersion = this._retrieveVersion()
// is nightly build newer than the last release
const isNewestNightly = currentCompilerName.includes('nightly') && semver.gt(pureVersion, releasedVersions[0])
// checking if the selected version is in the pragma range
const isInRange = semver.satisfies(pureVersion, pragma)
// checking if the selected version is from official compilers list(excluding custom versions) and in range or greater
const isOfficial = allVersions.includes(currentCompilerName)
if (isOfficial && (!isInRange && !isNewestNightly)) {
const compilerToLoad = semver.maxSatisfying(releasedVersions, pragma)
const compilerPath = this.data.allversions.filter(obj => !obj.prerelease && obj.version === compilerToLoad)[0].path
if (this.data.selectedVersion !== compilerPath) {
this.data.selectedVersion = compilerPath
this._updateVersionSelector()
}
}
}
})
}
_retrieveVersion (version) {
if (!version) version = this._view.versionSelector.value
if (version === 'builtin') version = this.data.defaultVersion
return semver.coerce(version) ? semver.coerce(version).version : ''
}
render () {
this.compileTabLogic.compiler.event.register('compilerLoaded', (version) => this.setVersionText(version))
this.fetchAllVersion((allversions, selectedVersion, isURL) => {
this.data.allversions = allversions
if (isURL) this._updateVersionSelector(selectedVersion)
else {
this.data.selectedVersion = selectedVersion
if (this._view.versionSelector) this._updateVersionSelector()
}
})
this.hardhatCompilation = yo`<div class="mt-2 ${css.compilerConfig} custom-control custom-checkbox" style="display:none">
<input class="${css.autocompile} custom-control-input" onchange=${(e) => this.updatehhCompilation(e)} id="enableHardhat" type="checkbox" title="Enable Hardhat Compilation">
<label class="form-check-label custom-control-label" for="enableHardhat">Enable Hardhat Compilation</label>
</div>`
this._view.warnCompilationSlow = yo`<i title="Compilation Slow" style="visibility:hidden" class="${css.warnCompilationSlow} fas fa-exclamation-triangle" aria-hidden="true"></i>`
this._view.compileIcon = yo`<i class="fas fa-sync ${css.icon}" aria-hidden="true"></i>`
this._view.autoCompile = yo`<input class="${css.autocompile} custom-control-input" onchange=${() => this.updateAutoCompile()} data-id="compilerContainerAutoCompile" id="autoCompile" type="checkbox" title="Auto compile">`
this._view.hideWarningsBox = yo`<input class="${css.autocompile} custom-control-input" onchange=${() => this.hideWarnings()} id="hideWarningsBox" type="checkbox" title="Hide warnings">`
if (this.data.autoCompile) this._view.autoCompile.setAttribute('checked', '')
if (this.data.hideWarnings) this._view.hideWarningsBox.setAttribute('checked', '')
this._view.optimize = yo`<input onchange=${() => this.onchangeOptimize()} class="custom-control-input" id="optimize" type="checkbox">`
if (this.compileTabLogic.optimize) this._view.optimize.setAttribute('checked', '')
this._view.runs = yo`<input
min="1"
class="custom-select ml-2 ${css.runs}"
id="runs"
placeholder="200"
value="200"
type="number"
title="Estimated number of times each opcode of the deployed code will be executed across the life-time of the contract."
onchange=${() => this.onchangeRuns()}
>`
if (this.compileTabLogic.optimize) {
this._view.runs.removeAttribute('disabled')
this._view.runs.value = this.compileTabLogic.runs
} else {
this._view.runs.setAttribute('disabled', '')
}
this._view.versionSelector = yo`
<select onchange="${() => this.onchangeLoadVersion()}" class="custom-select" id="versionSelector" disabled>
<option disabled selected>${this.data.defaultVersion}</option>
<option disabled>builtin</option>
</select>`
this._view.languageSelector = yo`
<select onchange="${() => this.onchangeLanguage()}" class="custom-select" id="compilierLanguageSelector" title="Available since v0.5.7">
<option>Solidity</option>
<option>Yul</option>
</select>`
this._view.version = yo`<span id="version"></span>`
this._view.evmVersionSelector = yo`
<select onchange="${() => this.onchangeEvmVersion()}" class="custom-select" id="evmVersionSelector">
<option value="default" selected="selected">compiler default</option>
<option>berlin</option>
<option>muirGlacier</option>
<option>istanbul</option>
<option>petersburg</option>
<option>constantinople</option>
<option>byzantium</option>
<option>spuriousDragon</option>
<option>tangerineWhistle</option>
<option>homestead</option>
</select>`
if (this.compileTabLogic.evmVersion) {
const s = this._view.evmVersionSelector
let i
for (i = 0; i < s.options.length; i++) {
if (s.options[i].value === this.compileTabLogic.evmVersion) {
break
}
}
if (i === s.options.length) { // invalid evmVersion from queryParams
s.selectedIndex = 0 // compiler default
this.onchangeEvmVersion()
} else {
s.selectedIndex = i
this.onchangeEvmVersion()
}
}
this._view.compilationButton = this.compilationButton()
this._view.includeNightlies = yo`
<input class="mr-2 custom-control-input" id="nightlies" type="checkbox" onchange=${() => this._updateVersionSelector()}>
`
this._view.compileContainer = yo`
<section>
<!-- Select Compiler Version -->
<article>
<header class="${css.compilerSection} border-bottom">
<div class="mb-2">
<label class="${css.compilerLabel} form-check-label" for="versionSelector">
Compiler
<button class="far fa-plus-square border-0 p-0 mx-2 btn-sm" onclick="${(e) => this.promtCompiler(e)}" title="Add a custom compiler with URL"></button>
</label>
${this._view.versionSelector}
</div>
<div class="mb-2 ${css.nightlyBuilds} custom-control custom-checkbox">
${this._view.includeNightlies}
<label for="nightlies" class="form-check-label custom-control-label">Include nightly builds</label>
</div>
<div class="mb-2">
<label class="${css.compilerLabel} form-check-label" for="compilierLanguageSelector">Language</label>
${this._view.languageSelector}
</div>
<div class="mb-2">
<label class="${css.compilerLabel} form-check-label" for="evmVersionSelector">EVM Version</label>
${this._view.evmVersionSelector}
</div>
<div class="mt-3">
<p class="mt-2 ${css.compilerLabel}">Compiler Configuration</p>
<div class="mt-2 ${css.compilerConfig} custom-control custom-checkbox">
${this._view.autoCompile}
<label class="form-check-label custom-control-label" for="autoCompile">Auto compile</label>
</div>
<div class="mt-2 ${css.compilerConfig} custom-control custom-checkbox">
<div class="justify-content-between align-items-center d-flex">
${this._view.optimize}
<label class="form-check-label custom-control-label" for="optimize">Enable optimization</label>
${this._view.runs}
</div>
</div>
<div class="mt-2 ${css.compilerConfig} custom-control custom-checkbox">
${this._view.hideWarningsBox}
<label class="form-check-label custom-control-label" for="hideWarningsBox">Hide warnings</label>
</div>
</div>
${this.hardhatCompilation}
${this._view.compilationButton}
</header>
</article>
<!-- Config -->
</section>`
return this._view.compileContainer
}
promtCompiler () {
modalDialogCustom.prompt(
'Add a custom compiler',
'URL',
'',
(url) => this.addCustomCompiler(url)
)
}
addCustomCompiler (url) {
this.data.selectedVersion = this._view.versionSelector.value
this._updateVersionSelector(url)
}
updateAutoCompile (event) {
this.config.set('autoCompile', this._view.autoCompile.checked)
}
updatehhCompilation (event) {
this.hhCompilation = event.target.checked
}
compile (event) {
const currentFile = this.config.get('currentFile')
if (!this.isSolFileSelected()) return
this._setCompilerVersionFromPragma(currentFile)
this.compileTabLogic.runCompiler(this.hhCompilation)
}
compileIfAutoCompileOn () {
if (this.config.get('autoCompile')) {
this.compile()
}
}
hideWarnings (event) {
this.config.set('hideWarnings', this._view.hideWarningsBox.checked)
this.compileIfAutoCompileOn()
}
/*
The following functions are handlers for internal events.
*/
onchangeOptimize () {
this.compileTabLogic.setOptimize(!!this._view.optimize.checked)
if (this.compileTabLogic.optimize) {
this._view.runs.removeAttribute('disabled')
this.compileTabLogic.setRuns(parseInt(this._view.runs.value))
} else {
this.compileTabLogic.setRuns(200)
this._view.runs.setAttribute('disabled', '')
}
this.compileIfAutoCompileOn()
}
onchangeRuns () {
this.compileTabLogic.setRuns(parseInt(this._view.runs.value))
this.compileIfAutoCompileOn()
}
onchangeLanguage () {
this.compileTabLogic.setLanguage(this._view.languageSelector.value)
this.compileIfAutoCompileOn()
}
onchangeEvmVersion () {
const s = this._view.evmVersionSelector
let v = s.value
if (v === 'default') {
v = null
}
this.compileTabLogic.setEvmVersion(v)
for (let i = 0; i < s.options.length; i++) {
if (i === s.selectedIndex) {
s.options[s.selectedIndex].setAttribute('selected', 'selected')
} else {
s.options[i].removeAttribute('selected')
}
}
this.compileIfAutoCompileOn()
}
onchangeLoadVersion () {
this.data.selectedVersion = this._view.versionSelector.value
this._updateVersionSelector()
this._updateLanguageSelector()
}
/*
The following functions map with the above event handlers.
They are an external API for modifying the compiler configuration.
*/
setConfiguration (settings) {
this.setLanguage(settings.language)
this.setEvmVersion(settings.evmVersion)
this.setOptimize(settings.optimize)
this.setRuns(settings.runs)
this.setVersion(settings.version)
}
setOptimize (enabled) {
this._view.optimize.checked = enabled
this.onchangeOptimize()
}
setRuns (value) {
if (value) {
this._view.runs.value = value
this.onchangeRuns()
}
}
setLanguage (lang) {
this._view.languageSelector.value = lang
this.onchangeLanguage()
}
setEvmVersion (version) {
this._view.evmVersionSelector.value = version || 'default'
this.onchangeEvmVersion()
}
setVersion (version) {
this._view.versionSelector.value = `soljson-v${version}.js`
this.onchangeLoadVersion()
}
_shouldBeAdded (version) {
return !version.includes('nightly') ||
(version.includes('nightly') && this._view.includeNightlies.checked)
}
_updateVersionSelector (customUrl = '') {
// update selectedversion if previous one got filtered out
if (!this.data.selectedVersion || !this._shouldBeAdded(this.data.selectedVersion)) {
this.data.selectedVersion = this.data.defaultVersion
}
this._view.versionSelector.innerHTML = ''
this._view.versionSelector.removeAttribute('disabled')
this.queryParams.update({ version: this.data.selectedVersion })
let url
if (customUrl !== '') {
this.data.selectedVersion = customUrl
this._view.versionSelector.appendChild(yo`<option value="${customUrl}" selected>custom</option>`)
url = customUrl
this.queryParams.update({ version: this.data.selectedVersion })
} else if (this.data.selectedVersion === 'builtin') {
let location = window.document.location
let path = location.pathname
if (!path.startsWith('/')) path = '/' + path
location = `${location.protocol}//${location.host}${path}assets/js`
if (location.endsWith('index.html')) location = location.substring(0, location.length - 10)
if (!location.endsWith('/')) location += '/'
url = location + 'soljson.js'
} else {
if (this.data.selectedVersion.indexOf('soljson') !== 0 || helper.checkSpecialChars(this.data.selectedVersion)) {
return console.log('loading ' + this.data.selectedVersion + ' not allowed')
}
url = `${urlFromVersion(this.data.selectedVersion)}`
}
this.data.allversions.forEach(build => {
const option = build.path === this.data.selectedVersion
? yo`<option value="${build.path}" selected>${build.longVersion}</option>`
: yo`<option value="${build.path}">${build.longVersion}</option>`
if (this._shouldBeAdded(option.innerText)) {
this._view.versionSelector.appendChild(option)
}
})
if (this.data.selectedVersion !== 'builtin' && semver.lt(this._retrieveVersion(), 'v0.4.12+commit.194ff033.js')) {
toaster(yo`
<div>
<b>Old compiler usage detected.</b>
<p>You are using a compiler older than v0.4.12.</p>
<p>Some functionality may not work.</p>
</div>`
)
}
// Workers cannot load js on "file:"-URLs and we get a
// "Uncaught RangeError: Maximum call stack size exceeded" error on Chromium,
// resort to non-worker version in that case.
if (canUseWorker(this._retrieveVersion())) {
this.compileTabLogic.compiler.loadVersion(true, url)
this.setVersionText('(loading using worker)')
} else {
this.compileTabLogic.compiler.loadVersion(false, url)
this.setVersionText('(loading)')
}
}
_updateLanguageSelector () {
// This is the first version when Yul is available
if (!semver.valid(this._retrieveVersion()) || semver.lt(this._retrieveVersion(), 'v0.5.7+commit.6da8b019.js')) {
this._view.languageSelector.setAttribute('disabled', '')
this._view.languageSelector.value = 'Solidity'
this.compileTabLogic.setLanguage('Solidity')
} else {
this._view.languageSelector.removeAttribute('disabled')
}
}
setVersionText (text) {
if (this._view.version) this._view.version.innerText = text
}
// fetching both normal and wasm builds and creating a [version, baseUrl] map
async fetchAllVersion (callback) {
let selectedVersion, allVersionsWasm, isURL
let allVersions = [{ path: 'builtin', longVersion: 'Stable local version - 0.8.4' }]
// fetch normal builds
const binRes = await promisedMiniXhr(`${baseURLBin}/list.json`)
// fetch wasm builds
const wasmRes = await promisedMiniXhr(`${baseURLWasm}/list.json`)
if (binRes.event.type === 'error' && wasmRes.event.type === 'error') {
selectedVersion = 'builtin'
return callback(allVersions, selectedVersion)
}
try {
const versions = JSON.parse(binRes.json).builds.slice().reverse()
allVersions = [...allVersions, ...versions]
selectedVersion = this.data.defaultVersion
if (this.queryParams.get().version) selectedVersion = this.queryParams.get().version
// Check if version is a URL and corresponding filename starts with 'soljson'
if (selectedVersion.startsWith('https://')) {
const urlArr = selectedVersion.split('/')
if (urlArr[urlArr.length - 1].startsWith('soljson')) isURL = true
}
if (wasmRes.event.type !== 'error') {
allVersionsWasm = JSON.parse(wasmRes.json).builds.slice().reverse()
}
} catch (e) {
addTooltip('Cannot load compiler version list. It might have been blocked by an advertisement blocker. Please try deactivating any of them from this page and reload. Error: ' + e)
}
// replace in allVersions those compiler builds which exist in allVersionsWasm with new once
if (allVersionsWasm && allVersions) {
allVersions.forEach((compiler, index) => {
const wasmIndex = allVersionsWasm.findIndex(wasmCompiler => { return wasmCompiler.longVersion === compiler.longVersion })
if (wasmIndex !== -1) {
allVersions[index] = allVersionsWasm[wasmIndex]
pathToURL[compiler.path] = baseURLWasm
} else {
pathToURL[compiler.path] = baseURLBin
}
})
}
callback(allVersions, selectedVersion, isURL)
}
scheduleCompilation () {
if (!this.config.get('autoCompile')) return
if (this.data.compileTimeout) window.clearTimeout(this.data.compileTimeout)
this.data.compileTimeout = window.setTimeout(() => this.compileIfAutoCompileOn(), this.data.timeout)
}
}
module.exports = CompilerContainer

@ -65,7 +65,7 @@ export default class HardhatProvider extends Plugin {
if (error) {
this.blocked = true
modalDialogCustom.alert('Hardhat Provider', `Error while connecting to the hardhat provider: ${error.message}`)
await this.call('udapp', 'setEnvironmentMode', 'vm')
await this.call('udapp', 'setEnvironmentMode', { context: 'vm', fork: 'london' })
this.provider = null
setTimeout(_ => { this.blocked = false }, 1000) // we wait 1 second for letting remix to switch to vm
return reject(error)

@ -361,7 +361,7 @@ class ContractDropdownUI {
return continueTxExecution(null)
}
const amount = this.blockchain.fromWei(tx.value, true, 'ether')
const content = confirmDialog(tx, amount, gasEstimation, null, this.blockchain.determineGasFees(tx), this.blockchain.determineGasPrice.bind(this.blockchain))
const content = confirmDialog(tx, network, amount, gasEstimation, this.blockchain.determineGasFees(tx), this.blockchain.determineGasPrice.bind(this.blockchain))
modalDialog('Confirm transaction', content,
{
@ -370,10 +370,9 @@ class ContractDropdownUI {
this.blockchain.config.setUnpersistedProperty('doNotShowTransactionConfirmationAgain', content.querySelector('input#confirmsetting').checked)
// TODO: check if this is check is still valid given the refactor
if (!content.gasPriceStatus) {
cancelCb('Given gas price is not correct')
cancelCb('Given transaction fee is not correct')
} else {
var gasPrice = this.blockchain.toWei(content.querySelector('#gasprice').value, 'gwei')
continueTxExecution(gasPrice)
continueTxExecution(content.txFee)
}
}
}, {

@ -1,6 +1,6 @@
import { CompilerAbstract } from '@remix-project/remix-solidity'
const remixLib = require('@remix-project/remix-lib')
const txHelper = remixLib.execution.txHelper
const CompilerAbstract = require('../../../compiler/compiler-abstract')
const EventManager = remixLib.EventManager
const _paq = window._paq = window._paq || []

@ -104,7 +104,7 @@ class RecorderUI extends Plugin {
return continueTxExecution(null)
}
const amount = this.blockchain.fromWei(tx.value, true, 'ether')
const content = confirmDialog(tx, amount, gasEstimation, null, this.blockchain.determineGasFees(tx), this.blockchain.determineGasPrice.bind(this.blockchain))
const content = confirmDialog(tx, network, amount, gasEstimation, this.blockchain.determineGasFees(tx), this.blockchain.determineGasPrice.bind(this.blockchain))
modalDialog('Confirm transaction', content,
{
@ -113,10 +113,9 @@ class RecorderUI extends Plugin {
this.config.setUnpersistedProperty('doNotShowTransactionConfirmationAgain', content.querySelector('input#confirmsetting').checked)
// TODO: check if this is check is still valid given the refactor
if (!content.gasPriceStatus) {
cancelCb('Given gas price is not correct')
cancelCb('Given transaction fee is not correct')
} else {
var gasPrice = this.blockchain.toWei(content.querySelector('#gasprice').value, 'gwei')
continueTxExecution(gasPrice)
continueTxExecution(content.txFee)
}
}
}, {

@ -98,14 +98,14 @@ class SettingsUI {
</label>
<div class="${css.environment}">
<select id="selectExEnvOptions" data-id="settingsSelectEnvOptions" class="form-control ${css.select} custom-select">
<option id="vm-mode-berlin" data-id="settingsVMBerlinMode"
title="Execution environment does not connect to any node, everything is local and in memory only."
value="vm-berlin" name="executionContext" fork="berlin" > JavaScript VM (Berlin)
</option>
<option id="vm-mode-london" data-id="settingsVMLondonMode"
title="Execution environment does not connect to any node, everything is local and in memory only."
value="vm-london" name="executionContext" fork="london"> JavaScript VM (London)
</option>
<option id="vm-mode-berlin" data-id="settingsVMBerlinMode"
title="Execution environment does not connect to any node, everything is local and in memory only."
value="vm-berlin" name="executionContext" fork="berlin" > JavaScript VM (Berlin)
</option>
<option id="injected-mode" data-id="settingsInjectedMode"
title="Execution environment has been provided by Metamask or similar provider."
value="injected" name="executionContext"> Injected Web3

@ -4,12 +4,11 @@ import ReactDOM from 'react-dom'
import * as packageJson from '../../../../../package.json'
import { RemixUiSettings } from '@remix-ui/settings' //eslint-disable-line
const globalRegistry = require('../../global/registry')
const EventManager = require('../../lib/events')
const profile = {
name: 'settings',
displayName: 'Settings',
methods: ['getGithubAccessToken'],
methods: ['get'],
events: [],
icon: 'assets/img/settings.webp',
description: 'Remix-IDE settings',
@ -21,36 +20,16 @@ const profile = {
}
module.exports = class SettingsTab extends ViewPlugin {
constructor (config, editor, appManager) {
constructor (config, editor) {
super(profile)
this.config = config
this.editor = editor
this.appManager = appManager
this._components = {}
this._deps = {
themeModule: globalRegistry.get('themeModule').api
}
this._view = { /* eslint-disable */
el: null,
optionVM: null,
optionVMLabel: null,
personal: null,
personalLabel: null,
useMatomoAnalytics: null,
useMatomoAnalyticsLabel: null,
useMatomoAnalyticsMode: null,
warnPersonalMode: null,
generateContractMetadata: null,
generateContractMetadataLabel: null,
config: {
general: null, themes: null
},
textWrap: null,
textWrapLabel: null
} /* eslint-enable */
this.event = new EventManager()
this.element = document.createElement('div')
this.element.setAttribute('id', 'settingsTab')
this.useMatomoAnalytics = null
}
onActivation () {
@ -67,25 +46,19 @@ module.exports = class SettingsTab extends ViewPlugin {
config = { this.config }
editor = { this.editor }
_deps = { this._deps }
useMatomoAnalytics = {this.useMatomoAnalytics}
/>,
this.element
)
}
getGithubAccessToken () {
return this.config.get('settings/gist-access-token')
get (key) {
return this.config.get(key)
}
updateMatomoAnalyticsChoice (isChecked) {
this.config.set('settings/matomo-analytics', isChecked)
if (isChecked) {
this._view.useMatomoAnalytics.setAttribute('checked', '')
this._view.useMatomoAnalyticsLabel.classList.remove('text-secondary')
this._view.useMatomoAnalyticsLabel.classList.add('text-dark')
} else {
this._view.useMatomoAnalytics.removeAttribute('checked')
this._view.useMatomoAnalyticsLabel.classList.remove('text-dark')
this._view.useMatomoAnalyticsLabel.classList.add('text-secondary')
}
this.useMatomoAnalytics = isChecked
this.renderComponent()
}
}

@ -1,7 +1,6 @@
import { ViewPlugin } from '@remixproject/engine-web'
import { canUseWorker, urlFromVersion } from '../compiler/compiler-utils'
import { removeMultipleSlashes, removeTrailingSlashes } from '../../lib/helper'
import { canUseWorker, urlFromVersion } from '@remix-project/remix-solidity'
var yo = require('yo-yo')
var async = require('async')
var tooltip = require('../ui/tooltip')
@ -71,9 +70,9 @@ module.exports = class TestTab extends ViewPlugin {
})
} catch (e) {
console.log(e)
this.data.allTests.push(file)
this.data.selectedTests.push(file)
}
this.data.allTests.push(file)
this.data.selectedTests.push(file)
})
this.on('filePanel', 'setWorkspace', async () => {

@ -1,6 +1,7 @@
var yo = require('yo-yo')
var csjs = require('csjs-inject')
const copyToClipboard = require('./copy-to-clipboard')
const Web3 = require('web3')
var css = csjs`
.txInfoBox {
@ -14,20 +15,47 @@ var css = csjs`
}
`
// TODO: self is not actually used and can be removed
function confirmDialog (tx, amount, gasEstimation, self, newGasPriceCb, initialParamsCb) {
var onGasPriceChange = function () {
function confirmDialog (tx, network, amount, gasEstimation, newGasPriceCb, initialParamsCb) {
const onGasPriceChange = function () {
var gasPrice = el.querySelector('#gasprice').value
newGasPriceCb(gasPrice, (txFeeText, priceStatus) => {
el.querySelector('#txfee').innerHTML = txFeeText
el.gasPriceStatus = priceStatus
el.txFee = { gasPrice }
})
}
var el = yo`
const onMaxFeeChange = function () {
var maxFee = el.querySelector('#maxfee').value
var confirmBtn = document.querySelector('#modal-footer-ok')
var maxPriorityFee = el.querySelector('#maxpriorityfee').value
if (parseInt(network.lastBlock.baseFeePerGas, 16) > Web3.utils.toWei(maxFee, 'Gwei')) {
el.querySelector('#txfee').innerHTML = 'Transaction is invalid. Max fee should not be less than Base fee'
el.gasPriceStatus = false
confirmBtn.hidden = true
return
} else {
el.gasPriceStatus = true
confirmBtn.hidden = false
}
newGasPriceCb(maxFee, (txFeeText, priceStatus) => {
el.querySelector('#txfee').innerHTML = txFeeText
if (priceStatus) {
confirmBtn.hidden = false
} else {
confirmBtn.hidden = true
}
el.gasPriceStatus = priceStatus
el.txFee = { maxFee, maxPriorityFee, baseFeePerGas: network.lastBlock.baseFeePerGas }
})
}
const el = yo`
<div>
<div>You are about to create a transaction on the Main Network. Confirm the details to send the info to your provider.
<br>The provider for many users is MetaMask. The provider will ask you to sign the transaction before it is sent to the Main Network.</div>
<div class="text-dark">You are about to create a transaction on ${network.name} Network. Confirm the details to send the info to your provider.
<br>The provider for many users is MetaMask. The provider will ask you to sign the transaction before it is sent to ${network.name} Network.
</div>
<div class="mt-3 ${css.txInfoBox}">
<div>
<span class="text-dark mr-2">From:</span>
@ -37,7 +65,11 @@ function confirmDialog (tx, amount, gasEstimation, self, newGasPriceCb, initialP
<span class="text-dark mr-2">To:</span>
<span>${tx.to ? tx.to : '(Contract Creation)'}</span>
</div>
<div>
<div class="d-flex align-items-center">
<span class="text-dark mr-2">Data:</span>
<pre class="${css.wrapword} mb-0">${tx.data && tx.data.length > 50 ? tx.data.substring(0, 49) + '...' : tx.data} ${copyToClipboard(() => { return tx.data })}</pre>
</div>
<div class="mb-3">
<span class="text-dark mr-2">Amount:</span>
<span>${amount} Ether</span>
</div>
@ -49,18 +81,32 @@ function confirmDialog (tx, amount, gasEstimation, self, newGasPriceCb, initialP
<span class="text-dark mr-2">Gas limit:</span>
<span>${tx.gas}</span>
</div>
<div>
${
network.lastBlock.baseFeePerGas ? yo`
<div class="align-items-center my-1" title="Represents the part of the tx fee that goes to the miner.">
<div class='d-flex'>
<span class="text-dark mr-2 text-nowrap">Max Priority fee:</span>
<input class="form-control mr-1 text-right" style='height: 1.2rem; width: 6rem;' value="0" id='maxpriorityfee' />
<span title="visit https://ethgasstation.info for current gas price info.">Gwei</span>
</div>
</div>
<div class="align-items-center my-1" title="Represents the maximum amount of fee that you will pay for this transaction. The minimun needs to be set to base fee.">
<div class='d-flex'>
<span class="text-dark mr-2 text-nowrap">Max fee (Not less than base fee ${Web3.utils.fromWei(Web3.utils.toBN(parseInt(network.lastBlock.baseFeePerGas, 16)), 'Gwei')} Gwei):</span>
<input class="form-control mr-1 text-right" style='height: 1.2rem; width: 6rem;' id='maxfee' oninput=${onMaxFeeChange} />
<span>Gwei</span>
<span class="text-dark ml-2"></span>
</div>
</div>`
: yo`<div class="d-flex align-items-center my-1">
<span class="text-dark mr-2 text-nowrap">Gas price:</span>
<input class="form-control mr-1 text-right" style='width: 40px; height: 28px;' id='gasprice' oninput=${onGasPriceChange} />
<span>Gwei (visit <a target='_blank' href='https://ethgasstation.info'>ethgasstation.info</a> for current gas price info.)</span>
</div>`
}
<div class="mb-3">
<span class="text-dark mr-2">Max transaction fee:</span>
<span id='txfee'></span>
</div>
<div class="d-flex align-items-center my-1">
<span class="text-dark mr-2">Gas price:</span>
<input class="form-control mr-1" style='width: 40px; height: 28px;'id='gasprice' oninput=${onGasPriceChange} />
<span>Gwei (visit <a target='_blank' href='https://ethgasstation.info'>ethgasstation.info</a> for current gas price info.)</span>
</div>
<div class="d-flex align-items-center">
<span class="text-dark mr-2 mb-3">Data:</span>
<pre class=${css.wrapword}>${tx.data && tx.data.length > 50 ? tx.data.substring(0, 49) + '...' : tx.data} ${copyToClipboard(() => { return tx.data })}</pre>
<span class="text-warning" id='txfee'></span>
</div>
</div>
<div class="d-flex py-1 align-items-center custom-control custom-checkbox ${css.checkbox}">
@ -74,10 +120,14 @@ function confirmDialog (tx, amount, gasEstimation, self, newGasPriceCb, initialP
if (txFeeText) {
el.querySelector('#txfee').innerHTML = txFeeText
}
if (gasPriceValue) {
if (el.querySelector('#gasprice') && gasPriceValue) {
el.querySelector('#gasprice').value = gasPriceValue
onGasPriceChange()
}
if (el.querySelector('#maxfee') && network && network.lastBlock && network.lastBlock.baseFeePerGas) {
el.querySelector('#maxfee').value = Web3.utils.fromWei(Web3.utils.toBN(parseInt(network.lastBlock.baseFeePerGas, 16)), 'Gwei')
onMaxFeeChange()
}
if (gasPriceStatus !== undefined) {
el.gasPriceStatus = gasPriceStatus
}

@ -1,6 +1,6 @@
var yo = require('yo-yo')
// -------------- copyToClipboard ----------------------
const copy = require('copy-text-to-clipboard')
const copy = require('copy-to-clipboard')
var addTooltip = require('./tooltip')
// -------------- styling ----------------------
var csjs = require('csjs-inject')

@ -6,7 +6,6 @@ import JSZip from 'jszip'
const yo = require('yo-yo')
const csjs = require('csjs-inject')
const globalRegistry = require('../../../global/registry')
const CompilerImport = require('../../compiler/compiler-imports')
const modalDialogCustom = require('../modal-dialog-custom')
const modalDialog = require('../modaldialog')
const tooltip = require('../tooltip')
@ -119,11 +118,12 @@ const profile = {
}
export class LandingPage extends ViewPlugin {
constructor (appManager, verticalIcons, fileManager, filePanel) {
constructor (appManager, verticalIcons, fileManager, filePanel, contentImport) {
super(profile)
this.profile = profile
this.fileManager = fileManager
this.filePanel = filePanel
this.contentImport = contentImport
this.appManager = appManager
this.verticalIcons = verticalIcons
this.gistHandler = new GistHandler()
@ -216,7 +216,7 @@ export class LandingPage extends ViewPlugin {
const invertNum = (themeQuality === 'dark') ? 1 : 0
if (this.solEnv.getElementsByTagName('img')[0]) this.solEnv.getElementsByTagName('img')[0].style.filter = `invert(${invertNum})`
if (this.debuggerEnv.getElementsByTagName('img')[0]) this.debuggerEnv.getElementsByTagName('img')[0].style.filter = `invert(${invertNum})`
if (this.optimismEnv.getElementsByTagName('img')[0]) this.optimismEnv.getElementsByTagName('img')[0].style.filter = `invert(${invertNum})`
if (this.solhintEnv.getElementsByTagName('img')[0]) this.solhintEnv.getElementsByTagName('img')[0].style.filter = `invert(${invertNum})`
if (this.learnEthEnv.getElementsByTagName('img')[0]) this.learnEthEnv.getElementsByTagName('img')[0].style.filter = `invert(${invertNum})`
if (this.sourcifyEnv.getElementsByTagName('img')[0]) this.sourcifyEnv.getElementsByTagName('img')[0].style.filter = `invert(${invertNum})`
@ -240,7 +240,7 @@ export class LandingPage extends ViewPlugin {
render () {
const load = (service, item, examples, info) => {
const compilerImport = new CompilerImport()
const contentImport = this.contentImport
const fileProviders = globalRegistry.get('fileproviders').api
const msg = yo`
<div class="p-2">
@ -252,7 +252,7 @@ export class LandingPage extends ViewPlugin {
const title = `Import from ${service}`
modalDialogCustom.prompt(title, msg, null, (target) => {
if (target !== '') {
compilerImport.import(
contentImport.import(
target,
(loadingMsg) => { tooltip(loadingMsg) },
(error, content, cleanUrl, type, url) => {
@ -277,10 +277,10 @@ export class LandingPage extends ViewPlugin {
this.verticalIcons.select('solidity')
_paq.push(['trackEvent', 'pluginManager', 'userActivate', 'solidity'])
}
const startDebugger = async () => {
await this.appManager.activatePlugin('debugger')
this.verticalIcons.select('debugger')
_paq.push(['trackEvent', 'pluginManager', 'userActivate', 'debugger'])
const startOptimism = async () => {
await this.appManager.activatePlugin('optimism-compiler')
this.verticalIcons.select('optimism-compiler')
_paq.push(['trackEvent', 'pluginManager', 'userActivate', 'optimism-compiler'])
}
const startSolhint = async () => {
await this.appManager.activatePlugin(['solidity', 'solhint'])
@ -385,8 +385,8 @@ export class LandingPage extends ViewPlugin {
// main
this.solEnv = createLargeButton('assets/img/solidityLogo.webp', 'solidityLogo', 'Solidity', startSolidity)
// Featured
this.debuggerEnv = createLargeButton('assets/img/debuggerLogo.webp', 'debuggerLogo', 'Debugger', startDebugger)
this.solhintEnv = createLargeButton('assets/img/solhintLogo.png', 'solhintLogo', 'solhint linter', startSolhint)
this.optimismEnv = createLargeButton('assets/img/optimismLogo.webp', 'optimismLogo', 'Optimism', startOptimism)
this.solhintEnv = createLargeButton('assets/img/solhintLogo.png', 'solhintLogo', 'Solhint linter', startSolhint)
this.learnEthEnv = createLargeButton('assets/img/learnEthLogo.webp', 'learnEthLogo', 'LearnEth', startLearnEth)
this.sourcifyEnv = createLargeButton('assets/img/sourcifyLogo.webp', 'sourcifyLogo', 'Sourcify', startSourceVerify)
this.moreEnv = createLargeButton('assets/img/moreLogo.webp', 'moreLogo', 'More', startPluginManager)
@ -395,7 +395,7 @@ export class LandingPage extends ViewPlugin {
const themeQuality = globalRegistry.get('themeModule').api.currentTheme().quality
const invertNum = (themeQuality === 'dark') ? 1 : 0
this.solEnv.getElementsByTagName('img')[0].style.filter = `invert(${invertNum})`
this.debuggerEnv.getElementsByTagName('img')[0].style.filter = `invert(${invertNum})`
this.optimismEnv.getElementsByTagName('img')[0].style.filter = `invert(${invertNum})`
this.solhintEnv.getElementsByTagName('img')[0].style.filter = `invert(${invertNum})`
this.learnEthEnv.getElementsByTagName('img')[0].style.filter = `invert(${invertNum})`
this.sourcifyEnv.getElementsByTagName('img')[0].style.filter = `invert(${invertNum})`
@ -505,10 +505,10 @@ export class LandingPage extends ViewPlugin {
<h4>Featured Plugins</h4>
<div class="d-flex flex-row pt-2">
${this.solEnv}
${this.optimismEnv}
${this.learnEthEnv}
${this.solhintEnv}
${this.sourcifyEnv}
${this.debuggerEnv}
${this.sourcifyEnv}
${this.moreEnv}
</div>
</div>

@ -51,7 +51,7 @@ const confirmationCb = function (network, tx, gasEstimation, continueTxExecution
return continueTxExecution(null)
}
var amount = Web3.utils.fromWei(typeConversion.toInt(tx.value), 'ether')
var content = confirmDialog(tx, amount, gasEstimation, self.udappUI,
var content = confirmDialog(tx, network, amount, gasEstimation,
(gasPrice, cb) => {
let txFeeText, priceStatus
// TODO: this try catch feels like an anti pattern, can/should be
@ -93,10 +93,9 @@ const confirmationCb = function (network, tx, gasEstimation, continueTxExecution
)
// TODO: check if this is check is still valid given the refactor
if (!content.gasPriceStatus) {
cancelCb('Given gas price is not correct')
cancelCb('Given transaction fee is not correct')
} else {
var gasPrice = Web3.utils.toWei(content.querySelector('#gasprice').value, 'gwei')
continueTxExecution(gasPrice)
continueTxExecution(content.txFee)
}
}
},

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

@ -30,7 +30,7 @@ ace.define("ace/theme/remixDark",["require","exports","module","ace/lib/dom"], f
}\
.ace-remixDark {\
background-color: #222336;\
color: #a2a3bd;\
color: #d5d5e9;\
}\
.ace-remixDark .ace_cursor {\
border-left: 2px solid #FFFFFF;\
@ -109,10 +109,10 @@ ace.define("ace/theme/remixDark",["require","exports","module","ace/lib/dom"], f
}\
.ace-remixDark .ace_type {\
color:#75ceef;\
}]\
.ace-remixDark .ace_visibility (\
}\
.ace-remixDark .ace_visibility {\
color:#f7d777;\
)\
}\
.ace-remixDark .ace_identifier {\
color:#bec1dd;\
}\

File diff suppressed because one or more lines are too long

@ -20,9 +20,10 @@ export class ExecutionContext {
constructor () {
this.event = new EventManager()
this.executionContext = null
this.lastBlock = null
this.blockGasLimitDefault = 4300000
this.blockGasLimit = this.blockGasLimitDefault
this.currentFork = 'berlin'
this.currentFork = 'london'
this.mainNetGenesisHash = '0xd4e56740f876aef8c010b86a40d5f56745a118d0906a34e69aec8c0db1cb8fa3'
this.customNetWorks = {}
this.blocks = {}
@ -86,10 +87,10 @@ export class ExecutionContext {
web3.eth.getBlock(0, (error, block) => {
if (error) console.log('cant query first block')
if (block && block.hash !== this.mainNetGenesisHash) name = 'Custom'
callback(err, { id, name })
callback(err, { id, name, lastBlock: this.lastBlock, currentFork: this.currentFork })
})
} else {
callback(err, { id, name })
callback(err, { id, name, lastBlock: this.lastBlock, currentFork: this.currentFork })
}
})
}
@ -176,10 +177,11 @@ export class ExecutionContext {
const block = await web3.eth.getBlock('latest')
// we can't use the blockGasLimit cause the next blocks could have a lower limit : https://github.com/ethereum/remix/issues/506
this.blockGasLimit = (block && block.gasLimit) ? Math.floor(block.gasLimit - (5 * block.gasLimit) / 1024) : this.blockGasLimitDefault
this.lastBlock = block
try {
this.currentFork = execution.forkAt(await web3.eth.net.getId(), block.number)
} catch (e) {
this.currentFork = 'berlin'
this.currentFork = 'london'
console.log(`unable to detect fork, defaulting to ${this.currentFork}..`)
console.error(e)
}

@ -30,6 +30,7 @@
<title>Remix - Ethereum IDE</title>
<link rel="stylesheet" href="assets/css/pygment_trac.css">
<link rel="icon" type="x-icon" href="assets/img/icon.png">
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/intro.js/4.1.0/introjs.min.css">
<script src="assets/js/browserfs.min.js"></script>
<meta name="viewport" content="width=device-width, initial-scale=1, user-scalable=no">
<!-- Matomo -->
@ -56,6 +57,7 @@
}
</script>
<!-- End Matomo Code -->
<link rel="stylesheet" type="text/css" href="https://cdnjs.cloudflare.com/ajax/libs/intro.js/2.7.0/introjs.min.css">
</head>
<body>
<script>
@ -112,7 +114,10 @@
})
}
</script>
<script src="runtime.js" type="module"></script><script src="polyfills.js" type="module"></script><script src="vendor.js" type="module"></script>
<script src="runtime.js" type="module"></script>
<script src="polyfills.js" type="module"></script>
<script src="vendor.js" type="module"></script>
<script src="https://kit.fontawesome.com/41dd021e94.js" crossorigin="anonymous"></script>
<script type="text/javascript" src="assets/js/intro.min.js"></script>
</body>
</html>

@ -1,9 +1,9 @@
'use strict'
import { CompilerImports } from '@remix-project/core-plugin'
var yo = require('yo-yo')
var async = require('async')
var EventManager = require('../lib/events')
var CompilerImport = require('../app/compiler/compiler-imports')
var toolTip = require('../app/ui/tooltip')
var globalRegistry = require('../global/registry')
var SourceHighlighter = require('../app/editor/sourceHighlighter')
@ -18,7 +18,7 @@ class CmdInterpreterAPI {
self._components.registry = localRegistry || globalRegistry
self._components.terminal = terminal
self._components.sourceHighlighter = new SourceHighlighter()
self._components.fileImport = new CompilerImport()
self._components.fileImport = new CompilerImports()
self._components.gistHandler = new GistHandler()
self._deps = {
fileManager: self._components.registry.get('filemanager').api,

@ -4,7 +4,7 @@ const async = require('async')
const IpfsClient = require('ipfs-mini')
const ipfsNodes = [
new IpfsClient({ host: 'ipfs.komputing.org', port: 443, protocol: 'https' }),
new IpfsClient({ host: 'ipfs.remixproject.org', port: 443, protocol: 'https' }),
new IpfsClient({ host: 'ipfs.infura.io', port: 5001, protocol: 'https' }),
new IpfsClient({ host: '127.0.0.1', port: 5001, protocol: 'http' })
]

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

@ -0,0 +1,54 @@
const introJs = require('intro.js')
export class WalkthroughService {
constructor (params) {
this.params = params
}
start (params) {
if (!localStorage.getItem('hadTour_initial')) {
introJs().setOptions({
steps: [{
title: 'Welcome to Remix IDE',
intro: 'Click to launch the Home tab that contains links, tips, and shortcuts..',
element: document.querySelector('#verticalIconsHomeIcon'),
tooltipClass: 'bg-light text-dark',
position: 'right'
},
{
element: document.querySelector('#compileIcons'),
title: 'Solidity Compiler',
intro: 'Having selected a .sol file in the File Explorers (the icon above), compile it with the Solidity Compiler.',
tooltipClass: 'bg-light text-dark',
position: 'right'
},
{
title: 'Deploy your contract',
element: document.querySelector('#runIcons'),
intro: 'Choose a chain, deploy a contract and play with your functions.',
tooltipClass: 'bg-light text-dark',
position: 'right'
}
]
}).onafterchange((targetElement) => {
const header = document.getElementsByClassName('introjs-tooltip-header')[0]
if (header) {
header.classList.add('d-flex')
header.classList.add('justify-content-between')
header.classList.add('text-nowrap')
header.classList.add('pr-0')
}
const skipbutton = document.getElementsByClassName('introjs-skipbutton')[0]
if (skipbutton) {
skipbutton.classList.add('ml-3')
skipbutton.classList.add('text-decoration-none')
skipbutton.id = 'remixTourSkipbtn'
}
}).start()
localStorage.setItem('hadTour_initial', true)
}
}
startFeatureTour () {
}
}

@ -1,6 +1,6 @@
{
"name": "@remix-project/remix-analyzer",
"version": "0.5.11",
"version": "0.5.14",
"description": "Tool to perform static analysis on Solidity smart contracts",
"main": "index.js",
"types": ".index.d.ts",
@ -19,14 +19,15 @@
}
],
"dependencies": {
"@ethereumjs/block": "^3.3.0",
"@ethereumjs/tx": "^3.2.1",
"@ethereumjs/vm": "^5.4.1",
"@ethereumjs/block": "^3.4.0",
"@ethereumjs/tx": "^3.3.0",
"@ethereumjs/vm": "^5.5.0",
"@remix-project/remix-astwalker": "^0.0.26",
"@remix-project/remix-lib": "^0.5.2",
"@remix-project/remix-lib": "^0.5.5",
"async": "^2.6.2",
"ethereumjs-util": "^7.0.10",
"ethers": "^5.1.4",
"ethers": "^5.4.2",
"string-similarity": "^4.0.4",
"web3": "1.2.4"
},
"publishConfig": {
@ -50,5 +51,5 @@
"typescript": "^3.7.5"
},
"typings": "index.d.ts",
"gitHead": "50b32cc20d2d4dcc793bf7de955957e073e5b5b8"
"gitHead": "df7abe1c219e361a947031d620f4ae6e3786a4d7"
}

@ -1,6 +1,6 @@
{
"name": "@remix-project/remix-astwalker",
"version": "0.0.32",
"version": "0.0.35",
"description": "Tool to walk through Solidity AST",
"main": "index.js",
"scripts": {
@ -34,15 +34,16 @@
]
},
"dependencies": {
"@ethereumjs/block": "^3.3.0",
"@ethereumjs/tx": "^3.2.1",
"@ethereumjs/vm": "^5.4.1",
"@remix-project/remix-lib": "^0.5.2",
"@ethereumjs/block": "^3.4.0",
"@ethereumjs/tx": "^3.3.0",
"@ethereumjs/vm": "^5.5.0",
"@remix-project/remix-lib": "^0.5.5",
"@types/tape": "^4.2.33",
"async": "^2.6.2",
"ethereumjs-util": "^7.0.10",
"ethers": "^5.1.4",
"ethers": "^5.4.2",
"nyc": "^13.3.0",
"string-similarity": "^4.0.4",
"tape": "^4.10.1",
"ts-node": "^8.0.3",
"typescript": "^3.4.3",
@ -52,5 +53,5 @@
"tap-spec": "^5.0.0"
},
"typings": "index.d.ts",
"gitHead": "50b32cc20d2d4dcc793bf7de955957e073e5b5b8"
"gitHead": "df7abe1c219e361a947031d620f4ae6e3786a4d7"
}

@ -0,0 +1 @@
{ "extends": "../../.eslintrc", "rules": {}, "ignorePatterns": ["!**/*"] }

@ -0,0 +1,3 @@
# remix-core-plugin-core-plugin
This library was generated with [Nx](https://nx.dev).

@ -0,0 +1,12 @@
{
"name": "@remix-project/core-plugin",
"version": "0.0.1",
"description": "This library was generated with [Nx](https://nx.dev).",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"author": "Remix Team",
"license": "ISC"
}

@ -0,0 +1,5 @@
export { OffsetToLineColumnConverter } from './lib/offset-line-to-column-converter'
export { CompilerMetadata } from './lib/compiler-metadata'
export { FetchAndCompile } from './lib/compiler-fetch-and-compile'
export { CompilerImports } from './lib/compiler-content-imports'
export { CompilerArtefacts } from './lib/compiler-artefacts'

@ -1,16 +1,17 @@
'use strict'
import { Plugin } from '@remixproject/engine'
import * as packageJson from '../../../../../package.json'
import CompilerAbstract from './compiler-abstract'
import { CompilerAbstract } from '@remix-project/remix-solidity'
const profile = {
name: 'compilerArtefacts',
methods: [],
methods: ['get', 'addResolvedContract'],
events: [],
version: packageJson.version
version: '0.0.1'
}
module.exports = class CompilerArtefacts extends Plugin {
export class CompilerArtefacts extends Plugin {
compilersArtefactsPerFile: any
compilersArtefacts: any
constructor () {
super(profile)
this.compilersArtefacts = {}

@ -0,0 +1,180 @@
'use strict'
import { Plugin } from '@remixproject/engine'
import { RemixURLResolver } from '@remix-project/remix-url-resolver'
const remixTests = require('@remix-project/remix-tests')
const async = require('async')
const profile = {
name: 'contentImport',
displayName: 'content import',
version: '0.0.1',
methods: ['resolve', 'resolveAndSave', 'isExternalUrl']
}
export class CompilerImports extends Plugin {
previouslyHandled: {}
urlResolver: any
constructor () {
super(profile)
this.urlResolver = new RemixURLResolver()
this.previouslyHandled = {} // cache import so we don't make the request at each compilation.
}
async setToken () {
try {
const protocol = typeof window !== 'undefined' && window.location.protocol
const token = await this.call('settings', 'get', 'settings/gist-access-token')
this.urlResolver.setGistToken(token, protocol)
} catch (error) {
console.log(error)
}
}
isRelativeImport (url) {
return /^([^/]+)/.exec(url)
}
isExternalUrl (url) {
const handlers = this.urlResolver.getHandlers()
return handlers.some(handler => handler.match(url))
}
/**
* resolve the content of @arg url. This only resolves external URLs.
*
* @param {String} url - external URL of the content. can be basically anything like raw HTTP, ipfs URL, github address etc...
* @returns {Promise} - { content, cleanUrl, type, url }
*/
resolve (url) {
return new Promise((resolve, reject) => {
this.import(url, null, (error, content, cleanUrl, type, url) => {
if (error) return reject(error)
resolve({ content, cleanUrl, type, url })
}, null)
})
}
async import (url, force, loadingCb, cb) {
if (typeof force !== 'boolean') {
const temp = loadingCb
loadingCb = force
cb = temp
force = false
}
if (!loadingCb) loadingCb = () => {}
if (!cb) cb = () => {}
var self = this
if (force) delete this.previouslyHandled[url]
var imported = this.previouslyHandled[url]
if (imported) {
return cb(null, imported.content, imported.cleanUrl, imported.type, url)
}
let resolved
try {
await this.setToken()
resolved = await this.urlResolver.resolve(url)
const { content, cleanUrl, type } = resolved
self.previouslyHandled[url] = {
content,
cleanUrl,
type
}
cb(null, content, cleanUrl, type, url)
} catch (e) {
return cb(new Error('not found ' + url))
}
}
importExternal (url, targetPath, cb) {
this.import(url,
// TODO: handle this event
(loadingMsg) => { this.emit('message', loadingMsg) },
async (error, content, cleanUrl, type, url) => {
if (error) return cb(error)
try {
const provider = await this.call('fileManager', 'getProviderOf', null)
const path = targetPath || type + '/' + cleanUrl
if (provider) provider.addExternal('.deps/' + path, content, url)
} catch (err) {
}
cb(null, content)
}, null)
}
/**
* import the content of @arg url.
* first look in the browser localstorage (browser explorer) or locahost explorer. if the url start with `browser/*` or `localhost/*`
* then check if the @arg url is located in the localhost, in the node_modules or installed_contracts folder
* then check if the @arg url match any external url
*
* @param {String} url - URL of the content. can be basically anything like file located in the browser explorer, in the localhost explorer, raw HTTP, github address etc...
* @param {String} targetPath - (optional) internal path where the content should be saved to
* @returns {Promise} - string content
*/
resolveAndSave (url, targetPath) {
return new Promise((resolve, reject) => {
if (url.indexOf('remix_tests.sol') !== -1) resolve(remixTests.assertLibCode)
this.call('fileManager', 'getProviderOf', url).then((provider) => {
if (provider) {
if (provider.type === 'localhost' && !provider.isConnected()) {
return reject(new Error(`file provider ${provider.type} not available while trying to resolve ${url}`))
}
provider.exists(url).then(exist => {
/*
if the path is absolute and the file does not exist, we can stop here
Doesn't make sense to try to resolve "localhost/node_modules/localhost/node_modules/<path>" and we'll end in an infinite loop.
*/
if (!exist && url.startsWith('browser/')) return reject(new Error(`not found ${url}`))
if (!exist && url.startsWith('localhost/')) return reject(new Error(`not found ${url}`))
if (exist) {
return provider.get(url, (error, content) => {
if (error) return reject(error)
resolve(content)
})
}
// try to resolve localhost modules (aka truffle imports) - e.g from the node_modules folder
this.call('fileManager', 'getProviderByName', 'localhost').then((localhostProvider) => {
if (localhostProvider.isConnected()) {
var splitted = /([^/]+)\/(.*)$/g.exec(url)
return async.tryEach([
(cb) => { this.resolveAndSave('localhost/installed_contracts/' + url, null).then((result) => cb(null, result)).catch((error) => cb(error.message)) },
// eslint-disable-next-line standard/no-callback-literal
(cb) => { if (!splitted) { cb('URL not parseable: ' + url) } else { this.resolveAndSave('localhost/installed_contracts/' + splitted[1] + '/contracts/' + splitted[2], null).then((result) => cb(null, result)).catch((error) => cb(error.message)) } },
(cb) => { this.resolveAndSave('localhost/node_modules/' + url, null).then((result) => cb(null, result)).catch((error) => cb(error.message)) },
// eslint-disable-next-line standard/no-callback-literal
(cb) => { if (!splitted) { cb('URL not parseable: ' + url) } else { this.resolveAndSave('localhost/node_modules/' + splitted[1] + '/contracts/' + splitted[2], null).then((result) => cb(null, result)).catch((error) => cb(error.message)) } }],
(error, result) => {
if (error) {
return this.importExternal(url, targetPath, (error, content) => {
if (error) return reject(error)
resolve(content)
})
}
resolve(result)
})
}
this.importExternal(url, targetPath, (error, content) => {
if (error) return reject(error)
resolve(content)
})
})
}).catch(error => {
return reject(error)
})
}
}).catch(() => {
// fallback to just resolving the file, it won't be saved in file manager
return this.importExternal(url, targetPath, (error, content) => {
if (error) return reject(error)
resolve(content)
})
})
})
}
}

@ -1,18 +1,19 @@
import * as packageJson from '../../../../../package.json'
import { Plugin } from '@remixproject/engine'
import { compile } from './compiler-helpers'
import globalRegistry from '../../global/registry'
import { compile } from '@remix-project/remix-solidity'
import { util } from '@remix-project/remix-lib'
import remixLib from '@remix-project/remix-lib'
const ethutil = require('ethereumjs-util')
const profile = {
name: 'fetchAndCompile',
methods: ['resolve'],
version: packageJson.version
version: '0.0.1'
}
export default class FetchAndCompile extends Plugin {
export class FetchAndCompile extends Plugin {
unresolvedAddresses: any[]
sourceVerifierNetWork: string[]
constructor () {
super(profile)
this.unresolvedAddresses = []
@ -32,11 +33,10 @@ export default class FetchAndCompile extends Plugin {
*/
async resolve (contractAddress, codeAtAddress, targetPath) {
contractAddress = ethutil.toChecksumAddress(contractAddress)
const compilersartefacts = globalRegistry.get('compilersartefacts').api
const localCompilation = () => compilersartefacts.get(contractAddress) ? compilersartefacts.get(contractAddress) : compilersartefacts.get('__last') ? compilersartefacts.get('__last') : null
const localCompilation = async () => await this.call('compilerArtefacts', 'get', contractAddress) ? await this.call('compilerArtefacts', 'get', contractAddress) : await this.call('compilerArtefacts', 'get', '__last') ? await this.call('compilerArtefacts', 'get', '__last') : null
const resolved = compilersartefacts.get(contractAddress)
const resolved = await this.call('compilerArtefacts', 'get', contractAddress)
if (resolved) return resolved
if (this.unresolvedAddresses.includes(contractAddress)) return localCompilation()
@ -53,15 +53,15 @@ export default class FetchAndCompile extends Plugin {
if (!this.sourceVerifierNetWork.includes(network.name)) return localCompilation()
// check if the contract if part of the local compilation result
const compilation = localCompilation()
const compilation = await localCompilation()
if (compilation) {
let found = false
compilation.visitContracts((contract) => {
found = remixLib.util.compareByteCode('0x' + contract.object.evm.deployedBytecode.object, codeAtAddress)
found = util.compareByteCode('0x' + contract.object.evm.deployedBytecode.object, codeAtAddress)
return found
})
if (found) {
compilersartefacts.addResolvedContract(contractAddress, compilation)
await this.call('compilerArtefacts', 'addResolvedContract', contractAddress, compilation)
setTimeout(_ => this.emit('usingLocalCompilation', contractAddress), 0)
return compilation
}
@ -118,8 +118,8 @@ export default class FetchAndCompile extends Plugin {
const compData = await compile(
compilationTargets,
settings,
(url, cb) => this.call('contentImport', 'resolveAndSave', url).then((result) => cb(null, result)).catch((error) => cb(error.message)))
compilersartefacts.addResolvedContract(contractAddress, compData)
async (url, cb) => await this.call('contentImport', 'resolveAndSave', url).then((result) => cb(null, result)).catch((error) => cb(error.message)))
await this.call('compilerArtefacts', 'addResolvedContract', contractAddress, compData)
return compData
} catch (e) {
this.unresolvedAddresses.push(contractAddress)

@ -0,0 +1,145 @@
'use strict'
import { Plugin } from '@remixproject/engine'
import { CompilerAbstract } from '@remix-project/remix-solidity'
const profile = {
name: 'compilerMetadata',
methods: ['deployMetadataOf'],
events: [],
version: '0.0.1'
}
export class CompilerMetadata extends Plugin {
networks: string[]
innerPath: string
constructor () {
super(profile)
this.networks = ['VM:-', 'main:1', 'ropsten:3', 'rinkeby:4', 'kovan:42', 'görli:5', 'Custom']
this.innerPath = 'artifacts'
}
_JSONFileName (path, contractName) {
return this.joinPath(path, this.innerPath, contractName + '.json')
}
_MetadataFileName (path, contractName) {
return this.joinPath(path, this.innerPath, contractName + '_metadata.json')
}
onActivation () {
var self = this
this.on('solidity', 'compilationFinished', async (file, source, languageVersion, data) => {
if (!await this.call('settings', 'get', 'settings/generate-contract-metadata')) return
const compiler = new CompilerAbstract(languageVersion, data, source)
var path = self._extractPathOf(source.target)
compiler.visitContracts((contract) => {
if (contract.file !== source.target) return
(async () => {
const fileName = self._JSONFileName(path, contract.name)
const content = await this.call('fileManager', 'exists', fileName) ? await this.call('fileManager', 'readFile', fileName) : null
await this._setArtefacts(content, contract, path)
})()
})
})
}
_extractPathOf (file) {
var reg = /(.*)(\/).*/
var path = reg.exec(file)
return path ? path[1] : '/'
}
async _setArtefacts (content, contract, path) {
content = content || '{}'
var metadata
try {
metadata = JSON.parse(content)
} catch (e) {
console.log(e)
}
var fileName = this._JSONFileName(path, contract.name)
var metadataFileName = this._MetadataFileName(path, contract.name)
var deploy = metadata.deploy || {}
this.networks.forEach((network) => {
deploy[network] = this._syncContext(contract, deploy[network] || {})
})
let parsedMetadata
try {
parsedMetadata = JSON.parse(contract.object.metadata)
} catch (e) {
console.log(e)
}
if (parsedMetadata) await this.call('fileManager', 'writeFile', metadataFileName, JSON.stringify(parsedMetadata, null, '\t'))
var data = {
deploy,
data: {
bytecode: contract.object.evm.bytecode,
deployedBytecode: contract.object.evm.deployedBytecode,
gasEstimates: contract.object.evm.gasEstimates,
methodIdentifiers: contract.object.evm.methodIdentifiers
},
abi: contract.object.abi
}
await this.call('fileManager', 'writeFile', fileName, JSON.stringify(data, null, '\t'))
}
_syncContext (contract, metadata) {
var linkReferences = metadata.linkReferences
var autoDeployLib = metadata.autoDeployLib
if (!linkReferences) linkReferences = {}
if (autoDeployLib === undefined) autoDeployLib = true
for (var libFile in contract.object.evm.bytecode.linkReferences) {
if (!linkReferences[libFile]) linkReferences[libFile] = {}
for (var lib in contract.object.evm.bytecode.linkReferences[libFile]) {
if (!linkReferences[libFile][lib]) {
linkReferences[libFile][lib] = '<address>'
}
}
}
metadata.linkReferences = linkReferences
metadata.autoDeployLib = autoDeployLib
return metadata
}
async deployMetadataOf (contractName, fileLocation) {
let path
if (fileLocation) {
path = fileLocation.split('/')
path.pop()
path = path.join('/')
} else {
try {
path = this._extractPathOf(await this.call('fileManager', 'getCurrentFile'))
} catch (err) {
console.log(err)
throw new Error(err)
}
}
try {
const { id, name } = await this.call('network', 'detectNetwork')
const fileName = this._JSONFileName(path, contractName)
try {
const content = await this.call('fileManager', 'readFile', fileName)
if (!content) return null
let metadata = JSON.parse(content)
metadata = metadata.deploy || {}
return metadata[name + ':' + id] || metadata[name] || metadata[id] || metadata[name.toLowerCase() + ':' + id] || metadata[name.toLowerCase()]
} catch (err) {
return null
}
} catch (err) {
console.log(err)
throw new Error(err)
}
}
joinPath (...paths) {
paths = paths.filter((value) => value !== '').map((path) => path.replace(/^\/|\/$/g, '')) // remove first and last slash)
if (paths.length === 1) return paths[0]
return paths.join('/')
}
}

@ -0,0 +1,77 @@
'use strict'
import { Plugin } from '@remixproject/engine'
import { sourceMappingDecoder } from '@remix-project/remix-debug'
const profile = {
name: 'offsetToLineColumnConverter',
methods: ['offsetToLineColumn'],
events: [],
version: '0.0.1'
}
export class OffsetToLineColumnConverter extends Plugin {
lineBreakPositionsByContent: {}
sourceMappingDecoder: any
constructor () {
super(profile)
this.lineBreakPositionsByContent = {}
this.sourceMappingDecoder = sourceMappingDecoder
}
/**
* Convert offset representation with line/column representation.
* This is also used to resolve the content:
* @arg file is the index of the file in the content sources array and content sources array does have filename as key and not index.
* So we use the asts (which references both index and filename) to look up the actual content targeted by the @arg file index.
* @param {{start, length}} rawLocation - offset location
* @param {number} file - The index where to find the source in the sources parameters
* @param {Object.<string, {content}>} sources - Map of content sources
* @param {Object.<string, {ast, id}>} asts - Map of content sources
*/
offsetToLineColumn (rawLocation, file, sources, asts) {
if (!this.lineBreakPositionsByContent[file]) {
const sourcesArray = Object.keys(sources)
if (!asts || (file === 0 && sourcesArray.length === 1)) {
// if we don't have ast, we process the only one available content (applicable also for compiler older than 0.4.12)
this.lineBreakPositionsByContent[file] = this.sourceMappingDecoder.getLinebreakPositions(sources[sourcesArray[0]].content)
} else {
for (var filename in asts) {
const source = asts[filename]
if (source.id === file) {
this.lineBreakPositionsByContent[file] = this.sourceMappingDecoder.getLinebreakPositions(sources[filename].content)
break
}
}
}
}
return this.sourceMappingDecoder.convertOffsetToLineColumn(rawLocation, this.lineBreakPositionsByContent[file])
}
/**
* Convert offset representation with line/column representation.
* @param {{start, length}} rawLocation - offset location
* @param {number} file - The index where to find the source in the sources parameters
* @param {string} content - source
*/
offsetToLineColumnWithContent (rawLocation, file, content) {
this.lineBreakPositionsByContent[file] = this.sourceMappingDecoder.getLinebreakPositions(content)
return this.sourceMappingDecoder.convertOffsetToLineColumn(rawLocation, this.lineBreakPositionsByContent[file])
}
/**
* Clear the cache
*/
clear () {
this.lineBreakPositionsByContent = {}
}
/**
* called by plugin API
*/
activate () {
this.on('solidity', 'compilationFinished', () => {
this.clear()
})
}
}

@ -0,0 +1,10 @@
{
"extends": "../../tsconfig.json",
"files": [],
"include": [],
"references": [
{
"path": "./tsconfig.lib.json"
}
]
}

@ -0,0 +1,12 @@
{
"extends": "./tsconfig.json",
"compilerOptions": {
"module": "commonjs",
"outDir": "../../dist/out-tsc",
"declaration": true,
"rootDir": "./src",
"types": ["node"]
},
"exclude": ["**/*.spec.ts"],
"include": ["**/*.ts"]
}

@ -1,6 +1,6 @@
{
"name": "@remix-project/remix-debug",
"version": "0.5.2",
"version": "0.5.5",
"description": "Tool to debug Ethereum transactions",
"contributors": [
{
@ -18,17 +18,18 @@
],
"main": "src/index.js",
"dependencies": {
"@ethereumjs/block": "^3.3.0",
"@ethereumjs/block": "^3.4.0",
"@ethereumjs/common": "^2.2.0",
"@ethereumjs/tx": "^3.2.1",
"@ethereumjs/vm": "^5.4.1",
"@ethereumjs/tx": "^3.3.0",
"@ethereumjs/vm": "^5.5.0",
"@remix-project/remix-astwalker": "^0.0.26",
"@remix-project/remix-lib": "^0.5.2",
"@remix-project/remix-lib": "^0.5.5",
"async": "^2.6.2",
"commander": "^2.19.0",
"deep-equal": "^1.0.1",
"ethereumjs-util": "^7.0.10",
"ethers": "^5.1.4",
"ethers": "^5.4.2",
"string-similarity": "^4.0.4",
"web3": "^1.2.4"
},
"devDependencies": {
@ -60,5 +61,5 @@
},
"homepage": "https://github.com/ethereum/remix-project/tree/master/libs/remix-debug#readme",
"typings": "src/index.d.ts",
"gitHead": "50b32cc20d2d4dcc793bf7de955957e073e5b5b8"
"gitHead": "df7abe1c219e361a947031d620f4ae6e3786a4d7"
}

@ -147,13 +147,17 @@ export class CodeManager {
private async retrieveIndexAndTrigger (codeMananger, address, step, code) {
let result
let next
const next = []
const returnInstructionIndexes = []
const outOfGasInstructionIndexes = []
try {
result = codeMananger.getInstructionIndex(address, step)
next = codeMananger.getInstructionIndex(address, step + 1)
for (let i = 1; i < 6; i++) {
if (this.traceManager.inRange(step + i)) {
next.push(codeMananger.getInstructionIndex(address, step + i))
}
}
let values = this.traceManager.getAllStopIndexes()
if (values) {

@ -46,7 +46,7 @@ type Opcode = {
* information about the opcode.
*/
export function parseCode (raw) {
const common = new Common({ chain: 'mainnet', hardfork: 'berlin' })
const common = new Common({ chain: 'mainnet', hardfork: 'london' })
const opcodes = getOpcodesForHF(common)
const code = []

@ -59,8 +59,8 @@ export class VmDebuggerLogic {
}
listenToCodeManagerEvents () {
this._codeManager.event.register('changed', (code, address, index, nextIndex, returnInstructionIndexes, outOfGasInstructionIndexes) => {
this.event.trigger('codeManagerChanged', [code, address, index, nextIndex, returnInstructionIndexes, outOfGasInstructionIndexes])
this._codeManager.event.register('changed', (code, address, index, nextIndexes, returnInstructionIndexes, outOfGasInstructionIndexes) => {
this.event.trigger('codeManagerChanged', [code, address, index, nextIndexes, returnInstructionIndexes, outOfGasInstructionIndexes])
})
}

@ -67,7 +67,7 @@ export class EventManager {
}
for (const listener in this.registered[eventName]) {
const l = this.registered[eventName][listener]
l.func.apply(l.obj === this.anonymous ? {} : l.obj, args)
if (l.func) l.func.apply(l.obj === this.anonymous ? {} : l.obj, args)
}
}
}

@ -48,7 +48,7 @@ export async function extractHexValue (location, storageResolver, byteLength) {
try {
slotvalue = await readFromStorage(location.slot, storageResolver)
} catch (e) {
return '0x'
return ''
}
return extractHexByteSlice(slotvalue, byteLength, location.offset)
}

@ -40,7 +40,7 @@ export class TraceManager {
const networkId = await this.web3.eth.net.getId()
this.fork = execution.forkAt(networkId, tx.blockNumber)
} catch (e) {
this.fork = 'berlin'
this.fork = 'london'
console.log(`unable to detect fork, defaulting to ${this.fork}..`)
console.error(e)
}
@ -151,7 +151,7 @@ export class TraceManager {
if (this.trace[stepIndex] && this.trace[stepIndex].stack) { // there's always a stack
const stack = this.trace[stepIndex].stack.slice(0)
stack.reverse()
return stack
return stack.map(el => el.startsWith('0x') ? el : '0x' + el)
} else {
throw new Error('no stack found')
}

@ -11,7 +11,7 @@ export function sendTx (vm, from, to, value, data, cb?) {
return new Promise ((resolve, reject) => {
var tx = new Tx({
nonce: new BN(from.nonce++),
gasPrice: new BN(1),
// gasPrice: new BN(1),
gasLimit: new BN(3000000, 10),
to: to,
value: new BN(value, 10),

@ -9,7 +9,7 @@ var remixLib = require('@remix-project/remix-lib')
function sendTx (vm, from, to, value, data, cb) {
var tx = new Tx({
nonce: new BN(from.nonce++),
gasPrice: new BN(1),
// gasPrice: new BN(1),
gasLimit: new BN(3000000, 10),
to: to,
value: new BN(value, 10),

@ -1,6 +1,6 @@
{
"name": "@remix-project/remix-lib",
"version": "0.5.2",
"version": "0.5.5",
"description": "Library to various Remix tools",
"contributors": [
{
@ -14,14 +14,15 @@
],
"main": "src/index.js",
"dependencies": {
"@ethereumjs/block": "^3.3.0",
"@ethereumjs/tx": "^3.2.1",
"@ethereumjs/vm": "^5.4.1",
"@ethereumjs/block": "^3.4.0",
"@ethereumjs/tx": "^3.3.0",
"@ethereumjs/vm": "^5.5.0",
"async": "^2.1.2",
"ethereumjs-util": "^7.0.10",
"ethers": "^4.0.40",
"events": "^3.0.0",
"solc": "^0.7.4",
"string-similarity": "^4.0.4",
"web3": "^1.2.4"
},
"devDependencies": {
@ -52,5 +53,5 @@
},
"homepage": "https://github.com/ethereum/remix-project/tree/master/libs/remix-lib#readme",
"typings": "src/index.d.ts",
"gitHead": "50b32cc20d2d4dcc793bf7de955957e073e5b5b8"
"gitHead": "df7abe1c219e361a947031d620f4ae6e3786a4d7"
}

@ -64,7 +64,7 @@ export class EventManager {
}
for (const listener in this.registered[eventName]) {
const l = this.registered[eventName][listener]
l.func.apply(l.obj === this.anonymous ? {} : l.obj, args)
if (l.func) l.func.apply(l.obj === this.anonymous ? {} : l.obj, args)
}
}
}

@ -17,7 +17,7 @@ export function forkAt (networkId, blockNumber) {
}
return currentForkName
}
return 'berlin'
return 'london'
}
// see https://github.com/ethereum/go-ethereum/blob/master/params/config.go
@ -46,6 +46,10 @@ const forks = {
{
number: 12244000,
name: 'berlin'
},
{
number: 12965000,
name: 'london'
}
],
3: [

@ -112,18 +112,32 @@ export function checkVMError (execResult, abi, contract) {
// "contract" reprensents the compilation result containing the NATSPEC documentation
if (contract && fn.functions && Object.keys(fn.functions).length) {
const functionSignature = Object.keys(fn.functions)[0]
// we check in the 'devdoc' if there's a developer documentation for this error
devdoc = contract.object.devdoc.errors[functionSignature][0] || {}
// we check in the 'userdoc' if there's an user documentation for this error
const userdoc = contract.object.userdoc.errors[functionSignature][0] || {}
if (userdoc) customError += ' : ' + (userdoc as any).notice // we append the user doc if any
// we check in the 'devdoc' if there's a developer documentation for this error
try {
devdoc = (contract.object.devdoc.errors && contract.object.devdoc.errors[functionSignature][0]) || {}
} catch (e) {
console.error(e.message)
}
// we check in the 'userdoc' if there's an user documentation for this error
try {
const userdoc = (contract.object.userdoc.errors && contract.object.userdoc.errors[functionSignature][0]) || {}
if (userdoc && (userdoc as any).notice) customError += ' : ' + (userdoc as any).notice // we append the user doc if any
} catch (e) {
console.error(e.message)
}
}
let inputIndex = 0
for (const input of functionDesc.inputs) {
const v = decodedCustomErrorInputs[input.name]
decodedCustomErrorInputsClean[input.name] = {
value: v.toString ? v.toString() : v,
documentation: (devdoc as any).params[input.name] // we add the developer documentation for this input parameter if any
const inputKey = input.name || inputIndex
const v = decodedCustomErrorInputs[inputKey]
decodedCustomErrorInputsClean[inputKey] = {
value: v.toString ? v.toString() : v
}
if (devdoc && (devdoc as any).params) {
decodedCustomErrorInputsClean[input.name].documentation = (devdoc as any).params[inputKey] // we add the developer documentation for this input parameter if any
}
inputIndex++
}
break
}

@ -72,7 +72,7 @@ export class TxRunnerVM {
}
}
const EIP1559 = this.commonContext.hardfork() !== 'berlin'
const EIP1559 = this.commonContext.hardfork() !== 'berlin' // berlin is the only pre eip1559 fork that we handle.
let tx
if (!EIP1559) {
tx = Transaction.fromTxData({

@ -15,8 +15,18 @@ export class TxRunnerWeb3 {
this._api = api
}
_executeTx (tx, gasPrice, api, promptCb, callback) {
if (gasPrice) tx.gasPrice = this.getWeb3().utils.toHex(gasPrice)
_executeTx (tx, txFee, api, promptCb, callback) {
if (txFee) {
if (txFee.baseFeePerGas) {
tx.maxPriorityFee = this.getWeb3().utils.toHex(this.getWeb3().utils.toWei(txFee.maxPriorityFee, 'gwei'))
tx.maxFee = this.getWeb3().utils.toHex(this.getWeb3().utils.toWei(txFee.maxFee, 'gwei'))
tx.type = 2
} else {
tx.gasPrice = this.getWeb3().utils.toHex(this.getWeb3().utils.toWei(txFee.gasPrice, 'gwei'))
tx.type = 1
}
}
if (api.personalMode()) {
promptCb(
(value) => {
@ -100,8 +110,8 @@ export class TxRunnerWeb3 {
return
}
confirmCb(network, tx, tx['gas'], (gasPrice) => {
return this._executeTx(tx, gasPrice, this._api, promptCb, callback)
confirmCb(network, tx, tx['gas'], (txFee) => {
return this._executeTx(tx, txFee, this._api, promptCb, callback)
}, (error) => {
callback(error)
})

@ -12,23 +12,6 @@ export function extendWeb3 (web3) {
this.extend(web3)
}
export function setProvider (web3, url) {
web3.setProvider(new web3.providers.HttpProvider(url))
}
export function web3DebugNode (network) {
const web3DebugNodes = {
Main: 'https://gethmainnet.komputing.org',
Rinkeby: 'https://remix-rinkeby.ethdevops.io',
Ropsten: 'https://remix-ropsten.ethdevops.io',
Goerli: 'https://remix-goerli.ethdevops.io'
}
if (web3DebugNodes[network]) {
return this.loadWeb3(web3DebugNodes[network])
}
return null
}
export function extend (web3) {
if (!web3.extend) {
return

@ -1,6 +1,6 @@
{
"name": "@remix-project/remix-simulator",
"version": "0.2.2",
"version": "0.2.5",
"description": "Ethereum IDE and tools for the web",
"contributors": [
{
@ -14,11 +14,11 @@
],
"main": "src/index.js",
"dependencies": {
"@ethereumjs/block": "^3.3.0",
"@ethereumjs/block": "^3.4.0",
"@ethereumjs/common": "^2.2.0",
"@ethereumjs/tx": "^3.2.1",
"@ethereumjs/vm": "^5.4.1",
"@remix-project/remix-lib": "^0.5.2",
"@ethereumjs/tx": "^3.3.0",
"@ethereumjs/vm": "^5.5.0",
"@remix-project/remix-lib": "^0.5.5",
"ansi-gray": "^0.1.1",
"async": "^3.1.0",
"body-parser": "^1.18.2",
@ -26,10 +26,11 @@
"commander": "^2.19.0",
"cors": "^2.8.5",
"ethereumjs-util": "^7.0.10",
"ethers": "^5.1.4",
"ethers": "^5.4.2",
"express": "^4.16.3",
"express-ws": "^4.0.0",
"merge": "^1.2.0",
"string-similarity": "^4.0.4",
"time-stamp": "^2.0.0",
"web3": "^1.2.4"
},
@ -65,5 +66,5 @@
},
"homepage": "https://github.com/ethereum/remix-project/tree/master/libs/remix-simulator#readme",
"typings": "src/index.d.ts",
"gitHead": "50b32cc20d2d4dcc793bf7de955957e073e5b5b8"
"gitHead": "df7abe1c219e361a947031d620f4ae6e3786a4d7"
}

@ -99,7 +99,7 @@ export class VMContext {
constructor (fork?) {
this.blockGasLimitDefault = 4300000
this.blockGasLimit = this.blockGasLimitDefault
this.currentFork = fork || 'berlin'
this.currentFork = fork || 'london'
this.currentVm = this.createVm(this.currentFork)
this.blocks = {}
this.latestBlockNumber = 0

@ -1,6 +1,6 @@
{
"name": "@remix-project/remix-solidity",
"version": "0.4.2",
"version": "0.4.5",
"description": "Tool to load and run Solidity compiler",
"main": "index.js",
"types": "./index.d.ts",
@ -15,15 +15,16 @@
}
],
"dependencies": {
"@ethereumjs/block": "^3.3.0",
"@ethereumjs/tx": "^3.2.1",
"@ethereumjs/vm": "^5.4.1",
"@remix-project/remix-lib": "^0.5.2",
"@ethereumjs/block": "^3.4.0",
"@ethereumjs/tx": "^3.3.0",
"@ethereumjs/vm": "^5.5.0",
"@remix-project/remix-lib": "^0.5.5",
"async": "^2.6.2",
"eslint-scope": "^5.0.0",
"ethereumjs-util": "^7.0.10",
"ethers": "^5.1.4",
"ethers": "^5.4.2",
"solc": "^0.7.4",
"string-similarity": "^4.0.4",
"web3": "1.2.4",
"webworkify-webpack": "^2.1.5"
},
@ -58,5 +59,5 @@
},
"homepage": "https://github.com/ethereum/remix-project/tree/master/libs/remix-solidity#readme",
"typings": "index.d.ts",
"gitHead": "50b32cc20d2d4dcc793bf7de955957e073e5b5b8"
"gitHead": "df7abe1c219e361a947031d620f4ae6e3786a4d7"
}

@ -0,0 +1,48 @@
'use strict'
import txHelper from './txHelper'
export class CompilerAbstract {
languageversion: any
data: any
source: any
constructor (languageversion, data, source) {
this.languageversion = languageversion
this.data = data
this.source = source // source code
}
getContracts () {
return this.data.contracts
}
getContract (name) {
return txHelper.getContract(name, this.data.contracts)
}
visitContracts (calllback) {
return txHelper.visitContracts(this.data.contracts, calllback)
}
getData () {
return this.data
}
getAsts () {
return this.data.sources // ast
}
getSourceName (fileIndex) {
if (this.data && this.data.sources) {
return Object.keys(this.data.sources)[fileIndex]
} else if (Object.keys(this.source.sources).length === 1) {
// if we don't have ast, we return the only one filename present.
const sourcesArray = Object.keys(this.source.sources)
return sourcesArray[0]
}
return null
}
getSourceCode () {
return this.source
}
}

@ -1,7 +1,7 @@
'use strict'
import { canUseWorker, urlFromVersion } from './compiler-utils'
import { Compiler } from '@remix-project/remix-solidity'
import CompilerAbstract from './compiler-abstract'
import { CompilerAbstract } from './compiler-abstract'
import { Compiler } from './compiler'
export const compile = async (compilationTargets, settings, contentResolverCallback) => {
const res = await (() => {

@ -1,3 +1,6 @@
export { Compiler } from './compiler/compiler'
export { compile } from './compiler/compiler-helpers'
export { default as CompilerInput } from './compiler/compiler-input'
export { CompilerAbstract } from './compiler/compiler-abstract'
export * from './compiler/types'
export { promisedMiniXhr, pathToURL, baseURLBin, baseURLWasm, canUseWorker, urlFromVersion } from './compiler/compiler-utils'

@ -62,7 +62,7 @@ export default class EventManager {
}
for (const listener in this.registered[eventName]) {
const l = this.registered[eventName][listener]
l.func.apply(l.obj === this.anonymous ? {} : l.obj, args)
if (l.func) l.func.apply(l.obj === this.anonymous ? {} : l.obj, args)
}
}
}

@ -1,6 +1,6 @@
{
"name": "@remix-project/remix-tests",
"version": "0.2.2",
"version": "0.2.5",
"description": "Tool to test Solidity smart contracts",
"main": "src/index.js",
"types": "./src/index.d.ts",
@ -35,13 +35,13 @@
},
"homepage": "https://github.com/ethereum/remix-project/tree/master/libs/remix-tests#readme",
"dependencies": {
"@ethereumjs/block": "^3.3.0",
"@ethereumjs/block": "^3.4.0",
"@ethereumjs/common": "^2.2.0",
"@ethereumjs/tx": "^3.2.1",
"@ethereumjs/vm": "^5.4.1",
"@remix-project/remix-lib": "^0.5.2",
"@remix-project/remix-simulator": "^0.2.2",
"@remix-project/remix-solidity": "^0.4.2",
"@ethereumjs/tx": "^3.3.0",
"@ethereumjs/vm": "^5.5.0",
"@remix-project/remix-lib": "^0.5.5",
"@remix-project/remix-simulator": "^0.2.5",
"@remix-project/remix-solidity": "^0.4.5",
"ansi-gray": "^0.1.1",
"async": "^2.6.0",
"axios": ">=0.21.1",
@ -50,10 +50,11 @@
"colors": "^1.1.2",
"commander": "^2.13.0",
"ethereumjs-util": "^7.0.10",
"ethers": "^5.1.4",
"ethers": "^5.4.2",
"express-ws": "^4.0.0",
"merge": "^1.2.0",
"signale": "^1.4.0",
"string-similarity": "^4.0.4",
"time-stamp": "^2.2.0",
"tslib": "^2.3.0",
"web3": "^1.2.4",
@ -76,5 +77,5 @@
"typescript": "^3.3.1"
},
"typings": "src/index.d.ts",
"gitHead": "50b32cc20d2d4dcc793bf7de955957e073e5b5b8"
"gitHead": "df7abe1c219e361a947031d620f4ae6e3786a4d7"
}

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

@ -1,27 +1,37 @@
import React, { useState } from 'react'
import copy from 'copy-text-to-clipboard'
import copy from 'copy-to-clipboard'
import { OverlayTrigger, Tooltip } from 'react-bootstrap'
import { Placement } from 'react-bootstrap/esm/Overlay'
import './copy-to-clipboard.css'
export const CopyToClipboard = ({ content, tip='Copy', icon='fa-copy', ...otherProps }) => {
interface ICopyToClipboard {
content: any,
tip?: string,
icon?: string,
direction?: Placement,
className?: string,
title?: string,
children?: JSX.Element
}
export const CopyToClipboard = (props: ICopyToClipboard) => {
let { content, tip = 'Copy', icon = 'fa-copy', direction = 'right', children, ...otherProps } = props
const [message, setMessage] = useState(tip)
const handleClick = (event) => {
const handleClick = (e) => {
if (content && content !== '') { // module `copy` keeps last copied thing in the memory, so don't show tooltip if nothing is copied, because nothing was added to memory
try {
if (typeof content !== 'string') {
content = JSON.stringify(content, null, '\t')
}
copy(content)
setMessage('Copied')
} catch (e) {
console.error(e)
}
copy(content)
setMessage('Copied')
} else {
setMessage('Cannot copy empty content!')
}
event.preventDefault()
e.preventDefault()
return false
}
@ -30,15 +40,18 @@ export const CopyToClipboard = ({ content, tip='Copy', icon='fa-copy', ...otherP
}
return (
// eslint-disable-next-line jsx-a11y/anchor-is-valid
<a href='#' onClick={handleClick} onMouseLeave={reset}>
<OverlayTrigger placement="right" overlay={
<OverlayTrigger placement={direction} overlay={
<Tooltip id="overlay-tooltip">
{ message }
</Tooltip>
}>
<i className={`far ${icon} ml-1 p-2`} aria-hidden="true"
{...otherProps}
></i>
{
children || (<i className={`far ${icon} ml-1 p-2`} aria-hidden="true"
{...otherProps}
></i>)
}
</OverlayTrigger>
</a>
)

@ -6,15 +6,15 @@ export const AssemblyItems = ({ registerEvent }) => {
const [assemblyItems, dispatch] = useReducer(reducer, initialState)
const [absoluteSelectedIndex, setAbsoluteSelectedIndex] = useState(0)
const [selectedItem, setSelectedItem] = useState(0)
const [nextSelectedItem, setNextSelectedItem] = useState(1)
const [nextSelectedItems, setNextSelectedItems] = useState([1])
const [returnInstructionIndexes, setReturnInstructionIndexes] = useState([])
const [outOfGasInstructionIndexes, setOutOfGasInstructionIndexes] = useState([])
const refs = useRef({})
const asmItemsRef = useRef(null)
useEffect(() => {
registerEvent && registerEvent('codeManagerChanged', (code, address, index, nextIndex, returnInstructionIndexes, outOfGasInstructionIndexes) => {
dispatch({ type: 'FETCH_OPCODES_SUCCESS', payload: { code, address, index, nextIndex, returnInstructionIndexes, outOfGasInstructionIndexes } })
registerEvent && registerEvent('codeManagerChanged', (code, address, index, nextIndexes, returnInstructionIndexes, outOfGasInstructionIndexes) => {
dispatch({ type: 'FETCH_OPCODES_SUCCESS', payload: { code, address, index, nextIndexes, returnInstructionIndexes, outOfGasInstructionIndexes } })
})
}, [])
@ -22,7 +22,7 @@ export const AssemblyItems = ({ registerEvent }) => {
if (absoluteSelectedIndex !== assemblyItems.index) {
clearItems()
indexChanged(assemblyItems.index)
nextIndexChanged(assemblyItems.nextIndex)
nextIndexesChanged(assemblyItems.nextIndexes)
returnIndexes(assemblyItems.returnInstructionIndexes)
outOfGasIndexes(assemblyItems.outOfGasInstructionIndexes)
}
@ -40,7 +40,11 @@ export const AssemblyItems = ({ registerEvent }) => {
const clearItems = () => {
clearItem(refs.current[selectedItem] ? refs.current[selectedItem] : null)
clearItem(refs.current[nextSelectedItem] ? refs.current[nextSelectedItem] : null)
if (nextSelectedItems) {
nextSelectedItems.map((index) => {
clearItem(refs.current[index] ? refs.current[index] : null)
})
}
returnInstructionIndexes.map((index) => {
if (index < 0) return
@ -70,18 +74,20 @@ export const AssemblyItems = ({ registerEvent }) => {
setAbsoluteSelectedIndex(assemblyItems.opCodes.index)
}
const nextIndexChanged = (index: number) => {
if (index < 0) return
const nextIndexesChanged = (indexes: Array<number>) => {
indexes.map((index) => {
if (index < 0) return
const codeView = asmItemsRef.current
const codeView = asmItemsRef.current
const currentItem = codeView.children[index]
if (currentItem) {
currentItem.style.setProperty('border-color', 'var(--secondary)')
currentItem.style.setProperty('border-style', 'dotted')
currentItem.setAttribute('selected', 'selected')
}
setNextSelectedItem(index)
const currentItem = codeView.children[index]
if (currentItem) {
currentItem.style.setProperty('color', 'var(--primary)')
currentItem.style.setProperty('font-weight', 'bold')
currentItem.setAttribute('selected', 'selected')
}
})
setNextSelectedItems(indexes)
}
const returnIndexes = (indexes) => {

@ -5,32 +5,36 @@ import { ExtractData } from '../../types' // eslint-disable-line
export const SolidityState = ({ calldata, message }) => {
const formatSelf = (key: string, data: ExtractData) => {
let color = 'var(--primary)'
if (data.isArray || data.isStruct || data.isMapping) {
color = 'var(--info)'
} else if (
data.type.indexOf('uint') === 0 ||
try {
let color = 'var(--primary)'
if (data.isArray || data.isStruct || data.isMapping) {
color = 'var(--info)'
} else if (
data.type.indexOf('uint') === 0 ||
data.type.indexOf('int') === 0 ||
data.type.indexOf('bool') === 0 ||
data.type.indexOf('enum') === 0
) {
color = 'var(--green)'
} else if (data.type === 'string') {
color = 'var(--teal)'
) {
color = 'var(--green)'
} else if (data.type === 'string') {
color = 'var(--teal)'
} else if (data.self == 0x0) { // eslint-disable-line
color = 'var(--gray)'
}
return (
<label className='mb-0' style={{ color: data.isProperty ? 'var(--info)' : '', whiteSpace: 'pre-wrap' }}>
{' ' + key}:
<label className='mb-0' style={{ color }}>
{' ' + data.self}
</label>
<label style={{ fontStyle: 'italic' }}>
{data.isProperty || !data.type ? '' : ' ' + data.type}
color = 'var(--gray)'
}
return (
<label className='mb-0' style={{ color: data.isProperty ? 'var(--info)' : '', whiteSpace: 'pre-wrap' }}>
{' ' + key}:
<label className='mb-0' style={{ color }}>
{' ' + data.self}
</label>
<label style={{ fontStyle: 'italic' }}>
{data.isProperty || !data.type ? '' : ' ' + data.type}
</label>
</label>
</label>
)
)
} catch (e) {
return (<></>)
}
}
return (

@ -13,7 +13,7 @@ export const initialState = {
},
display: [],
index: 0,
nextIndex: -1,
nextIndexes: [-1],
returnInstructionIndexes: [],
outOfGasInstructionIndexes: [],
top: 0,
@ -30,7 +30,7 @@ const reducedOpcode = (opCodes, payload) => {
const top = bottom + length
return {
index: opCodes.index - bottom,
nextIndex: opCodes.nextIndex - bottom,
nextIndexes: opCodes.nextIndexes.map(index => index - bottom),
display: opCodes.code.slice(bottom, top),
returnInstructionIndexes: payload.returnInstructionIndexes.map((index) => index.instructionIndex - bottom),
outOfGasInstructionIndexes: payload.outOfGasInstructionIndexes.map((index) => index.instructionIndex - bottom)
@ -49,7 +49,7 @@ export const reducer = (state = initialState, action: Action) => {
}
case 'FETCH_OPCODES_SUCCESS': {
const opCodes = action.payload.address === state.opCodes.address ? {
...state.opCodes, index: action.payload.index, nextIndex: action.payload.nextIndex
...state.opCodes, index: action.payload.index, nextIndexes: action.payload.nextIndexes
} : deepEqual(action.payload.code, state.opCodes.code) ? state.opCodes : action.payload
const reduced = reducedOpcode(opCodes, action.payload)
@ -57,7 +57,7 @@ export const reducer = (state = initialState, action: Action) => {
opCodes,
display: reduced.display,
index: reduced.index,
nextIndex: reduced.nextIndex,
nextIndexes: reduced.nextIndexes,
isRequesting: false,
isSuccessful: true,
hasError: null,

@ -4,41 +4,45 @@ import { ExtractData } from '../types' // eslint-disable-line
export function extractData (item, parent): ExtractData {
const ret: ExtractData = {}
if (item.isProperty) {
if (item.isProperty || !item.type) {
return item
}
if (item.type.lastIndexOf(']') === item.type.length - 1) {
ret.children = (item.value || []).map(function (item, index) {
return { key: index, value: item }
})
ret.children.unshift({
key: 'length',
value: {
self: (new BN(item.length.replace('0x', ''), 16)).toString(10),
type: 'uint',
isProperty: true
}
})
ret.isArray = true
ret.self = parent.isArray ? '' : item.type
ret.cursor = item.cursor
ret.hasNext = item.hasNext
} else if (item.type.indexOf('struct') === 0) {
ret.children = Object.keys((item.value || {})).map(function (key) {
return { key: key, value: item.value[key] }
})
ret.self = item.type
ret.isStruct = true
} else if (item.type.indexOf('mapping') === 0) {
ret.children = Object.keys((item.value || {})).map(function (key) {
return { key: key, value: item.value[key] }
})
ret.isMapping = true
ret.self = item.type
} else {
ret.children = null
ret.self = item.value
ret.type = item.type
try {
if (item.type.lastIndexOf(']') === item.type.length - 1) {
ret.children = (item.value || []).map(function (item, index) {
return { key: index, value: item }
})
ret.children.unshift({
key: 'length',
value: {
self: (new BN(item.length.replace('0x', ''), 16)).toString(10),
type: 'uint',
isProperty: true
}
})
ret.isArray = true
ret.self = parent.isArray ? '' : item.type
ret.cursor = item.cursor
ret.hasNext = item.hasNext
} else if (item.type.indexOf('struct') === 0) {
ret.children = Object.keys((item.value || {})).map(function (key) {
return { key: key, value: item.value[key] }
})
ret.self = item.type
ret.isStruct = true
} else if (item.type.indexOf('mapping') === 0) {
ret.children = Object.keys((item.value || {})).map(function (key) {
return { key: key, value: item.value[key] }
})
ret.isMapping = true
ret.self = item.type
} else {
ret.children = null
ret.self = item.value
ret.type = item.type
}
} catch (e) {
console.log(e)
}
return ret
}

@ -2,6 +2,7 @@ import React, { useRef, useEffect } from 'react' // eslint-disable-line
import { action, FileExplorerContextMenuProps } from './types'
import './css/file-explorer-context-menu.css'
import { customAction } from '@remixproject/plugin-api/lib/file-system/file-panel'
export const FileExplorerContextMenu = (props: FileExplorerContextMenuProps) => {
const { actions, createNewFile, createNewFolder, deletePath, renamePath, hideContextMenu, pushChangesToGist, publishFileToGist, publishFolderToGist, copy, paste, runScript, emit, pageX, pageY, path, type, focus, ...otherProps } = props
@ -96,11 +97,11 @@ export const FileExplorerContextMenu = (props: FileExplorerContextMenuProps) =>
deletePath(getPath())
break
default:
emit && emit(item.id, getPath())
emit && emit({ ...item, path: [path] } as customAction)
break
}
hideContextMenu()
}}>{item.name}</li>
}}>{item.label || item.name}</li>
})
}

@ -11,13 +11,14 @@ import { fileSystemReducer, fileSystemInitialState } from './reducers/fileSystem
import { fetchDirectory, init, resolveDirectory, addInputField, removeInputField } from './actions/fileSystem'
import * as helper from '../../../../../apps/remix-ide/src/lib/helper'
import QueryParams from '../../../../../apps/remix-ide/src/lib/query-params'
import { customAction } from '@remixproject/plugin-api/lib/file-system/file-panel'
import './css/file-explorer.css'
const queryParams = new QueryParams()
export const FileExplorer = (props: FileExplorerProps) => {
const { name, registry, plugin, focusRoot, contextMenuItems, displayInput, externalUploads } = props
const { name, registry, plugin, focusRoot, contextMenuItems, displayInput, externalUploads, removedContextMenuItems } = props
const [state, setState] = useState({
focusElement: [{
key: '',
@ -34,7 +35,8 @@ export const FileExplorer = (props: FileExplorerProps) => {
path: [],
extension: [],
pattern: [],
multiselect: false
multiselect: false,
label: ''
}, {
id: 'newFolder',
name: 'New Folder',
@ -42,7 +44,8 @@ export const FileExplorer = (props: FileExplorerProps) => {
path: [],
extension: [],
pattern: [],
multiselect: false
multiselect: false,
label: ''
}, {
id: 'rename',
name: 'Rename',
@ -50,7 +53,8 @@ export const FileExplorer = (props: FileExplorerProps) => {
path: [],
extension: [],
pattern: [],
multiselect: false
multiselect: false,
label: ''
}, {
id: 'delete',
name: 'Delete',
@ -58,7 +62,8 @@ export const FileExplorer = (props: FileExplorerProps) => {
path: [],
extension: [],
pattern: [],
multiselect: false
multiselect: false,
label: ''
}, {
id: 'run',
name: 'Run',
@ -66,7 +71,8 @@ export const FileExplorer = (props: FileExplorerProps) => {
path: [],
extension: ['.js'],
pattern: [],
multiselect: false
multiselect: false,
label: ''
}, {
id: 'pushChangesToGist',
name: 'Push changes to gist',
@ -74,7 +80,8 @@ export const FileExplorer = (props: FileExplorerProps) => {
path: [],
extension: [],
pattern: [],
multiselect: false
multiselect: false,
label: ''
}, {
id: 'publishFolderToGist',
name: 'Publish folder to gist',
@ -82,7 +89,8 @@ export const FileExplorer = (props: FileExplorerProps) => {
path: [],
extension: [],
pattern: [],
multiselect: false
multiselect: false,
label: ''
}, {
id: 'publishFileToGist',
name: 'Publish file to gist',
@ -90,7 +98,8 @@ export const FileExplorer = (props: FileExplorerProps) => {
path: [],
extension: [],
pattern: [],
multiselect: false
multiselect: false,
label: ''
}, {
id: 'copy',
name: 'Copy',
@ -98,7 +107,8 @@ export const FileExplorer = (props: FileExplorerProps) => {
path: [],
extension: [],
pattern: [],
multiselect: false
multiselect: false,
label: ''
}, {
id: 'deleteAll',
name: 'Delete All',
@ -106,7 +116,8 @@ export const FileExplorer = (props: FileExplorerProps) => {
path: [],
extension: [],
pattern: [],
multiselect: true
multiselect: true,
label: ''
}],
focusContext: {
element: null,
@ -203,6 +214,12 @@ export const FileExplorer = (props: FileExplorerProps) => {
}
}, [contextMenuItems])
useEffect(() => {
if (removedContextMenuItems) {
removeMenuItems(removedContextMenuItems)
}
}, [contextMenuItems])
useEffect(() => {
if (displayInput) {
handleNewFileInput()
@ -275,10 +292,20 @@ export const FileExplorer = (props: FileExplorerProps) => {
path: [],
extension: [],
pattern: [],
multiselect: false
multiselect: false,
label: ''
}])
} else {
removeMenuItems(['paste'])
removeMenuItems([{
id: 'paste',
name: 'Paste',
type: ['folder', 'file'],
path: [],
extension: [],
pattern: [],
multiselect: false,
label: ''
}])
}
}, [canPaste])
@ -291,10 +318,9 @@ export const FileExplorer = (props: FileExplorerProps) => {
})
}
const removeMenuItems = (ids: string[]) => {
const removeMenuItems = (items: MenuItems) => {
setState(prevState => {
const actions = prevState.actions.filter(({ id }) => ids.findIndex(value => value === id) === -1)
const actions = prevState.actions.filter(({ id, name }) => items.findIndex(item => id === item.id && name === item.name) === -1)
return { ...prevState, actions }
})
}
@ -382,7 +408,7 @@ export const FileExplorer = (props: FileExplorerProps) => {
try {
await fileManager.remove(p)
} catch (e) {
const isDir = state.fileManager.isDirectory(p)
const isDir = await state.fileManager.isDirectory(p)
toast(`Failed to remove ${isDir ? 'folder' : 'file'} ${p}.`)
}
}
@ -601,8 +627,8 @@ export const FileExplorer = (props: FileExplorerProps) => {
})
}
const emitContextMenuEvent = (id: string, path: string | string[]) => {
plugin.emit(id, path)
const emitContextMenuEvent = (cmd: customAction) => {
plugin.call(cmd.id, cmd.name, cmd)
}
const handleHideModal = () => {

Some files were not shown because too many files have changed in this diff Show More

Loading…
Cancel
Save