Merge branch 'master' into intl

pull/2581/head
drafish 2 years ago
commit 3b4a8f9913
  1. 6
      .circleci/config.yml
  2. 4
      README.md
  3. 16
      apps/remix-ide-e2e/src/commands/createContract.ts
  4. 10
      apps/remix-ide-e2e/src/tests/debugger.test.ts
  5. 8
      apps/remix-ide-e2e/src/tests/libraryDeployment.test.ts
  6. 20
      apps/remix-ide-e2e/src/tests/recorder.test.ts
  7. 2
      apps/remix-ide-e2e/src/tests/signingMessage.test.ts
  8. 12
      apps/remix-ide-e2e/src/tests/specialFunctions.test.ts
  9. 2
      apps/remix-ide-e2e/src/tests/terminal.test.ts
  10. 4
      apps/remix-ide-e2e/src/tests/transactionExecution.test.ts
  11. 2
      apps/remix-ide-e2e/src/types/index.d.ts
  12. 2
      apps/remix-ide/ci/deploy_from_travis_remix-alpha.sh
  13. 2
      apps/remix-ide/ci/deploy_from_travis_remix-beta.sh
  14. 2
      apps/remix-ide/ci/deploy_from_travis_remix-live.sh
  15. 15
      apps/remix-ide/src/404.html
  16. 2
      apps/remix-ide/src/app/components/hidden-panel.tsx
  17. 2
      apps/remix-ide/src/app/components/main-panel.tsx
  18. 2
      apps/remix-ide/src/app/components/side-panel.tsx
  19. 2
      apps/remix-ide/src/app/components/vertical-icons.tsx
  20. 2
      apps/remix-ide/src/app/files/dgitProvider.js
  21. 5
      apps/remix-ide/src/app/panels/file-panel.js
  22. 2
      apps/remix-ide/src/app/panels/terminal.js
  23. 2
      apps/remix-ide/src/app/plugins/permission-handler-plugin.tsx
  24. 3
      apps/remix-ide/src/app/tabs/analysis-tab.js
  25. 2
      apps/remix-ide/src/app/tabs/compile-and-run.ts
  26. 1
      apps/remix-ide/src/app/tabs/compile-tab.js
  27. 3
      apps/remix-ide/src/app/tabs/debugger-tab.js
  28. 2
      apps/remix-ide/src/app/tabs/external-http-provider.tsx
  29. 2
      apps/remix-ide/src/app/tabs/foundry-provider.tsx
  30. 2
      apps/remix-ide/src/app/tabs/ganache-provider.tsx
  31. 13
      apps/remix-ide/src/app/tabs/runTab/model/recorder.js
  32. 5
      apps/remix-ide/src/app/tabs/search.tsx
  33. 5
      apps/remix-ide/src/app/tabs/test-tab.js
  34. 3
      apps/remix-ide/src/app/udapp/run-tab.js
  35. 2
      apps/remix-ide/src/app/ui/landing-page/landing-page.js
  36. 57
      apps/remix-ide/src/blockchain/blockchain.js
  37. 3
      apps/remix-ide/src/remixAppManager.js
  38. 2
      apps/remix-ide/src/walkthroughService.js
  39. 14
      libs/remix-core-plugin/src/lib/constants/uups.ts
  40. 60
      libs/remix-core-plugin/src/lib/openzeppelin-proxy.ts
  41. 215
      libs/remix-core-plugin/src/types/contract.ts
  42. 26
      libs/remix-ui/helper/src/lib/helper-components.tsx
  43. 47
      libs/remix-ui/panel/src/lib/plugins/panel-header.tsx
  44. 1
      libs/remix-ui/panel/src/lib/plugins/panel.css
  45. 3
      libs/remix-ui/plugin-manager/src/lib/components/ActivePluginCard.tsx
  46. 3
      libs/remix-ui/plugin-manager/src/lib/components/InactivePluginCard.tsx
  47. 9
      libs/remix-ui/run-tab/src/lib/actions/deploy.ts
  48. 13
      libs/remix-ui/run-tab/src/lib/actions/events.ts
  49. 17
      libs/remix-ui/run-tab/src/lib/actions/payload.ts
  50. 24
      libs/remix-ui/run-tab/src/lib/components/contractDropdownUI.tsx
  51. 337
      libs/remix-ui/run-tab/src/lib/components/contractGUI.tsx
  52. 26
      libs/remix-ui/run-tab/src/lib/components/recorderCardUI.tsx
  53. 3
      libs/remix-ui/run-tab/src/lib/constants/index.ts
  54. 39
      libs/remix-ui/run-tab/src/lib/reducers/runTab.ts
  55. 6
      libs/remix-ui/run-tab/src/lib/run-tab.tsx
  56. 15
      libs/remix-ui/run-tab/src/lib/types/index.ts
  57. 28
      libs/remix-ui/workspace/src/lib/actions/index.ts
  58. 15
      libs/remix-ui/workspace/src/lib/remix-ui-workspace.tsx
  59. 2
      libs/remixd/package.json
  60. 14
      package.json
  61. 1
      workspace.json
  62. 84
      yarn.lock

@ -323,7 +323,7 @@ jobs:
environment:
- COMMIT_AUTHOR_EMAIL: "yann@ethereum.org"
- COMMIT_AUTHOR: "Circle CI"
- FILES_TO_PACKAGE: "dist/apps/remix-ide/index.html dist/apps/remix-ide/raw-loader*.js dist/apps/remix-ide/assets dist/apps/remix-ide/main*.js dist/apps/remix-ide/polyfills*.js dist/apps/remix-ide/favicon.ico dist/apps/remix-ide/vendors~app*.js dist/apps/remix-ide/app*.js"
- FILES_TO_PACKAGE: "dist/apps/remix-ide/index.html dist/apps/remix-ide/404.html dist/apps/remix-ide/raw-loader*.js dist/apps/remix-ide/assets dist/apps/remix-ide/main*.js dist/apps/remix-ide/polyfills*.js dist/apps/remix-ide/favicon.ico dist/apps/remix-ide/vendors~app*.js dist/apps/remix-ide/app*.js"
working_directory: ~/remix-project
steps:
@ -352,7 +352,7 @@ jobs:
environment:
- COMMIT_AUTHOR_EMAIL: "yann@ethereum.org"
- COMMIT_AUTHOR: "Circle CI"
- FILES_TO_PACKAGE: "dist/apps/remix-ide/index.html dist/apps/remix-ide/raw-loader*.js dist/apps/remix-ide/assets dist/apps/remix-ide/main*.js dist/apps/remix-ide/polyfills*.js dist/apps/remix-ide/favicon.ico dist/apps/remix-ide/vendors~app*.js dist/apps/remix-ide/app*.js"
- FILES_TO_PACKAGE: "dist/apps/remix-ide/index.html dist/apps/remix-ide/404.html dist/apps/remix-ide/raw-loader*.js dist/apps/remix-ide/assets dist/apps/remix-ide/main*.js dist/apps/remix-ide/polyfills*.js dist/apps/remix-ide/favicon.ico dist/apps/remix-ide/vendors~app*.js dist/apps/remix-ide/app*.js"
working_directory: ~/remix-project
steps:
@ -380,7 +380,7 @@ jobs:
environment:
- COMMIT_AUTHOR_EMAIL: "yann@ethereum.org"
- COMMIT_AUTHOR: "Circle CI"
- FILES_TO_PACKAGE: "dist/apps/remix-ide/index.html dist/apps/remix-ide/raw-loader*.js dist/apps/remix-ide/assets dist/apps/remix-ide/main*.js dist/apps/remix-ide/polyfills*.js dist/apps/remix-ide/favicon.ico dist/apps/remix-ide/vendors~app*.js dist/apps/remix-ide/app*.js"
- FILES_TO_PACKAGE: "dist/apps/remix-ide/index.html dist/apps/remix-ide/404.html dist/apps/remix-ide/raw-loader*.js dist/apps/remix-ide/assets dist/apps/remix-ide/main*.js dist/apps/remix-ide/polyfills*.js dist/apps/remix-ide/favicon.ico dist/apps/remix-ide/vendors~app*.js dist/apps/remix-ide/app*.js"
working_directory: ~/remix-project
steps:

@ -54,7 +54,7 @@ Note: It contains the latest supported version of Solidity available at the time
"npm": "^6.14.15"
}
```
* Install [Nx CLI](https://nx.dev/react/cli/overview) globally to enable running **nx executable commands**.
* Install [Nx CLI](https://nx.dev/using-nx/nx-cli) globally to enable running **nx executable commands**.
```bash
yarn global add @nrwl/cli
```
@ -127,7 +127,7 @@ curl https://raw.githubusercontent.com/ethereum/remix-project/master/docker-comp
### Troubleshooting
If you have trouble building the project, make sure that you have the correct version of `node`, `npm` and `nvm`. Also ensure [Nx CLI](https://nx.dev/react/cli/overview) is installed globally.
If you have trouble building the project, make sure that you have the correct version of `node`, `npm` and `nvm`. Also ensure [Nx CLI](https://nx.dev/using-nx/nx-cli) is installed globally.
Run:

@ -2,7 +2,7 @@ import { NightwatchBrowser } from 'nightwatch'
import EventEmitter from 'events'
class CreateContract extends EventEmitter {
command (this: NightwatchBrowser, inputParams: string[]): NightwatchBrowser {
command (this: NightwatchBrowser, inputParams: string): NightwatchBrowser {
this.api.perform((done) => {
createContract(this.api, inputParams, () => {
done()
@ -13,19 +13,11 @@ class CreateContract extends EventEmitter {
}
}
function createContract (browser: NightwatchBrowser, inputParams: string[], callback: VoidFunction) {
if (inputParams.length === 1) {
browser.setValue('.udapp_contractActionsContainerSingle > input', inputParams[0], function () {
function createContract (browser: NightwatchBrowser, inputParams: string, callback: VoidFunction) {
if (inputParams) {
browser.setValue('.udapp_contractActionsContainerSingle > input', inputParams, function () {
browser.click('.udapp_contractActionsContainerSingle > button').pause(500).perform(function () { callback() })
})
} else if (inputParams.length > 1) {
browser.perform((done) => {
for (let i = 0; i < inputParams.length; i++) {
browser.setValue(`div.udapp_multiArg:nth-child(${i + 1}) > input`, inputParams[i])
}
done()
})
.click('div.udapp_multiArg > button').pause(500).perform(function () { callback() })
} else {
browser
.click('.udapp_contractActionsContainerSingle > button')

@ -86,7 +86,7 @@ module.exports = {
.clickLaunchIcon('udapp')
.waitForElementPresent('*[title="Deploy - transact (not payable)"]', 35000)
.selectContract('ERC20')
.createContract(["tokenName", "symbol"])
.createContract('"tokenName", "symbol"')
.debugTransaction(0)
.pause(2000)
.waitForElementVisible('#stepdetail')
@ -115,7 +115,7 @@ module.exports = {
.testContracts('withABIEncoderV2.sol', sources[2]['withABIEncoderV2.sol'], ['test'])
.clickLaunchIcon('udapp')
.selectContract('test')
.createContract([])
.createContract('')
.clearConsole()
.clickInstance(0)
.clickFunction('test1 - transact (not payable)', { types: 'bytes userData', values: '0x000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000015b38da6a701c568545dcfcb03fcb875f56beddc4' })
@ -148,7 +148,7 @@ module.exports = {
.testContracts('locals.sol', sources[3]['locals.sol'], ['testLocals'])
.clickLaunchIcon('udapp')
.waitForElementPresent('*[title="Deploy - transact (not payable)"]', 40000)
.createContract([])
.createContract('')
.pause(2000)
.clearConsole()
.clickInstance(0)
@ -173,7 +173,7 @@ module.exports = {
.pause(2000)
.testContracts('withGeneratedSources.sol', sources[4]['withGeneratedSources.sol'], ['A'])
.clickLaunchIcon('udapp')
.createContract([])
.createContract('')
.clearConsole()
.clickInstance(0)
.clickFunction('f - transact (not payable)', { types: 'uint256[] ', values: '[]' })
@ -235,7 +235,7 @@ module.exports = {
.testContracts('reverted.sol', sources[6]['reverted.sol'], ['A', 'B', 'C'])
.clickLaunchIcon('udapp')
.selectContract('A')
.createContract([])
.createContract('')
.pause(500)
.clickInstance(0)
.clickFunction('callA - transact (not payable)')

@ -22,7 +22,7 @@ module.exports = {
browser.verifyContracts(['test'])
.clickLaunchIcon('udapp')
.selectContract('test')
.createContract([])
.createContract('')
.getAddressAtPosition(0, (address) => {
console.log('testAutoDeployLib ' + address)
addressRef = address
@ -46,7 +46,7 @@ module.exports = {
.verifyContracts(['test'])
.clickLaunchIcon('udapp')
.selectContract('lib') // deploy lib
.createContract([])
.createContract('')
.perform((done) => {
browser.getAddressAtPosition(0, (address) => {
console.log(address)
@ -74,7 +74,7 @@ function checkDeployShouldFail (browser: NightwatchBrowser, callback: VoidFuncti
.openFile('Untitled5.sol')
.clickLaunchIcon('udapp')
.selectContract('test') // deploy lib
.createContract([])
.createContract('')
.pause(2000)
.getText('div[class^="terminal"]', (value) => {
console.log('value: ', value)
@ -98,7 +98,7 @@ function checkDeployShouldSucceed (browser: NightwatchBrowser, address: string,
.openFile('Untitled5.sol')
.clickLaunchIcon('udapp')
.selectContract('test') // deploy lib
.createContract([])
.createContract('')
.getAddressAtPosition(1, (address) => {
addressRef = address
})

@ -3,6 +3,7 @@ import { NightwatchBrowser } from 'nightwatch'
import init from '../helpers/init'
module.exports = {
'@disabled': true,
before: function (browser: NightwatchBrowser, done: VoidFunction) {
init(browser, done)
},
@ -11,7 +12,7 @@ module.exports = {
return sources
},
'Run Scenario': function (browser: NightwatchBrowser) {
'Run Scenario #group1': function (browser: NightwatchBrowser) {
let addressRef
browser.addFile('scenario.json', { content: records })
.pause(5000)
@ -36,10 +37,10 @@ module.exports = {
.click('*[data-id="deployAndRunClearInstances"]')
},
'Save scenario': function (browser: NightwatchBrowser) {
'Save scenario #group1': function (browser: NightwatchBrowser) {
browser.testContracts('testRecorder.sol', sources[0]['testRecorder.sol'], ['testRecorder'])
.clickLaunchIcon('udapp')
.createContract(['12'])
.createContract('12')
.clickInstance(0)
.clickFunction('set - transact (not payable)', { types: 'uint256 _p', values: '34' })
.click('.savetransaction')
@ -64,7 +65,7 @@ module.exports = {
})
},
'Record more than one contract': function (browser: NightwatchBrowser) {
'Record more than one contract #group1': function (browser: NightwatchBrowser) {
// deploy 2 contracts (2 different ABIs), save the record, reexecute and test one of the function.
browser
.click('*[data-id="deployAndRunClearInstances"]')
@ -72,11 +73,11 @@ module.exports = {
.clickLaunchIcon('udapp')
.selectContract('t1est')
.pause(1000)
.createContract([])
.createContract('')
.clickInstance(0)
.selectContract('t2est')
.pause(1000)
.createContract([])
.createContract('')
.click('.savetransaction')
.waitForElementVisible('[data-id="udappNotify-modal-footer-ok-react"]')
.execute(function () {
@ -84,6 +85,7 @@ module.exports = {
modalOk.click()
})
.pause(1000)
.click('*[data-id="deployAndRunClearInstances"]') // clear udapp
.click('*[data-id="terminalClearConsole"]') // clear terminal
.click('[data-id="runtransaction"]')
@ -98,7 +100,7 @@ module.exports = {
},
'Run with live "mode"': function (browser: NightwatchBrowser) {
'Run with live "mode" #group1': function (browser: NightwatchBrowser) {
let addressRef: string
browser.addFile('scenario_live_mode.json', { content: JSON.stringify(liveModeScenario, null, '\t') })
.addFile('scenario_live_mode_storage.sol', { content: testStorageForLiveMode })
@ -116,7 +118,7 @@ module.exports = {
})
.clickFunction('retrieve - call')
.perform((done) => {
browser.verifyCallReturnValue(addressRef, ['', '0:uint256: 350'])
browser.verifyCallReturnValue(addressRef, ['0:uint256: 350'])
.perform(() => done())
})
// change the init state and recompile the same contract.
@ -136,7 +138,7 @@ module.exports = {
})
.clickFunction('retrieve - call')
.perform((done) => {
browser.verifyCallReturnValue(addressRef, ['', '0:uint256: 300'])
browser.verifyCallReturnValue(addressRef, ['0:uint256: 300'])
.perform(() => done())
})
.end()

@ -33,7 +33,7 @@ module.exports = {
.clickLaunchIcon('udapp')
.pause(5000)
.selectContract('ECVerify')
.createContract([])
.createContract('')
.clickInstance(0)
.perform((done) => {
browser.getAddressAtPosition(0, (address) => {

@ -24,7 +24,7 @@ module.exports = {
.clickLaunchIcon('udapp')
.selectAccount('0xCA35b7d915458EF540aDe6068dFe2F44E8fa733c') // this account will be used for this test suite
.selectContract('CheckSpecials')
.createContract([]) // deploy
.createContract('') // deploy
.clickInstance(0)
.perform((done) => {
browser.getAddressAtPosition(0, (address) => {
@ -92,7 +92,7 @@ module.exports = {
.testContracts('receiveOnly.sol', sources[1]['receiveOnly.sol'], ['CheckSpecials'])
.clickLaunchIcon('udapp')
.selectContract('CheckSpecials')
.createContract([])
.createContract('')
.clickInstance(0)
.perform((done) => {
browser.getAddressAtPosition(0, (address) => {
@ -122,7 +122,7 @@ module.exports = {
.testContracts('fallbackOnlyPayable.sol', sources[2]['fallbackOnlyPayable.sol'], ['CheckSpecials'])
.clickLaunchIcon('udapp')
.selectContract('CheckSpecials')
.createContract([])
.createContract('')
.clickInstance(0)
.perform((done) => {
browser.getAddressAtPosition(0, (address) => {
@ -153,7 +153,7 @@ module.exports = {
.testContracts('fallbackOnlyNotPayable.sol', sources[3]['fallbackOnlyNotPayable.sol'], ['CheckSpecials'])
.clickLaunchIcon('udapp')
.selectContract('CheckSpecials')
.createContract([])
.createContract('')
.clickInstance(0)
.perform((done) => {
browser.getAddressAtPosition(0, (address) => {
@ -174,7 +174,7 @@ module.exports = {
.clearValue('#value')
.setValue('#value', '0')
.pause(2000)
.createContract([])
.createContract('')
.pause(1000)
.clickInstance(0).pause(1000)
.perform((done) => {
@ -208,7 +208,7 @@ module.exports = {
.waitForElementVisible('#value')
.clearValue('#value')
.setValue('#value', '0').pause(2000)
.createContract([])
.createContract('')
.clickInstance(0)
.pause(1000)
.perform((done) => {

@ -172,7 +172,7 @@ module.exports = {
.clickLaunchIcon('udapp')
.click('*[data-id="deployAndRunClearInstances"]')
.selectContract('OwnerTest')
.createContract([])
.createContract('')
.pause(1000)
.journalChildIncludes('constructor', { shouldHaveOnlyOneOccurence: true })
.pause(5000)

@ -202,10 +202,10 @@ module.exports = {
.addFile('Storage.sol', sources[6]['Storage.sol'])
.addFile('Owner.sol', sources[6]['Owner.sol'])
.clickLaunchIcon('udapp')
.createContract(['42', '24'])
.createContract('42, 24')
.openFile('Storage.sol')
.clickLaunchIcon('udapp')
.createContract(['102']) // this creation will fail if the component hasn't been properly reset.
.createContract('102') // this creation will fail if the component hasn't been properly reset.
.clickInstance(1)
.clickFunction('store - transact (not payable)', { types: 'uint256 num', values: '24' })
.testFunction('last', // we check if the contract is actually reachable.

@ -34,7 +34,7 @@ declare module 'nightwatch' {
getModalBody(callback: (value: string, cb: VoidFunction) => void): NightwatchBrowser,
modalFooterCancelClick(id?: string): NightwatchBrowser,
selectContract(contractName: string): NightwatchBrowser,
createContract(inputParams: string[]): NightwatchBrowser,
createContract(inputParams: string): NightwatchBrowser,
getAddressAtPosition(index: number, cb: (pos: string) => void): NightwatchBrowser,
testConstantFunction(address: string, fnFullName: string, expectedInput: NightwatchTestConstantFunctionExpectedInput | null, expectedOutput: string): NightwatchBrowser,
getEditorValue(callback: (content: string) => void): NightwatchBrowser,

@ -14,7 +14,7 @@ echo "To use an offline copy, download \`remix-$SHA.zip\`." >> README.md
cp -r $FILES_TO_PACKAGE "./"
rm -rf dist
ls
FILES_TO_DEPLOY="assets index.html main*.js polyfills*.js favicon.ico vendors~app*.js app*.js raw-loader*.js"
FILES_TO_DEPLOY="assets index.html 404.html main*.js polyfills*.js favicon.ico vendors~app*.js app*.js raw-loader*.js"
# ZIP the whole directory
zip -r remix-$SHA.zip $FILES_TO_DEPLOY
# -f is needed because "build" is part of .gitignore

@ -14,7 +14,7 @@ echo "To use an offline copy, download \`remix-$SHA.zip\`." >> README.md
cp -r $FILES_TO_PACKAGE "./"
rm -rf dist
ls
FILES_TO_DEPLOY="assets index.html main*.js polyfills*.js favicon.ico vendors~app*.js app*.js raw-loader*.js"
FILES_TO_DEPLOY="assets index.html 404.html main*.js polyfills*.js favicon.ico vendors~app*.js app*.js raw-loader*.js"
# ZIP the whole directory
zip -r remix-$SHA.zip $FILES_TO_DEPLOY
# -f is needed because "build" is part of .gitignore

@ -14,7 +14,7 @@ echo "To use an offline copy, download \`remix-$SHA.zip\`." >> README.md
cp -r $FILES_TO_PACKAGE "./"
rm -rf dist
ls
FILES_TO_DEPLOY="assets index.html main*.js polyfills*.js vendors~app*.js app*.js raw-loader*.js"
FILES_TO_DEPLOY="assets index.html 404.html main*.js polyfills*.js vendors~app*.js app*.js raw-loader*.js"
# ZIP the whole directory
zip -r remix-$SHA.zip $FILES_TO_DEPLOY
# -f is needed because "build" is part of .gitignore

@ -0,0 +1,15 @@
<!DOCTYPE html>
<html>
<head>
<title></title>
</head>
<body></body>
<script>
let route = window.location.pathname
if (route.startsWith('/address/0x') && route.length === 51) {
window.location.href = '/#address=' + route.replace('/address/', '')
} else if (route.endsWith('.sol')) {
window.location.href = '/#url=https://github.com' + route
} else window.location.href = '/'
</script>
</html>

@ -8,7 +8,7 @@ import { PluginViewWrapper } from '@remix-ui/helper'
const profile = {
name: 'hiddenPanel',
displayName: 'Hidden Panel',
description: '',
description: 'Remix IDE hidden panel',
version: packageJson.version,
methods: ['addView', 'removeView']
}

@ -7,7 +7,7 @@ import { PluginViewWrapper } from '@remix-ui/helper'
const profile = {
name: 'mainPanel',
displayName: 'Main Panel',
description: '',
description: 'Remix IDE main panel',
version: packageJson.version,
methods: ['addView', 'removeView', 'showContent']
}

@ -10,7 +10,7 @@ import { PluginViewWrapper } from '@remix-ui/helper'
const sidePanel = {
name: 'sidePanel',
displayName: 'Side Panel',
description: '',
description: 'Remix IDE side panel',
version: packageJson.version,
methods: ['addView', 'removeView']
}

@ -10,7 +10,7 @@ import { PluginViewWrapper } from '@remix-ui/helper'
const profile = {
name: 'menuicons',
displayName: 'Vertical Icons',
description: '',
description: 'Remix IDE vertical icons',
version: packageJson.version,
methods: ['select', 'unlinkContent', 'linkContent'],
events: ['toggleContent', 'showContent']

@ -18,7 +18,7 @@ const axios = require('axios')
const profile = {
name: 'dGitProvider',
displayName: 'Decentralized git',
description: '',
description: 'Decentralized git provider',
icon: 'assets/img/fileManager.webp',
version: '0.0.1',
methods: ['init', 'localStorageUsed', 'addremote', 'delremote', 'remotes', 'fetch', 'clone', 'export', 'import', 'status', 'log', 'commit', 'add', 'remove', 'rm', 'lsfiles', 'readblob', 'resolveref', 'branches', 'branch', 'checkout', 'currentbranch', 'push', 'pin', 'pull', 'pinList', 'unPin', 'setIpfsConfig', 'zip', 'setItem', 'getItem'],

@ -34,11 +34,12 @@ const profile = {
methods: ['createNewFile', 'uploadFile', 'getCurrentWorkspace', 'getWorkspaces', 'createWorkspace', 'setWorkspace', 'registerContextMenuItem', 'renameWorkspace', 'deleteWorkspace'],
events: ['setWorkspace', 'workspaceRenamed', 'workspaceDeleted', 'workspaceCreated'],
icon: 'assets/img/fileManager.webp',
description: ' - ',
description: 'Remix IDE file explorer',
kind: 'fileexplorer',
location: 'sidePanel',
documentation: 'https://remix-ide.readthedocs.io/en/latest/file_explorer.html',
version: packageJson.version
version: packageJson.version,
maintainedBy: 'Remix'
}
module.exports = class Filepanel extends ViewPlugin {
constructor (appManager) {

@ -20,7 +20,7 @@ const profile = {
name: 'terminal',
methods: ['log', 'logHtml'],
events: [],
description: ' - ',
description: 'Remix IDE terminal',
version: packageJson.version
}

@ -7,7 +7,7 @@ import { Profile } from '@remixproject/plugin-utils'
const profile = {
name: 'permissionhandler',
displayName: 'permissionhandler',
description: 'permissionhandler',
description: 'Plugin to handle permissions',
methods: ['askPermission']
}

@ -18,7 +18,8 @@ const profile = {
kind: 'analysis',
location: 'sidePanel',
documentation: 'https://remix-ide.readthedocs.io/en/latest/static_analysis.html',
version: packageJson.version
version: packageJson.version,
maintainedBy: 'Remix'
}
class AnalysisTab extends ViewPlugin {

@ -10,7 +10,7 @@ const _paq = window._paq = window._paq || []
export const profile = {
name: 'compileAndRun',
displayName: 'Compile and Run',
description: 'after each compilation, run the script defined in Natspec.',
description: 'After each compilation, run the script defined in Natspec.',
methods: ['runScriptAfterCompilation'],
version: packageJson.version,
kind: 'none'

@ -20,6 +20,7 @@ const profile = {
location: 'sidePanel',
documentation: 'https://remix-ide.readthedocs.io/en/latest/solidity_editor.html',
version: packageJson.version,
maintainedBy: 'Remix',
methods: ['getCompilationResult', 'compile', 'compileWithParameters', 'setCompilerConfig', 'compileFile', 'getCompilerState']
}

@ -18,7 +18,8 @@ const profile = {
kind: 'debugging',
location: 'sidePanel',
documentation: 'https://remix-ide.readthedocs.io/en/latest/debugger.html',
version: packageJson.version
version: packageJson.version,
maintainedBy: 'Remix'
}
export class DebuggerTab extends DebuggerApiMixin(ViewPlugin) {

@ -6,7 +6,7 @@ const profile = {
name: 'basic-http-provider',
displayName: 'External Http Provider',
kind: 'provider',
description: '',
description: 'External Http Provider',
methods: ['sendAsync'],
version: packageJson.version
}

@ -10,7 +10,7 @@ const profile = {
name: 'foundry-provider',
displayName: 'Foundry Provider',
kind: 'provider',
description: 'Anvil',
description: 'Foundry Anvil provider',
methods: ['sendAsync'],
version: packageJson.version
}

@ -10,7 +10,7 @@ const profile = {
name: 'ganache-provider',
displayName: 'Ganache Provider',
kind: 'provider',
description: 'Ganache',
description: 'Truffle Ganache provider',
methods: ['sendAsync'],
version: packageJson.version
}

@ -13,7 +13,7 @@ const _paq = window._paq = window._paq || [] //eslint-disable-line
const profile = {
name: 'recorder',
displayName: 'Recorder',
description: '',
description: 'Records transactions to save and run',
version: packageJson.version,
methods: [ ]
}
@ -200,13 +200,16 @@ class Recorder extends Plugin {
*/
run (records, accounts, options, abis, linkReferences, confirmationCb, continueCb, promptCb, alertCb, logCallBack, liveMode, newContractFn) {
this.setListen(false)
const liveMsg = liveMode ? ' in live mode' : ''
const liveMsg = liveMode ? ' with updated contracts' : ''
logCallBack(`Running ${records.length} transaction(s)${liveMsg} ...`)
async.eachOfSeries(records, async (tx, index, cb) => {
if (liveMode && tx.record.type === 'constructor') {
// resolve the bytecode using the contract name, this ensure getting the last compiled one.
// resolve the bytecode and ABI using the contract name, this ensure getting the last compiled one.
const data = await this.call('compilerArtefacts', 'getArtefactsByContractName', tx.record.contractName)
tx.record.bytecode = data.artefact.evm.bytecode.object
const updatedABIKeccak = ethutil.bufferToHex(ethutil.keccakFromString(JSON.stringify(data.artefact.abi)))
abis[updatedABIKeccak] = data.artefact.abi
tx.record.abi = updatedABIKeccak
}
var record = this.resolveAddress(tx.record, accounts, options)
var abi = abis[tx.record.abi]
@ -312,11 +315,11 @@ class Recorder extends Plugin {
abis = json.abis || {}
linkReferences = json.linkReferences || {}
} catch (e) {
return cb('Invalid Scenario File. Please try again')
return cb('Invalid scenario file. Please try again')
}
if (!txArray.length) {
return
return cb('No transactions found in scenario file')
}
this.run(txArray, accounts, options, abis, linkReferences, confirmationCb, continueCb, promptCb, alertCb, logCallBack, liveMode, (abi, address, contractName) => {

@ -9,11 +9,12 @@ const profile = {
methods: [''],
events: [],
icon: 'assets/img/search_icon.webp',
description: '',
description: 'Find and replace in file explorer',
kind: '',
location: 'sidePanel',
documentation: '',
version: packageJson.version
version: packageJson.version,
maintainedBy: 'Remix'
}
export class SearchPlugin extends ViewPlugin {

@ -16,9 +16,10 @@ const profile = {
methods: ['testFromPath', 'testFromSource', 'setTestFolderPath', 'getTestlibs', 'createTestLibs'],
events: [],
icon: 'assets/img/unitTesting.webp',
description: 'Fast tool to generate unit tests for your contracts',
description: 'Write and run unit tests for your contracts in Solidity',
location: 'sidePanel',
documentation: 'https://remix-ide.readthedocs.io/en/latest/unittesting.html'
documentation: 'https://remix-ide.readthedocs.io/en/latest/unittesting.html',
maintainedBy: 'Remix'
}
module.exports = class TestTab extends ViewPlugin {

@ -13,11 +13,12 @@ const profile = {
displayName: 'Deploy & run transactions',
intlId: 'udapp.displayName',
icon: 'assets/img/deployAndRun.webp',
description: 'execute and save transactions',
description: 'Execute, save and replay transactions',
kind: 'udapp',
location: 'sidePanel',
documentation: 'https://remix-ide.readthedocs.io/en/latest/run.html',
version: packageJson.version,
maintainedBy: 'Remix',
permission: true,
events: ['newTransaction'],
methods: ['createVMAccount', 'sendTransaction', 'getAccounts', 'pendingTransactionsCount', 'getSettings', 'setEnvironmentMode', 'clearAllInstances', 'addInstance', 'resolveContractAndAddInstance']

@ -9,7 +9,7 @@ const profile = {
displayName: 'Home',
methods: [],
events: [],
description: ' - ',
description: 'Remix home tab ',
icon: 'assets/img/remixLogo.webp',
location: 'mainPanel',
version: packageJson.version

@ -11,8 +11,7 @@ import InjectedProvider from './providers/injected.js'
import NodeProvider from './providers/node.js'
import { execution, EventManager, helpers } from '@remix-project/remix-lib'
import { etherScanLink } from './helper'
import { logBuilder, confirmProxyMsg } from "@remix-ui/helper"
import { cancelProxyMsg } from '@remix-ui/helper'
import { logBuilder, cancelUpgradeMsg, cancelProxyMsg } from "@remix-ui/helper"
const { txFormat, txExecution, typeConversion, txListener: Txlistener, TxRunner, TxRunnerWeb3, txHelper } = execution
const { txResultHelper: resultToRemixTx } = helpers
const packageJson = require('../../../../package.json')
@ -142,17 +141,19 @@ export class Blockchain extends Plugin {
async deployProxy (proxyData, implementationContractObject) {
const proxyModal = {
id: 'confirmProxyDeployment',
title: 'ERC1967',
title: 'Confirm Deploy Proxy (ERC1967)',
message: `Confirm you want to deploy an ERC1967 proxy contract that is connected to your implementation.
For more info on ERC1967, see https://docs.openzeppelin.com/contracts/4.x/api/proxy#ERC1967Proxy`,
For more info on ERC1967, see: https://docs.openzeppelin.com/contracts/4.x/api/proxy#ERC1967Proxy`,
modalType: 'modal',
okLabel: 'OK',
cancelLabel: 'Cancel',
okFn: () => {
this.runProxyTx(proxyData, implementationContractObject)
_paq.push(['trackEvent', 'blockchain', 'Deploy With Proxy', 'modal ok confirmation'])
},
cancelFn: () => {
this.call('notification', 'toast', cancelProxyMsg())
_paq.push(['trackEvent', 'blockchain', 'Deploy With Proxy', 'cancel proxy deployment'])
},
hideFn: () => null
}
@ -161,7 +162,9 @@ export class Blockchain extends Plugin {
async runProxyTx (proxyData, implementationContractObject) {
const args = { useCall: false, data: proxyData }
let networkInfo
const confirmationCb = (network, tx, gasEstimation, continueTxExecution, cancelCb) => {
networkInfo = network
// continue using original authorization given by user
continueTxExecution(null)
}
@ -171,14 +174,60 @@ export class Blockchain extends Plugin {
if (error) {
const log = logBuilder(error)
_paq.push(['trackEvent', 'blockchain', 'Deploy With Proxy', 'Proxy deployment failed: ' + error])
return this.call('terminal', 'logHtml', log)
}
if (networkInfo.name === 'VM') this.config.set('vm/proxy', address)
else this.config.set(`${networkInfo.name}/${networkInfo.currentFork}/${networkInfo.id}/proxy`, address)
_paq.push(['trackEvent', 'blockchain', 'Deploy With Proxy', 'Proxy deployment successful'])
return this.call('udapp', 'resolveContractAndAddInstance', implementationContractObject, address)
}
this.runTx(args, confirmationCb, continueCb, promptCb, finalCb)
}
async upgradeProxy(proxyAddress, newImplAddress, data, newImplementationContractObject) {
const upgradeModal = {
id: 'confirmProxyDeployment',
title: 'Confirm Update Proxy (ERC1967)',
message: `Confirm you want to update your proxy contract with the new implementation contract's address: ${newImplAddress}.`,
modalType: 'modal',
okLabel: 'OK',
cancelLabel: 'Cancel',
okFn: () => {
this.runUpgradeTx(proxyAddress, data, newImplementationContractObject)
_paq.push(['trackEvent', 'blockchain', 'Upgrade With Proxy', 'proxy upgrade confirmation click'])
},
cancelFn: () => {
this.call('notification', 'toast', cancelUpgradeMsg())
_paq.push(['trackEvent', 'blockchain', 'Upgrade With Proxy', 'proxy upgrade cancel click'])
},
hideFn: () => null
}
this.call('notification', 'modal', upgradeModal)
}
async runUpgradeTx (proxyAddress, data, newImplementationContractObject) {
const args = { useCall: false, data, to: proxyAddress }
const confirmationCb = (network, tx, gasEstimation, continueTxExecution, cancelCb) => {
// continue using original authorization given by user
continueTxExecution(null)
}
const continueCb = (error, continueTxExecution, cancelCb) => { continueTxExecution() }
const promptCb = (okCb, cancelCb) => { okCb() }
const finalCb = (error, txResult, address, returnValue) => {
if (error) {
const log = logBuilder(error)
_paq.push(['trackEvent', 'blockchain', 'Upgrade With Proxy', 'Upgrade failed'])
return this.call('terminal', 'logHtml', log)
}
_paq.push(['trackEvent', 'blockchain', 'Upgrade With Proxy', 'Upgrade Successful'])
return this.call('udapp', 'resolveContractAndAddInstance', newImplementationContractObject, proxyAddress)
}
this.runTx(args, confirmationCb, continueCb, promptCb, finalCb)
}
async getEncodedFunctionHex (args, funABI) {
return new Promise((resolve, reject) => {
txFormat.encodeFunctionCall(args, funABI, (error, data) => {

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

@ -5,7 +5,7 @@ const introJs = require('intro.js')
const profile = {
name: 'walkthrough',
displayName: 'Walkthrough',
description: '',
description: 'Remix walkthrough for beginner',
version: packageJson.version,
methods: ['start']
}

@ -88,4 +88,18 @@ export const UUPSfunAbi = {
type: "constructor",
outputs: [],
stateMutability: "payable"
}
export const UUPSupgradeAbi = {
"inputs": [
{
"internalType": "address",
"name": "newImplementation",
"type": "address"
}
],
"name": "upgradeTo",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
}

@ -1,12 +1,12 @@
import { Plugin } from '@remixproject/engine';
import { ContractABI, ContractAST, DeployOption } from '../types/contract';
import { UUPS, UUPSABI, UUPSBytecode, UUPSfunAbi } from './constants/uups';
import { Plugin } from '@remixproject/engine'
import { ContractABI, ContractAST, ContractSources, DeployOptions } from '../types/contract'
import { UUPS, UUPSABI, UUPSBytecode, UUPSfunAbi, UUPSupgradeAbi } from './constants/uups'
const proxyProfile = {
name: 'openzeppelin-proxy',
displayName: 'openzeppelin-proxy',
description: 'openzeppelin-proxy',
methods: ['isConcerned', 'execute', 'getDeployOptions']
methods: ['isConcerned', 'executeUUPSProxy', 'executeUUPSContractUpgrade', 'getProxyOptions', 'getUpgradeOptions']
};
export class OpenZeppelinProxy extends Plugin {
blockchain: any
@ -28,26 +28,37 @@ export class OpenZeppelinProxy extends Plugin {
return false
}
async getDeployOptions (contracts: ContractABI): Promise<{ [name: string]: DeployOption }> {
async getProxyOptions (data: ContractSources, file: string): Promise<{ [name: string]: DeployOptions }> {
const contracts = data.contracts[file]
const ast = data.sources[file].ast
const inputs = {}
if (this.kind === 'UUPS') {
Object.keys(contracts).map(name => {
const abi = contracts[name].abi
const initializeInput = abi.find(node => node.name === 'initialize')
if (ast) {
const UUPSSymbol = ast.exportedSymbols['UUPSUpgradeable'] ? ast.exportedSymbols['UUPSUpgradeable'][0] : null
if (initializeInput) {
inputs[name] = {
inputs: initializeInput,
initializeInputs: this.blockchain.getInputs(initializeInput)
}
ast.absolutePath === file && ast.nodes.map((node) => {
if (node.name === name && node.linearizedBaseContracts.includes(UUPSSymbol)) {
const abi = contracts[name].abi
const initializeInput = abi.find(node => node.name === 'initialize')
inputs[name] = {
options: [{ title: 'Deploy with Proxy', active: false }, { title: 'Upgrade with Proxy', active: false }],
initializeOptions: {
inputs: initializeInput,
initializeInputs: initializeInput ? this.blockchain.getInputs(initializeInput) : null
}
}
}
})
}
})
}
return inputs
}
async execute(implAddress: string, args: string | string [] = '', initializeABI, implementationContractObject): Promise<void> {
async executeUUPSProxy(implAddress: string, args: string | string [] = '', initializeABI, implementationContractObject): Promise<void> {
// deploy the proxy, or use an existing one
if (!initializeABI) throw new Error('Cannot deploy proxy: Missing initialize ABI')
args = args === '' ? [] : args
@ -56,6 +67,13 @@ export class OpenZeppelinProxy extends Plugin {
if (this.kind === 'UUPS') this.deployUUPSProxy(implAddress, _data, implementationContractObject)
}
async executeUUPSContractUpgrade (proxyAddress: string, newImplAddress: string, newImplementationContractObject): Promise<void> {
if (!newImplAddress) throw new Error('Cannot upgrade: Missing implementation address')
if (!proxyAddress) throw new Error('Cannot upgrade: Missing proxy address')
if (this.kind === 'UUPS') this.upgradeUUPSProxy(proxyAddress, newImplAddress, newImplementationContractObject)
}
async deployUUPSProxy (implAddress: string, _data: string, implementationContractObject): Promise<void> {
const args = [implAddress, _data]
const constructorData = await this.blockchain.getEncodedParams(args, UUPSfunAbi)
@ -74,4 +92,20 @@ export class OpenZeppelinProxy extends Plugin {
implementationContractObject.name = proxyName
this.blockchain.deployProxy(data, implementationContractObject)
}
async upgradeUUPSProxy (proxyAddress: string, newImplAddress: string, newImplementationContractObject): Promise<void> {
const fnData = await this.blockchain.getEncodedFunctionHex([newImplAddress], UUPSupgradeAbi)
const proxyName = 'ERC1967Proxy'
const data = {
contractABI: UUPSABI,
contractName: proxyName,
funAbi: UUPSupgradeAbi,
funArgs: [newImplAddress],
linkReferences: {},
dataHex: fnData.replace('0x', '')
}
// re-use implementation contract's ABI for UI display in udapp and change name to proxy name.
newImplementationContractObject.name = proxyName
this.blockchain.upgradeProxy(proxyAddress, newImplAddress, data, newImplementationContractObject)
}
}

@ -54,101 +54,132 @@ export interface ContractAST {
}[]
}
export interface ContractABI {
[key: string]: {
abi: ({
inputs: never[];
stateMutability: string;
type: string;
anonymous?: undefined;
name?: undefined;
outputs?: undefined;
} | {
anonymous: boolean;
inputs: {
indexed: boolean;
internalType: string;
name: string;
type: string;
}[];
name: string;
type: string;
stateMutability?: undefined;
outputs?: undefined;
} | {
inputs: {
internalType: string;
name: string;
type: string;
}[];
name: string;
outputs: {
internalType: string;
name: string;
type: string;
}[];
stateMutability: string;
type: string;
anonymous?: undefined;
})[];
devdoc: {
kind: string;
methods: {
[key: string]: {
[key: string]: string
}
};
version: number;
};
evm: any
metadata: string;
storageLayout: {
storage: {
astId: number;
contract: string;
label: string;
offset: number;
slot: string;
type: string;
}[];
types: {
[key: string]: {
base: string;
encoding: string;
label: string;
numberOfBytes: string;
members?: {
astId: number;
contract: string;
label: string;
offset: number;
slot: string;
type: string;
}[];
};
};
};
userdoc: {
kind: string;
methods: any;
version: number;
};
};
export type ContractABI = {
inputs: [];
stateMutability: string;
type: string;
anonymous?: undefined;
name?: string;
outputs?: undefined;
} | {
anonymous: boolean;
inputs: {
indexed: boolean;
internalType: string;
name: string;
type: string;
}[];
name: string;
type: string;
stateMutability?: undefined;
outputs?: undefined;
} | {
inputs: {
internalType: string;
name: string;
type: string;
}[];
name: string;
outputs: {
internalType: string;
name: string;
type: string;
}[];
stateMutability: string;
type: string;
anonymous?: undefined;
}
export type DeployMode = 'Deploy with Proxy' | 'Upgrade with Proxy'
export type DeployOption = {
initializeInputs: string,
initializeInputs: string,
inputs: {
inputs: {
inputs: {
internalType?: string,
name: string,
type: string
}[],
name: "initialize",
outputs?: any[],
stateMutability: string,
type: string,
payable?: boolean,
constant?: any
internalType?: string,
name: string,
type: string
}[],
name: "initialize",
outputs?: any[],
stateMutability: string,
type: string,
payable?: boolean,
constant?: any
}
}
export interface DeployOptions {
initializeOptions: DeployOption,
options: { title: DeployMode, active: boolean }[]
}
export interface ContractSources {
contracts: {
[path: string]: {
[contractName: string]: {
abi: ContractABI[],
devdoc: {
kind: string
methods: {
[key: string]: {
[key: string]: string
}
};
version: number
}
evm: any
metadata: string
storageLayout: {
storage: {
astId: number
contract: string
label: string
offset: number
slot: string
type: string
}[]
types: {
[key: string]: {
base: string
encoding: string
label: string
numberOfBytes: string
members?: {
astId: number
contract: string
label: string
offset: number
slot: string
type: string
}[]
}
}
}
userdoc: {
kind: string
methods: any
version: number
}
}
}
},
error: {
component: string,
errorCode: string,
formattedMessage: string,
message: string,
severity: string,
sourceLocation: {
end: number,
file: string,
start: number
},
type: string
}[],
sources: {
[path: string]: {
ast: ContractAST,
id: number
}
}
}

@ -89,3 +89,29 @@ export const cancelProxyMsg = () => (
<b>Proxy deployment cancelled.</b>
</div>
)
export const cancelUpgradeMsg = () => (
<div>
<b>Upgrade with proxy cancelled.</b>
</div>
)
export const deployWithProxyMsg = () => (
<div>
<b>Deploy with Proxy</b> will initiate two (2) transactions:
<ol className="pl-3">
<li>Deploying the implementation contract</li>
<li>Deploying an ERC1967 proxy contract</li>
</ol>
</div>
)
export const upgradeWithProxyMsg = () => (
<div>
<b>Upgrade with Proxy</b> will initiate two (2) transactions:
<ol className="pl-3">
<li>Deploying the new implementation contract</li>
<li>Updating the proxy contract with the address of the new implementation contract</li>
</ol>
</div>
)

@ -5,10 +5,11 @@ import { PluginRecord } from '../types'
import './panel.css'
export interface RemixPanelProps {
plugins: Record<string, PluginRecord>;
}
plugins: Record<string, PluginRecord>;
}
const RemixUIPanelHeader = (props: RemixPanelProps) => {
const [plugin, setPlugin] = useState<PluginRecord>()
const [toggleExpander, setToggleExpander] = useState<boolean>(false)
useEffect(() => {
if (props.plugins) {
@ -19,13 +20,51 @@ const RemixUIPanelHeader = (props: RemixPanelProps) => {
}
}, [props])
const toggleClass = () => {
setToggleExpander(!toggleExpander)
}
return (
<header className='swapitHeader'>
<header className='d-flex flex-column'>
<div className="swapitHeader px-3 pt-3 pb-0 d-flex flex-row">
<h6 data-id='sidePanelSwapitTitle'>
{/* @ts-ignore */}
<FormattedMessage id={plugin?.profile.intlId || 'defaultId'} defaultMessage={plugin?.profile.displayName || plugin?.profile.name} />
</h6>
{plugin?.profile.documentation ? (<a href={plugin.profile.documentation} className="titleInfo mb-2" title="link to documentation" target="_blank" rel="noreferrer"><i aria-hidden="true" className="fas fa-book"></i></a>) : ''}
<div className="d-flex flex-row">
<div className="d-flex flex-row">
{plugin?.profile?.maintainedBy?.toLowerCase() === "remix" && (<i aria-hidden="true" className="text-success fas fa-check" title="Maintained by Remix"></i>)}
{plugin?.profile.documentation && (<a href={plugin.profile.documentation} className="titleInfo mb-2" title="link to documentation" target="_blank" rel="noreferrer"><i aria-hidden="true" className="fas fa-book"></i></a>)}
</div>
<div className="swapitHeaderInfoSection d-flex justify-content-between" data-id='swapitHeaderInfoSectionId' onClick={toggleClass} title="Plugin info">
<i className={`px-2 ml-2 pt-1 ${!toggleExpander ? 'fas fa-angle-right' : 'fas fa-angle-down bg-light'}`} aria-hidden="true"></i>
</div>
</div>
</div>
<div className={`bg-light p-3 pt-1 border-bottom flex-column ${toggleExpander ? "d-flex" : "d-none"}`}>
{plugin?.profile?.author && <span className="d-flex flex-row align-items-center">
<label className="mb-0 pr-2">Author:</label>
<span> { plugin?.profile.author } </span>
</span>}
{plugin?.profile?.maintainedBy && <span className="d-flex flex-row align-items-center">
<label className="mb-0 pr-2">Maintained by:</label>
<span> { plugin?.profile.maintainedBy } </span>
</span>}
{plugin?.profile?.documentation && <span className="d-flex flex-row align-items-center">
<label className="mb-0 pr-2">Documentation:</label>
<span>
<a href={plugin?.profile?.documentation} className="titleInfo mb-2" title="link to documentation" target="_blank" rel="noreferrer"><i aria-hidden="true" className="fas fa-book"></i></a>
</span>
</span>}
{plugin?.profile?.description && <span className="d-flex flex-row align-items-baseline">
<label className="mb-0 pr-2">Description:</label>
<span> { plugin?.profile.description } </span>
</span>}
{plugin?.profile?.repo && <span className="d-flex flex-row align-items-center">
<a href={plugin?.profile?.repo} target="_blank" rel="noreferrer">
Make an issue</a>
</span>}
</div>
</header>)
}

@ -22,7 +22,6 @@
.swapitHeader {
display: flex;
align-items: center;
padding: 16px 24px 15px;
justify-content: space-between;
text-transform: uppercase;
}

@ -19,6 +19,9 @@ function ActivePluginCard ({
<h6 className="remixui_displayName plugin-name">
<div>
{ profile.displayName || profile.name }
{ profile?.maintainedBy?.toLowerCase() == "remix" &&
<i aria-hidden="true" className="px-1 text-success fas fa-check" title="Maintained by Remix"></i>
}
{ profile.documentation &&
<a href={profile.documentation} className="px-1" title="link to documentation" target="_blank" rel="noreferrer">
<i aria-hidden="true" className="fas fa-book"/>

@ -22,6 +22,9 @@ function InactivePluginCard ({
<h6 className="remixui_displayName plugin-name">
<div>
{ profile.displayName || profile.name }
{ profile?.maintainedBy?.toLowerCase() == "remix" &&
<i aria-hidden="true" className="px-1 text-success fas fa-check" title="Verified by Remix"></i>
}
{ profile.documentation &&
<a href={profile.documentation} className="px-1" title="link to documentation" target="_blank" rel="noreferrer">
<i aria-hidden="true" className="fas fa-book"/>

@ -135,6 +135,7 @@ export const createInstance = async (
args,
deployMode: DeployMode[]) => {
const isProxyDeployment = (deployMode || []).find(mode => mode === 'Deploy with Proxy')
const isContractUpgrade = (deployMode || []).find(mode => mode === 'Upgrade with Proxy')
const statusCb = (msg: string) => {
const log = logBuilder(msg)
@ -160,7 +161,9 @@ export const createInstance = async (
if (isProxyDeployment) {
const initABI = contractObject.abi.find(abi => abi.name === 'initialize')
plugin.call('openzeppelin-proxy', 'execute', addressToString(address), args, initABI, contractObject)
plugin.call('openzeppelin-proxy', 'executeUUPSProxy', addressToString(address), args, initABI, contractObject)
} else if (isContractUpgrade) {
plugin.call('openzeppelin-proxy', 'executeUUPSContractUpgrade', args, addressToString(address), contractObject)
}
}
@ -176,7 +179,7 @@ export const createInstance = async (
if (selectedContract.isOverSizeLimit()) {
return dispatch(displayNotification('Contract code size over limit', isOverSizePrompt(), 'Force Send', 'Cancel', () => {
deployContract(plugin, selectedContract, !isProxyDeployment ? args : '', contractMetadata, compilerContracts, {
deployContract(plugin, selectedContract, !isProxyDeployment && !isContractUpgrade ? args : '', contractMetadata, compilerContracts, {
continueCb: (error, continueTxExecution, cancelCb) => {
continueHandler(dispatch, gasEstimationPrompt, error, continueTxExecution, cancelCb)
},
@ -192,7 +195,7 @@ export const createInstance = async (
return terminalLogger(plugin, log)
}))
}
deployContract(plugin, selectedContract, !isProxyDeployment ? args : '', contractMetadata, compilerContracts, {
deployContract(plugin, selectedContract, !isProxyDeployment && !isContractUpgrade ? args : '', contractMetadata, compilerContracts, {
continueCb: (error, continueTxExecution, cancelCb) => {
continueHandler(dispatch, gasEstimationPrompt, error, continueTxExecution, cancelCb)
},

@ -2,7 +2,7 @@ import { envChangeNotification } from "@remix-ui/helper"
import { RunTab } from "../types/run-tab"
import { setExecutionContext, setFinalContext, updateAccountBalances } from "./account"
import { addExternalProvider, addInstance, removeExternalProvider, setNetworkNameFromProvider } from "./actions"
import { clearAllInstances, clearRecorderCount, fetchContractListSuccess, resetUdapp, setCurrentContract, setCurrentFile, setDeployOptions, setLoadType, setRecorderCount, setSendValue } from "./payload"
import { addDeployOption, clearAllInstances, clearRecorderCount, fetchContractListSuccess, resetUdapp, setCurrentContract, setCurrentFile, setLoadType, setProxyEnvAddress, setRecorderCount, setSendValue } from "./payload"
import { CompilerAbstract } from '@remix-project/remix-solidity'
import * as ethJSUtil from 'ethereumjs-util'
import Web3 from 'web3'
@ -24,7 +24,7 @@ export const setupEvents = (plugin: RunTab, dispatch: React.Dispatch<any>) => {
plugin.blockchain.event.register('networkStatus', ({ error, network }) => {
if (error) {
const netUI = 'can\'t detect network '
const netUI = 'can\'t detect network'
setNetworkNameFromProvider(dispatch, netUI)
return
@ -33,6 +33,8 @@ export const setupEvents = (plugin: RunTab, dispatch: React.Dispatch<any>) => {
const netUI = (networkProvider() !== 'vm') ? `${network.name} (${network.id || '-'}) network` : 'VM'
setNetworkNameFromProvider(dispatch, netUI)
if (network.name === 'VM') dispatch(setProxyEnvAddress(plugin.config.get('vm/proxy')))
else dispatch(setProxyEnvAddress(plugin.config.get(`${network.name}/${network.currentFork}/${network.id}/proxy`)))
})
plugin.blockchain.event.register('addProvider', provider => addExternalProvider(dispatch, provider))
@ -106,11 +108,12 @@ const broadcastCompilationResult = async (plugin: RunTab, dispatch: React.Dispat
const isUpgradeable = await plugin.call('openzeppelin-proxy', 'isConcerned', data.sources[file] ? data.sources[file].ast : {})
if (isUpgradeable) {
const options = await plugin.call('openzeppelin-proxy', 'getDeployOptions', data.contracts[file])
const options = await plugin.call('openzeppelin-proxy', 'getProxyOptions', data, file)
dispatch(setDeployOptions({ options: [{ title: 'Deploy with Proxy', active: false }], initializeOptions: options }))
dispatch(addDeployOption({ [file]: options }))
} else {
dispatch(addDeployOption({ [file]: {} }))
}
else dispatch(setDeployOptions({} as any))
dispatch(fetchContractListSuccess({ [file]: contracts }))
dispatch(setCurrentFile(file))
// TODO: set current contract

@ -1,6 +1,6 @@
import { ContractList } from '../reducers/runTab'
import { ContractData } from '@remix-project/core-plugin'
import { ADD_DEPLOY_OPTION, ADD_INSTANCE, ADD_PROVIDER, CLEAR_INSTANCES, CLEAR_RECORDER_COUNT, DISPLAY_NOTIFICATION, DISPLAY_POPUP_MESSAGE, FETCH_ACCOUNTS_LIST_FAILED, FETCH_ACCOUNTS_LIST_REQUEST, FETCH_ACCOUNTS_LIST_SUCCESS, FETCH_CONTRACT_LIST_FAILED, FETCH_CONTRACT_LIST_REQUEST, FETCH_CONTRACT_LIST_SUCCESS, HIDE_NOTIFICATION, HIDE_POPUP_MESSAGE, REMOVE_DEPLOY_OPTION, REMOVE_INSTANCE, REMOVE_PROVIDER, RESET_STATE, SET_BASE_FEE_PER_GAS, SET_CONFIRM_SETTINGS, SET_CURRENT_CONTRACT, SET_CURRENT_FILE, SET_DECODED_RESPONSE, SET_DEPLOY_OPTIONS, SET_EXECUTION_ENVIRONMENT, SET_EXTERNAL_WEB3_ENDPOINT, SET_GAS_LIMIT, SET_GAS_PRICE, SET_GAS_PRICE_STATUS, SET_IPFS_CHECKED_STATE, SET_LOAD_TYPE, SET_MATCH_PASSPHRASE, SET_MAX_FEE, SET_MAX_PRIORITY_FEE, SET_NETWORK_NAME, SET_PASSPHRASE, SET_PATH_TO_SCENARIO, SET_PERSONAL_MODE, SET_RECORDER_COUNT, SET_SELECTED_ACCOUNT, SET_SEND_UNIT, SET_SEND_VALUE, SET_TX_FEE_CONTENT } from '../constants'
import { ADD_DEPLOY_OPTION, ADD_INSTANCE, ADD_PROVIDER, CLEAR_INSTANCES, CLEAR_RECORDER_COUNT, DISPLAY_NOTIFICATION, DISPLAY_POPUP_MESSAGE, FETCH_ACCOUNTS_LIST_FAILED, FETCH_ACCOUNTS_LIST_REQUEST, FETCH_ACCOUNTS_LIST_SUCCESS, FETCH_CONTRACT_LIST_FAILED, FETCH_CONTRACT_LIST_REQUEST, FETCH_CONTRACT_LIST_SUCCESS, HIDE_NOTIFICATION, HIDE_POPUP_MESSAGE, REMOVE_DEPLOY_OPTION, REMOVE_INSTANCE, REMOVE_PROVIDER, RESET_STATE, SET_BASE_FEE_PER_GAS, SET_CONFIRM_SETTINGS, SET_CURRENT_CONTRACT, SET_CURRENT_FILE, SET_DECODED_RESPONSE, SET_DEPLOY_OPTIONS, SET_EXECUTION_ENVIRONMENT, SET_EXTERNAL_WEB3_ENDPOINT, SET_GAS_LIMIT, SET_GAS_PRICE, SET_GAS_PRICE_STATUS, SET_IPFS_CHECKED_STATE, SET_LOAD_TYPE, SET_MATCH_PASSPHRASE, SET_MAX_FEE, SET_MAX_PRIORITY_FEE, SET_NETWORK_NAME, SET_PASSPHRASE, SET_PATH_TO_SCENARIO, SET_PERSONAL_MODE, SET_PROXY_ENV_ADDRESS, SET_RECORDER_COUNT, SET_SELECTED_ACCOUNT, SET_SEND_UNIT, SET_SEND_VALUE, SET_TX_FEE_CONTENT } from '../constants'
import { DeployMode, DeployOptions } from '../types'
export const fetchAccountsListRequest = () => {
@ -280,21 +280,21 @@ export const resetUdapp = () => {
}
}
export const addDeployOption = (deployOption: DeployOptions) => {
export const addDeployOption = (deployOption: { [file: string]: { [name: string]: DeployOptions } }) => {
return {
payload: deployOption,
type: ADD_DEPLOY_OPTION
}
}
export const removeDeployOption = (title: DeployMode) => {
export const removeDeployOption = (file: string) => {
return {
payload: title,
payload: file,
type: REMOVE_DEPLOY_OPTION
}
}
export const setDeployOptions = (deployOptions: DeployOptions) => {
export const setDeployOptions = (deployOptions: { [file: string]: { [name: string]: DeployOptions } }) => {
return {
payload: deployOptions,
type: SET_DEPLOY_OPTIONS
@ -307,3 +307,10 @@ export const setCurrentContract = (contractName: string) => {
type: SET_CURRENT_CONTRACT
}
}
export const setProxyEnvAddress = (key: string) => {
return {
payload: key,
type: SET_PROXY_ENV_ADDRESS
}
}

@ -5,6 +5,7 @@ import { ContractDropdownProps, DeployMode } from '../types'
import { ContractData, FuncABI } from '@remix-project/core-plugin'
import * as ethJSUtil from 'ethereumjs-util'
import { ContractGUI } from './contractGUI'
import { deployWithProxyMsg, upgradeWithProxyMsg } from '@remix-ui/helper'
export function ContractDropdownUI (props: ContractDropdownProps) {
const intl = useIntl()
@ -29,7 +30,7 @@ export function ContractDropdownUI (props: ContractDropdownProps) {
const [constructorInterface, setConstructorInterface] = useState<FuncABI>(null)
const [constructorInputs, setConstructorInputs] = useState(null)
const contractsRef = useRef<HTMLSelectElement>(null)
const { contractList, loadType, currentFile, currentContract, compilationCount, deployOptions } = props.contracts
const { contractList, loadType, currentFile, currentContract, compilationCount, deployOptions, proxyKey } = props.contracts
useEffect(() => {
enableAtAddress(false)
@ -157,7 +158,21 @@ export function ContractDropdownUI (props: ContractDropdownProps) {
if (selectedContract.bytecodeObject.length === 0) {
return props.modal('Alert', 'This contract may be abstract, it may not implement an abstract parent\'s methods completely or it may not invoke an inherited contract\'s constructor correctly.', 'OK', () => {})
}
props.createInstance(loadedContractData, props.gasEstimationPrompt, props.passphrasePrompt, props.publishToStorage, props.mainnetPrompt, isOverSizePrompt, args, deployMode)
if ((selectedContract.name !== currentContract) && (selectedContract.name === 'ERC1967Proxy')) selectedContract.name = currentContract
const isProxyDeployment = (deployMode || []).find(mode => mode === 'Deploy with Proxy')
const isContractUpgrade = (deployMode || []).find(mode => mode === 'Upgrade with Proxy')
if (isProxyDeployment) {
props.modal('Deploy Implementation & Proxy (ERC1967)', deployWithProxyMsg(), 'Proceed', () => {
props.createInstance(loadedContractData, props.gasEstimationPrompt, props.passphrasePrompt, props.publishToStorage, props.mainnetPrompt, isOverSizePrompt, args, deployMode)
}, 'Cancel', () => {})
} else if (isContractUpgrade) {
props.modal('Deploy Implementation & Update Proxy', upgradeWithProxyMsg(), 'Proceed', () => {
props.createInstance(loadedContractData, props.gasEstimationPrompt, props.passphrasePrompt, props.publishToStorage, props.mainnetPrompt, isOverSizePrompt, args, deployMode)
}, 'Cancel', () => {})
} else {
props.createInstance(loadedContractData, props.gasEstimationPrompt, props.passphrasePrompt, props.publishToStorage, props.mainnetPrompt, isOverSizePrompt, args, deployMode)
}
}
const atAddressChanged = (event) => {
@ -236,14 +251,15 @@ export function ContractDropdownUI (props: ContractDropdownProps) {
<ContractGUI
title='Deploy'
isDeploy={true}
deployOption={deployOptions.options}
initializerOptions={deployOptions.initializeOptions ? deployOptions.initializeOptions[currentContract] : null}
deployOption={deployOptions[currentFile] && deployOptions[currentFile][currentContract] ? deployOptions[currentFile][currentContract].options : null}
initializerOptions={deployOptions[currentFile] && deployOptions[currentFile][currentContract] ? deployOptions[currentFile][currentContract].initializeOptions : null}
funcABI={constructorInterface}
clickCallBack={clickCallback}
inputs={constructorInputs}
widthClass='w-50'
evmBC={loadedContractData.bytecodeObject}
lookupOnly={false}
savedProxyAddress={proxyKey}
/>
<div className="d-flex py-1 align-items-center custom-control custom-checkbox">
<input

@ -3,8 +3,6 @@ import React, { useEffect, useRef, useState } from 'react'
import * as remixLib from '@remix-project/remix-lib'
import { ContractGUIProps } from '../types'
import { CopyToClipboard } from '@remix-ui/clipboard'
import { MultiDeployInput } from './multiDeployInput'
import { DeployInput } from './deployInput'
const txFormat = remixLib.execution.txFormat
export function ContractGUI (props: ContractGUIProps) {
@ -15,20 +13,15 @@ export function ContractGUI (props: ContractGUIProps) {
title: string,
content: string,
classList: string,
dataId: string,
widthClass: string
}>({ title: '', content: '', classList: '', dataId: '', widthClass: '' })
const [selectedDeployIndex, setSelectedDeployIndex] = useState<number>(null)
const [showOptions, setShowOptions] = useState<boolean>(false)
const [hasArgs, setHasArgs] = useState<boolean>(false)
const [isMultiField, setIsMultiField] = useState<boolean>(false)
const [deployInputs, setDeployInputs] = useState<{
internalType?: string,
name: string,
type: string
}[]>([])
const [deployPlaceholder, setDeployPlaceholder] = useState<string>('')
dataId: string
}>({ title: '', content: '', classList: '', dataId: '' })
const [toggleDeployProxy, setToggleDeployProxy] = useState<boolean>(false)
const [toggleUpgradeImp, setToggleUpgradeImp] = useState<boolean>(false)
const [deployState, setDeployState] = useState<{ deploy: boolean, upgrade: boolean }>({ deploy: false, upgrade: false })
const [useLastProxy, setUseLastProxy] = useState<boolean>(false)
const [proxyAddress, setProxyAddress] = useState<string>('')
const multiFields = useRef<Array<HTMLInputElement | null>>([])
const initializeFields = useRef<Array<HTMLInputElement | null>>([])
const basicInputRef = useRef<HTMLInputElement>()
useEffect(() => {
@ -41,7 +34,7 @@ export function ContractGUI (props: ContractGUIProps) {
}
setBasicInput('')
// we have the reset the fields before reseting the previous references.
if (basicInputRef.current) basicInputRef.current.value = ''
basicInputRef.current.value = ''
multiFields.current.filter((el) => el !== null && el !== undefined).forEach((el) => el.value = '')
multiFields.current = []
}, [props.title, props.funcABI])
@ -53,8 +46,7 @@ export function ContractGUI (props: ContractGUIProps) {
title: title + ' - call',
content: 'call',
classList: 'btn-info',
dataId: title + ' - call',
widthClass: props.widthClass
dataId: title + ' - call'
})
} else if (props.funcABI.stateMutability === 'payable' || props.funcABI.payable) {
// // transact. stateMutability = payable
@ -62,8 +54,7 @@ export function ContractGUI (props: ContractGUIProps) {
title: title + ' - transact (payable)',
content: 'transact',
classList: 'btn-danger',
dataId: title + ' - transact (payable)',
widthClass: props.widthClass
dataId: title + ' - transact (payable)'
})
} else {
// // transact. stateMutability = nonpayable
@ -71,59 +62,13 @@ export function ContractGUI (props: ContractGUIProps) {
title: title + ' - transact (not payable)',
content: 'transact',
classList: 'btn-warning',
dataId: title + ' - transact (not payable)',
widthClass: props.widthClass
dataId: title + ' - transact (not payable)'
})
}
}, [props.lookupOnly, props.funcABI, title])
useEffect(() => {
if (props.deployOption && props.deployOption[selectedDeployIndex]) {
if (props.deployOption[selectedDeployIndex].title === 'Deploy with Proxy') {
if (props.initializerOptions) {
setDeployInputs(props.initializerOptions.inputs.inputs)
setDeployPlaceholder(props.initializerOptions.initializeInputs)
setHasArgs(true)
if (props.initializerOptions.inputs.inputs.length > 1) setIsMultiField(true)
else setIsMultiField(false)
} else {
setDeployInputs([])
setDeployPlaceholder('')
setHasArgs(false)
setIsMultiField(false)
}
} else {
if (props.funcABI) {
setDeployInputs(props.funcABI.inputs)
setDeployPlaceholder(props.inputs)
setHasArgs(true)
if (props.funcABI.inputs.length > 1) setIsMultiField(true)
else setIsMultiField(false)
} else {
setDeployInputs([])
setDeployPlaceholder('')
setHasArgs(false)
setIsMultiField(false)
}
}
} else {
if (props.funcABI) {
setDeployInputs(props.funcABI.inputs)
setDeployPlaceholder(props.inputs)
setHasArgs(true)
if (props.funcABI.inputs.length > 1) setIsMultiField(true)
else setIsMultiField(false)
} else {
setDeployInputs([])
setDeployPlaceholder('')
setHasArgs(false)
setIsMultiField(false)
}
}
}, [selectedDeployIndex, props.funcABI, props.initializerOptions])
const getContentOnCTC = (fields: HTMLInputElement[]) => {
const multiString = getMultiValsString(fields)
const getContentOnCTC = () => {
const multiString = getMultiValsString(multiFields.current)
// copy-to-clipboard icon is only visible for method requiring input params
if (!multiString) {
return 'cannot encode empty arguments'
@ -191,8 +136,7 @@ export function ContractGUI (props: ContractGUIProps) {
if (inputString) {
inputString = inputString.replace(/(^|,\s+|,)(\d+)(\s+,|,|$)/g, '$1"$2"$3') // replace non quoted number by quoted number
inputString = inputString.replace(/(^|,\s+|,)(0[xX][0-9a-fA-F]+)(\s+,|,|$)/g, '$1"$2"$3') // replace non quoted hex string by quoted hex string
inputString = JSON.stringify([inputString])
const inputJSON = JSON.parse(inputString)
const inputJSON = JSON.parse('[' + inputString + ']')
const multiInputs = multiFields.current
for (let k = 0; k < multiInputs.length; k++) {
@ -204,9 +148,15 @@ export function ContractGUI (props: ContractGUIProps) {
}
const handleActionClick = () => {
const deployMode = selectedDeployIndex !== null ? [props.deployOption[selectedDeployIndex].title] : []
if (deployState.deploy) {
const proxyInitializeString = getMultiValsString(initializeFields.current)
props.clickCallBack(props.funcABI.inputs, basicInput, deployMode)
props.clickCallBack(props.initializerOptions.inputs.inputs, proxyInitializeString, ['Deploy with Proxy'])
} else if (deployState.upgrade) {
props.clickCallBack(props.funcABI.inputs, proxyAddress, ['Upgrade with Proxy'])
} else {
props.clickCallBack(props.funcABI.inputs, basicInput)
}
}
const handleBasicInput = (e) => {
@ -215,90 +165,197 @@ export function ContractGUI (props: ContractGUIProps) {
setBasicInput(value)
}
const handleMultiValsSubmit = (fields: HTMLInputElement[]) => {
const valsString = getMultiValsString(fields)
const deployMode = selectedDeployIndex !== null ? [props.deployOption[selectedDeployIndex].title] : []
const handleExpandMultiClick = () => {
const valsString = getMultiValsString(multiFields.current)
if (valsString) {
props.clickCallBack(props.funcABI.inputs, valsString, deployMode)
props.clickCallBack(props.funcABI.inputs, valsString)
} else {
props.clickCallBack(props.funcABI.inputs, '', deployMode)
props.clickCallBack(props.funcABI.inputs, '')
}
}
const setSelectedDeploy = (index: number) => {
setSelectedDeployIndex(index !== selectedDeployIndex ? index : null)
if (basicInputRef.current) basicInputRef.current.value = ''
setBasicInput('')
const handleToggleDeployProxy = () => {
setToggleDeployProxy(!toggleDeployProxy)
}
const handleDeployProxySelect = (e) => {
const value = e.target.checked
if (value) setToggleUpgradeImp(false)
setToggleDeployProxy(value)
setDeployState({ upgrade: false, deploy: value })
}
const handleToggleUpgradeImp = () => {
setToggleUpgradeImp(!toggleUpgradeImp)
}
const handleUpgradeImpSelect = (e) => {
const value = e.target.checked
setToggleUpgradeImp(value)
if (value) {
setToggleDeployProxy(false)
if (useLastProxy) setProxyAddress(props.savedProxyAddress)
}
setDeployState({ deploy: false, upgrade: value })
}
const toggleOptions = () => {
setShowOptions(!showOptions)
const handleUseLastProxySelect = (e) => {
const value = e.target.checked
const address = props.savedProxyAddress
setUseLastProxy(value)
setProxyAddress(address || '')
}
const handleSetProxyAddress = (e) => {
const value = e.target.value
setProxyAddress(value)
}
return (
<div className={`udapp_contractProperty ${hasArgs ? 'udapp_hasArgs' : ''}`}>
{
props.isDeploy ? !isMultiField ?
<DeployInput
buttonOptions={buttonOptions}
funcABI={props.initializerOptions ? props.initializerOptions.inputs : props.funcABI}
inputs={deployPlaceholder}
handleBasicInput={handleBasicInput}
basicInputRef={basicInputRef}
selectedIndex={selectedDeployIndex}
setSelectedIndex={setSelectedDeploy}
handleActionClick={handleActionClick}
deployOptions={props.deployOption}
/> : <MultiDeployInput
buttonOptions={buttonOptions}
selectedIndex={selectedDeployIndex}
setSelectedIndex={setSelectedDeploy}
handleMultiValsSubmit={handleMultiValsSubmit}
inputs={deployInputs}
getMultiValsString={getMultiValsString}
deployOptions={props.deployOption}
/> :
<>
<div className="udapp_contractActionsContainerSingle pt-2" style={{ display: toggleContainer ? 'none' : 'flex' }}>
<button onClick={handleActionClick} title={buttonOptions.title} className={`udapp_instanceButton ${props.widthClass} btn btn-sm ${buttonOptions.classList}`} data-id={buttonOptions.dataId}>{title}</button>
<input
className="form-control"
data-id={props.funcABI.type === 'fallback' || props.funcABI.type === 'receive' ? `'(${props.funcABI.type}')` : 'multiParamManagerBasicInputField'}
placeholder={props.inputs}
title={props.funcABI.type === 'fallback' || props.funcABI.type === 'receive' ? `'(${props.funcABI.type}')` : props.inputs}
onChange={handleBasicInput}
ref={basicInputRef}
style={{ visibility: !((props.funcABI.inputs && props.funcABI.inputs.length > 0) || (props.funcABI.type === 'fallback') || (props.funcABI.type === 'receive')) ? 'hidden' : 'visible' }} />
<i
className="fas fa-angle-down udapp_methCaret"
onClick={switchMethodViewOn}
title={title}
style={{ visibility: !(props.funcABI.inputs && props.funcABI.inputs.length > 0) ? 'hidden' : 'visible' }}></i>
<div className={`udapp_contractProperty ${(props.funcABI.inputs && props.funcABI.inputs.length > 0) || (props.funcABI.type === 'fallback') || (props.funcABI.type === 'receive') ? 'udapp_hasArgs' : ''}`}>
<div className="udapp_contractActionsContainerSingle pt-2" style={{ display: toggleContainer ? 'none' : 'flex' }}>
<button onClick={handleActionClick} title={buttonOptions.title} className={`udapp_instanceButton ${props.widthClass} btn btn-sm ${buttonOptions.classList}`} data-id={buttonOptions.dataId}>{title}</button>
<input
className="form-control"
data-id={props.funcABI.type === 'fallback' || props.funcABI.type === 'receive' ? `'(${props.funcABI.type}')` : 'multiParamManagerBasicInputField'}
placeholder={props.inputs}
title={props.funcABI.type === 'fallback' || props.funcABI.type === 'receive' ? `'(${props.funcABI.type}')` : props.inputs}
onChange={handleBasicInput}
ref={basicInputRef}
style={{ visibility: !((props.funcABI.inputs && props.funcABI.inputs.length > 0) || (props.funcABI.type === 'fallback') || (props.funcABI.type === 'receive')) ? 'hidden' : 'visible' }} />
<i
className="fas fa-angle-down udapp_methCaret"
onClick={switchMethodViewOn}
title={title}
style={{ visibility: !(props.funcABI.inputs && props.funcABI.inputs.length > 0) ? 'hidden' : 'visible' }}></i>
</div>
<div className="udapp_contractActionsContainerMulti" style={{ display: toggleContainer ? 'flex' : 'none' }}>
<div className="udapp_contractActionsContainerMultiInner text-dark">
<div onClick={switchMethodViewOff} className="udapp_multiHeader">
<div className="udapp_multiTitle run-instance-multi-title">{title}</div>
<i className='fas fa-angle-up udapp_methCaret'></i>
</div>
<div>
{props.funcABI.inputs.map((inp, index) => {
return (
<div className="udapp_multiArg" key={index}>
<label htmlFor={inp.name}> {inp.name}: </label>
<input ref={el => { multiFields.current[index] = el }} className="form-control" placeholder={inp.type} title={inp.name} data-id={`multiParamManagerInput${inp.name}`} />
</div>)
})}
</div>
<div className="udapp_group udapp_multiArg">
<CopyToClipboard tip='Encode values of input fields & copy to clipboard' icon='fa-clipboard' direction={'bottom'} getContent={getContentOnCTC} />
<button onClick={handleExpandMultiClick} title={buttonOptions.title} data-id={buttonOptions.dataId} className={`udapp_instanceButton ${buttonOptions.classList}`}>{ buttonOptions.content }</button>
</div>
</div>
<div className="udapp_contractActionsContainerMulti" style={{ display: toggleContainer ? 'flex' : 'none' }}>
<div className="udapp_contractActionsContainerMultiInner text-dark">
<div onClick={switchMethodViewOff} className="udapp_multiHeader">
<div className="udapp_multiTitle run-instance-multi-title">{title}</div>
<i className='fas fa-angle-up udapp_methCaret'></i>
</div>
{ props.deployOption && (props.deployOption || []).length > 0 ?
<>
<div className='d-flex justify-content-between'>
<div className="d-flex py-1 align-items-center custom-control custom-checkbox">
<input
id="deployWithProxy"
data-id="contractGUIDeployWithProxy"
className="form-check-input custom-control-input"
type="checkbox"
onChange={handleDeployProxySelect}
checked={deployState.deploy}
/>
<label
htmlFor="deployWithProxy"
data-id="contractGUIDeployWithProxyLabel"
className="m-0 form-check-label custom-control-label udapp_checkboxAlign"
title="An ERC1967 proxy contract will be deployed along with the selected implementation contract."
>
Deploy with Proxy
</label>
</div>
<div>
{props.funcABI.inputs.map((inp, index) => {
return (
<div className="udapp_multiArg" key={index}>
<label htmlFor={inp.name}> {inp.name}: </label>
<input ref={el => { multiFields.current[index] = el }} className="form-control" placeholder={inp.type} title={inp.name} data-id={`multiParamManagerInput${inp.name}`} />
</div>)
})}
{
props.initializerOptions && props.initializerOptions.initializeInputs ?
<span onClick={handleToggleDeployProxy}>
<i className={!toggleDeployProxy ? 'fas fa-angle-right pt-2' : 'fas fa-angle-down'} aria-hidden="true"></i>
</span> : null
}
</div>
<div className="udapp_group udapp_multiArg">
<CopyToClipboard tip='Encode values of input fields & copy to clipboard' icon='fa-clipboard' direction={'bottom'} getContent={() => getContentOnCTC(multiFields.current)} />
<button onClick={() => handleMultiValsSubmit(multiFields.current)} title={buttonOptions.title} data-id={buttonOptions.dataId} className={`udapp_instanceButton ${buttonOptions.classList}`}>{ buttonOptions.content }</button>
</div>
{
props.initializerOptions && props.initializerOptions.initializeInputs ?
<div className={`pl-4 flex-column ${toggleDeployProxy ? "d-flex" : "d-none"}`}>
<div className={`flex-column 'd-flex'}`}>{
props.initializerOptions.inputs.inputs.map((inp, index) => {
return (
<div className="mb-2" key={index}>
<label className='mt-2 text-left d-block' htmlFor={inp.name}> {inp.name}: </label>
<input ref={el => { initializeFields.current[index] = el }} style={{ height: 32 }} className="form-control udapp_input" placeholder={inp.type} title={inp.name} />
</div>
)})
}
</div>
</div> : null
}
<div className='d-flex justify-content-between'>
<div className="d-flex py-1 align-items-center custom-control custom-checkbox">
<input
id="upgradeImplementation"
data-id="contractGUIUpgradeImplementation"
className="form-check-input custom-control-input"
type="checkbox"
onChange={handleUpgradeImpSelect}
checked={deployState.upgrade}
/>
<label
htmlFor="upgradeImplementation"
data-id="contractGUIUpgradeImplementationLabel"
className="m-0 form-check-label custom-control-label udapp_checkboxAlign"
title="The implementation contract will be deployed and then the proxy contract will be updated with new implementation's address."
>
Upgrade with Proxy
</label>
</div>
<span onClick={handleToggleUpgradeImp}>
<i className={!toggleUpgradeImp ? 'fas fa-angle-right pt-2' : 'fas fa-angle-down'} aria-hidden="true"></i>
</span>
</div>
</div>
</>
}
<div className={`pl-4 flex-column ${toggleUpgradeImp ? "d-flex" : "d-none"}`}>
<div className={`flex-column 'd-flex'}`}>
<div className="d-flex py-1 align-items-center custom-control custom-checkbox">
<input
id="proxyAddress"
data-id="contractGUIProxyAddress"
className="form-check-input custom-control-input"
type="checkbox"
onChange={handleUseLastProxySelect}
checked={useLastProxy}
/>
<label
htmlFor="proxyAddress"
data-id="contractGUIProxyAddressLabel"
className="m-0 form-check-label custom-control-label udapp_checkboxAlign"
title="Select this option to use the last deployed ERC1967 contract on the current network."
style={{ fontSize: 12 }}
>
Use last deployed ERC1967 contract
</label>
</div>
{
!useLastProxy ?
<div className="mb-2">
<label className='mt-2 text-left d-block'>Proxy Address: </label>
<input style={{ height: 32 }} className="form-control udapp_input" placeholder='proxy address' title='Enter previously deployed proxy address on the selected network' onChange={handleSetProxyAddress} />
</div> :
<span className='text-capitalize' style={{ fontSize: '.8em' }}>{ proxyAddress || 'No proxy address available' }</span>
}
</div>
</div>
</> : null
}
</div>
)
}

@ -1,5 +1,5 @@
// eslint-disable-next-line no-use-before-define
import React, {useRef, useState} from 'react'
import React, {useRef, useState, useEffect} from 'react'
import { FormattedMessage } from 'react-intl'
import { RecorderProps } from '../types'
import { OverlayTrigger, Tooltip } from 'react-bootstrap' // eslint-disable-line
@ -7,6 +7,7 @@ import { OverlayTrigger, Tooltip } from 'react-bootstrap' // eslint-disable-line
export function RecorderUI (props: RecorderProps) {
const inputLive = useRef<HTMLInputElement>()
const [toggleExpander, setToggleExpander] = useState<boolean>(false)
const [enableRunButton, setEnableRunButton] = useState<boolean>(true)
const triggerRecordButton = () => {
props.storeScenario(props.scenarioPrompt)
}
@ -16,6 +17,11 @@ export function RecorderUI (props: RecorderProps) {
props.runCurrentScenario(liveMode, props.gasEstimationPrompt, props.passphrasePrompt, props.mainnetPrompt)
}
useEffect(() => {
if (props.currentFile.endsWith('.json')) setEnableRunButton(false)
else setEnableRunButton(true)
}, [props.currentFile])
const toggleClass = () => {
setToggleExpander(!toggleExpander)
}
@ -46,24 +52,28 @@ export function RecorderUI (props: RecorderProps) {
<div className={`flex-column ${toggleExpander ? "d-flex" : "d-none"}`}>
<div className="mb-1 mt-1 fmt-2 custom-control custom-checkbox mb-1">
<input ref={inputLive} type="checkbox" id="livemode-recorder" className="custom-control-input custom-select" name="input-livemode"/>
<label className="form-check-label custom-control-label" data-id="runtabLivemodeInput" htmlFor="livemode-recorder">Use live mode (Run transactions against latest compiled contracts).</label>
<OverlayTrigger placement={'right'} overlay={
<Tooltip className="text-nowrap" id="tooltip-livemode-recorder">
<span>If contracts are updated after recording transactions, checking this box<br/>will run recorded transactions with the latest copy of the compiled contracts</span>
</Tooltip>
}>
<label className="form-check-label custom-control-label" data-id="runtabLivemodeInput" htmlFor="livemode-recorder">Run transactions using the latest compilation result</label>
</OverlayTrigger>
</div>
<div className="mb-1 mt-1 udapp_transactionActions">
<OverlayTrigger placement={'right'} overlay={
<Tooltip className="text-nowrap" id="tooltip-save-recorder">
<span>Save {props.count} transaction(s) as scenario file.
</span>
<span>Save {props.count} transaction{props.count === 1 ? '' : 's'} as scenario file</span>
</Tooltip>
}>
<button className="btn btn-sm btn-info savetransaction udapp_recorder" onClick={triggerRecordButton}>Save</button>
<button className="btn btn-sm btn-info savetransaction udapp_recorder" title={props.count === 0 ? 'No transactions to save' : ''} disabled={props.count === 0 ? true: false} onClick={triggerRecordButton}>Save</button>
</OverlayTrigger>
<OverlayTrigger placement={'right'} overlay={
<Tooltip className="text-nowrap" id="tooltip-run-recorder">
<span>Run transaction(s) from the current scenario file.
</span>
<span>Run transaction(s) from the current scenario file</span>
</Tooltip>
}>
<button className="btn btn-sm btn-info runtransaction udapp_runTxs" data-id="runtransaction" onClick={handleClickRunButton}>Run</button>
<button className="btn btn-sm btn-info runtransaction udapp_runTxs" data-id="runtransaction" title={enableRunButton ? 'No scenario file selected' : ''} disabled={enableRunButton} onClick={handleClickRunButton}>Run</button>
</OverlayTrigger>
</div>
</div>

@ -44,4 +44,5 @@ export const RESET_STATE = 'RESET_STATE'
export const ADD_DEPLOY_OPTION = 'ADD_DEPLOY_OPTION'
export const REMOVE_DEPLOY_OPTION = 'REMOVE_DEPLOY_OPTION'
export const SET_DEPLOY_OPTIONS = 'SET_DEPLOY_OPTIONS'
export const SET_CURRENT_CONTRACT = 'SET_CURRENT_CONTRACT'
export const SET_CURRENT_CONTRACT = 'SET_CURRENT_CONTRACT'
export const SET_PROXY_ENV_ADDRESS = 'SET_PROXY_ENV_ADDRESS'

@ -1,8 +1,7 @@
import { CompilerAbstract } from '@remix-project/remix-solidity-ts'
import { ContractData } from '@remix-project/core-plugin'
import { DeployMode, DeployOption, DeployOptions } from '../types'
import { ADD_DEPLOY_OPTION, ADD_INSTANCE, ADD_PROVIDER, CLEAR_INSTANCES, CLEAR_RECORDER_COUNT, DISPLAY_NOTIFICATION, DISPLAY_POPUP_MESSAGE, FETCH_ACCOUNTS_LIST_FAILED, FETCH_ACCOUNTS_LIST_REQUEST, FETCH_ACCOUNTS_LIST_SUCCESS, FETCH_CONTRACT_LIST_FAILED, FETCH_CONTRACT_LIST_REQUEST, FETCH_CONTRACT_LIST_SUCCESS, FETCH_PROVIDER_LIST_FAILED, FETCH_PROVIDER_LIST_REQUEST, FETCH_PROVIDER_LIST_SUCCESS, HIDE_NOTIFICATION, HIDE_POPUP_MESSAGE, REMOVE_DEPLOY_OPTION, REMOVE_INSTANCE, REMOVE_PROVIDER, RESET_STATE, SET_BASE_FEE_PER_GAS, SET_CONFIRM_SETTINGS, SET_CURRENT_CONTRACT, SET_CURRENT_FILE, SET_DECODED_RESPONSE, SET_DEPLOY_OPTIONS, SET_EXECUTION_ENVIRONMENT, SET_EXTERNAL_WEB3_ENDPOINT, SET_GAS_LIMIT, SET_GAS_PRICE, SET_GAS_PRICE_STATUS, SET_IPFS_CHECKED_STATE, SET_LOAD_TYPE, SET_MATCH_PASSPHRASE, SET_MAX_FEE, SET_MAX_PRIORITY_FEE, SET_NETWORK_NAME, SET_PASSPHRASE, SET_PATH_TO_SCENARIO, SET_PERSONAL_MODE, SET_RECORDER_COUNT, SET_SELECTED_ACCOUNT, SET_SEND_UNIT, SET_SEND_VALUE, SET_TX_FEE_CONTENT } from '../constants'
import Web3 from 'web3'
import { DeployOptions } from '../types'
import { ADD_INSTANCE, ADD_PROVIDER, CLEAR_INSTANCES, CLEAR_RECORDER_COUNT, DISPLAY_NOTIFICATION, DISPLAY_POPUP_MESSAGE, FETCH_ACCOUNTS_LIST_FAILED, FETCH_ACCOUNTS_LIST_REQUEST, FETCH_ACCOUNTS_LIST_SUCCESS, FETCH_CONTRACT_LIST_FAILED, FETCH_CONTRACT_LIST_REQUEST, FETCH_CONTRACT_LIST_SUCCESS, FETCH_PROVIDER_LIST_FAILED, FETCH_PROVIDER_LIST_REQUEST, FETCH_PROVIDER_LIST_SUCCESS, HIDE_NOTIFICATION, HIDE_POPUP_MESSAGE, REMOVE_INSTANCE, REMOVE_PROVIDER, RESET_STATE, SET_BASE_FEE_PER_GAS, SET_CONFIRM_SETTINGS, SET_CURRENT_CONTRACT, SET_CURRENT_FILE, SET_DECODED_RESPONSE, SET_DEPLOY_OPTIONS, SET_EXECUTION_ENVIRONMENT, SET_EXTERNAL_WEB3_ENDPOINT, SET_GAS_LIMIT, SET_GAS_PRICE, SET_GAS_PRICE_STATUS, SET_IPFS_CHECKED_STATE, SET_LOAD_TYPE, SET_MATCH_PASSPHRASE, SET_MAX_FEE, SET_MAX_PRIORITY_FEE, SET_NETWORK_NAME, SET_PASSPHRASE, SET_PATH_TO_SCENARIO, SET_PERSONAL_MODE, SET_RECORDER_COUNT, SET_SELECTED_ACCOUNT, SET_SEND_UNIT, SET_SEND_VALUE, SET_TX_FEE_CONTENT, SET_PROXY_ENV_ADDRESS, ADD_DEPLOY_OPTION, REMOVE_DEPLOY_OPTION } from '../constants'
declare const window: any
interface Action {
@ -67,7 +66,8 @@ export interface RunTabState {
compiler: CompilerAbstract
}[]
},
deployOptions: DeployOptions
deployOptions: { [file: string]: { [name: string]: DeployOptions } },
proxyKey: string,
loadType: 'abi' | 'sol' | 'other'
currentFile: string,
currentContract: string,
@ -155,6 +155,7 @@ export const runTabInitialState: RunTabState = {
contracts: {
contractList: {},
deployOptions: {} as any,
proxyKey: '',
loadType: 'other',
currentFile: '',
currentContract: '',
@ -680,39 +681,33 @@ export const runTabReducer = (state: RunTabState = runTabInitialState, action: A
}
case ADD_DEPLOY_OPTION: {
const payload: { title: DeployMode, active: boolean } = action.payload
const payload: { [file: string]: { [name: string]: DeployOptions } } = action.payload
return {
...state,
contracts: {
...state.contracts,
deployOptions: {
...state.contracts.deployOptions,
options: [...state.contracts.deployOptions.options, payload]
}
deployOptions: {...state.contracts.deployOptions, ...payload }
}
}
}
case REMOVE_DEPLOY_OPTION: {
const payload: string = action.payload
const options = state.contracts.deployOptions.options.filter(val => val.title !== payload)
const options = state.contracts.deployOptions
delete options[payload]
return {
...state,
contracts: {
...state.contracts,
deployOptions: {
...state.contracts.deployOptions,
options
}
deployOptions: options
}
}
}
case SET_DEPLOY_OPTIONS: {
const payload: DeployOptions = action.payload
const payload: { [file: string]: { [name: string]: DeployOptions } } = action.payload
return {
...state,
@ -723,6 +718,18 @@ export const runTabReducer = (state: RunTabState = runTabInitialState, action: A
}
}
case SET_PROXY_ENV_ADDRESS: {
const payload: string = action.payload
return {
...state,
contracts: {
...state.contracts,
proxyKey: payload
}
}
}
default:
return state
}

@ -56,10 +56,11 @@ export function RunTabUI (props: RunTabProps) {
storage: null,
contract: null
})
runTabInitialState.selectExEnv = props.plugin.blockchain.getProvider()
runTabInitialState.selectExEnv = plugin.blockchain.getProvider()
runTabInitialState.selectExEnv = runTabInitialState.selectExEnv === 'vm' ? 'vm-london' : runTabInitialState.selectExEnv
const [runTab, dispatch] = useReducer(runTabReducer, runTabInitialState)
const REACT_API = { runTab }
const currentfile = plugin.config.get('currentFile')
useEffect(() => {
initRunTab(plugin)(dispatch)
@ -249,6 +250,7 @@ export function RunTabUI (props: RunTabProps) {
runCurrentScenario={runScenario}
scenarioPrompt={scenarioPrompt}
count={runTab.recorder.transactionCount}
currentFile={currentfile}
/>
<InstanceContainerUI
instances={runTab.instances}
@ -266,7 +268,7 @@ export function RunTabUI (props: RunTabProps) {
</div>
<ModalDialog id='udappNotify' { ...focusModal } handleHide={ handleHideModal } />
<Toaster message={focusToaster} handleHide={handleToaster} />
<PublishToStorage id='udapp' api={props.plugin} resetStorage={resetStorage} storage={publishData.storage} contract={publishData.contract} />
<PublishToStorage id='udapp' api={plugin} resetStorage={resetStorage} storage={publishData.storage} contract={publishData.contract} />
</Fragment>
)
}

@ -128,7 +128,8 @@ export interface ContractDropdownProps {
exEnvironment: string,
contracts: {
contractList: ContractList,
deployOptions: DeployOptions,
deployOptions: { [file: string]: { [name: string]: DeployOptions } },
proxyKey: string,
loadType: 'abi' | 'sol' | 'other',
currentFile: string,
currentContract: string,
@ -172,6 +173,7 @@ export interface RecorderProps {
passphrasePrompt: (msg: string) => JSX.Element,
scenarioPrompt: (msg: string, defaultValue: string) => JSX.Element,
count: number
currentFile: string
}
export interface InstanceContainerProps {
@ -219,7 +221,7 @@ export interface Modal {
cancelFn: () => void
}
export type DeployMode = 'Deploy with Proxy' | 'Upgrade Proxy'
export type DeployMode = 'Deploy with Proxy' | 'Upgrade with Proxy'
export type DeployOption = {
initializeInputs: string,
@ -238,10 +240,8 @@ export type DeployOption = {
}
}
export interface DeployOptions {
initializeOptions: {
[key: string]: DeployOption
},
options: { title: DeployMode, active: boolean }[],
initializeOptions: DeployOption,
options: { title: DeployMode, active: boolean }[]
}
export interface ContractGUIProps {
@ -255,7 +255,8 @@ export interface ContractGUIProps {
disabled?: boolean,
isDeploy?: boolean,
deployOption?: { title: DeployMode, active: boolean }[],
initializerOptions?: DeployOption
initializerOptions?: DeployOption,
savedProxyAddress?: string
}
export interface MainnetProps {
network: Network,

@ -8,7 +8,6 @@ import { createWorkspaceTemplate, getWorkspaces, loadWorkspacePreset, setPlugin
import { QueryParams } from '@remix-project/remix-lib'
import { fetchContractFromEtherscan } from '@remix-project/core-plugin' // eslint-disable-line
import JSZip from 'jszip'
import axios, { AxiosResponse } from 'axios'
export * from './events'
export * from './workspace'
@ -21,7 +20,8 @@ let plugin, dispatch: React.Dispatch<any>
export type UrlParametersType = {
gist: string,
code: string,
url: string
url: string,
address: string
}
const basicWorkspaceInit = async (workspaces: { name: string; isGitRepo: boolean; }[], workspaceProvider) => {
@ -63,10 +63,9 @@ export const initWorkspace = (filePanelPlugin) => async (reducerDispatch: React.
dispatch(setCurrentWorkspace({ name: 'code-sample', isGitRepo: false }))
const filePath = await loadWorkspacePreset('code-template')
plugin.on('editor', 'editorMounted', async () => await plugin.fileManager.openFile(filePath))
} else if (window.location.pathname && window.location.pathname !== '/') {
let route = window.location.pathname
if (route.startsWith('/address/0x') && route.length === 51) {
const contractAddress = route.split('/')[2]
} else if (params.address) {
if (params.address.startsWith('0x') && params.address.length === 42) {
const contractAddress = params.address
plugin.call('notification', 'toast', `Looking for contract(s) verified on different networks of Etherscan for contract address ${contractAddress} .....`)
let data
let count = 0
@ -109,23 +108,6 @@ export const initWorkspace = (filePanelPlugin) => async (reducerDispatch: React.
} catch (error) {
await basicWorkspaceInit(workspaces, workspaceProvider)
}
} else if (route.endsWith('.sol')) {
if (route.includes('blob')) route = route.replace('/blob', '')
let response: AxiosResponse
try {
response = await axios.get(`https://raw.githubusercontent.com${route}`)
} catch (error) {
plugin.call('notification', 'toast', `cound not find ${route} on GitHub`)
await basicWorkspaceInit(workspaces, workspaceProvider)
}
if (response && response.status === 200) {
const content = response.data
await createWorkspaceTemplate('github-code-sample', 'code-template')
plugin.setWorkspace({ name: 'github-code-sample', isLocalhost: false })
dispatch(setCurrentWorkspace({ name: 'github-code-sample', isGitRepo: false }))
await workspaceProvider.set(route, content)
plugin.on('editor', 'editorMounted', async () => await plugin.fileManager.openFile(route))
} else await basicWorkspaceInit(workspaces, workspaceProvider)
} else await basicWorkspaceInit(workspaces, workspaceProvider)
} else if (localStorage.getItem("currentWorkspace")) {
const index = workspaces.findIndex(element => element.name == localStorage.getItem("currentWorkspace"))

@ -13,6 +13,7 @@ export function Workspace () {
const NO_WORKSPACE = ' - none - '
const [currentWorkspace, setCurrentWorkspace] = useState<string>(NO_WORKSPACE)
const [selectedWorkspace, setSelectedWorkspace] = useState<{ name: string, isGitRepo: boolean}>(null)
const [showDropdown, setShowDropdown] = useState<boolean>(false)
const global = useContext(FileSystemContext)
const workspaceRenameInput = useRef()
const workspaceCreateInput = useRef()
@ -157,6 +158,10 @@ export function Workspace () {
}
}
const toggleDropdown = (isOpen: boolean) => {
setShowDropdown(isOpen)
}
const createModalMessage = () => {
return (
<>
@ -196,7 +201,7 @@ export function Workspace () {
<div>
<header>
<div className="mb-2">
<label className="form-check-label" htmlFor="workspacesSelect">
<label className="pl-1 form-check-label" htmlFor="workspacesSelect">
<FormattedMessage id='filePanel.workspace' defaultMessage='Workspaces' />
</label>
<span className="remixui_menu">
@ -266,12 +271,12 @@ export function Workspace () {
title='Clone Git Repository'>
</span>
</span>
<Dropdown id="workspacesSelect" data-id="workspacesSelect">
<Dropdown.Toggle as={CustomToggle} id="dropdown-custom-components" className="btn btn-light btn-block w-100 d-inline-block border border-dark form-control" icon={selectedWorkspace && selectedWorkspace.isGitRepo ? 'far fa-code-branch' : null}>
<Dropdown id="workspacesSelect" data-id="workspacesSelect" onToggle={toggleDropdown} show={showDropdown}>
<Dropdown.Toggle as={CustomToggle} id="dropdown-custom-components" className="btn btn-light btn-block w-100 d-inline-block border border-dark form-control" icon={selectedWorkspace && selectedWorkspace.isGitRepo && !(currentWorkspace === LOCALHOST) ? 'far fa-code-branch' : null}>
{ selectedWorkspace ? selectedWorkspace.name : currentWorkspace === LOCALHOST ? 'localhost' : NO_WORKSPACE }
</Dropdown.Toggle>
<Dropdown.Menu as={CustomMenu} className='w-100 custom-dropdown-items' data-id="custom-dropdown-items" >
<Dropdown.Menu as={CustomMenu} className='w-100 custom-dropdown-items' data-id="custom-dropdown-items">
{
global.fs.browser.workspaces.map(({ name, isGitRepo }, index) => (
<Dropdown.Item
@ -298,7 +303,7 @@ export function Workspace () {
</div>
</header>
</div>
<div className='h-100 remixui_fileExplorerTree'>
<div className='h-100 remixui_fileExplorerTree' onFocus={() => { toggleDropdown(false) }}>
<div className='h-100'>
{ global.fs.browser.isRequestingWorkspace || global.fs.browser.isRequestingCloning ? <div className="text-center py-5"><i className="fas fa-spinner fa-pulse fa-2x"></i></div>
: <div className='pl-2 remixui_treeview' data-id='filePanelFileExplorerTree'>

@ -1,6 +1,6 @@
{
"name": "@remix-project/remixd",
"version": "0.6.2",
"version": "0.6.3",
"description": "remix server: allow accessing file system from remix.ethereum.org and start a dev environment (see help section)",
"main": "index.js",
"types": "./index.d.ts",

@ -153,13 +153,13 @@
"@ethersphere/bee-js": "^3.2.0",
"@isomorphic-git/lightning-fs": "^4.4.1",
"@monaco-editor/react": "^4.3.1",
"@remixproject/engine": "^0.3.28",
"@remixproject/engine-web": "^0.3.28",
"@remixproject/plugin": "^0.3.28",
"@remixproject/plugin-api": "^0.3.28",
"@remixproject/plugin-utils": "^0.3.28",
"@remixproject/plugin-webview": "^0.3.28",
"@remixproject/plugin-ws": "^0.3.28",
"@remixproject/engine": "^0.3.31",
"@remixproject/engine-web": "^0.3.31",
"@remixproject/plugin": "^0.3.31",
"@remixproject/plugin-api": "^0.3.31",
"@remixproject/plugin-utils": "^0.3.31",
"@remixproject/plugin-webview": "^0.3.31",
"@remixproject/plugin-ws": "^0.3.31",
"ansi-gray": "^0.1.1",
"async": "^2.6.2",
"axios": ">=0.26.0",

@ -18,6 +18,7 @@
"assets": [
"apps/remix-ide/src/assets",
"apps/remix-ide/src/index.html",
"apps/remix-ide/src/404.html",
"apps/remix-ide/src/favicon.ico"
],
"styles": [],

@ -3819,63 +3819,63 @@
resolved "https://registry.yarnpkg.com/@popperjs/core/-/core-2.10.2.tgz#0798c03351f0dea1a5a4cabddf26a55a7cbee590"
integrity sha512-IXf3XA7+XyN7CP9gGh/XB0UxVMlvARGEgGXLubFICsUMGz6Q+DU+i4gGlpOxTjKvXjkJDJC8YdqdKkDj9qZHEQ==
"@remixproject/engine-web@^0.3.28":
version "0.3.28"
resolved "https://registry.yarnpkg.com/@remixproject/engine-web/-/engine-web-0.3.28.tgz#9a47c979a89764cb96d3f5da0d6802d1ca875c94"
integrity sha512-JTwTuonW+pI7WsSsgGHAzPynGjIdG34VnPrspR9XKBQO4+NX7m3lbNIMUUviyrbqIoGiGtgYd/ry98uLpTHr4g==
"@remixproject/engine-web@^0.3.31":
version "0.3.31"
resolved "https://registry.yarnpkg.com/@remixproject/engine-web/-/engine-web-0.3.31.tgz#42ee5c5d874ef9646090342ca8b430e3fe1046e8"
integrity sha512-GYeUB4Y0NaVc23mSmVByhx6pCvjzfz8iBbJCwaUB3q61uyRQ1wMM0P/47qqwZMukPi7x4bFUpN41C6rKq7ECug==
dependencies:
"@remixproject/engine" "0.3.28"
"@remixproject/plugin-api" "0.3.28"
"@remixproject/plugin-utils" "0.3.28"
"@remixproject/engine" "0.3.31"
"@remixproject/plugin-api" "0.3.31"
"@remixproject/plugin-utils" "0.3.31"
"@remixproject/engine@0.3.28", "@remixproject/engine@^0.3.28":
version "0.3.28"
resolved "https://registry.yarnpkg.com/@remixproject/engine/-/engine-0.3.28.tgz#3656ee029d3cbc22f8ad95bd925176a75057a475"
integrity sha512-27SHaCrG3KrPfMa1MYud4tE9xUIJKITEUsql99fhN7x12lOmgGZFjiqIG/WWjCMmT7OMG3vtLayiZrsYkzHCVw==
"@remixproject/engine@0.3.31", "@remixproject/engine@^0.3.31":
version "0.3.31"
resolved "https://registry.yarnpkg.com/@remixproject/engine/-/engine-0.3.31.tgz#6dcc209dbd993646430335c9454ce1a739d1a45f"
integrity sha512-5VTl9bkeU3fd3IUV8wy7kXHMd3RfnUsf6dlmORM9Np1oMZ9nhdhUsyJO/qXyrvR1PbyJIxPz/qM4+W5MGvOrnQ==
dependencies:
"@remixproject/plugin-api" "0.3.28"
"@remixproject/plugin-utils" "0.3.28"
"@remixproject/plugin-api" "0.3.31"
"@remixproject/plugin-utils" "0.3.31"
"@remixproject/plugin-api@0.3.28", "@remixproject/plugin-api@^0.3.28":
version "0.3.28"
resolved "https://registry.yarnpkg.com/@remixproject/plugin-api/-/plugin-api-0.3.28.tgz#660f068681bc1780284768e4ce1b4f70ea4ba859"
integrity sha512-tOzVFR504037weEkNHQGZsk+Ebxcu/xnBsVCkJaQaBMb+H/i6YevKQmoR1aGcH+JfQxc8eS8LGC65MaV8zmcWg==
"@remixproject/plugin-api@0.3.31", "@remixproject/plugin-api@^0.3.31":
version "0.3.31"
resolved "https://registry.yarnpkg.com/@remixproject/plugin-api/-/plugin-api-0.3.31.tgz#86e7c458c58ff200bd927fd3d642877f4b5a0013"
integrity sha512-LOJRHxORNp7zW8k7//DQz5aZ7eqB7TwhYXrvzqvaryDTvtvJGWrlTHg81hzALynaxZKEWneohxjUxKvGp/eA4g==
dependencies:
"@remixproject/plugin-utils" "0.3.28"
"@remixproject/plugin-utils" "0.3.31"
"@remixproject/plugin-utils@0.3.28", "@remixproject/plugin-utils@^0.3.28":
version "0.3.28"
resolved "https://registry.yarnpkg.com/@remixproject/plugin-utils/-/plugin-utils-0.3.28.tgz#eefd3b603f223cf6fba9b914ef3c9a62bd8435b6"
integrity sha512-5CayTgMqRiOguanTy6kpuRgCmjEFLUu2K4Rs7Zmt+GOzHucmwkDxYQO+3hFv0Jz/M/6yC5wkKYTx8vfPLLdEBQ==
"@remixproject/plugin-utils@0.3.31", "@remixproject/plugin-utils@^0.3.31":
version "0.3.31"
resolved "https://registry.yarnpkg.com/@remixproject/plugin-utils/-/plugin-utils-0.3.31.tgz#80771e00c1a1b776432abb17b1f4b2e25600d4f6"
integrity sha512-OOAjoSd+ErBMrcNQEh80NU3BjJ9fHXuftRfy5Ul9aGXN3b1LJSNVvfrG+FxX6lpyaAK5JBj+aB9pgFozgb2wlw==
dependencies:
tslib "2.0.1"
"@remixproject/plugin-webview@^0.3.28":
version "0.3.28"
resolved "https://registry.yarnpkg.com/@remixproject/plugin-webview/-/plugin-webview-0.3.28.tgz#ce03ad0801e0fd1e5bce59565b77b6698ad07596"
integrity sha512-DodSkN0vMSo2DEEoBOWyKxC/ElvBA826vhX+JVGXA8HGS45aavplZIN967hsc+SdzjV1UZfSNKEHttLGwM1BIw==
"@remixproject/plugin-webview@^0.3.31":
version "0.3.31"
resolved "https://registry.yarnpkg.com/@remixproject/plugin-webview/-/plugin-webview-0.3.31.tgz#e5cce7d0089439b35aee4ab2a724add1e5d36b40"
integrity sha512-8yoKwIkRi9S+rqvFShNt01FfXX0H/Fijn7UkWFWJ/V6ULcw2cw9ViCz8cYZLhNUpxqezyu/LKDQL9g1TbJJoYw==
dependencies:
"@remixproject/plugin" "0.3.28"
"@remixproject/plugin-api" "0.3.28"
"@remixproject/plugin-utils" "0.3.28"
"@remixproject/plugin" "0.3.31"
"@remixproject/plugin-api" "0.3.31"
"@remixproject/plugin-utils" "0.3.31"
axios "^0.21.1"
"@remixproject/plugin-ws@^0.3.28":
version "0.3.28"
resolved "https://registry.yarnpkg.com/@remixproject/plugin-ws/-/plugin-ws-0.3.28.tgz#4f6022b1c8ef6356299c545f6164bce48aac60fe"
integrity sha512-i3He9t4qvcBQxzshFx66D6drqLlySmV7Cb+NeYtMOYlWxQSJtUakp/Px1Tl3IDFQXDfpXxvDvYhn2w0AWPBOqw==
"@remixproject/plugin-ws@^0.3.31":
version "0.3.31"
resolved "https://registry.yarnpkg.com/@remixproject/plugin-ws/-/plugin-ws-0.3.31.tgz#a2bb415cc32a5b036b9b3759520088b8ceaa0d5a"
integrity sha512-Z4G6vkGxxCP+ibGNHAvNaFjYE4hbsazOEL50pMnda6LZNci9akMSiI/1MIZscVSMU8l2sYOoNUsWvLfLkIkYKQ==
dependencies:
"@remixproject/plugin" "0.3.28"
"@remixproject/plugin-api" "0.3.28"
"@remixproject/plugin-utils" "0.3.28"
"@remixproject/plugin" "0.3.31"
"@remixproject/plugin-api" "0.3.31"
"@remixproject/plugin-utils" "0.3.31"
"@remixproject/plugin@0.3.28", "@remixproject/plugin@^0.3.28":
version "0.3.28"
resolved "https://registry.yarnpkg.com/@remixproject/plugin/-/plugin-0.3.28.tgz#3e40de6d0441e6b47ca17c904e57562cb285a4d3"
integrity sha512-sw+cow3GKAcEacXLvdJwtHHIphPty4KRMZ6tcQHdtx/iCKTMNdw2hribKPnvTO+6mXE+4oAKCrFqeTdVp/rd1w==
"@remixproject/plugin@0.3.31", "@remixproject/plugin@^0.3.31":
version "0.3.31"
resolved "https://registry.yarnpkg.com/@remixproject/plugin/-/plugin-0.3.31.tgz#b6c6b58d2c7964e37024eeca4819c70ece1f3953"
integrity sha512-9ntMU9CzStloahm/wXt4V8n64ERgJzY5nG0bzQfjnI12knrdTmUo+LC42M2xaTBDDP9CzMPdqClg7XhhRLzohA==
dependencies:
"@remixproject/plugin-api" "0.3.28"
"@remixproject/plugin-utils" "0.3.28"
"@remixproject/plugin-api" "0.3.31"
"@remixproject/plugin-utils" "0.3.31"
events "3.2.0"
"@restart/context@^2.1.4":

Loading…
Cancel
Save