diff --git a/.circleci/config.yml b/.circleci/config.yml index 8bcbed1fca..988b8940ea 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -323,7 +323,7 @@ jobs: environment: - COMMIT_AUTHOR_EMAIL: "yann@ethereum.org" - COMMIT_AUTHOR: "Circle CI" - - FILES_TO_PACKAGE: "dist/apps/remix-ide/index.html dist/apps/remix-ide/raw-loader*.js dist/apps/remix-ide/assets dist/apps/remix-ide/main*.js dist/apps/remix-ide/polyfills*.js dist/apps/remix-ide/favicon.ico dist/apps/remix-ide/vendors~app*.js dist/apps/remix-ide/app*.js" + - FILES_TO_PACKAGE: "dist/apps/remix-ide/index.html dist/apps/remix-ide/404.html dist/apps/remix-ide/raw-loader*.js dist/apps/remix-ide/assets dist/apps/remix-ide/main*.js dist/apps/remix-ide/polyfills*.js dist/apps/remix-ide/favicon.ico dist/apps/remix-ide/vendors~app*.js dist/apps/remix-ide/app*.js" working_directory: ~/remix-project steps: @@ -352,7 +352,7 @@ jobs: environment: - COMMIT_AUTHOR_EMAIL: "yann@ethereum.org" - COMMIT_AUTHOR: "Circle CI" - - FILES_TO_PACKAGE: "dist/apps/remix-ide/index.html dist/apps/remix-ide/raw-loader*.js dist/apps/remix-ide/assets dist/apps/remix-ide/main*.js dist/apps/remix-ide/polyfills*.js dist/apps/remix-ide/favicon.ico dist/apps/remix-ide/vendors~app*.js dist/apps/remix-ide/app*.js" + - FILES_TO_PACKAGE: "dist/apps/remix-ide/index.html dist/apps/remix-ide/404.html dist/apps/remix-ide/raw-loader*.js dist/apps/remix-ide/assets dist/apps/remix-ide/main*.js dist/apps/remix-ide/polyfills*.js dist/apps/remix-ide/favicon.ico dist/apps/remix-ide/vendors~app*.js dist/apps/remix-ide/app*.js" working_directory: ~/remix-project steps: @@ -380,7 +380,7 @@ jobs: environment: - COMMIT_AUTHOR_EMAIL: "yann@ethereum.org" - COMMIT_AUTHOR: "Circle CI" - - FILES_TO_PACKAGE: "dist/apps/remix-ide/index.html dist/apps/remix-ide/raw-loader*.js dist/apps/remix-ide/assets dist/apps/remix-ide/main*.js dist/apps/remix-ide/polyfills*.js dist/apps/remix-ide/favicon.ico dist/apps/remix-ide/vendors~app*.js dist/apps/remix-ide/app*.js" + - FILES_TO_PACKAGE: "dist/apps/remix-ide/index.html dist/apps/remix-ide/404.html dist/apps/remix-ide/raw-loader*.js dist/apps/remix-ide/assets dist/apps/remix-ide/main*.js dist/apps/remix-ide/polyfills*.js dist/apps/remix-ide/favicon.ico dist/apps/remix-ide/vendors~app*.js dist/apps/remix-ide/app*.js" working_directory: ~/remix-project steps: diff --git a/README.md b/README.md index aea4299352..84cc44a09b 100644 --- a/README.md +++ b/README.md @@ -1,13 +1,24 @@ -[![CircleCI](https://circleci.com/gh/ethereum/remix-project.svg?style=svg)](https://circleci.com/gh/ethereum/remix-project) -[![Documentation Status](https://readthedocs.org/projects/docs/badge/?version=latest)](https://remix-ide.readthedocs.io/en/latest/index.html) -[![contributions welcome](https://img.shields.io/badge/contributions-welcome-brightgreen.svg?style=flat)](https://github.com/ethereum/remix-project/blob/master/CONTRIBUTING.md) -![GitHub contributors](https://img.shields.io/github/contributors/ethereum/remix-project) -[![Awesome Remix](https://img.shields.io/badge/Awesome--Remix-resources-green)](https://github.com/ethereum/awesome-remix) +

+ Remix Logo +

+

Remix Project

+ +
+ + +[![CircleCI](https://img.shields.io/circleci/build/github/ethereum/remix-project?logo=circleci)](https://circleci.com/gh/ethereum/remix-project) +[![Documentation Status](https://readthedocs.org/projects/remix-ide/badge/?version=latest)](https://remix-ide.readthedocs.io/en/latest/index.html) +[![contributions welcome](https://img.shields.io/badge/contributions-welcome-brightgreen.svg?style=flat&logo=github)](https://github.com/ethereum/remix-project/blob/master/CONTRIBUTING.md) +[![GitHub contributors](https://img.shields.io/github/contributors/ethereum/remix-project?style=flat&logo=github)](https://github.com/ethereum/remix-project/blob/master/CONTRIBUTING.md) +[![Awesome Remix](https://img.shields.io/badge/Awesome--Remix-resources-green?logo=awesomelists)](https://github.com/ethereum/awesome-remix) ![GitHub](https://img.shields.io/github/license/ethereum/remix-project) -[![Join the chat at https://gitter.im/ethereum/remix](https://badges.gitter.im/ethereum/remix.svg)](https://gitter.im/ethereum/remix?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) -[![Twitter Follow](https://img.shields.io/twitter/follow/ethereumremix?style=social)](https://twitter.com/ethereumremix) +[![Gitter Chat](https://img.shields.io/badge/Gitter%20-chat-brightgreen?style=plastic&logo=gitter)](https://gitter.im/ethereum/remix) +[![Twitter Follow](https://img.shields.io/twitter/follow/ethereumremix?style=flat&logo=twitter&color=green)](https://twitter.com/ethereumremix) + +
+ +## Remix Project -# Remix Project **Remix Project** is a rich toolset including Remix IDE, a comprehensive smart contract development tool. The Remix Project also includes Remix Plugin Engine and Remix Libraries which are low-level tools for wider use. ## Remix IDE @@ -43,7 +54,7 @@ Note: It contains the latest supported version of Solidity available at the time "npm": "^6.14.15" } ``` -* Install [Nx CLI](https://nx.dev/react/cli/overview) globally to enable running **nx executable commands**. +* Install [Nx CLI](https://nx.dev/using-nx/nx-cli) globally to enable running **nx executable commands**. ```bash yarn global add @nrwl/cli ``` @@ -116,7 +127,7 @@ curl https://raw.githubusercontent.com/ethereum/remix-project/master/docker-comp ### Troubleshooting -If you have trouble building the project, make sure that you have the correct version of `node`, `npm` and `nvm`. Also ensure [Nx CLI](https://nx.dev/react/cli/overview) is installed globally. +If you have trouble building the project, make sure that you have the correct version of `node`, `npm` and `nvm`. Also ensure [Nx CLI](https://nx.dev/using-nx/nx-cli) is installed globally. Run: diff --git a/apps/remix-ide-e2e/src/commands/createContract.ts b/apps/remix-ide-e2e/src/commands/createContract.ts index 44ff4780c7..015a4f2012 100644 --- a/apps/remix-ide-e2e/src/commands/createContract.ts +++ b/apps/remix-ide-e2e/src/commands/createContract.ts @@ -2,7 +2,7 @@ import { NightwatchBrowser } from 'nightwatch' import EventEmitter from 'events' class CreateContract extends EventEmitter { - command (this: NightwatchBrowser, inputParams: string[]): NightwatchBrowser { + command (this: NightwatchBrowser, inputParams: string): NightwatchBrowser { this.api.perform((done) => { createContract(this.api, inputParams, () => { done() @@ -13,19 +13,11 @@ class CreateContract extends EventEmitter { } } -function createContract (browser: NightwatchBrowser, inputParams: string[], callback: VoidFunction) { - if (inputParams.length === 1) { - browser.setValue('.udapp_contractActionsContainerSingle > input', inputParams[0], function () { +function createContract (browser: NightwatchBrowser, inputParams: string, callback: VoidFunction) { + if (inputParams) { + browser.setValue('.udapp_contractActionsContainerSingle > input', inputParams, function () { browser.click('.udapp_contractActionsContainerSingle > button').pause(500).perform(function () { callback() }) }) - } else if (inputParams.length > 1) { - browser.perform((done) => { - for (let i = 0; i < inputParams.length; i++) { - browser.setValue(`div.udapp_multiArg:nth-child(${i + 1}) > input`, inputParams[i]) - } - done() - }) - .click('div.udapp_multiArg > button').pause(500).perform(function () { callback() }) } else { browser .click('.udapp_contractActionsContainerSingle > button') diff --git a/apps/remix-ide-e2e/src/commands/currentWorkspaceIs.ts b/apps/remix-ide-e2e/src/commands/currentWorkspaceIs.ts index a090735f45..26da9cf9c8 100644 --- a/apps/remix-ide-e2e/src/commands/currentWorkspaceIs.ts +++ b/apps/remix-ide-e2e/src/commands/currentWorkspaceIs.ts @@ -3,15 +3,15 @@ import EventEmitter from 'events' class CurrentWorkspaceIs extends EventEmitter { command (this: NightwatchBrowser, name: string): NightwatchBrowser { - this.api - .execute(function () { - const el = document.querySelector('select[data-id="workspacesSelect"]') as HTMLSelectElement - return el.value - }, [], (result) => { - console.log(result) - this.api.assert.equal(result.value, name) - this.emit('complete') - }) + const browser = this.api + + browser.getText('[data-id="workspacesSelect"]', function (result) { + browser.assert.equal(result.value, name) + }) + .perform((done) => { + done() + this.emit('complete') + }) return this } } diff --git a/apps/remix-ide-e2e/src/commands/switchEnvironment.ts b/apps/remix-ide-e2e/src/commands/switchEnvironment.ts new file mode 100644 index 0000000000..03564ced41 --- /dev/null +++ b/apps/remix-ide-e2e/src/commands/switchEnvironment.ts @@ -0,0 +1,18 @@ +import { NightwatchBrowser } from 'nightwatch' +import EventEmitter from 'events' + +class switchEnvironment extends EventEmitter { + command (this: NightwatchBrowser, provider: string): NightwatchBrowser { + this.api.waitForElementVisible('[data-id="settingsSelectEnvOptions"]') + .click('[data-id="settingsSelectEnvOptions"] button') + .waitForElementVisible(`[data-id="dropdown-item-${provider}"]`) + .click(`[data-id="dropdown-item-${provider}"]`) + .perform((done) => { + done() + this.emit('complete') + }) + return this + } +} + +module.exports = switchEnvironment diff --git a/apps/remix-ide-e2e/src/commands/switchWorkspace.ts b/apps/remix-ide-e2e/src/commands/switchWorkspace.ts new file mode 100644 index 0000000000..6219ddd218 --- /dev/null +++ b/apps/remix-ide-e2e/src/commands/switchWorkspace.ts @@ -0,0 +1,18 @@ +import { NightwatchBrowser } from 'nightwatch' +import EventEmitter from 'events' + +class switchWorkspace extends EventEmitter { + command (this: NightwatchBrowser, workspaceName: string): NightwatchBrowser { + this.api.waitForElementVisible('[data-id="workspacesSelect"]') + .click('[data-id="workspacesSelect"]') + .waitForElementVisible(`[data-id="dropdown-item-${workspaceName}"]`) + .click(`[data-id="dropdown-item-${workspaceName}"]`) + .perform((done) => { + done() + this.emit('complete') + }) + return this + } +} + +module.exports = switchWorkspace diff --git a/apps/remix-ide-e2e/src/tests/ballot.test.ts b/apps/remix-ide-e2e/src/tests/ballot.test.ts index 9d2e9c5b07..1c8383f047 100644 --- a/apps/remix-ide-e2e/src/tests/ballot.test.ts +++ b/apps/remix-ide-e2e/src/tests/ballot.test.ts @@ -83,7 +83,7 @@ module.exports = { browser .openFile('Untitled.sol') .clickLaunchIcon('udapp') - .click('*[data-id="settingsSelectEnvOptions"] *[data-id="External Http Provider"]') + .switchEnvironment('External Http Provider') .waitForElementPresent('[data-id="basic-http-provider-modal-footer-ok-react"]') .execute(function () { const modal = document.querySelector('[data-id="basic-http-provider-modal-footer-ok-react"]') as any @@ -91,13 +91,7 @@ module.exports = { modal.click() }) .pause(5000) - .execute(function () { - const env: any = document.getElementById('selectExEnvOptions') - - return env.value - }, [], function (result) { - browser.assert.ok(result.value === 'External Http Provider', 'Web3 Provider not selected') - }) + .waitForElementContainsText('#selectExEnvOptions button', 'External Http Provider') .clickLaunchIcon('solidity') .clickLaunchIcon('udapp') .pause(2000) diff --git a/apps/remix-ide-e2e/src/tests/ballot_0_4_11.test.ts b/apps/remix-ide-e2e/src/tests/ballot_0_4_11.test.ts index 95696e3882..9a36ad67f4 100644 --- a/apps/remix-ide-e2e/src/tests/ballot_0_4_11.test.ts +++ b/apps/remix-ide-e2e/src/tests/ballot_0_4_11.test.ts @@ -78,7 +78,7 @@ module.exports = { browser .openFile('Untitled.sol') .clickLaunchIcon('udapp') - .click('*[data-id="settingsSelectEnvOptions"] *[data-id="External Http Provider"]') + .switchEnvironment('External Http Provider') .waitForElementPresent('[data-id="basic-http-provider-modal-footer-ok-react"]') .execute(function () { const modal = document.querySelector('[data-id="basic-http-provider-modal-footer-ok-react"]') as any diff --git a/apps/remix-ide-e2e/src/tests/debugger.test.ts b/apps/remix-ide-e2e/src/tests/debugger.test.ts index a56670f3e8..09d378e5f2 100644 --- a/apps/remix-ide-e2e/src/tests/debugger.test.ts +++ b/apps/remix-ide-e2e/src/tests/debugger.test.ts @@ -86,7 +86,7 @@ module.exports = { .clickLaunchIcon('udapp') .waitForElementPresent('*[title="Deploy - transact (not payable)"]', 35000) .selectContract('ERC20') - .createContract(["tokenName", "symbol"]) + .createContract('"tokenName", "symbol"') .debugTransaction(0) .pause(2000) .waitForElementVisible('#stepdetail') @@ -115,7 +115,7 @@ module.exports = { .testContracts('withABIEncoderV2.sol', sources[2]['withABIEncoderV2.sol'], ['test']) .clickLaunchIcon('udapp') .selectContract('test') - .createContract([]) + .createContract('') .clearConsole() .clickInstance(0) .clickFunction('test1 - transact (not payable)', { types: 'bytes userData', values: '0x000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000015b38da6a701c568545dcfcb03fcb875f56beddc4' }) @@ -148,7 +148,7 @@ module.exports = { .testContracts('locals.sol', sources[3]['locals.sol'], ['testLocals']) .clickLaunchIcon('udapp') .waitForElementPresent('*[title="Deploy - transact (not payable)"]', 40000) - .createContract([]) + .createContract('') .pause(2000) .clearConsole() .clickInstance(0) @@ -173,7 +173,7 @@ module.exports = { .pause(2000) .testContracts('withGeneratedSources.sol', sources[4]['withGeneratedSources.sol'], ['A']) .clickLaunchIcon('udapp') - .createContract([]) + .createContract('') .clearConsole() .clickInstance(0) .clickFunction('f - transact (not payable)', { types: 'uint256[] ', values: '[]' }) @@ -214,10 +214,10 @@ module.exports = { .setSolidityCompilerVersion('soljson-v0.8.7+commit.e28d00a7.js') .addFile('useDebugNodes.sol', sources[5]['useDebugNodes.sol']) // compile contract .clickLaunchIcon('udapp') - .click('*[data-id="settingsSelectEnvOptions"] *[data-id="External Http Provider"]') // select web3 provider with debug nodes URL - .clearValue('*[data-id="modalDialogCustomPromp"]') - .setValue('*[data-id="modalDialogCustomPromp"]', 'https://remix-rinkeby.ethdevops.io') - .modalFooterOKClick('basic-http-provider') + .switchEnvironment('External Http Provider') // select web3 provider with debug nodes URL + .clearValue('*[data-id="modalDialogCustomPromptText"]') + .setValue('*[data-id="modalDialogCustomPromptText"]', 'https://remix-rinkeby.ethdevops.io') + .modalFooterOKClick() .waitForElementPresent('*[title="Deploy - transact (not payable)"]', 65000) // wait for the compilation to succeed .clickLaunchIcon('debugger') .clearValue('*[data-id="debuggerTransactionInput"]') @@ -235,7 +235,7 @@ module.exports = { .testContracts('reverted.sol', sources[6]['reverted.sol'], ['A', 'B', 'C']) .clickLaunchIcon('udapp') .selectContract('A') - .createContract([]) + .createContract('') .pause(500) .clickInstance(0) .clickFunction('callA - transact (not payable)') diff --git a/apps/remix-ide-e2e/src/tests/generalSettings.test.ts b/apps/remix-ide-e2e/src/tests/generalSettings.test.ts index ca54678a7b..a4932b05fd 100644 --- a/apps/remix-ide-e2e/src/tests/generalSettings.test.ts +++ b/apps/remix-ide-e2e/src/tests/generalSettings.test.ts @@ -41,7 +41,7 @@ module.exports = { .setValue('*[data-id="settingsTabGistAccessToken"]', '**********') .click('*[data-id="settingsTabSaveGistToken"]') .waitForElementVisible('*[data-shared="tooltipPopup"]', 5000) - .assert.containsText('*[data-shared="tooltipPopup"]', 'Access token has been saved') + .assert.containsText('*[data-shared="tooltipPopup"]', 'GitHub credentials updated') .pause(3000) }, @@ -59,7 +59,7 @@ module.exports = { .pause(1000) .click('*[data-id="settingsTabRemoveGistToken"]') .waitForElementVisible('*[data-shared="tooltipPopup"]', 5000) - .assert.containsText('*[data-shared="tooltipPopup"]', 'Access token removed') + .assert.containsText('*[data-shared="tooltipPopup"]', 'GitHub credentials removed') .assert.containsText('*[data-id="settingsTabGistAccessToken"]', '') }, diff --git a/apps/remix-ide-e2e/src/tests/libraryDeployment.test.ts b/apps/remix-ide-e2e/src/tests/libraryDeployment.test.ts index 276e9136d5..4b3c4528c4 100644 --- a/apps/remix-ide-e2e/src/tests/libraryDeployment.test.ts +++ b/apps/remix-ide-e2e/src/tests/libraryDeployment.test.ts @@ -22,7 +22,7 @@ module.exports = { browser.verifyContracts(['test']) .clickLaunchIcon('udapp') .selectContract('test') - .createContract([]) + .createContract('') .getAddressAtPosition(0, (address) => { console.log('testAutoDeployLib ' + address) addressRef = address @@ -46,7 +46,7 @@ module.exports = { .verifyContracts(['test']) .clickLaunchIcon('udapp') .selectContract('lib') // deploy lib - .createContract([]) + .createContract('') .perform((done) => { browser.getAddressAtPosition(0, (address) => { console.log(address) @@ -74,7 +74,7 @@ function checkDeployShouldFail (browser: NightwatchBrowser, callback: VoidFuncti .openFile('Untitled5.sol') .clickLaunchIcon('udapp') .selectContract('test') // deploy lib - .createContract([]) + .createContract('') .pause(2000) .getText('div[class^="terminal"]', (value) => { console.log('value: ', value) @@ -98,7 +98,7 @@ function checkDeployShouldSucceed (browser: NightwatchBrowser, address: string, .openFile('Untitled5.sol') .clickLaunchIcon('udapp') .selectContract('test') // deploy lib - .createContract([]) + .createContract('') .getAddressAtPosition(1, (address) => { addressRef = address }) diff --git a/apps/remix-ide-e2e/src/tests/migrateFileSystem.test.ts b/apps/remix-ide-e2e/src/tests/migrateFileSystem.test.ts index 3d04a3edfc..0914f6c2f5 100644 --- a/apps/remix-ide-e2e/src/tests/migrateFileSystem.test.ts +++ b/apps/remix-ide-e2e/src/tests/migrateFileSystem.test.ts @@ -81,7 +81,7 @@ module.exports = { // these are test data entries 'Should have a workspace_test #group1 #group3 #group5 #group7': function (browser: NightwatchBrowser) { browser.waitForElementVisible('*[data-id="remixIdeSidePanel"]', 5000) - .click('*[data-id="workspacesSelect"] option[value="workspace_test"]') + .switchWorkspace('workspace_test') .waitForElementVisible('*[data-id="treeViewLitreeViewItemtest_contracts"]') }, 'Should have a sol file with test data #group1 #group3 #group5 #group7': function (browser: NightwatchBrowser) { @@ -103,7 +103,7 @@ module.exports = { }, 'Should have a empty workspace #group1 #group3 #group5 #group7': function (browser: NightwatchBrowser) { browser.waitForElementVisible('*[data-id="remixIdeSidePanel"]', 5000) - .click('*[data-id="workspacesSelect"] option[value="emptyspace"]') + .switchWorkspace('emptyspace') }, // end of test data entries 'Should load with all storage blocked #group4': function (browser: NightwatchBrowser) { diff --git a/apps/remix-ide-e2e/src/tests/plugin_api.ts b/apps/remix-ide-e2e/src/tests/plugin_api.ts index 7cf8cc9598..16439be438 100644 --- a/apps/remix-ide-e2e/src/tests/plugin_api.ts +++ b/apps/remix-ide-e2e/src/tests/plugin_api.ts @@ -188,7 +188,7 @@ module.exports = { .frameParent() .useCss() .clickLaunchIcon('udapp') - .waitForElementContainsText('#selectExEnvOptions option:checked', 'Remix VM (Berlin)') + .waitForElementContainsText('#selectExEnvOptions button', 'Remix VM (Berlin)') .clickLaunchIcon('localPlugin') .useXpath() // @ts-ignore @@ -298,25 +298,25 @@ module.exports = { }, null, null) }, 'Should get all workspaces #group2': async function (browser: NightwatchBrowser) { - await clickAndCheckLog(browser, 'filePanel:getWorkspaces', ['default_workspace', 'emptyworkspace', 'testspace'], null, null) + await clickAndCheckLog(browser, 'filePanel:getWorkspaces', [{name:"default_workspace",isGitRepo:false}, {name:"emptyworkspace",isGitRepo:false}, {name:"testspace",isGitRepo:false}], null, null) }, 'Should have set workspace event #group2': async function (browser: NightwatchBrowser) { await clickAndCheckLog(browser, 'filePanel:createWorkspace', null, { event: 'setWorkspace', args: [{ name: 'newspace', isLocalhost: false }] }, 'newspace') }, 'Should have event when switching workspace #group2': async function (browser: NightwatchBrowser) { // @ts-ignore - browser.frameParent().useCss().clickLaunchIcon('filePanel').click('*[data-id="workspacesSelect"] option[value="default_workspace"]').useXpath().click('//*[@data-id="verticalIconsKindlocalPlugin"]').frame(0, async () => { + browser.frameParent().useCss().clickLaunchIcon('filePanel').switchWorkspace('default_workspace').useXpath().click('//*[@data-id="verticalIconsKindlocalPlugin"]').frame(0, async () => { await clickAndCheckLog(browser, null, null, { event: 'setWorkspace', args: [{ name: 'default_workspace', isLocalhost: false }] }, null) }) }, 'Should rename workspace #group2': async function (browser: NightwatchBrowser) { await clickAndCheckLog(browser, 'filePanel:renameWorkspace', null, null, ['default_workspace', 'renamed']) - await clickAndCheckLog(browser, 'filePanel:getWorkspaces', ['emptyworkspace', 'testspace', 'newspace', 'renamed'], null, null) + await clickAndCheckLog(browser, 'filePanel:getWorkspaces', [{name:"emptyworkspace",isGitRepo:false},{name:"testspace",isGitRepo:false},{name:"newspace",isGitRepo:false},{name:"renamed",isGitRepo:false}], null, null) }, 'Should delete workspace #group2': async function (browser: NightwatchBrowser) { await clickAndCheckLog(browser, 'filePanel:deleteWorkspace', null, null, ['testspace']) - await clickAndCheckLog(browser, 'filePanel:getWorkspaces', ['emptyworkspace', 'newspace', 'renamed'], null, null) + await clickAndCheckLog(browser, 'filePanel:getWorkspaces', [{name:"emptyworkspace",isGitRepo:false},{name:"newspace",isGitRepo:false},{name:"renamed",isGitRepo:false}], null, null) }, // DGIT 'Should have changes on new workspace #group3': async function (browser: NightwatchBrowser) { @@ -391,7 +391,7 @@ module.exports = { .useCss() .clickLaunchIcon('pluginManager') .clickLaunchIcon('udapp') - .click('*[data-id="Hardhat Provider"]') + .switchEnvironment('Hardhat Provider') .modalFooterOKClick('hardhat-provider') .waitForElementContainsText('*[data-id="settingsNetworkEnv"]', 'Custom') // e.g Custom (1337) network .clickLaunchIcon('localPlugin') diff --git a/apps/remix-ide-e2e/src/tests/providers.test.ts b/apps/remix-ide-e2e/src/tests/providers.test.ts index fe6dcde8bf..5cb1048d91 100644 --- a/apps/remix-ide-e2e/src/tests/providers.test.ts +++ b/apps/remix-ide-e2e/src/tests/providers.test.ts @@ -10,7 +10,7 @@ module.exports = { 'Should switch to ganache provider, set a custom URL and fail to connect': function (browser: NightwatchBrowser) { browser.waitForElementVisible('div[data-id="remixIdeIconPanel"]', 10000) .clickLaunchIcon('udapp') - .click('*[data-id="Ganache Provider"]') + .switchEnvironment('Ganache Provider') .waitForElementVisible('*[data-id="ganache-providerModalDialogModalBody-react"]') .execute(() => { (document.querySelector('*[data-id="ganache-providerModalDialogModalBody-react"] input') as any).focus() @@ -25,7 +25,7 @@ module.exports = { }, 'Should switch to ganache provider, use the default ganache URL and succeed to connect': function (browser: NightwatchBrowser) { - browser.click('*[data-id="Ganache Provider"]') + browser.switchEnvironment('Ganache Provider') .waitForElementVisible('*[data-id="ganache-providerModalDialogModalBody-react"]') .modalFooterOKClick('ganache-provider') .waitForElementContainsText('*[data-id="settingsNetworkEnv"]', 'Custom (') @@ -33,7 +33,7 @@ module.exports = { 'Should switch to foundry provider, set a custom URL and fail to connect': function (browser: NightwatchBrowser) { browser.waitForElementVisible('div[data-id="remixIdeIconPanel"]', 10000) - .click('*[data-id="Foundry Provider"]') + .switchEnvironment('Foundry Provider') .waitForElementVisible('*[data-id="foundry-providerModalDialogModalBody-react"]') .execute(() => { (document.querySelector('*[data-id="foundry-providerModalDialogModalBody-react"] input') as any).focus() @@ -48,7 +48,7 @@ module.exports = { }, 'Should switch to foundry provider, use the default foundry URL and succeed to connect': function (browser: NightwatchBrowser) { - browser.click('*[data-id="Foundry Provider"]') + browser.switchEnvironment('Foundry Provider') .waitForElementVisible('*[data-id="foundry-providerModalDialogModalBody-react"]') .modalFooterOKClick('foundry-provider') .waitForElementContainsText('*[data-id="settingsNetworkEnv"]', 'Custom (') diff --git a/apps/remix-ide-e2e/src/tests/recorder.test.ts b/apps/remix-ide-e2e/src/tests/recorder.test.ts index 7e8748b737..0f896a179e 100644 --- a/apps/remix-ide-e2e/src/tests/recorder.test.ts +++ b/apps/remix-ide-e2e/src/tests/recorder.test.ts @@ -3,6 +3,7 @@ import { NightwatchBrowser } from 'nightwatch' import init from '../helpers/init' module.exports = { + '@disabled': true, before: function (browser: NightwatchBrowser, done: VoidFunction) { init(browser, done) }, @@ -11,7 +12,7 @@ module.exports = { return sources }, - 'Run Scenario': function (browser: NightwatchBrowser) { + 'Run Scenario #group1': function (browser: NightwatchBrowser) { let addressRef browser.addFile('scenario.json', { content: records }) .pause(5000) @@ -36,10 +37,10 @@ module.exports = { .click('*[data-id="deployAndRunClearInstances"]') }, - 'Save scenario': function (browser: NightwatchBrowser) { + 'Save scenario #group1': function (browser: NightwatchBrowser) { browser.testContracts('testRecorder.sol', sources[0]['testRecorder.sol'], ['testRecorder']) .clickLaunchIcon('udapp') - .createContract(['12']) + .createContract('12') .clickInstance(0) .clickFunction('set - transact (not payable)', { types: 'uint256 _p', values: '34' }) .click('.savetransaction') @@ -64,7 +65,7 @@ module.exports = { }) }, - 'Record more than one contract': function (browser: NightwatchBrowser) { + 'Record more than one contract #group1': function (browser: NightwatchBrowser) { // deploy 2 contracts (2 different ABIs), save the record, reexecute and test one of the function. browser .click('*[data-id="deployAndRunClearInstances"]') @@ -72,11 +73,11 @@ module.exports = { .clickLaunchIcon('udapp') .selectContract('t1est') .pause(1000) - .createContract([]) + .createContract('') .clickInstance(0) .selectContract('t2est') .pause(1000) - .createContract([]) + .createContract('') .click('.savetransaction') .waitForElementVisible('[data-id="udappNotify-modal-footer-ok-react"]') .execute(function () { @@ -84,6 +85,7 @@ module.exports = { modalOk.click() }) + .pause(1000) .click('*[data-id="deployAndRunClearInstances"]') // clear udapp .click('*[data-id="terminalClearConsole"]') // clear terminal .click('[data-id="runtransaction"]') @@ -98,7 +100,7 @@ module.exports = { }, - 'Run with live "mode"': function (browser: NightwatchBrowser) { + 'Run with live "mode" #group1': function (browser: NightwatchBrowser) { let addressRef: string browser.addFile('scenario_live_mode.json', { content: JSON.stringify(liveModeScenario, null, '\t') }) .addFile('scenario_live_mode_storage.sol', { content: testStorageForLiveMode }) diff --git a/apps/remix-ide-e2e/src/tests/runAndDeploy.test.ts b/apps/remix-ide-e2e/src/tests/runAndDeploy.test.ts index ee5d8eae5e..c313be5772 100644 --- a/apps/remix-ide-e2e/src/tests/runAndDeploy.test.ts +++ b/apps/remix-ide-e2e/src/tests/runAndDeploy.test.ts @@ -32,7 +32,7 @@ module.exports = { 'Should sign message using account key #group2': function (browser: NightwatchBrowser) { browser.waitForElementVisible('*[data-id="settingsRemixRunSignMsg"]') - .click('select[id="selectExEnvOptions"] option[value="vm-berlin"]') + .switchEnvironment('vm-berlin') .pause(2000) .click('*[data-id="settingsRemixRunSignMsg"]') .pause(2000) diff --git a/apps/remix-ide-e2e/src/tests/signingMessage.test.ts b/apps/remix-ide-e2e/src/tests/signingMessage.test.ts index 177dac88a0..913851fa38 100644 --- a/apps/remix-ide-e2e/src/tests/signingMessage.test.ts +++ b/apps/remix-ide-e2e/src/tests/signingMessage.test.ts @@ -33,7 +33,7 @@ module.exports = { .clickLaunchIcon('udapp') .pause(5000) .selectContract('ECVerify') - .createContract([]) + .createContract('') .clickInstance(0) .perform((done) => { browser.getAddressAtPosition(0, (address) => { diff --git a/apps/remix-ide-e2e/src/tests/solidityUnittests.test.ts b/apps/remix-ide-e2e/src/tests/solidityUnittests.test.ts index 30a2913891..7e61167107 100644 --- a/apps/remix-ide-e2e/src/tests/solidityUnittests.test.ts +++ b/apps/remix-ide-e2e/src/tests/solidityUnittests.test.ts @@ -181,7 +181,7 @@ module.exports = { .execute(function () { document.querySelector('*[data-id="modalDialogCustomPromptTextCreate"]')['value'] = 'workspace_new' }) .waitForElementVisible('*[data-id="fileSystem-modal-footer-ok-react"]') .execute(function () { (document.querySelector('[data-id="fileSystem-modal-footer-ok-react"]') as HTMLElement).click() }) - .waitForElementPresent('*[data-id="workspacesSelect"] option[value="workspace_new"]') + .currentWorkspaceIs('workspace_new') .waitForElementVisible('li[data-id="treeViewLitreeViewItem.deps/remix-tests/remix_tests.sol"]') .waitForElementVisible('li[data-id="treeViewLitreeViewItem.deps/remix-tests/remix_accounts.sol"]') .openFile('.deps/remix-tests/remix_tests.sol') diff --git a/apps/remix-ide-e2e/src/tests/specialFunctions.test.ts b/apps/remix-ide-e2e/src/tests/specialFunctions.test.ts index 48137e3b8b..bcdc3c4977 100644 --- a/apps/remix-ide-e2e/src/tests/specialFunctions.test.ts +++ b/apps/remix-ide-e2e/src/tests/specialFunctions.test.ts @@ -24,7 +24,7 @@ module.exports = { .clickLaunchIcon('udapp') .selectAccount('0xCA35b7d915458EF540aDe6068dFe2F44E8fa733c') // this account will be used for this test suite .selectContract('CheckSpecials') - .createContract([]) // deploy + .createContract('') // deploy .clickInstance(0) .perform((done) => { browser.getAddressAtPosition(0, (address) => { @@ -92,7 +92,7 @@ module.exports = { .testContracts('receiveOnly.sol', sources[1]['receiveOnly.sol'], ['CheckSpecials']) .clickLaunchIcon('udapp') .selectContract('CheckSpecials') - .createContract([]) + .createContract('') .clickInstance(0) .perform((done) => { browser.getAddressAtPosition(0, (address) => { @@ -122,7 +122,7 @@ module.exports = { .testContracts('fallbackOnlyPayable.sol', sources[2]['fallbackOnlyPayable.sol'], ['CheckSpecials']) .clickLaunchIcon('udapp') .selectContract('CheckSpecials') - .createContract([]) + .createContract('') .clickInstance(0) .perform((done) => { browser.getAddressAtPosition(0, (address) => { @@ -153,7 +153,7 @@ module.exports = { .testContracts('fallbackOnlyNotPayable.sol', sources[3]['fallbackOnlyNotPayable.sol'], ['CheckSpecials']) .clickLaunchIcon('udapp') .selectContract('CheckSpecials') - .createContract([]) + .createContract('') .clickInstance(0) .perform((done) => { browser.getAddressAtPosition(0, (address) => { @@ -174,7 +174,7 @@ module.exports = { .clearValue('#value') .setValue('#value', '0') .pause(2000) - .createContract([]) + .createContract('') .pause(1000) .clickInstance(0).pause(1000) .perform((done) => { @@ -208,7 +208,7 @@ module.exports = { .waitForElementVisible('#value') .clearValue('#value') .setValue('#value', '0').pause(2000) - .createContract([]) + .createContract('') .clickInstance(0) .pause(1000) .perform((done) => { diff --git a/apps/remix-ide-e2e/src/tests/terminal.test.ts b/apps/remix-ide-e2e/src/tests/terminal.test.ts index dbf6542bd8..12a6b73657 100644 --- a/apps/remix-ide-e2e/src/tests/terminal.test.ts +++ b/apps/remix-ide-e2e/src/tests/terminal.test.ts @@ -46,11 +46,11 @@ module.exports = { .waitForElementContainsText('*[data-id="terminalJournal"]', '["0x5B38Da6a701c568545dCfcB03FcB875f56beddC4","0xAb8483F64d9C6d1EcF9b849Ae677dD3315835cb2","0x4B20993Bc481177ec7E8f571ceCaE8A9e22C02db","0x78731D3Ca6b7E34aC0F824c42a7cC18A495cabaB","0x617F2E2fD72FD9D5503197092aC168c91465E7f2","0x17F6AD8Ef982297579C203069C1DbfFE4348c372","0x5c6B0f7Bf3E7ce046039Bd8FABdfD3f9F5021678","0x03C6FcED478cBbC9a4FAB34eF9f40767739D1Ff7","0x1aE0EA34a72D944a8C7603FfB3eC30a6669E454C","0x0A098Eda01Ce92ff4A4CCb7A4fFFb5A43EBC70DC","0xCA35b7d915458EF540aDe6068dFe2F44E8fa733c","0x14723A09ACff6D2A60DcdF7aA4AFf308FDDC160C","0x4B0897b0513fdC7C541B6d9D7E929C4e5364D2dB","0x583031D1113aD414F02576BD6afaBfb302140225","0xdD870fA1b7C4700F2BD7f44238821C26f7392148"]') }, - 'Call web3.eth.getAccounts() using Web3 Provider #group5': function (browser: NightwatchBrowser) { + 'Call web3.eth.getAccounts() using External Http Provider #group5': function (browser: NightwatchBrowser) { browser .click('*[data-id="terminalClearConsole"]') // clear the terminal .clickLaunchIcon('udapp') - .click('*[data-id="settingsSelectEnvOptions"] *[data-id="External Http Provider"]') + .switchEnvironment('External Http Provider') .modalFooterOKClick('basic-http-provider') .ExecuteScriptInTerminal('web3.eth.getAccounts()') .waitForElementContainsText('*[data-id="terminalJournal"]', '["', 60000) // we check if an array is present, don't need to check for the content @@ -95,7 +95,7 @@ module.exports = { browser .clickLaunchIcon('settings') .clickLaunchIcon('udapp') - .click('*[data-id="settingsVMLondonMode"]') + .switchEnvironment('vm-london') .click('*[data-id="terminalClearConsole"]') // clear the terminal .clickLaunchIcon('filePanel') .click('*[data-id="treeViewDivtreeViewItem"]') // make sure we create the file at the root folder @@ -172,7 +172,7 @@ module.exports = { .clickLaunchIcon('udapp') .click('*[data-id="deployAndRunClearInstances"]') .selectContract('OwnerTest') - .createContract([]) + .createContract('') .pause(1000) .journalChildIncludes('constructor', { shouldHaveOnlyOneOccurence: true }) .pause(5000) diff --git a/apps/remix-ide-e2e/src/tests/transactionExecution.test.ts b/apps/remix-ide-e2e/src/tests/transactionExecution.test.ts index 3eac406fcd..635218881f 100644 --- a/apps/remix-ide-e2e/src/tests/transactionExecution.test.ts +++ b/apps/remix-ide-e2e/src/tests/transactionExecution.test.ts @@ -161,7 +161,7 @@ module.exports = { browser .clickLaunchIcon('udapp') .clearTransactions() - .click('*[data-id="settingsVMLondonMode"]') // switch to London fork + .switchEnvironment('vm-london') // switch to London fork .selectAccount('0xCA35b7d915458EF540aDe6068dFe2F44E8fa733c') // this account will be used for this test suite .click('.udapp_contractActionsContainerSingle > button') .clickInstance(0) @@ -202,10 +202,10 @@ module.exports = { .addFile('Storage.sol', sources[6]['Storage.sol']) .addFile('Owner.sol', sources[6]['Owner.sol']) .clickLaunchIcon('udapp') - .createContract(['42', '24']) + .createContract('42, 24') .openFile('Storage.sol') .clickLaunchIcon('udapp') - .createContract(['102']) // this creation will fail if the component hasn't been properly reset. + .createContract('102') // this creation will fail if the component hasn't been properly reset. .clickInstance(1) .clickFunction('store - transact (not payable)', { types: 'uint256 num', values: '24' }) .testFunction('last', // we check if the contract is actually reachable. diff --git a/apps/remix-ide-e2e/src/tests/url.test.ts b/apps/remix-ide-e2e/src/tests/url.test.ts index f794f38c24..7b94cd3abd 100644 --- a/apps/remix-ide-e2e/src/tests/url.test.ts +++ b/apps/remix-ide-e2e/src/tests/url.test.ts @@ -29,6 +29,7 @@ module.exports = { .click('[for="autoCompile"]') // we set it too false again .click('[for="autoCompile"]') // back to True in the local storage .assert.containsText('*[data-id="compilerContainerCompileBtn"]', 'contract-76747f6e19.sol') + .clickLaunchIcon('filePanel') .currentWorkspaceIs('code-sample') .getEditorValue((content) => { browser.assert.ok(content && content.indexOf( @@ -57,6 +58,7 @@ module.exports = { .url('http://127.0.0.1:8080/#optimize=true&runs=300&evmVersion=istanbul&version=soljson-v0.7.4+commit.3f05b770.js&url=https://github.com/ethereum/remix-project/blob/master/apps/remix-ide/contracts/app/solidity/mode.sol&code=cHJhZ21hIHNvbGlkaXR5ID49MC42LjAgPDAuNy4wOwoKaW1wb3J0ICJodHRwczovL2dpdGh1Yi5jb20vT3BlblplcHBlbGluL29wZW56ZXBwZWxpbi1jb250cmFjdHMvYmxvYi9tYXN0ZXIvY29udHJhY3RzL2FjY2Vzcy9Pd25hYmxlLnNvbCI7Cgpjb250cmFjdCBHZXRQYWlkIGlzIE93bmFibGUgewogIGZ1bmN0aW9uIHdpdGhkcmF3KCkgZXh0ZXJuYWwgb25seU93bmVyIHsKICB9Cn0') .refresh() // we do one reload for making sure we already have the default workspace .pause(5000) + .clickLaunchIcon('filePanel') .currentWorkspaceIs('code-sample') .getEditorValue((content) => { browser.assert.ok(content && content.indexOf( @@ -113,7 +115,7 @@ module.exports = { .url('http://127.0.0.1:8080/#optimize=false&runs=200&evmVersion=null&version=soljson-v0.6.12+commit.27d51765.js&url=https://raw.githubusercontent.com/EthVM/evm-source-verification/main/contracts/1/0x011e5846975c6463a8c6337eecf3cbf64e328884/input.json') .refresh() .pause(5000) - .waitForElementPresent('*[data-id="workspacesSelect"] option[value="code-sample"]') + .switchWorkspace('code-sample') .openFile('@openzeppelin') .openFile('@openzeppelin/contracts') .openFile('@openzeppelin/contracts/access') diff --git a/apps/remix-ide-e2e/src/tests/workspace.test.ts b/apps/remix-ide-e2e/src/tests/workspace.test.ts index 95819b7598..aa0d2bbaf1 100644 --- a/apps/remix-ide-e2e/src/tests/workspace.test.ts +++ b/apps/remix-ide-e2e/src/tests/workspace.test.ts @@ -235,7 +235,7 @@ module.exports = { .pause(2000) .waitForElementNotPresent('*[data-id="treeViewLitreeViewItemtest.sol"]') .pause(2000) - .click('*[data-id="workspacesSelect"] option[value="workspace_name"]') + .switchWorkspace('workspace_name') .waitForElementVisible('*[data-id="treeViewLitreeViewItemtests"]') }, @@ -249,23 +249,23 @@ module.exports = { .setValue('*[data-id="modalDialogCustomPromptTextRename"]', 'workspace_name_renamed') .waitForElementPresent('[data-id="fileSystemModalDialogModalFooter-react"] .modal-ok') .click('[data-id="fileSystemModalDialogModalFooter-react"] .modal-ok') - .waitForElementPresent('*[data-id="workspacesSelect"] option[value="workspace_name_1"]') - .click('*[data-id="workspacesSelect"] option[value="workspace_name_1"]') + .switchWorkspace('workspace_name_1') .pause(2000) .waitForElementNotPresent('*[data-id="treeViewLitreeViewItemtest.sol"]') - .waitForElementPresent('*[data-id="workspacesSelect"] option[value="workspace_name_renamed"]') - .click('*[data-id="workspacesSelect"] option[value="workspace_name_renamed"]') + .switchWorkspace('workspace_name_renamed') .pause(2000) .waitForElementVisible('*[data-id="treeViewLitreeViewItemtest.sol"]') }, 'Should delete a workspace #group1': function (browser: NightwatchBrowser) { browser - .click('*[data-id="workspacesSelect"] option[value="workspace_name_1"]') + .switchWorkspace('workspace_name_1') .click('*[data-id="workspaceDelete"]') // delete workspace_name_1 .waitForElementVisible('[data-id="fileSystemModalDialogModalFooter-react"] .modal-ok') .click('[data-id="fileSystemModalDialogModalFooter-react"] .modal-ok') - .waitForElementNotPresent('*[data-id="workspacesSelect"] option[value="workspace_name_1"]') + .waitForElementVisible('[data-id="workspacesSelect"]') + .click('[data-id="workspacesSelect"]') + .waitForElementNotPresent(`[data-id="dropdown-item-workspace_name_1"]`) .end() }, diff --git a/apps/remix-ide-e2e/src/types/index.d.ts b/apps/remix-ide-e2e/src/types/index.d.ts index 0e2b394cc5..e7882e45e9 100644 --- a/apps/remix-ide-e2e/src/types/index.d.ts +++ b/apps/remix-ide-e2e/src/types/index.d.ts @@ -34,7 +34,7 @@ declare module 'nightwatch' { getModalBody(callback: (value: string, cb: VoidFunction) => void): NightwatchBrowser, modalFooterCancelClick(id?: string): NightwatchBrowser, selectContract(contractName: string): NightwatchBrowser, - createContract(inputParams: string[]): NightwatchBrowser, + createContract(inputParams: string): NightwatchBrowser, getAddressAtPosition(index: number, cb: (pos: string) => void): NightwatchBrowser, testConstantFunction(address: string, fnFullName: string, expectedInput: NightwatchTestConstantFunctionExpectedInput | null, expectedOutput: string): NightwatchBrowser, getEditorValue(callback: (content: string) => void): NightwatchBrowser, @@ -62,7 +62,9 @@ declare module 'nightwatch' { clearConsole (this: NightwatchBrowser): NightwatchBrowser clearTransactions (this: NightwatchBrowser): NightwatchBrowser getBrowserLogs (this: NightwatchBrowser): NightwatchBrowser - currentSelectedFileIs (name: string): NightwatchBrowser + currentSelectedFileIs (name: string): NightwatchBrowser, + switchWorkspace: (workspaceName: string) => NightwatchBrowser + switchEnvironment: (provider: string) => NightwatchBrowser } export interface NightwatchBrowser { diff --git a/apps/remix-ide/ci/deploy_from_travis_remix-alpha.sh b/apps/remix-ide/ci/deploy_from_travis_remix-alpha.sh index 19860c9c1c..9ea9fa20ee 100755 --- a/apps/remix-ide/ci/deploy_from_travis_remix-alpha.sh +++ b/apps/remix-ide/ci/deploy_from_travis_remix-alpha.sh @@ -14,7 +14,7 @@ echo "To use an offline copy, download \`remix-$SHA.zip\`." >> README.md cp -r $FILES_TO_PACKAGE "./" rm -rf dist ls -FILES_TO_DEPLOY="assets index.html main*.js polyfills*.js favicon.ico vendors~app*.js app*.js raw-loader*.js" +FILES_TO_DEPLOY="assets index.html 404.html main*.js polyfills*.js favicon.ico vendors~app*.js app*.js raw-loader*.js" # ZIP the whole directory zip -r remix-$SHA.zip $FILES_TO_DEPLOY # -f is needed because "build" is part of .gitignore diff --git a/apps/remix-ide/ci/deploy_from_travis_remix-beta.sh b/apps/remix-ide/ci/deploy_from_travis_remix-beta.sh index 441a127410..9eb72f1d4d 100755 --- a/apps/remix-ide/ci/deploy_from_travis_remix-beta.sh +++ b/apps/remix-ide/ci/deploy_from_travis_remix-beta.sh @@ -14,7 +14,7 @@ echo "To use an offline copy, download \`remix-$SHA.zip\`." >> README.md cp -r $FILES_TO_PACKAGE "./" rm -rf dist ls -FILES_TO_DEPLOY="assets index.html main*.js polyfills*.js favicon.ico vendors~app*.js app*.js raw-loader*.js" +FILES_TO_DEPLOY="assets index.html 404.html main*.js polyfills*.js favicon.ico vendors~app*.js app*.js raw-loader*.js" # ZIP the whole directory zip -r remix-$SHA.zip $FILES_TO_DEPLOY # -f is needed because "build" is part of .gitignore diff --git a/apps/remix-ide/ci/deploy_from_travis_remix-live.sh b/apps/remix-ide/ci/deploy_from_travis_remix-live.sh index 3460d25fa2..9a82fbb26a 100755 --- a/apps/remix-ide/ci/deploy_from_travis_remix-live.sh +++ b/apps/remix-ide/ci/deploy_from_travis_remix-live.sh @@ -14,7 +14,7 @@ echo "To use an offline copy, download \`remix-$SHA.zip\`." >> README.md cp -r $FILES_TO_PACKAGE "./" rm -rf dist ls -FILES_TO_DEPLOY="assets index.html main*.js polyfills*.js vendors~app*.js app*.js raw-loader*.js" +FILES_TO_DEPLOY="assets index.html 404.html main*.js polyfills*.js vendors~app*.js app*.js raw-loader*.js" # ZIP the whole directory zip -r remix-$SHA.zip $FILES_TO_DEPLOY # -f is needed because "build" is part of .gitignore diff --git a/apps/remix-ide/src/404.html b/apps/remix-ide/src/404.html new file mode 100644 index 0000000000..28268a1ab1 --- /dev/null +++ b/apps/remix-ide/src/404.html @@ -0,0 +1,15 @@ + + + + + + + + \ No newline at end of file diff --git a/apps/remix-ide/src/app.js b/apps/remix-ide/src/app.js index 847011a30c..41ede2d848 100644 --- a/apps/remix-ide/src/app.js +++ b/apps/remix-ide/src/app.js @@ -31,6 +31,8 @@ import { HardhatProvider } from './app/tabs/hardhat-provider' import { GanacheProvider } from './app/tabs/ganache-provider' import { FoundryProvider } from './app/tabs/foundry-provider' import { ExternalHttpProvider } from './app/tabs/external-http-provider' +import { Injected0ptimismProvider } from './app/tabs/injected-optimism-provider' +import { InjectedArbitrumOneProvider } from './app/tabs/injected-arbitrum-one-provider' const isElectron = require('is-electron') @@ -183,6 +185,8 @@ class AppComponent { const ganacheProvider = new GanacheProvider(blockchain) const foundryProvider = new FoundryProvider(blockchain) const externalHttpProvider = new ExternalHttpProvider(blockchain) + const injected0ptimismProvider = new Injected0ptimismProvider(blockchain) + const injectedArbitrumOneProvider = new InjectedArbitrumOneProvider(blockchain) // ----------------- convert offset to line/column service ----------- const offsetToLineColumnConverter = new OffsetToLineColumnConverter() Registry.getInstance().put({ @@ -244,6 +248,8 @@ class AppComponent { ganacheProvider, foundryProvider, externalHttpProvider, + injected0ptimismProvider, + injectedArbitrumOneProvider, this.walkthroughService, search ]) diff --git a/apps/remix-ide/src/app/files/dgitProvider.js b/apps/remix-ide/src/app/files/dgitProvider.js index 249640a0f3..8b5ce925c5 100644 --- a/apps/remix-ide/src/app/files/dgitProvider.js +++ b/apps/remix-ide/src/app/files/dgitProvider.js @@ -233,12 +233,11 @@ class DGitProvider extends Plugin { return this.calculateLocalStorage() } - async clone (input) { + async clone (input, workspaceName, workspaceExists = false) { const permission = await this.askUserPermission('clone', 'Import multiple files into your workspaces.') if (!permission) return false if (this.calculateLocalStorage() > 10000) throw new Error('The local storage of the browser is full.') - await this.call('filePanel', 'createWorkspace', `workspace_${Date.now()}`, true) - + if (!workspaceExists) await this.call('filePanel', 'createWorkspace', workspaceName || `workspace_${Date.now()}`, true) const cmd = { url: input.url, singleBranch: input.singleBranch, @@ -249,9 +248,11 @@ class DGitProvider extends Plugin { } const result = await git.clone(cmd) - setTimeout(async () => { - await this.call('fileManager', 'refresh') - }, 1000) + if (!workspaceExists) { + setTimeout(async () => { + await this.call('fileManager', 'refresh') + }, 1000) + } return result } diff --git a/apps/remix-ide/src/app/files/fileManager.ts b/apps/remix-ide/src/app/files/fileManager.ts index e7ae4763df..44b604b8d4 100644 --- a/apps/remix-ide/src/app/files/fileManager.ts +++ b/apps/remix-ide/src/app/files/fileManager.ts @@ -19,7 +19,7 @@ const profile = { icon: 'assets/img/fileManager.webp', permission: true, version: packageJson.version, - methods: ['closeAllFiles', 'closeFile', 'file', 'exists', 'open', 'writeFile', 'readFile', 'copyFile', 'copyDir', 'rename', 'mkdir', 'readdir', 'dirList', 'fileList', 'remove', 'getCurrentFile', 'getFile', 'getFolder', 'setFile', 'switchFile', 'refresh', 'getProviderOf', 'getProviderByName', 'getPathFromUrl', 'getUrlFromPath', 'saveCurrentFile', 'setBatchFiles'], + methods: ['closeAllFiles', 'closeFile', 'file', 'exists', 'open', 'writeFile', 'readFile', 'copyFile', 'copyDir', 'rename', 'mkdir', 'readdir', 'dirList', 'fileList', 'remove', 'getCurrentFile', 'getFile', 'getFolder', 'setFile', 'switchFile', 'refresh', 'getProviderOf', 'getProviderByName', 'getPathFromUrl', 'getUrlFromPath', 'saveCurrentFile', 'setBatchFiles', 'isGitRepo'], kind: 'file-system' } const errorMsg = { @@ -810,6 +810,13 @@ class FileManager extends Plugin { return provider.workspace } } + + async isGitRepo (directory: string): Promise { + const path = directory + '/.git' + const exists = await this.exists(path) + + return exists + } } module.exports = FileManager diff --git a/apps/remix-ide/src/app/panels/file-panel.js b/apps/remix-ide/src/app/panels/file-panel.js index b25fe16c82..a0d4e5dff3 100644 --- a/apps/remix-ide/src/app/panels/file-panel.js +++ b/apps/remix-ide/src/app/panels/file-panel.js @@ -152,6 +152,9 @@ module.exports = class Filepanel extends ViewPlugin { const workspaceProvider = this.fileProviders.workspace this.currentWorkspaceMetadata = { name: workspace.name, isLocalhost: workspace.isLocalhost, absolutePath: `${workspaceProvider.workspacesPath}/${workspace.name}` } + if (workspace.name !== " - connect to localhost - ") { + localStorage.setItem('currentWorkspace', workspace.name) + } this.emit('setWorkspace', workspace) } diff --git a/apps/remix-ide/src/app/tabs/abstract-provider.tsx b/apps/remix-ide/src/app/tabs/abstract-provider.tsx index ff924a618a..31b1a4e317 100644 --- a/apps/remix-ide/src/app/tabs/abstract-provider.tsx +++ b/apps/remix-ide/src/app/tabs/abstract-provider.tsx @@ -3,21 +3,21 @@ import { AppModal, AlertModal, ModalTypes } from '@remix-ui/app' import { Blockchain } from '../../blockchain/blockchain' import { ethers } from 'ethers' -type JsonDataRequest = { +export type JsonDataRequest = { id: number, jsonrpc: string // version method: string, params: Array, } -type JsonDataResult = { +export type JsonDataResult = { id: number, jsonrpc: string // version result: any } -type RejectRequest = (error: Error) => void -type SuccessRequest = (data: JsonDataResult) => void +export type RejectRequest = (error: Error) => void +export type SuccessRequest = (data: JsonDataResult) => void export abstract class AbstractProvider extends Plugin { provider: ethers.providers.JsonRpcProvider diff --git a/apps/remix-ide/src/app/tabs/external-http-provider.tsx b/apps/remix-ide/src/app/tabs/external-http-provider.tsx index 5a22b48ba4..71c7c249c0 100644 --- a/apps/remix-ide/src/app/tabs/external-http-provider.tsx +++ b/apps/remix-ide/src/app/tabs/external-http-provider.tsx @@ -30,7 +30,7 @@ export class ExternalHttpProvider extends AbstractProvider {
WARNING: It is not safe to use the --http.corsdomain flag with a wildcard: --http.corsdomain *
-
For more info: Remix Docs on Web3 Provider +
For more info: Remix Docs on External HTTP Provider

External HTTP Provider Endpoint diff --git a/apps/remix-ide/src/app/tabs/injected-arbitrum-one-provider.tsx b/apps/remix-ide/src/app/tabs/injected-arbitrum-one-provider.tsx new file mode 100644 index 0000000000..283dacdf24 --- /dev/null +++ b/apps/remix-ide/src/app/tabs/injected-arbitrum-one-provider.tsx @@ -0,0 +1,21 @@ +import * as packageJson from '../../../../../package.json' +import { InjectedProvider } from './injected-provider' + +const profile = { + name: 'injected-arbitrum-one-provider', + displayName: 'Injected Arbitrum One Provider', + kind: 'provider', + description: 'injected Arbitrum One Provider', + methods: ['sendAsync'], + version: packageJson.version +} + +export class InjectedArbitrumOneProvider extends InjectedProvider { + + constructor () { + super(profile) + this.chainName = 'Arbitrum One' + this.chainId = '0xa4b1' + this.rpcUrls = ['https://arb1.arbitrum.io/rpc'] + } +} \ No newline at end of file diff --git a/apps/remix-ide/src/app/tabs/injected-optimism-provider.tsx b/apps/remix-ide/src/app/tabs/injected-optimism-provider.tsx new file mode 100644 index 0000000000..64c8c4e91a --- /dev/null +++ b/apps/remix-ide/src/app/tabs/injected-optimism-provider.tsx @@ -0,0 +1,21 @@ +import * as packageJson from '../../../../../package.json' +import { InjectedProvider } from './injected-provider' + +const profile = { + name: 'injected-optimism-provider', + displayName: 'Injected Optimism Provider', + kind: 'provider', + description: 'injected Optimism Provider', + methods: ['sendAsync'], + version: packageJson.version +} + +export class Injected0ptimismProvider extends InjectedProvider { + + constructor () { + super(profile) + this.chainName = 'Optimism' + this.chainId = '0xa' + this.rpcUrls = ['https://mainnet.optimism.io'] + } +} \ No newline at end of file diff --git a/apps/remix-ide/src/app/tabs/injected-provider.tsx b/apps/remix-ide/src/app/tabs/injected-provider.tsx new file mode 100644 index 0000000000..038919206c --- /dev/null +++ b/apps/remix-ide/src/app/tabs/injected-provider.tsx @@ -0,0 +1,75 @@ +import { Plugin } from '@remixproject/engine' +import { JsonDataRequest, RejectRequest, SuccessRequest } from './abstract-provider' +import { ethers } from 'ethers' +import Web3 from 'web3' + +export class InjectedProvider extends Plugin { + provider: any + chainName: string + chainId: string + rpcUrls: Array + + constructor (profile) { + super(profile) + if ((window as any).ethereum) { + this.provider = new Web3((window as any).ethereum) + } + } + + sendAsync (data: JsonDataRequest): Promise { + return new Promise((resolve, reject) => { + this.sendAsyncInternal(data, resolve, reject) + }) + } + + private async sendAsyncInternal (data: JsonDataRequest, resolve: SuccessRequest, reject: RejectRequest): Promise { + // Check the case where current environment is VM on UI and it still sends RPC requests + // This will be displayed on UI tooltip as 'cannot get account list: Environment Updated !!' + if (!this.provider) { + this.call('notification', 'toast', 'No injected provider (e.g Metamask) has been found.') + return reject(new Error('no injected provider found.')) + } + try { + if ((window as any) && typeof (window as any).ethereum.enable === 'function') (window as any).ethereum.enable() + if (!await (window as any).ethereum._metamask.isUnlocked()) this.call('notification', 'toast', 'Please make sure the injected provider is unlocked (e.g Metamask).') + await addL2Network(this.chainName, this.chainId, this.rpcUrls) + const resultData = await this.provider.currentProvider.send(data.method, data.params) + resolve({ jsonrpc: '2.0', result: resultData.result, id: data.id }) + } catch (error) { + reject(error) + } + } +} + +export const addL2Network = async (chainName: string, chainId: string, rpcUrls: Array) => { + try { + await (window as any).ethereum.request({ + method: 'wallet_switchEthereumChain', + params: [{ chainId: chainId }], + }); + } catch (switchError) { + // This error code indicates that the chain has not been added to MetaMask. + if (switchError.code === 4902) { + try { + await (window as any).ethereum.request({ + method: 'wallet_addEthereumChain', + params: [ + { + chainId: chainId, + chainName: chainName, + rpcUrls: rpcUrls, + }, + ], + }); + + await (window as any).ethereum.request({ + method: 'wallet_switchEthereumChain', + params: [{ chainId: chainId }], + }); + } catch (addError) { + // handle "add" error + } + } + // handle other "switch" errors + } +} \ No newline at end of file diff --git a/apps/remix-ide/src/app/udapp/run-tab.js b/apps/remix-ide/src/app/udapp/run-tab.js index cf71ad3780..c9912ecb4c 100644 --- a/apps/remix-ide/src/app/udapp/run-tab.js +++ b/apps/remix-ide/src/app/udapp/run-tab.js @@ -102,6 +102,7 @@ export class RunTab extends ViewPlugin { await this.call('blockchain', 'addProvider', { name: 'Hardhat Provider', + isInjected: false, provider: { async sendAsync (payload, callback) { try { @@ -116,6 +117,7 @@ export class RunTab extends ViewPlugin { await this.call('blockchain', 'addProvider', { name: 'Ganache Provider', + isInjected: false, provider: { async sendAsync (payload, callback) { try { @@ -130,6 +132,7 @@ export class RunTab extends ViewPlugin { await this.call('blockchain', 'addProvider', { name: 'Foundry Provider', + isInjected: false, provider: { async sendAsync (payload, callback) { try { @@ -144,6 +147,7 @@ export class RunTab extends ViewPlugin { await this.call('blockchain', 'addProvider', { name: 'Wallet Connect', + isInjected: false, provider: { async sendAsync (payload, callback) { try { @@ -169,6 +173,36 @@ export class RunTab extends ViewPlugin { } } }) + + await this.call('blockchain', 'addProvider', { + name: 'Optimism Provider', + isInjected: true, + provider: { + async sendAsync (payload, callback) { + try { + const result = await udapp.call('injected-optimism-provider', 'sendAsync', payload) + callback(null, result) + } catch (e) { + callback(e) + } + } + } + }) + + await this.call('blockchain', 'addProvider', { + name: 'Arbitrum One Provider', + isInjected: true, + provider: { + async sendAsync (payload, callback) { + try { + const result = await udapp.call('injected-arbitrum-one-provider', 'sendAsync', payload) + callback(null, result) + } catch (e) { + callback(e) + } + } + } + }) } writeFile (fileName, content) { diff --git a/apps/remix-ide/src/blockchain/blockchain.js b/apps/remix-ide/src/blockchain/blockchain.js index 3d6e885cec..08d4b8f3bd 100644 --- a/apps/remix-ide/src/blockchain/blockchain.js +++ b/apps/remix-ide/src/blockchain/blockchain.js @@ -11,8 +11,7 @@ import InjectedProvider from './providers/injected.js' import NodeProvider from './providers/node.js' import { execution, EventManager, helpers } from '@remix-project/remix-lib' import { etherScanLink } from './helper' -import { logBuilder, confirmProxyMsg } from "@remix-ui/helper" -import { cancelProxyMsg } from '@remix-ui/helper' +import { logBuilder, cancelUpgradeMsg, cancelProxyMsg } from "@remix-ui/helper" const { txFormat, txExecution, typeConversion, txListener: Txlistener, TxRunner, TxRunnerWeb3, txHelper } = execution const { txResultHelper: resultToRemixTx } = helpers const packageJson = require('../../../../package.json') @@ -150,9 +149,11 @@ export class Blockchain extends Plugin { cancelLabel: 'Cancel', okFn: () => { this.runProxyTx(proxyData, implementationContractObject) + _paq.push(['trackEvent', 'blockchain', 'Deploy With Proxy', 'modal ok confirmation']) }, cancelFn: () => { this.call('notification', 'toast', cancelProxyMsg()) + _paq.push(['trackEvent', 'blockchain', 'Deploy With Proxy', 'cancel proxy deployment']) }, hideFn: () => null } @@ -161,7 +162,9 @@ export class Blockchain extends Plugin { async runProxyTx (proxyData, implementationContractObject) { const args = { useCall: false, data: proxyData } + let networkInfo const confirmationCb = (network, tx, gasEstimation, continueTxExecution, cancelCb) => { + networkInfo = network // continue using original authorization given by user continueTxExecution(null) } @@ -171,14 +174,60 @@ export class Blockchain extends Plugin { if (error) { const log = logBuilder(error) + _paq.push(['trackEvent', 'blockchain', 'Deploy With Proxy', 'Proxy deployment failed: ' + error]) return this.call('terminal', 'logHtml', log) } + if (networkInfo.name === 'VM') this.config.set('vm/proxy', address) + else this.config.set(`${networkInfo.name}/${networkInfo.currentFork}/${networkInfo.id}/proxy`, address) + _paq.push(['trackEvent', 'blockchain', 'Deploy With Proxy', 'Proxy deployment successful']) return this.call('udapp', 'resolveContractAndAddInstance', implementationContractObject, address) } this.runTx(args, confirmationCb, continueCb, promptCb, finalCb) } + async upgradeProxy(proxyAddress, newImplAddress, data, newImplementationContractObject) { + const upgradeModal = { + id: 'confirmProxyDeployment', + title: 'ERC1967', + message: `Confirm you want to upgrade your contract to new implementation ${newImplAddress}.`, + modalType: 'modal', + okLabel: 'OK', + cancelLabel: 'Cancel', + okFn: () => { + this.runUpgradeTx(proxyAddress, data, newImplementationContractObject) + _paq.push(['trackEvent', 'blockchain', 'Upgrade With Proxy', 'proxy upgrade confirmation click']) + }, + cancelFn: () => { + this.call('notification', 'toast', cancelUpgradeMsg()) + _paq.push(['trackEvent', 'blockchain', 'Upgrade With Proxy', 'proxy upgrade cancel click']) + }, + hideFn: () => null + } + this.call('notification', 'modal', upgradeModal) + } + + async runUpgradeTx (proxyAddress, data, newImplementationContractObject) { + const args = { useCall: false, data, to: proxyAddress } + const confirmationCb = (network, tx, gasEstimation, continueTxExecution, cancelCb) => { + // continue using original authorization given by user + continueTxExecution(null) + } + const continueCb = (error, continueTxExecution, cancelCb) => { continueTxExecution() } + const promptCb = (okCb, cancelCb) => { okCb() } + const finalCb = (error, txResult, address, returnValue) => { + if (error) { + const log = logBuilder(error) + + _paq.push(['trackEvent', 'blockchain', 'Upgrade With Proxy', 'Upgrade failed']) + return this.call('terminal', 'logHtml', log) + } + _paq.push(['trackEvent', 'blockchain', 'Upgrade With Proxy', 'Upgrade Successful']) + return this.call('udapp', 'resolveContractAndAddInstance', newImplementationContractObject, proxyAddress) + } + this.runTx(args, confirmationCb, continueCb, promptCb, finalCb) + } + async getEncodedFunctionHex (args, funABI) { return new Promise((resolve, reject) => { txFormat.encodeFunctionCall(args, funABI, (error, data) => { diff --git a/apps/remix-ide/src/blockchain/execution-context.js b/apps/remix-ide/src/blockchain/execution-context.js index 95b4abe45d..1fe181acfb 100644 --- a/apps/remix-ide/src/blockchain/execution-context.js +++ b/apps/remix-ide/src/blockchain/execution-context.js @@ -152,9 +152,12 @@ export class ExecutionContext { if (context === 'injected') { if (injectedProvider === undefined) { - infoCb('No injected Web3 provider found. Make sure your provider (e.g. MetaMask) is active and running (when recently activated you may have to reload the page).') + infoCb('No injected provider found. Make sure your provider (e.g. MetaMask) is active and running (when recently activated you may have to reload the page).') return cb() } else { + if (injectedProvider && injectedProvider._metamask && injectedProvider._metamask.isUnlocked) { + if (!await injectedProvider._metamask.isUnlocked()) this.call('notification', 'toast', 'Please make sure the injected provider is unlocked (e.g Metamask).') + } this.askPermission() this.executionContext = context web3.setProvider(injectedProvider) @@ -166,11 +169,21 @@ export class ExecutionContext { if (this.customNetWorks[context]) { var network = this.customNetWorks[context] - this.setProviderFromEndpoint(network.provider, { context: network.name }, (error) => { - if (error) infoCb(error) - cb() - }) - } + if (!this.customNetWorks[context].isInjected) { + this.setProviderFromEndpoint(network.provider, { context: network.name }, (error) => { + if (error) infoCb(error) + cb() + }) + } else { + // injected + this.askPermission() + this.executionContext = context + web3.setProvider(network.provider) + await this._updateChainContext() + this.event.trigger('contextChanged', [context]) + return cb() + } + } } currentblockGasLimit () { diff --git a/apps/remix-ide/src/remixAppManager.js b/apps/remix-ide/src/remixAppManager.js index 9c00f8ab46..fbe147a67b 100644 --- a/apps/remix-ide/src/remixAppManager.js +++ b/apps/remix-ide/src/remixAppManager.js @@ -19,7 +19,7 @@ const sensitiveCalls = { } export function isNative(name) { - const nativePlugins = ['vyper', 'workshops', 'debugger', 'remixd', 'menuicons', 'solidity', 'solidity-logic', 'solidityStaticAnalysis', 'solidityUnitTesting', 'layout', 'notification', 'hardhat-provider', 'ganache-provider', 'foundry-provider', 'basic-http-provider'] + const nativePlugins = ['vyper', 'workshops', 'debugger', 'remixd', 'menuicons', 'solidity', 'solidity-logic', 'solidityStaticAnalysis', 'solidityUnitTesting', 'layout', 'notification', 'hardhat-provider', 'ganache-provider', 'foundry-provider', 'basic-http-provider', 'injected-optimism-provider', 'injected-arbitrum-one-provider'] return nativePlugins.includes(name) || requiredModules.includes(name) } diff --git a/libs/remix-core-plugin/src/lib/constants/uups.ts b/libs/remix-core-plugin/src/lib/constants/uups.ts index 2fb521a999..977d3769a8 100644 --- a/libs/remix-core-plugin/src/lib/constants/uups.ts +++ b/libs/remix-core-plugin/src/lib/constants/uups.ts @@ -88,4 +88,18 @@ export const UUPSfunAbi = { type: "constructor", outputs: [], stateMutability: "payable" +} + +export const UUPSupgradeAbi = { + "inputs": [ + { + "internalType": "address", + "name": "newImplementation", + "type": "address" + } + ], + "name": "upgradeTo", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" } \ No newline at end of file diff --git a/libs/remix-core-plugin/src/lib/openzeppelin-proxy.ts b/libs/remix-core-plugin/src/lib/openzeppelin-proxy.ts index 2de757f4d2..bf3302f625 100644 --- a/libs/remix-core-plugin/src/lib/openzeppelin-proxy.ts +++ b/libs/remix-core-plugin/src/lib/openzeppelin-proxy.ts @@ -1,12 +1,12 @@ -import { Plugin } from '@remixproject/engine'; -import { ContractABI, ContractAST, DeployOption } from '../types/contract'; -import { UUPS, UUPSABI, UUPSBytecode, UUPSfunAbi } from './constants/uups'; +import { Plugin } from '@remixproject/engine' +import { ContractABI, ContractAST, ContractSources, DeployOptions } from '../types/contract' +import { UUPS, UUPSABI, UUPSBytecode, UUPSfunAbi, UUPSupgradeAbi } from './constants/uups' const proxyProfile = { name: 'openzeppelin-proxy', displayName: 'openzeppelin-proxy', description: 'openzeppelin-proxy', - methods: ['isConcerned', 'execute', 'getDeployOptions'] + methods: ['isConcerned', 'executeUUPSProxy', 'executeUUPSContractUpgrade', 'getProxyOptions', 'getUpgradeOptions'] }; export class OpenZeppelinProxy extends Plugin { blockchain: any @@ -28,26 +28,37 @@ export class OpenZeppelinProxy extends Plugin { return false } - async getDeployOptions (contracts: ContractABI): Promise<{ [name: string]: DeployOption }> { + async getProxyOptions (data: ContractSources, file: string): Promise<{ [name: string]: DeployOptions }> { + const contracts = data.contracts[file] + const ast = data.sources[file].ast const inputs = {} if (this.kind === 'UUPS') { Object.keys(contracts).map(name => { - const abi = contracts[name].abi - const initializeInput = abi.find(node => node.name === 'initialize') + if (ast) { + const UUPSSymbol = ast.exportedSymbols['UUPSUpgradeable'] ? ast.exportedSymbols['UUPSUpgradeable'][0] : null - if (initializeInput) { - inputs[name] = { - inputs: initializeInput, - initializeInputs: this.blockchain.getInputs(initializeInput) - } + ast.absolutePath === file && ast.nodes.map((node) => { + if (node.name === name && node.linearizedBaseContracts.includes(UUPSSymbol)) { + const abi = contracts[name].abi + const initializeInput = abi.find(node => node.name === 'initialize') + + inputs[name] = { + options: [{ title: 'Deploy with Proxy', active: false }, { title: 'Upgrade with Proxy', active: false }], + initializeOptions: { + inputs: initializeInput, + initializeInputs: initializeInput ? this.blockchain.getInputs(initializeInput) : null + } + } + } + }) } }) } return inputs } - async execute(implAddress: string, args: string | string [] = '', initializeABI, implementationContractObject): Promise { + async executeUUPSProxy(implAddress: string, args: string | string [] = '', initializeABI, implementationContractObject): Promise { // deploy the proxy, or use an existing one if (!initializeABI) throw new Error('Cannot deploy proxy: Missing initialize ABI') args = args === '' ? [] : args @@ -56,6 +67,13 @@ export class OpenZeppelinProxy extends Plugin { if (this.kind === 'UUPS') this.deployUUPSProxy(implAddress, _data, implementationContractObject) } + async executeUUPSContractUpgrade (proxyAddress: string, newImplAddress: string, newImplementationContractObject): Promise { + 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 { const args = [implAddress, _data] const constructorData = await this.blockchain.getEncodedParams(args, UUPSfunAbi) @@ -74,4 +92,20 @@ export class OpenZeppelinProxy extends Plugin { implementationContractObject.name = proxyName this.blockchain.deployProxy(data, implementationContractObject) } + + async upgradeUUPSProxy (proxyAddress: string, newImplAddress: string, newImplementationContractObject): Promise { + 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) + } } diff --git a/libs/remix-core-plugin/src/types/contract.ts b/libs/remix-core-plugin/src/types/contract.ts index 92c32653b5..a327539a60 100644 --- a/libs/remix-core-plugin/src/types/contract.ts +++ b/libs/remix-core-plugin/src/types/contract.ts @@ -54,101 +54,132 @@ export interface ContractAST { }[] } -export interface ContractABI { - [key: string]: { - abi: ({ - inputs: never[]; - stateMutability: string; - type: string; - anonymous?: undefined; - name?: undefined; - outputs?: undefined; - } | { - anonymous: boolean; - inputs: { - indexed: boolean; - internalType: string; - name: string; - type: string; - }[]; - name: string; - type: string; - stateMutability?: undefined; - outputs?: undefined; - } | { - inputs: { - internalType: string; - name: string; - type: string; - }[]; - name: string; - outputs: { - internalType: string; - name: string; - type: string; - }[]; - stateMutability: string; - type: string; - anonymous?: undefined; - })[]; - devdoc: { - kind: string; - methods: { - [key: string]: { - [key: string]: string - } - }; - version: number; - }; - evm: any - metadata: string; - storageLayout: { - storage: { - astId: number; - contract: string; - label: string; - offset: number; - slot: string; - type: string; - }[]; - types: { - [key: string]: { - base: string; - encoding: string; - label: string; - numberOfBytes: string; - members?: { - astId: number; - contract: string; - label: string; - offset: number; - slot: string; - type: string; - }[]; - }; - }; - }; - userdoc: { - kind: string; - methods: any; - version: number; - }; - }; +export type ContractABI = { + inputs: []; + stateMutability: string; + type: string; + anonymous?: undefined; + name?: string; + outputs?: undefined; +} | { + anonymous: boolean; + inputs: { + indexed: boolean; + internalType: string; + name: string; + type: string; + }[]; + name: string; + type: string; + stateMutability?: undefined; + outputs?: undefined; +} | { + inputs: { + internalType: string; + name: string; + type: string; + }[]; + name: string; + outputs: { + internalType: string; + name: string; + type: string; + }[]; + stateMutability: string; + type: string; + anonymous?: undefined; } +export type DeployMode = 'Deploy with Proxy' | 'Upgrade with Proxy' + export type DeployOption = { - initializeInputs: string, + initializeInputs: string, + inputs: { inputs: { - inputs: { - internalType?: string, - name: string, - type: string - }[], - name: "initialize", - outputs?: any[], - stateMutability: string, - type: string, - payable?: boolean, - constant?: any + internalType?: string, + name: string, + type: string + }[], + name: "initialize", + outputs?: any[], + stateMutability: string, + type: string, + payable?: boolean, + constant?: any + } +} +export interface DeployOptions { + initializeOptions: DeployOption, + options: { title: DeployMode, active: boolean }[] +} + +export interface ContractSources { + contracts: { + [path: string]: { + [contractName: string]: { + abi: ContractABI[], + devdoc: { + kind: string + methods: { + [key: string]: { + [key: string]: string + } + }; + version: number + } + evm: any + metadata: string + storageLayout: { + storage: { + astId: number + contract: string + label: string + offset: number + slot: string + type: string + }[] + types: { + [key: string]: { + base: string + encoding: string + label: string + numberOfBytes: string + members?: { + astId: number + contract: string + label: string + offset: number + slot: string + type: string + }[] + } + } + } + userdoc: { + kind: string + methods: any + version: number + } + } + } + }, + error: { + component: string, + errorCode: string, + formattedMessage: string, + message: string, + severity: string, + sourceLocation: { + end: number, + file: string, + start: number + }, + type: string + }[], + sources: { + [path: string]: { + ast: ContractAST, + id: number + } } } diff --git a/libs/remix-ui/helper/src/index.ts b/libs/remix-ui/helper/src/index.ts index 36d73ef523..9f050ea8b8 100644 --- a/libs/remix-ui/helper/src/index.ts +++ b/libs/remix-ui/helper/src/index.ts @@ -1,3 +1,4 @@ export * from './lib/remix-ui-helper' export * from './lib/helper-components' -export * from './lib/components/PluginViewWrapper' \ No newline at end of file +export * from './lib/components/PluginViewWrapper' +export * from './lib/components/custom-dropdown' \ No newline at end of file diff --git a/libs/remix-ui/helper/src/lib/components/custom-dropdown.tsx b/libs/remix-ui/helper/src/lib/components/custom-dropdown.tsx new file mode 100644 index 0000000000..5f0696a554 --- /dev/null +++ b/libs/remix-ui/helper/src/lib/components/custom-dropdown.tsx @@ -0,0 +1,42 @@ +// The forwardRef is important!! + +import React, { Ref } from "react" + +// Dropdown needs access to the DOM node in order to position the Menu +export const CustomToggle = React.forwardRef(({ children, onClick, icon, className = '' }: { children: React.ReactNode, onClick: (e) => void, icon: string, className: string }, ref: Ref) => ( + +)) + +// forwardRef again here! +// Dropdown needs access to the DOM of the Menu to measure it +export const CustomMenu = React.forwardRef( + ({ children, style, className, 'aria-labelledby': labeledBy }: { children: React.ReactNode, style?: React.CSSProperties, className: string, 'aria-labelledby'?: string }, ref: Ref) => { + return ( +
+
    + { + children + } +
+
+ ) + }, +) diff --git a/libs/remix-ui/helper/src/lib/helper-components.tsx b/libs/remix-ui/helper/src/lib/helper-components.tsx index 206fbec926..8dc4e95a16 100644 --- a/libs/remix-ui/helper/src/lib/helper-components.tsx +++ b/libs/remix-ui/helper/src/lib/helper-components.tsx @@ -89,3 +89,21 @@ export const cancelProxyMsg = () => ( Proxy deployment cancelled. ) + +export const cancelUpgradeMsg = () => ( +
+ Upgrade with proxy cancelled. +
+) + +export const deployWithProxyMsg = () => ( +
+ NOTE: Deploy With Proxy will initiate two (2) transactions. The first is for the deployment of your implementation contract and the second will be a deployment of an ERC1967 proxy contract. +
+) + +export const upgradeWithProxyMsg = () => ( +
+ NOTE: Upgrade With Proxy will initiate two (2) transactions. The first is for the deployment of your implementation contract and the second will intiate a call to the upgradeTo function in your proxy contract. +
+) diff --git a/libs/remix-ui/helper/src/lib/remix-ui-helper.ts b/libs/remix-ui/helper/src/lib/remix-ui-helper.ts index 71abad338d..d9fd9b03b9 100644 --- a/libs/remix-ui/helper/src/lib/remix-ui-helper.ts +++ b/libs/remix-ui/helper/src/lib/remix-ui-helper.ts @@ -47,6 +47,22 @@ export const createNonClashingNameAsync = async (name: string, fileManager, pref return name + counter + prefix + '.' + ext } +export const createNonClashingTitle = async (name: string, fileManager) => { + if (!name) name = 'Undefined' + let _counter + let exist = true + + do { + const isDuplicate = await fileManager.exists(name + (_counter || '')) + + if (isDuplicate) _counter = (_counter || 0) + 1 + else exist = false + } while (exist) + const counter = _counter || '' + + return name + counter +} + export const joinPath = (...paths) => { paths = paths.filter((value) => value !== '').map((path) => path.replace(/^\/|\/$/g, '')) // remove first and last slash) if (paths.length === 1) return paths[0] diff --git a/libs/remix-ui/panel/src/lib/plugins/panel-header.tsx b/libs/remix-ui/panel/src/lib/plugins/panel-header.tsx index 6cb3ed8116..b76a17859b 100644 --- a/libs/remix-ui/panel/src/lib/plugins/panel-header.tsx +++ b/libs/remix-ui/panel/src/lib/plugins/panel-header.tsx @@ -4,10 +4,11 @@ import { PluginRecord } from '../types' import './panel.css' export interface RemixPanelProps { - plugins: Record; - } + plugins: Record; +} const RemixUIPanelHeader = (props: RemixPanelProps) => { const [plugin, setPlugin] = useState() + const [toggleExpander, setToggleExpander] = useState(false) useEffect(() => { if (props.plugins) { @@ -18,9 +19,48 @@ const RemixUIPanelHeader = (props: RemixPanelProps) => { } }, [props]) + const toggleClass = () => { + setToggleExpander(!toggleExpander) + } + return ( -
{plugin?.profile.displayName || plugin?.profile.name}
- {plugin?.profile.documentation ? () : ''} +
+
+
{plugin?.profile.displayName || plugin?.profile.name}
+
+
+ {plugin?.profile?.maintainedBy?.toLowerCase() === "remix" && ()} + {plugin?.profile.documentation && ()} +
+
+ +
+
+
+
+ {plugin?.profile?.author && + + { plugin?.profile.author } + } + {plugin?.profile?.maintainedBy && + + { plugin?.profile.maintainedBy } + } + {plugin?.profile?.documentation && + + + + + } + {plugin?.profile?.description && + + { plugin?.profile.description } + } + {plugin?.profile?.repo && + + Make an issue + } +
) } diff --git a/libs/remix-ui/panel/src/lib/plugins/panel.css b/libs/remix-ui/panel/src/lib/plugins/panel.css index 8356d6ef15..b9988e19af 100644 --- a/libs/remix-ui/panel/src/lib/plugins/panel.css +++ b/libs/remix-ui/panel/src/lib/plugins/panel.css @@ -22,7 +22,6 @@ .swapitHeader { display: flex; align-items: center; - padding: 16px 24px 15px; justify-content: space-between; text-transform: uppercase; } diff --git a/libs/remix-ui/plugin-manager/src/lib/components/ActivePluginCard.tsx b/libs/remix-ui/plugin-manager/src/lib/components/ActivePluginCard.tsx index 593c6a2edd..47e35a55db 100644 --- a/libs/remix-ui/plugin-manager/src/lib/components/ActivePluginCard.tsx +++ b/libs/remix-ui/plugin-manager/src/lib/components/ActivePluginCard.tsx @@ -19,6 +19,9 @@ function ActivePluginCard ({
{ profile.displayName || profile.name } + { profile?.maintainedBy?.toLowerCase() == "remix" && + + } { profile.documentation &&