Merge branch 'master' into intl

pull/2581/head
drafish 3 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. 1
      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: environment:
- COMMIT_AUTHOR_EMAIL: "yann@ethereum.org" - COMMIT_AUTHOR_EMAIL: "yann@ethereum.org"
- COMMIT_AUTHOR: "Circle CI" - 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 working_directory: ~/remix-project
steps: steps:
@ -352,7 +352,7 @@ jobs:
environment: environment:
- COMMIT_AUTHOR_EMAIL: "yann@ethereum.org" - COMMIT_AUTHOR_EMAIL: "yann@ethereum.org"
- COMMIT_AUTHOR: "Circle CI" - 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 working_directory: ~/remix-project
steps: steps:
@ -380,7 +380,7 @@ jobs:
environment: environment:
- COMMIT_AUTHOR_EMAIL: "yann@ethereum.org" - COMMIT_AUTHOR_EMAIL: "yann@ethereum.org"
- COMMIT_AUTHOR: "Circle CI" - 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 working_directory: ~/remix-project
steps: steps:

@ -54,7 +54,7 @@ Note: It contains the latest supported version of Solidity available at the time
"npm": "^6.14.15" "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 ```bash
yarn global add @nrwl/cli yarn global add @nrwl/cli
``` ```
@ -127,7 +127,7 @@ curl https://raw.githubusercontent.com/ethereum/remix-project/master/docker-comp
### Troubleshooting ### 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: Run:

@ -2,7 +2,7 @@ import { NightwatchBrowser } from 'nightwatch'
import EventEmitter from 'events' import EventEmitter from 'events'
class CreateContract extends EventEmitter { class CreateContract extends EventEmitter {
command (this: NightwatchBrowser, inputParams: string[]): NightwatchBrowser { command (this: NightwatchBrowser, inputParams: string): NightwatchBrowser {
this.api.perform((done) => { this.api.perform((done) => {
createContract(this.api, inputParams, () => { createContract(this.api, inputParams, () => {
done() done()
@ -13,19 +13,11 @@ class CreateContract extends EventEmitter {
} }
} }
function createContract (browser: NightwatchBrowser, inputParams: string[], callback: VoidFunction) { function createContract (browser: NightwatchBrowser, inputParams: string, callback: VoidFunction) {
if (inputParams.length === 1) { if (inputParams) {
browser.setValue('.udapp_contractActionsContainerSingle > input', inputParams[0], function () { browser.setValue('.udapp_contractActionsContainerSingle > input', inputParams, function () {
browser.click('.udapp_contractActionsContainerSingle > button').pause(500).perform(function () { callback() }) 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 { } else {
browser browser
.click('.udapp_contractActionsContainerSingle > button') .click('.udapp_contractActionsContainerSingle > button')

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

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

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

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

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

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

@ -202,10 +202,10 @@ module.exports = {
.addFile('Storage.sol', sources[6]['Storage.sol']) .addFile('Storage.sol', sources[6]['Storage.sol'])
.addFile('Owner.sol', sources[6]['Owner.sol']) .addFile('Owner.sol', sources[6]['Owner.sol'])
.clickLaunchIcon('udapp') .clickLaunchIcon('udapp')
.createContract(['42', '24']) .createContract('42, 24')
.openFile('Storage.sol') .openFile('Storage.sol')
.clickLaunchIcon('udapp') .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) .clickInstance(1)
.clickFunction('store - transact (not payable)', { types: 'uint256 num', values: '24' }) .clickFunction('store - transact (not payable)', { types: 'uint256 num', values: '24' })
.testFunction('last', // we check if the contract is actually reachable. .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, getModalBody(callback: (value: string, cb: VoidFunction) => void): NightwatchBrowser,
modalFooterCancelClick(id?: string): NightwatchBrowser, modalFooterCancelClick(id?: string): NightwatchBrowser,
selectContract(contractName: string): NightwatchBrowser, selectContract(contractName: string): NightwatchBrowser,
createContract(inputParams: string[]): NightwatchBrowser, createContract(inputParams: string): NightwatchBrowser,
getAddressAtPosition(index: number, cb: (pos: string) => void): NightwatchBrowser, getAddressAtPosition(index: number, cb: (pos: string) => void): NightwatchBrowser,
testConstantFunction(address: string, fnFullName: string, expectedInput: NightwatchTestConstantFunctionExpectedInput | null, expectedOutput: string): NightwatchBrowser, testConstantFunction(address: string, fnFullName: string, expectedInput: NightwatchTestConstantFunctionExpectedInput | null, expectedOutput: string): NightwatchBrowser,
getEditorValue(callback: (content: string) => void): 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 "./" cp -r $FILES_TO_PACKAGE "./"
rm -rf dist rm -rf dist
ls 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 the whole directory
zip -r remix-$SHA.zip $FILES_TO_DEPLOY zip -r remix-$SHA.zip $FILES_TO_DEPLOY
# -f is needed because "build" is part of .gitignore # -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 "./" cp -r $FILES_TO_PACKAGE "./"
rm -rf dist rm -rf dist
ls 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 the whole directory
zip -r remix-$SHA.zip $FILES_TO_DEPLOY zip -r remix-$SHA.zip $FILES_TO_DEPLOY
# -f is needed because "build" is part of .gitignore # -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 "./" cp -r $FILES_TO_PACKAGE "./"
rm -rf dist rm -rf dist
ls 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 the whole directory
zip -r remix-$SHA.zip $FILES_TO_DEPLOY zip -r remix-$SHA.zip $FILES_TO_DEPLOY
# -f is needed because "build" is part of .gitignore # -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 = { const profile = {
name: 'hiddenPanel', name: 'hiddenPanel',
displayName: 'Hidden Panel', displayName: 'Hidden Panel',
description: '', description: 'Remix IDE hidden panel',
version: packageJson.version, version: packageJson.version,
methods: ['addView', 'removeView'] methods: ['addView', 'removeView']
} }

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

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

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

@ -18,7 +18,7 @@ const axios = require('axios')
const profile = { const profile = {
name: 'dGitProvider', name: 'dGitProvider',
displayName: 'Decentralized git', displayName: 'Decentralized git',
description: '', description: 'Decentralized git provider',
icon: 'assets/img/fileManager.webp', icon: 'assets/img/fileManager.webp',
version: '0.0.1', 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'], 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'], methods: ['createNewFile', 'uploadFile', 'getCurrentWorkspace', 'getWorkspaces', 'createWorkspace', 'setWorkspace', 'registerContextMenuItem', 'renameWorkspace', 'deleteWorkspace'],
events: ['setWorkspace', 'workspaceRenamed', 'workspaceDeleted', 'workspaceCreated'], events: ['setWorkspace', 'workspaceRenamed', 'workspaceDeleted', 'workspaceCreated'],
icon: 'assets/img/fileManager.webp', icon: 'assets/img/fileManager.webp',
description: ' - ', description: 'Remix IDE file explorer',
kind: 'fileexplorer', kind: 'fileexplorer',
location: 'sidePanel', location: 'sidePanel',
documentation: 'https://remix-ide.readthedocs.io/en/latest/file_explorer.html', 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 { module.exports = class Filepanel extends ViewPlugin {
constructor (appManager) { constructor (appManager) {

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

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

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

@ -10,7 +10,7 @@ const _paq = window._paq = window._paq || []
export const profile = { export const profile = {
name: 'compileAndRun', name: 'compileAndRun',
displayName: 'Compile and Run', 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'], methods: ['runScriptAfterCompilation'],
version: packageJson.version, version: packageJson.version,
kind: 'none' kind: 'none'

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

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

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

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

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

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

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

@ -16,9 +16,10 @@ const profile = {
methods: ['testFromPath', 'testFromSource', 'setTestFolderPath', 'getTestlibs', 'createTestLibs'], methods: ['testFromPath', 'testFromSource', 'setTestFolderPath', 'getTestlibs', 'createTestLibs'],
events: [], events: [],
icon: 'assets/img/unitTesting.webp', 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', 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 { module.exports = class TestTab extends ViewPlugin {

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

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

@ -11,8 +11,7 @@ import InjectedProvider from './providers/injected.js'
import NodeProvider from './providers/node.js' import NodeProvider from './providers/node.js'
import { execution, EventManager, helpers } from '@remix-project/remix-lib' import { execution, EventManager, helpers } from '@remix-project/remix-lib'
import { etherScanLink } from './helper' import { etherScanLink } from './helper'
import { logBuilder, confirmProxyMsg } from "@remix-ui/helper" import { logBuilder, cancelUpgradeMsg, cancelProxyMsg } from "@remix-ui/helper"
import { cancelProxyMsg } from '@remix-ui/helper'
const { txFormat, txExecution, typeConversion, txListener: Txlistener, TxRunner, TxRunnerWeb3, txHelper } = execution const { txFormat, txExecution, typeConversion, txListener: Txlistener, TxRunner, TxRunnerWeb3, txHelper } = execution
const { txResultHelper: resultToRemixTx } = helpers const { txResultHelper: resultToRemixTx } = helpers
const packageJson = require('../../../../package.json') const packageJson = require('../../../../package.json')
@ -142,17 +141,19 @@ export class Blockchain extends Plugin {
async deployProxy (proxyData, implementationContractObject) { async deployProxy (proxyData, implementationContractObject) {
const proxyModal = { const proxyModal = {
id: 'confirmProxyDeployment', 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. 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', modalType: 'modal',
okLabel: 'OK', okLabel: 'OK',
cancelLabel: 'Cancel', cancelLabel: 'Cancel',
okFn: () => { okFn: () => {
this.runProxyTx(proxyData, implementationContractObject) this.runProxyTx(proxyData, implementationContractObject)
_paq.push(['trackEvent', 'blockchain', 'Deploy With Proxy', 'modal ok confirmation'])
}, },
cancelFn: () => { cancelFn: () => {
this.call('notification', 'toast', cancelProxyMsg()) this.call('notification', 'toast', cancelProxyMsg())
_paq.push(['trackEvent', 'blockchain', 'Deploy With Proxy', 'cancel proxy deployment'])
}, },
hideFn: () => null hideFn: () => null
} }
@ -161,7 +162,9 @@ export class Blockchain extends Plugin {
async runProxyTx (proxyData, implementationContractObject) { async runProxyTx (proxyData, implementationContractObject) {
const args = { useCall: false, data: proxyData } const args = { useCall: false, data: proxyData }
let networkInfo
const confirmationCb = (network, tx, gasEstimation, continueTxExecution, cancelCb) => { const confirmationCb = (network, tx, gasEstimation, continueTxExecution, cancelCb) => {
networkInfo = network
// continue using original authorization given by user // continue using original authorization given by user
continueTxExecution(null) continueTxExecution(null)
} }
@ -171,14 +174,60 @@ export class Blockchain extends Plugin {
if (error) { if (error) {
const log = logBuilder(error) const log = logBuilder(error)
_paq.push(['trackEvent', 'blockchain', 'Deploy With Proxy', 'Proxy deployment failed: ' + error])
return this.call('terminal', 'logHtml', log) 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) return this.call('udapp', 'resolveContractAndAddInstance', implementationContractObject, address)
} }
this.runTx(args, confirmationCb, continueCb, promptCb, finalCb) 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) { async getEncodedFunctionHex (args, funABI) {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
txFormat.encodeFunctionCall(args, funABI, (error, data) => { 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', 'manager', 'config', 'compilerArtefacts', 'compilerMetadata', 'contextualListener', 'editor', 'offsetToLineColumnConverter', 'network', 'theme', 'locale',
'fileManager', 'contentImport', 'blockchain', 'web3Provider', 'scriptRunner', 'fetchAndCompile', 'mainPanel', 'hiddenPanel', 'sidePanel', 'menuicons', 'fileManager', 'contentImport', 'blockchain', 'web3Provider', 'scriptRunner', 'fetchAndCompile', 'mainPanel', 'hiddenPanel', 'sidePanel', 'menuicons',
'filePanel', 'terminal', 'settings', 'pluginManager', 'tabs', 'udapp', 'dGitProvider', 'solidity', 'solidity-logic', 'gistHandler', 'layout', '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) 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 = { const profile = {
name: 'walkthrough', name: 'walkthrough',
displayName: 'Walkthrough', displayName: 'Walkthrough',
description: '', description: 'Remix walkthrough for beginner',
version: packageJson.version, version: packageJson.version,
methods: ['start'] methods: ['start']
} }

@ -89,3 +89,17 @@ export const UUPSfunAbi = {
outputs: [], outputs: [],
stateMutability: "payable" 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 { Plugin } from '@remixproject/engine'
import { ContractABI, ContractAST, DeployOption } from '../types/contract'; import { ContractABI, ContractAST, ContractSources, DeployOptions } from '../types/contract'
import { UUPS, UUPSABI, UUPSBytecode, UUPSfunAbi } from './constants/uups'; import { UUPS, UUPSABI, UUPSBytecode, UUPSfunAbi, UUPSupgradeAbi } from './constants/uups'
const proxyProfile = { const proxyProfile = {
name: 'openzeppelin-proxy', name: 'openzeppelin-proxy',
displayName: 'openzeppelin-proxy', displayName: 'openzeppelin-proxy',
description: 'openzeppelin-proxy', description: 'openzeppelin-proxy',
methods: ['isConcerned', 'execute', 'getDeployOptions'] methods: ['isConcerned', 'executeUUPSProxy', 'executeUUPSContractUpgrade', 'getProxyOptions', 'getUpgradeOptions']
}; };
export class OpenZeppelinProxy extends Plugin { export class OpenZeppelinProxy extends Plugin {
blockchain: any blockchain: any
@ -28,26 +28,37 @@ export class OpenZeppelinProxy extends Plugin {
return false 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 = {} const inputs = {}
if (this.kind === 'UUPS') { if (this.kind === 'UUPS') {
Object.keys(contracts).map(name => { Object.keys(contracts).map(name => {
const abi = contracts[name].abi if (ast) {
const initializeInput = abi.find(node => node.name === 'initialize') const UUPSSymbol = ast.exportedSymbols['UUPSUpgradeable'] ? ast.exportedSymbols['UUPSUpgradeable'][0] : null
if (initializeInput) { ast.absolutePath === file && ast.nodes.map((node) => {
inputs[name] = { if (node.name === name && node.linearizedBaseContracts.includes(UUPSSymbol)) {
inputs: initializeInput, const abi = contracts[name].abi
initializeInputs: this.blockchain.getInputs(initializeInput) 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 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 // deploy the proxy, or use an existing one
if (!initializeABI) throw new Error('Cannot deploy proxy: Missing initialize ABI') if (!initializeABI) throw new Error('Cannot deploy proxy: Missing initialize ABI')
args = args === '' ? [] : args args = args === '' ? [] : args
@ -56,6 +67,13 @@ export class OpenZeppelinProxy extends Plugin {
if (this.kind === 'UUPS') this.deployUUPSProxy(implAddress, _data, implementationContractObject) 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> { async deployUUPSProxy (implAddress: string, _data: string, implementationContractObject): Promise<void> {
const args = [implAddress, _data] const args = [implAddress, _data]
const constructorData = await this.blockchain.getEncodedParams(args, UUPSfunAbi) const constructorData = await this.blockchain.getEncodedParams(args, UUPSfunAbi)
@ -74,4 +92,20 @@ export class OpenZeppelinProxy extends Plugin {
implementationContractObject.name = proxyName implementationContractObject.name = proxyName
this.blockchain.deployProxy(data, implementationContractObject) 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 { export type ContractABI = {
[key: string]: { inputs: [];
abi: ({ stateMutability: string;
inputs: never[]; type: string;
stateMutability: string; anonymous?: undefined;
type: string; name?: string;
anonymous?: undefined; outputs?: undefined;
name?: undefined; } | {
outputs?: undefined; anonymous: boolean;
} | { inputs: {
anonymous: boolean; indexed: boolean;
inputs: { internalType: string;
indexed: boolean; name: string;
internalType: string; type: string;
name: string; }[];
type: string; name: string;
}[]; type: string;
name: string; stateMutability?: undefined;
type: string; outputs?: undefined;
stateMutability?: undefined; } | {
outputs?: undefined; inputs: {
} | { internalType: string;
inputs: { name: string;
internalType: string; type: string;
name: string; }[];
type: string; name: string;
}[]; outputs: {
name: string; internalType: string;
outputs: { name: string;
internalType: string; type: string;
name: string; }[];
type: string; stateMutability: string;
}[]; type: string;
stateMutability: string; anonymous?: undefined;
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 DeployMode = 'Deploy with Proxy' | 'Upgrade with Proxy'
export type DeployOption = { export type DeployOption = {
initializeInputs: string, initializeInputs: string,
inputs: {
inputs: { inputs: {
inputs: { internalType?: string,
internalType?: string, name: string,
name: string, type: string
type: string }[],
}[], name: "initialize",
name: "initialize", outputs?: any[],
outputs?: any[], stateMutability: string,
stateMutability: string, type: string,
type: string, payable?: boolean,
payable?: boolean, constant?: any
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> <b>Proxy deployment cancelled.</b>
</div> </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' import './panel.css'
export interface RemixPanelProps { export interface RemixPanelProps {
plugins: Record<string, PluginRecord>; plugins: Record<string, PluginRecord>;
} }
const RemixUIPanelHeader = (props: RemixPanelProps) => { const RemixUIPanelHeader = (props: RemixPanelProps) => {
const [plugin, setPlugin] = useState<PluginRecord>() const [plugin, setPlugin] = useState<PluginRecord>()
const [toggleExpander, setToggleExpander] = useState<boolean>(false)
useEffect(() => { useEffect(() => {
if (props.plugins) { if (props.plugins) {
@ -19,13 +20,51 @@ const RemixUIPanelHeader = (props: RemixPanelProps) => {
} }
}, [props]) }, [props])
const toggleClass = () => {
setToggleExpander(!toggleExpander)
}
return ( 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'> <h6 data-id='sidePanelSwapitTitle'>
{/* @ts-ignore */} {/* @ts-ignore */}
<FormattedMessage id={plugin?.profile.intlId || 'defaultId'} defaultMessage={plugin?.profile.displayName || plugin?.profile.name} /> <FormattedMessage id={plugin?.profile.intlId || 'defaultId'} defaultMessage={plugin?.profile.displayName || plugin?.profile.name} />
</h6> </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>) </header>)
} }

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

@ -19,6 +19,9 @@ function ActivePluginCard ({
<h6 className="remixui_displayName plugin-name"> <h6 className="remixui_displayName plugin-name">
<div> <div>
{ profile.displayName || profile.name } { 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 && { profile.documentation &&
<a href={profile.documentation} className="px-1" title="link to documentation" target="_blank" rel="noreferrer"> <a href={profile.documentation} className="px-1" title="link to documentation" target="_blank" rel="noreferrer">
<i aria-hidden="true" className="fas fa-book"/> <i aria-hidden="true" className="fas fa-book"/>

@ -22,6 +22,9 @@ function InactivePluginCard ({
<h6 className="remixui_displayName plugin-name"> <h6 className="remixui_displayName plugin-name">
<div> <div>
{ profile.displayName || profile.name } { 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 && { profile.documentation &&
<a href={profile.documentation} className="px-1" title="link to documentation" target="_blank" rel="noreferrer"> <a href={profile.documentation} className="px-1" title="link to documentation" target="_blank" rel="noreferrer">
<i aria-hidden="true" className="fas fa-book"/> <i aria-hidden="true" className="fas fa-book"/>

@ -135,6 +135,7 @@ export const createInstance = async (
args, args,
deployMode: DeployMode[]) => { deployMode: DeployMode[]) => {
const isProxyDeployment = (deployMode || []).find(mode => mode === 'Deploy with Proxy') const isProxyDeployment = (deployMode || []).find(mode => mode === 'Deploy with Proxy')
const isContractUpgrade = (deployMode || []).find(mode => mode === 'Upgrade with Proxy')
const statusCb = (msg: string) => { const statusCb = (msg: string) => {
const log = logBuilder(msg) const log = logBuilder(msg)
@ -160,7 +161,9 @@ export const createInstance = async (
if (isProxyDeployment) { if (isProxyDeployment) {
const initABI = contractObject.abi.find(abi => abi.name === 'initialize') 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()) { if (selectedContract.isOverSizeLimit()) {
return dispatch(displayNotification('Contract code size over limit', isOverSizePrompt(), 'Force Send', 'Cancel', () => { 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) => { continueCb: (error, continueTxExecution, cancelCb) => {
continueHandler(dispatch, gasEstimationPrompt, error, continueTxExecution, cancelCb) continueHandler(dispatch, gasEstimationPrompt, error, continueTxExecution, cancelCb)
}, },
@ -192,7 +195,7 @@ export const createInstance = async (
return terminalLogger(plugin, log) return terminalLogger(plugin, log)
})) }))
} }
deployContract(plugin, selectedContract, !isProxyDeployment ? args : '', contractMetadata, compilerContracts, { deployContract(plugin, selectedContract, !isProxyDeployment && !isContractUpgrade ? args : '', contractMetadata, compilerContracts, {
continueCb: (error, continueTxExecution, cancelCb) => { continueCb: (error, continueTxExecution, cancelCb) => {
continueHandler(dispatch, gasEstimationPrompt, 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 { RunTab } from "../types/run-tab"
import { setExecutionContext, setFinalContext, updateAccountBalances } from "./account" import { setExecutionContext, setFinalContext, updateAccountBalances } from "./account"
import { addExternalProvider, addInstance, removeExternalProvider, setNetworkNameFromProvider } from "./actions" 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 { CompilerAbstract } from '@remix-project/remix-solidity'
import * as ethJSUtil from 'ethereumjs-util' import * as ethJSUtil from 'ethereumjs-util'
import Web3 from 'web3' import Web3 from 'web3'
@ -24,7 +24,7 @@ export const setupEvents = (plugin: RunTab, dispatch: React.Dispatch<any>) => {
plugin.blockchain.event.register('networkStatus', ({ error, network }) => { plugin.blockchain.event.register('networkStatus', ({ error, network }) => {
if (error) { if (error) {
const netUI = 'can\'t detect network ' const netUI = 'can\'t detect network'
setNetworkNameFromProvider(dispatch, netUI) setNetworkNameFromProvider(dispatch, netUI)
return return
@ -33,6 +33,8 @@ export const setupEvents = (plugin: RunTab, dispatch: React.Dispatch<any>) => {
const netUI = (networkProvider() !== 'vm') ? `${network.name} (${network.id || '-'}) network` : 'VM' const netUI = (networkProvider() !== 'vm') ? `${network.name} (${network.id || '-'}) network` : 'VM'
setNetworkNameFromProvider(dispatch, netUI) 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)) 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 : {}) const isUpgradeable = await plugin.call('openzeppelin-proxy', 'isConcerned', data.sources[file] ? data.sources[file].ast : {})
if (isUpgradeable) { 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(fetchContractListSuccess({ [file]: contracts }))
dispatch(setCurrentFile(file)) dispatch(setCurrentFile(file))
// TODO: set current contract // TODO: set current contract

@ -1,6 +1,6 @@
import { ContractList } from '../reducers/runTab' import { ContractList } from '../reducers/runTab'
import { ContractData } from '@remix-project/core-plugin' 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' import { DeployMode, DeployOptions } from '../types'
export const fetchAccountsListRequest = () => { 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 { return {
payload: deployOption, payload: deployOption,
type: ADD_DEPLOY_OPTION type: ADD_DEPLOY_OPTION
} }
} }
export const removeDeployOption = (title: DeployMode) => { export const removeDeployOption = (file: string) => {
return { return {
payload: title, payload: file,
type: REMOVE_DEPLOY_OPTION type: REMOVE_DEPLOY_OPTION
} }
} }
export const setDeployOptions = (deployOptions: DeployOptions) => { export const setDeployOptions = (deployOptions: { [file: string]: { [name: string]: DeployOptions } }) => {
return { return {
payload: deployOptions, payload: deployOptions,
type: SET_DEPLOY_OPTIONS type: SET_DEPLOY_OPTIONS
@ -307,3 +307,10 @@ export const setCurrentContract = (contractName: string) => {
type: SET_CURRENT_CONTRACT 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 { ContractData, FuncABI } from '@remix-project/core-plugin'
import * as ethJSUtil from 'ethereumjs-util' import * as ethJSUtil from 'ethereumjs-util'
import { ContractGUI } from './contractGUI' import { ContractGUI } from './contractGUI'
import { deployWithProxyMsg, upgradeWithProxyMsg } from '@remix-ui/helper'
export function ContractDropdownUI (props: ContractDropdownProps) { export function ContractDropdownUI (props: ContractDropdownProps) {
const intl = useIntl() const intl = useIntl()
@ -29,7 +30,7 @@ export function ContractDropdownUI (props: ContractDropdownProps) {
const [constructorInterface, setConstructorInterface] = useState<FuncABI>(null) const [constructorInterface, setConstructorInterface] = useState<FuncABI>(null)
const [constructorInputs, setConstructorInputs] = useState(null) const [constructorInputs, setConstructorInputs] = useState(null)
const contractsRef = useRef<HTMLSelectElement>(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(() => { useEffect(() => {
enableAtAddress(false) enableAtAddress(false)
@ -157,7 +158,21 @@ export function ContractDropdownUI (props: ContractDropdownProps) {
if (selectedContract.bytecodeObject.length === 0) { 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', () => {}) 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) => { const atAddressChanged = (event) => {
@ -236,14 +251,15 @@ export function ContractDropdownUI (props: ContractDropdownProps) {
<ContractGUI <ContractGUI
title='Deploy' title='Deploy'
isDeploy={true} isDeploy={true}
deployOption={deployOptions.options} deployOption={deployOptions[currentFile] && deployOptions[currentFile][currentContract] ? deployOptions[currentFile][currentContract].options : null}
initializerOptions={deployOptions.initializeOptions ? deployOptions.initializeOptions[currentContract] : null} initializerOptions={deployOptions[currentFile] && deployOptions[currentFile][currentContract] ? deployOptions[currentFile][currentContract].initializeOptions : null}
funcABI={constructorInterface} funcABI={constructorInterface}
clickCallBack={clickCallback} clickCallBack={clickCallback}
inputs={constructorInputs} inputs={constructorInputs}
widthClass='w-50' widthClass='w-50'
evmBC={loadedContractData.bytecodeObject} evmBC={loadedContractData.bytecodeObject}
lookupOnly={false} lookupOnly={false}
savedProxyAddress={proxyKey}
/> />
<div className="d-flex py-1 align-items-center custom-control custom-checkbox"> <div className="d-flex py-1 align-items-center custom-control custom-checkbox">
<input <input

@ -3,8 +3,6 @@ import React, { useEffect, useRef, useState } from 'react'
import * as remixLib from '@remix-project/remix-lib' import * as remixLib from '@remix-project/remix-lib'
import { ContractGUIProps } from '../types' import { ContractGUIProps } from '../types'
import { CopyToClipboard } from '@remix-ui/clipboard' import { CopyToClipboard } from '@remix-ui/clipboard'
import { MultiDeployInput } from './multiDeployInput'
import { DeployInput } from './deployInput'
const txFormat = remixLib.execution.txFormat const txFormat = remixLib.execution.txFormat
export function ContractGUI (props: ContractGUIProps) { export function ContractGUI (props: ContractGUIProps) {
@ -15,20 +13,15 @@ export function ContractGUI (props: ContractGUIProps) {
title: string, title: string,
content: string, content: string,
classList: string, classList: string,
dataId: string, dataId: string
widthClass: string }>({ title: '', content: '', classList: '', dataId: '' })
}>({ title: '', content: '', classList: '', dataId: '', widthClass: '' }) const [toggleDeployProxy, setToggleDeployProxy] = useState<boolean>(false)
const [selectedDeployIndex, setSelectedDeployIndex] = useState<number>(null) const [toggleUpgradeImp, setToggleUpgradeImp] = useState<boolean>(false)
const [showOptions, setShowOptions] = useState<boolean>(false) const [deployState, setDeployState] = useState<{ deploy: boolean, upgrade: boolean }>({ deploy: false, upgrade: false })
const [hasArgs, setHasArgs] = useState<boolean>(false) const [useLastProxy, setUseLastProxy] = useState<boolean>(false)
const [isMultiField, setIsMultiField] = useState<boolean>(false) const [proxyAddress, setProxyAddress] = useState<string>('')
const [deployInputs, setDeployInputs] = useState<{
internalType?: string,
name: string,
type: string
}[]>([])
const [deployPlaceholder, setDeployPlaceholder] = useState<string>('')
const multiFields = useRef<Array<HTMLInputElement | null>>([]) const multiFields = useRef<Array<HTMLInputElement | null>>([])
const initializeFields = useRef<Array<HTMLInputElement | null>>([])
const basicInputRef = useRef<HTMLInputElement>() const basicInputRef = useRef<HTMLInputElement>()
useEffect(() => { useEffect(() => {
@ -41,7 +34,7 @@ export function ContractGUI (props: ContractGUIProps) {
} }
setBasicInput('') setBasicInput('')
// we have the reset the fields before reseting the previous references. // 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.filter((el) => el !== null && el !== undefined).forEach((el) => el.value = '')
multiFields.current = [] multiFields.current = []
}, [props.title, props.funcABI]) }, [props.title, props.funcABI])
@ -53,8 +46,7 @@ export function ContractGUI (props: ContractGUIProps) {
title: title + ' - call', title: title + ' - call',
content: 'call', content: 'call',
classList: 'btn-info', classList: 'btn-info',
dataId: title + ' - call', dataId: title + ' - call'
widthClass: props.widthClass
}) })
} else if (props.funcABI.stateMutability === 'payable' || props.funcABI.payable) { } else if (props.funcABI.stateMutability === 'payable' || props.funcABI.payable) {
// // transact. stateMutability = payable // // transact. stateMutability = payable
@ -62,8 +54,7 @@ export function ContractGUI (props: ContractGUIProps) {
title: title + ' - transact (payable)', title: title + ' - transact (payable)',
content: 'transact', content: 'transact',
classList: 'btn-danger', classList: 'btn-danger',
dataId: title + ' - transact (payable)', dataId: title + ' - transact (payable)'
widthClass: props.widthClass
}) })
} else { } else {
// // transact. stateMutability = nonpayable // // transact. stateMutability = nonpayable
@ -71,59 +62,13 @@ export function ContractGUI (props: ContractGUIProps) {
title: title + ' - transact (not payable)', title: title + ' - transact (not payable)',
content: 'transact', content: 'transact',
classList: 'btn-warning', classList: 'btn-warning',
dataId: title + ' - transact (not payable)', dataId: title + ' - transact (not payable)'
widthClass: props.widthClass
}) })
} }
}, [props.lookupOnly, props.funcABI, title]) }, [props.lookupOnly, props.funcABI, title])
useEffect(() => { const getContentOnCTC = () => {
if (props.deployOption && props.deployOption[selectedDeployIndex]) { const multiString = getMultiValsString(multiFields.current)
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)
// copy-to-clipboard icon is only visible for method requiring input params // copy-to-clipboard icon is only visible for method requiring input params
if (!multiString) { if (!multiString) {
return 'cannot encode empty arguments' return 'cannot encode empty arguments'
@ -191,8 +136,7 @@ export function ContractGUI (props: ContractGUIProps) {
if (inputString) { if (inputString) {
inputString = inputString.replace(/(^|,\s+|,)(\d+)(\s+,|,|$)/g, '$1"$2"$3') // replace non quoted number by quoted number 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 = 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 const multiInputs = multiFields.current
for (let k = 0; k < multiInputs.length; k++) { for (let k = 0; k < multiInputs.length; k++) {
@ -204,9 +148,15 @@ export function ContractGUI (props: ContractGUIProps) {
} }
const handleActionClick = () => { 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) => { const handleBasicInput = (e) => {
@ -215,90 +165,197 @@ export function ContractGUI (props: ContractGUIProps) {
setBasicInput(value) setBasicInput(value)
} }
const handleMultiValsSubmit = (fields: HTMLInputElement[]) => { const handleExpandMultiClick = () => {
const valsString = getMultiValsString(fields) const valsString = getMultiValsString(multiFields.current)
const deployMode = selectedDeployIndex !== null ? [props.deployOption[selectedDeployIndex].title] : []
if (valsString) { if (valsString) {
props.clickCallBack(props.funcABI.inputs, valsString, deployMode) props.clickCallBack(props.funcABI.inputs, valsString)
} else { } else {
props.clickCallBack(props.funcABI.inputs, '', deployMode) props.clickCallBack(props.funcABI.inputs, '')
} }
} }
const setSelectedDeploy = (index: number) => { const handleToggleDeployProxy = () => {
setSelectedDeployIndex(index !== selectedDeployIndex ? index : null) setToggleDeployProxy(!toggleDeployProxy)
if (basicInputRef.current) basicInputRef.current.value = '' }
setBasicInput('')
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 = () => { const handleUseLastProxySelect = (e) => {
setShowOptions(!showOptions) const value = e.target.checked
const address = props.savedProxyAddress
setUseLastProxy(value)
setProxyAddress(address || '')
}
const handleSetProxyAddress = (e) => {
const value = e.target.value
setProxyAddress(value)
} }
return ( return (
<div className={`udapp_contractProperty ${hasArgs ? 'udapp_hasArgs' : ''}`}> <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' }}>
props.isDeploy ? !isMultiField ? <button onClick={handleActionClick} title={buttonOptions.title} className={`udapp_instanceButton ${props.widthClass} btn btn-sm ${buttonOptions.classList}`} data-id={buttonOptions.dataId}>{title}</button>
<DeployInput <input
buttonOptions={buttonOptions} className="form-control"
funcABI={props.initializerOptions ? props.initializerOptions.inputs : props.funcABI} data-id={props.funcABI.type === 'fallback' || props.funcABI.type === 'receive' ? `'(${props.funcABI.type}')` : 'multiParamManagerBasicInputField'}
inputs={deployPlaceholder} placeholder={props.inputs}
handleBasicInput={handleBasicInput} title={props.funcABI.type === 'fallback' || props.funcABI.type === 'receive' ? `'(${props.funcABI.type}')` : props.inputs}
basicInputRef={basicInputRef} onChange={handleBasicInput}
selectedIndex={selectedDeployIndex} ref={basicInputRef}
setSelectedIndex={setSelectedDeploy} style={{ visibility: !((props.funcABI.inputs && props.funcABI.inputs.length > 0) || (props.funcABI.type === 'fallback') || (props.funcABI.type === 'receive')) ? 'hidden' : 'visible' }} />
handleActionClick={handleActionClick} <i
deployOptions={props.deployOption} className="fas fa-angle-down udapp_methCaret"
/> : <MultiDeployInput onClick={switchMethodViewOn}
buttonOptions={buttonOptions} title={title}
selectedIndex={selectedDeployIndex} style={{ visibility: !(props.funcABI.inputs && props.funcABI.inputs.length > 0) ? 'hidden' : 'visible' }}></i>
setSelectedIndex={setSelectedDeploy} </div>
handleMultiValsSubmit={handleMultiValsSubmit} <div className="udapp_contractActionsContainerMulti" style={{ display: toggleContainer ? 'flex' : 'none' }}>
inputs={deployInputs} <div className="udapp_contractActionsContainerMultiInner text-dark">
getMultiValsString={getMultiValsString} <div onClick={switchMethodViewOff} className="udapp_multiHeader">
deployOptions={props.deployOption} <div className="udapp_multiTitle run-instance-multi-title">{title}</div>
/> : <i className='fas fa-angle-up udapp_methCaret'></i>
<> </div>
<div className="udapp_contractActionsContainerSingle pt-2" style={{ display: toggleContainer ? 'none' : 'flex' }}> <div>
<button onClick={handleActionClick} title={buttonOptions.title} className={`udapp_instanceButton ${props.widthClass} btn btn-sm ${buttonOptions.classList}`} data-id={buttonOptions.dataId}>{title}</button> {props.funcABI.inputs.map((inp, index) => {
<input return (
className="form-control" <div className="udapp_multiArg" key={index}>
data-id={props.funcABI.type === 'fallback' || props.funcABI.type === 'receive' ? `'(${props.funcABI.type}')` : 'multiParamManagerBasicInputField'} <label htmlFor={inp.name}> {inp.name}: </label>
placeholder={props.inputs} <input ref={el => { multiFields.current[index] = el }} className="form-control" placeholder={inp.type} title={inp.name} data-id={`multiParamManagerInput${inp.name}`} />
title={props.funcABI.type === 'fallback' || props.funcABI.type === 'receive' ? `'(${props.funcABI.type}')` : props.inputs} </div>)
onChange={handleBasicInput} })}
ref={basicInputRef} </div>
style={{ visibility: !((props.funcABI.inputs && props.funcABI.inputs.length > 0) || (props.funcABI.type === 'fallback') || (props.funcABI.type === 'receive')) ? 'hidden' : 'visible' }} /> <div className="udapp_group udapp_multiArg">
<i <CopyToClipboard tip='Encode values of input fields & copy to clipboard' icon='fa-clipboard' direction={'bottom'} getContent={getContentOnCTC} />
className="fas fa-angle-down udapp_methCaret" <button onClick={handleExpandMultiClick} title={buttonOptions.title} data-id={buttonOptions.dataId} className={`udapp_instanceButton ${buttonOptions.classList}`}>{ buttonOptions.content }</button>
onClick={switchMethodViewOn} </div>
title={title}
style={{ visibility: !(props.funcABI.inputs && props.funcABI.inputs.length > 0) ? 'hidden' : 'visible' }}></i>
</div> </div>
<div className="udapp_contractActionsContainerMulti" style={{ display: toggleContainer ? 'flex' : 'none' }}> </div>
<div className="udapp_contractActionsContainerMultiInner text-dark"> { props.deployOption && (props.deployOption || []).length > 0 ?
<div onClick={switchMethodViewOff} className="udapp_multiHeader"> <>
<div className="udapp_multiTitle run-instance-multi-title">{title}</div> <div className='d-flex justify-content-between'>
<i className='fas fa-angle-up udapp_methCaret'></i> <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>
<div> <div>
{props.funcABI.inputs.map((inp, index) => { {
return ( props.initializerOptions && props.initializerOptions.initializeInputs ?
<div className="udapp_multiArg" key={index}> <span onClick={handleToggleDeployProxy}>
<label htmlFor={inp.name}> {inp.name}: </label> <i className={!toggleDeployProxy ? 'fas fa-angle-right pt-2' : 'fas fa-angle-down'} aria-hidden="true"></i>
<input ref={el => { multiFields.current[index] = el }} className="form-control" placeholder={inp.type} title={inp.name} data-id={`multiParamManagerInput${inp.name}`} /> </span> : null
</div>) }
})}
</div> </div>
<div className="udapp_group udapp_multiArg"> </div>
<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> 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> </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> <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> </div>
) )
} }

@ -1,5 +1,5 @@
// eslint-disable-next-line no-use-before-define // 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 { FormattedMessage } from 'react-intl'
import { RecorderProps } from '../types' import { RecorderProps } from '../types'
import { OverlayTrigger, Tooltip } from 'react-bootstrap' // eslint-disable-line 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) { export function RecorderUI (props: RecorderProps) {
const inputLive = useRef<HTMLInputElement>() const inputLive = useRef<HTMLInputElement>()
const [toggleExpander, setToggleExpander] = useState<boolean>(false) const [toggleExpander, setToggleExpander] = useState<boolean>(false)
const [enableRunButton, setEnableRunButton] = useState<boolean>(true)
const triggerRecordButton = () => { const triggerRecordButton = () => {
props.storeScenario(props.scenarioPrompt) props.storeScenario(props.scenarioPrompt)
} }
@ -16,6 +17,11 @@ export function RecorderUI (props: RecorderProps) {
props.runCurrentScenario(liveMode, props.gasEstimationPrompt, props.passphrasePrompt, props.mainnetPrompt) props.runCurrentScenario(liveMode, props.gasEstimationPrompt, props.passphrasePrompt, props.mainnetPrompt)
} }
useEffect(() => {
if (props.currentFile.endsWith('.json')) setEnableRunButton(false)
else setEnableRunButton(true)
}, [props.currentFile])
const toggleClass = () => { const toggleClass = () => {
setToggleExpander(!toggleExpander) setToggleExpander(!toggleExpander)
} }
@ -46,24 +52,28 @@ export function RecorderUI (props: RecorderProps) {
<div className={`flex-column ${toggleExpander ? "d-flex" : "d-none"}`}> <div className={`flex-column ${toggleExpander ? "d-flex" : "d-none"}`}>
<div className="mb-1 mt-1 fmt-2 custom-control custom-checkbox mb-1"> <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"/> <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>
<div className="mb-1 mt-1 udapp_transactionActions"> <div className="mb-1 mt-1 udapp_transactionActions">
<OverlayTrigger placement={'right'} overlay={ <OverlayTrigger placement={'right'} overlay={
<Tooltip className="text-nowrap" id="tooltip-save-recorder"> <Tooltip className="text-nowrap" id="tooltip-save-recorder">
<span>Save {props.count} transaction(s) as scenario file. <span>Save {props.count} transaction{props.count === 1 ? '' : 's'} as scenario file</span>
</span>
</Tooltip> </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>
<OverlayTrigger placement={'right'} overlay={ <OverlayTrigger placement={'right'} overlay={
<Tooltip className="text-nowrap" id="tooltip-run-recorder"> <Tooltip className="text-nowrap" id="tooltip-run-recorder">
<span>Run transaction(s) from the current scenario file. <span>Run transaction(s) from the current scenario file</span>
</span>
</Tooltip> </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> </OverlayTrigger>
</div> </div>
</div> </div>

@ -45,3 +45,4 @@ export const ADD_DEPLOY_OPTION = 'ADD_DEPLOY_OPTION'
export const REMOVE_DEPLOY_OPTION = 'REMOVE_DEPLOY_OPTION' export const REMOVE_DEPLOY_OPTION = 'REMOVE_DEPLOY_OPTION'
export const SET_DEPLOY_OPTIONS = 'SET_DEPLOY_OPTIONS' 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 { CompilerAbstract } from '@remix-project/remix-solidity-ts'
import { ContractData } from '@remix-project/core-plugin' import { ContractData } from '@remix-project/core-plugin'
import { DeployMode, DeployOption, DeployOptions } from '../types' import { 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 { 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'
import Web3 from 'web3'
declare const window: any declare const window: any
interface Action { interface Action {
@ -67,7 +66,8 @@ export interface RunTabState {
compiler: CompilerAbstract compiler: CompilerAbstract
}[] }[]
}, },
deployOptions: DeployOptions deployOptions: { [file: string]: { [name: string]: DeployOptions } },
proxyKey: string,
loadType: 'abi' | 'sol' | 'other' loadType: 'abi' | 'sol' | 'other'
currentFile: string, currentFile: string,
currentContract: string, currentContract: string,
@ -155,6 +155,7 @@ export const runTabInitialState: RunTabState = {
contracts: { contracts: {
contractList: {}, contractList: {},
deployOptions: {} as any, deployOptions: {} as any,
proxyKey: '',
loadType: 'other', loadType: 'other',
currentFile: '', currentFile: '',
currentContract: '', currentContract: '',
@ -680,39 +681,33 @@ export const runTabReducer = (state: RunTabState = runTabInitialState, action: A
} }
case ADD_DEPLOY_OPTION: { case ADD_DEPLOY_OPTION: {
const payload: { title: DeployMode, active: boolean } = action.payload const payload: { [file: string]: { [name: string]: DeployOptions } } = action.payload
return { return {
...state, ...state,
contracts: { contracts: {
...state.contracts, ...state.contracts,
deployOptions: { deployOptions: {...state.contracts.deployOptions, ...payload }
...state.contracts.deployOptions,
options: [...state.contracts.deployOptions.options, payload]
}
} }
} }
} }
case REMOVE_DEPLOY_OPTION: { case REMOVE_DEPLOY_OPTION: {
const payload: string = action.payload 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 { return {
...state, ...state,
contracts: { contracts: {
...state.contracts, ...state.contracts,
deployOptions: { deployOptions: options
...state.contracts.deployOptions,
options
}
} }
} }
} }
case SET_DEPLOY_OPTIONS: { case SET_DEPLOY_OPTIONS: {
const payload: DeployOptions = action.payload const payload: { [file: string]: { [name: string]: DeployOptions } } = action.payload
return { return {
...state, ...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: default:
return state return state
} }

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

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

@ -8,7 +8,6 @@ import { createWorkspaceTemplate, getWorkspaces, loadWorkspacePreset, setPlugin
import { QueryParams } from '@remix-project/remix-lib' import { QueryParams } from '@remix-project/remix-lib'
import { fetchContractFromEtherscan } from '@remix-project/core-plugin' // eslint-disable-line import { fetchContractFromEtherscan } from '@remix-project/core-plugin' // eslint-disable-line
import JSZip from 'jszip' import JSZip from 'jszip'
import axios, { AxiosResponse } from 'axios'
export * from './events' export * from './events'
export * from './workspace' export * from './workspace'
@ -21,7 +20,8 @@ let plugin, dispatch: React.Dispatch<any>
export type UrlParametersType = { export type UrlParametersType = {
gist: string, gist: string,
code: string, code: string,
url: string url: string,
address: string
} }
const basicWorkspaceInit = async (workspaces: { name: string; isGitRepo: boolean; }[], workspaceProvider) => { 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 })) dispatch(setCurrentWorkspace({ name: 'code-sample', isGitRepo: false }))
const filePath = await loadWorkspacePreset('code-template') const filePath = await loadWorkspacePreset('code-template')
plugin.on('editor', 'editorMounted', async () => await plugin.fileManager.openFile(filePath)) plugin.on('editor', 'editorMounted', async () => await plugin.fileManager.openFile(filePath))
} else if (window.location.pathname && window.location.pathname !== '/') { } else if (params.address) {
let route = window.location.pathname if (params.address.startsWith('0x') && params.address.length === 42) {
if (route.startsWith('/address/0x') && route.length === 51) { const contractAddress = params.address
const contractAddress = route.split('/')[2]
plugin.call('notification', 'toast', `Looking for contract(s) verified on different networks of Etherscan for contract address ${contractAddress} .....`) plugin.call('notification', 'toast', `Looking for contract(s) verified on different networks of Etherscan for contract address ${contractAddress} .....`)
let data let data
let count = 0 let count = 0
@ -109,23 +108,6 @@ export const initWorkspace = (filePanelPlugin) => async (reducerDispatch: React.
} catch (error) { } catch (error) {
await basicWorkspaceInit(workspaces, workspaceProvider) 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 await basicWorkspaceInit(workspaces, workspaceProvider)
} else if (localStorage.getItem("currentWorkspace")) { } else if (localStorage.getItem("currentWorkspace")) {
const index = workspaces.findIndex(element => element.name == 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 NO_WORKSPACE = ' - none - '
const [currentWorkspace, setCurrentWorkspace] = useState<string>(NO_WORKSPACE) const [currentWorkspace, setCurrentWorkspace] = useState<string>(NO_WORKSPACE)
const [selectedWorkspace, setSelectedWorkspace] = useState<{ name: string, isGitRepo: boolean}>(null) const [selectedWorkspace, setSelectedWorkspace] = useState<{ name: string, isGitRepo: boolean}>(null)
const [showDropdown, setShowDropdown] = useState<boolean>(false)
const global = useContext(FileSystemContext) const global = useContext(FileSystemContext)
const workspaceRenameInput = useRef() const workspaceRenameInput = useRef()
const workspaceCreateInput = useRef() const workspaceCreateInput = useRef()
@ -157,6 +158,10 @@ export function Workspace () {
} }
} }
const toggleDropdown = (isOpen: boolean) => {
setShowDropdown(isOpen)
}
const createModalMessage = () => { const createModalMessage = () => {
return ( return (
<> <>
@ -196,7 +201,7 @@ export function Workspace () {
<div> <div>
<header> <header>
<div className="mb-2"> <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' /> <FormattedMessage id='filePanel.workspace' defaultMessage='Workspaces' />
</label> </label>
<span className="remixui_menu"> <span className="remixui_menu">
@ -266,12 +271,12 @@ export function Workspace () {
title='Clone Git Repository'> title='Clone Git Repository'>
</span> </span>
</span> </span>
<Dropdown id="workspacesSelect" data-id="workspacesSelect"> <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 ? 'far fa-code-branch' : null}> <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 } { selectedWorkspace ? selectedWorkspace.name : currentWorkspace === LOCALHOST ? 'localhost' : NO_WORKSPACE }
</Dropdown.Toggle> </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) => ( global.fs.browser.workspaces.map(({ name, isGitRepo }, index) => (
<Dropdown.Item <Dropdown.Item
@ -298,7 +303,7 @@ export function Workspace () {
</div> </div>
</header> </header>
</div> </div>
<div className='h-100 remixui_fileExplorerTree'> <div className='h-100 remixui_fileExplorerTree' onFocus={() => { toggleDropdown(false) }}>
<div className='h-100'> <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> { 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'> : <div className='pl-2 remixui_treeview' data-id='filePanelFileExplorerTree'>

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

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

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

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

Loading…
Cancel
Save