Merge branch 'master' into theme-hacker-owl

pull/1527/head
Liana Husikyan 3 years ago committed by GitHub
commit 9182de4e89
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 62
      .circleci/config.yml
  2. 14
      .github/workflows/publish-action.yml
  3. 2
      CONTRIBUTING.md
  4. 175
      README.md
  5. 30
      apps/remix-ide-e2e/src/commands/verifyContracts.ts
  6. 4
      apps/remix-ide-e2e/src/select_tests.sh
  7. 38
      apps/remix-ide-e2e/src/tests/ballot.test.ts
  8. 3
      apps/remix-ide-e2e/src/tests/compiler_api.test.ts
  9. 2
      apps/remix-ide-e2e/src/tests/fileManager_api.test.ts
  10. 6
      apps/remix-ide-e2e/src/tests/importFromGithub.test.ts
  11. 16
      apps/remix-ide-e2e/src/tests/plugin_api.ts
  12. 26
      apps/remix-ide-e2e/src/tests/providers.test.ts
  13. 15
      apps/remix-ide-e2e/src/tests/publishContract.test.ts
  14. 2
      apps/remix-ide-e2e/src/tests/recorder.test.ts
  15. 6
      apps/remix-ide-e2e/src/tests/search.test.ts
  16. 10
      apps/remix-ide-e2e/src/tests/solidityImport.test.ts
  17. 8
      apps/remix-ide-e2e/src/tests/terminal.test.ts
  18. 2
      apps/remix-ide-e2e/src/tests/url.test.ts
  19. 117
      apps/remix-ide-e2e/src/tests/workspace.test.ts
  20. 2
      apps/remix-ide-e2e/src/types/index.d.ts
  21. 2
      apps/remix-ide/.babelrc
  22. 1
      apps/remix-ide/.npmignore
  23. 4
      apps/remix-ide/.travis.yml
  24. 44
      apps/remix-ide/README.md
  25. 8
      apps/remix-ide/ci/browser_test.sh
  26. 6
      apps/remix-ide/ci/browser_tests.sh
  27. 6
      apps/remix-ide/ci/browser_tests_plugin_api.sh
  28. 8
      apps/remix-ide/ci/flaky.sh
  29. 2
      apps/remix-ide/ci/lint.sh
  30. 10
      apps/remix-ide/ci/publishIpfs
  31. 4
      apps/remix-ide/docs/file_explorer.md
  32. 8
      apps/remix-ide/docs/locations.md
  33. 2
      apps/remix-ide/docs/remix_tutorials_github.md
  34. 5
      apps/remix-ide/docs/remixd.md
  35. 6
      apps/remix-ide/release-process.md
  36. BIN
      apps/remix-ide/remix-screenshot-400h.png
  37. BIN
      apps/remix-ide/remix_screenshot.png
  38. 3
      apps/remix-ide/src/app.js
  39. 59
      apps/remix-ide/src/app/editor/editor.js
  40. 4
      apps/remix-ide/src/app/files/fileManager.ts
  41. 6
      apps/remix-ide/src/app/files/fileProvider.js
  42. 3
      apps/remix-ide/src/app/panels/layout.ts
  43. 27
      apps/remix-ide/src/app/panels/tab-proxy.js
  44. 2
      apps/remix-ide/src/app/plugins/config.ts
  45. 25
      apps/remix-ide/src/app/plugins/remixd-handle.tsx
  46. 4
      apps/remix-ide/src/app/tabs/compile-tab.js
  47. 33
      apps/remix-ide/src/app/tabs/foundry-provider.tsx
  48. 2
      apps/remix-ide/src/app/tabs/ganache-provider.tsx
  49. 14
      apps/remix-ide/src/app/udapp/run-tab.js
  50. 4
      apps/remix-ide/src/remixAppManager.js
  51. 4
      apps/remix-ide/team-best-practices.md
  52. 74
      apps/solidity-compiler/src/app/compiler-api.ts
  53. 27
      apps/solidity-compiler/src/app/compiler.ts
  54. 2
      libs/remix-analyzer/README.md
  55. 8
      libs/remix-analyzer/package.json
  56. 2
      libs/remix-astwalker/README.md
  57. 6
      libs/remix-astwalker/package.json
  58. 2
      libs/remix-debug/README.md
  59. 10
      libs/remix-debug/package.json
  60. 2
      libs/remix-lib/README.md
  61. 4
      libs/remix-lib/package.json
  62. 3
      libs/remix-lib/src/types/ICompilerApi.ts
  63. 2
      libs/remix-simulator/README.md
  64. 6
      libs/remix-simulator/package.json
  65. 2
      libs/remix-simulator/src/methods/blocks.ts
  66. 2
      libs/remix-solidity/README.md
  67. 6
      libs/remix-solidity/package.json
  68. 6
      libs/remix-solidity/src/compiler/compiler-input.ts
  69. 46
      libs/remix-solidity/src/compiler/compiler.ts
  70. 2
      libs/remix-solidity/src/compiler/types.ts
  71. 4
      libs/remix-tests/README.md
  72. 10
      libs/remix-tests/package.json
  73. 2
      libs/remix-tests/tests/testRunner.cli.spec.ts
  74. 6311
      libs/remix-ui/editor/src/lib/remix-plugin-types.ts
  75. 22
      libs/remix-ui/editor/src/lib/remix-ui-editor.tsx
  76. 135
      libs/remix-ui/editor/src/lib/web-types.ts
  77. 6
      libs/remix-ui/helper/src/lib/helper-components.tsx
  78. 12
      libs/remix-ui/helper/src/lib/remix-ui-helper.ts
  79. 12
      libs/remix-ui/home-tab/src/lib/components/rssFeed.css
  80. 42
      libs/remix-ui/home-tab/src/lib/components/rssFeed.tsx
  81. 31
      libs/remix-ui/home-tab/src/lib/remix-ui-home-tab.tsx
  82. 75
      libs/remix-ui/publish-to-storage/src/lib/publish-to-storage.tsx
  83. 9
      libs/remix-ui/publish-to-storage/src/lib/publishOnSwarm.tsx
  84. 46
      libs/remix-ui/publish-to-storage/src/lib/publishToIPFS.tsx
  85. 2
      libs/remix-ui/run-tab/src/lib/components/environment.tsx
  86. 6
      libs/remix-ui/run-tab/src/lib/components/instanceContainerUI.tsx
  87. 67
      libs/remix-ui/run-tab/src/lib/components/recorderCardUI.tsx
  88. 4
      libs/remix-ui/run-tab/src/lib/components/universalDappUI.tsx
  89. 7
      libs/remix-ui/run-tab/src/lib/css/card.css
  90. 17
      libs/remix-ui/run-tab/src/lib/css/run-tab.css
  91. 3
      libs/remix-ui/run-tab/src/lib/types/index.ts
  92. 4
      libs/remix-ui/search/src/lib/context/context.tsx
  93. 5
      libs/remix-ui/settings/src/lib/constants.ts
  94. 113
      libs/remix-ui/settings/src/lib/remix-ui-settings.tsx
  95. 9
      libs/remix-ui/settings/src/lib/settingsAction.ts
  96. 328
      libs/remix-ui/solidity-compiler/src/lib/compiler-container.tsx
  97. 18
      libs/remix-ui/solidity-compiler/src/lib/compilerConfiguration.tsx
  98. 9
      libs/remix-ui/solidity-compiler/src/lib/css/style.css
  99. 20
      libs/remix-ui/solidity-compiler/src/lib/logic/compileTabLogic.ts
  100. 35
      libs/remix-ui/solidity-compiler/src/lib/solidity-compiler.tsx
  101. Some files were not shown because too many files have changed in this diff Show More

@ -29,17 +29,17 @@ jobs:
- restore_cache: - restore_cache:
keys: keys:
- v1-deps-{{ checksum "package-lock.json" }} - v1-deps-{{ checksum "yarn.lock" }}
- run: npm install - run: yarn install
- save_cache: - save_cache:
key: v1-deps-{{ checksum "package-lock.json" }} key: v1-deps-{{ checksum "yarn.lock" }}
paths: paths:
- node_modules - node_modules
- run: npm run downloadsolc_assets - run: yarn run downloadsolc_assets
- run: npx nx build remix-ide - run: npx nx build remix-ide
- run: npx nx build remix-ide-e2e-src-local-plugin - run: npx nx build remix-ide-e2e-src-local-plugin
- run: npm run build:libs - run: yarn run build:libs
- run: mkdir persist && zip -r persist/dist.zip dist - run: mkdir persist && zip -r persist/dist.zip dist
- persist_to_workspace: - persist_to_workspace:
root: . root: .
@ -64,8 +64,8 @@ jobs:
- checkout - checkout
- restore_cache: - restore_cache:
keys: keys:
- v1-deps-{{ checksum "package-lock.json" }} - v1-deps-{{ checksum "yarn.lock" }}
- run: npm install - run: yarn install
- run: - run:
name: Remix Libs Linting name: Remix Libs Linting
command: ./apps/remix-ide/ci/lint.sh command: ./apps/remix-ide/ci/lint.sh
@ -91,10 +91,10 @@ jobs:
- run: unzip ./persist/dist.zip - run: unzip ./persist/dist.zip
- restore_cache: - restore_cache:
keys: keys:
- v1-deps-{{ checksum "package-lock.json" }} - v1-deps-{{ checksum "yarn.lock" }}
- run: npm i - run: yarn install
- run: cd dist/libs/remix-tests && npm install - run: cd dist/libs/remix-tests && yarn install
- run: npm run test:libs - run: yarn run test:libs
remix-ide-chrome: remix-ide-chrome:
docker: docker:
@ -127,8 +127,8 @@ jobs:
- run: unzip ./persist/dist.zip - run: unzip ./persist/dist.zip
- restore_cache: - restore_cache:
keys: keys:
- v1-deps-{{ checksum "package-lock.json" }} - v1-deps-{{ checksum "yarn.lock" }}
- run: npm install - run: yarn install
- run: - run:
name: Start Selenium name: Start Selenium
command: java -jar /usr/local/bin/selenium.jar command: java -jar /usr/local/bin/selenium.jar
@ -170,8 +170,8 @@ jobs:
- run: unzip ./persist/dist.zip - run: unzip ./persist/dist.zip
- restore_cache: - restore_cache:
keys: keys:
- v1-deps-{{ checksum "package-lock.json" }} - v1-deps-{{ checksum "yarn.lock" }}
- run: npm install - run: yarn install
- run: - run:
name: Start Selenium name: Start Selenium
command: java -jar /usr/local/bin/selenium.jar command: java -jar /usr/local/bin/selenium.jar
@ -213,8 +213,8 @@ jobs:
- run: unzip ./persist/dist.zip - run: unzip ./persist/dist.zip
- restore_cache: - restore_cache:
keys: keys:
- v1-deps-{{ checksum "package-lock.json" }} - v1-deps-{{ checksum "yarn.lock" }}
- run: npm install - run: yarn install
- run: - run:
name: Start Selenium name: Start Selenium
command: java -jar /usr/local/bin/selenium.jar command: java -jar /usr/local/bin/selenium.jar
@ -255,8 +255,8 @@ jobs:
- run: unzip ./persist/dist.zip - run: unzip ./persist/dist.zip
- restore_cache: - restore_cache:
keys: keys:
- v1-deps-{{ checksum "package-lock.json" }} - v1-deps-{{ checksum "yarn.lock" }}
- run: npm install - run: yarn install
- run: - run:
name: Start Selenium name: Start Selenium
command: java -jar /usr/local/bin/selenium.jar command: java -jar /usr/local/bin/selenium.jar
@ -298,8 +298,8 @@ jobs:
- run: unzip ./persist/dist.zip - run: unzip ./persist/dist.zip
- restore_cache: - restore_cache:
keys: keys:
- v1-deps-{{ checksum "package-lock.json" }} - v1-deps-{{ checksum "yarn.lock" }}
- run: npm install - run: yarn install
- run: - run:
name: Start Selenium name: Start Selenium
command: java -jar /usr/local/bin/selenium.jar command: java -jar /usr/local/bin/selenium.jar
@ -328,9 +328,9 @@ jobs:
steps: steps:
- checkout - checkout
- run: npm install - run: yarn install
- run: npm run downloadsolc_assets - run: yarn run downloadsolc_assets
- run: npm run build:production - run: yarn run build:production
- run: - run:
name: Deploy name: Deploy
command: | command: |
@ -357,9 +357,9 @@ jobs:
steps: steps:
- checkout - checkout
- run: npm install - run: yarn install
- run: npm run downloadsolc_assets - run: yarn run downloadsolc_assets
- run: npm run build:production - run: yarn run build:production
- run: - run:
name: Deploy name: Deploy
command: | command: |
@ -385,10 +385,10 @@ jobs:
steps: steps:
- checkout - checkout
- run: npm install - run: yarn install
- run: npm run build:libs - run: yarn run build:libs
- run: npm run downloadsolc_assets - run: yarn run downloadsolc_assets
- run: npm run build:production - run: yarn run build:production
- run: - run:
name: Deploy name: Deploy
command: | command: |

@ -9,18 +9,20 @@ jobs:
steps: steps:
- name: Checkout - name: Checkout
uses: actions/checkout@v2 uses: actions/checkout@v2
- run: npm install - uses: actions/setup-node@v3
with:
node-version: 14.17.6
- run: yarn install
- run: ls - run: ls
- run: pwd - run: pwd
- run: npm run downloadsolc_assets - run: yarn run downloadsolc_assets
- run: npm run build:production - run: yarn run build:production
- run: echo "action_state=$('./apps/remix-ide/ci/publishIpfs')" >> $GITHUB_ENV - run: echo "action_state=$('./apps/remix-ide/ci/publishIpfs' ${{ secrets.IPFS_PROJET_ID }} ${{ secrets.IPFS_PROJECT_SECRET }})" >> $GITHUB_ENV
- uses: mshick/add-pr-comment@v1 - uses: mshick/add-pr-comment@v1
with: with:
message: | message: |
ipfs://${{ env.action_state }} ipfs://${{ env.action_state }}
https://ipfs.remixproject.org/ipfs/${{ env.action_state }} https://remix-project.infura-ipfs.io/ipfs/${{ env.action_state }}
https://gateway.ipfs.io/ipfs/${{ env.action_state }}
repo-token: ${{ secrets.GITHUB_TOKEN }} repo-token: ${{ secrets.GITHUB_TOKEN }}
repo-token-user-login: 'github-actions[bot]' # The user.login for temporary GitHub tokens repo-token-user-login: 'github-actions[bot]' # The user.login for temporary GitHub tokens
allow-repeats: false # This is the default allow-repeats: false # This is the default

@ -12,6 +12,6 @@ When you add a code in any library, please ensure you add related tests. You can
Please conform to [standard](https://standardjs.com/) for code styles. Please conform to [standard](https://standardjs.com/) for code styles.
## Submitting Pull Request ## Submitting Pull Request
Please follow Github's standard model of making changes & submitting pull request which is very well explained [here](https://guides.github.com/activities/forking/). Make sure your code works fine locally before submitting a pull request. Please follow GitHub's standard model of making changes & submitting pull request which is very well explained [here](https://guides.github.com/activities/forking/). Make sure your code works fine locally before submitting a pull request.

@ -8,18 +8,23 @@
[![Twitter Follow](https://img.shields.io/twitter/follow/ethereumremix?style=social)](https://twitter.com/ethereumremix) [![Twitter Follow](https://img.shields.io/twitter/follow/ethereumremix?style=social)](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 Project** is a platform for development tools that use a plugin architecture. It encompasses sub-projects including Remix Plugin Engine, Remix Libraries, and of course Remix IDE. ## Remix IDE
**Remix IDE** is used for the entire journey of contract development by users of any knowledge level. It fosters a fast development cycle and has a rich set of plugins with intuitive GUIs. The IDE comes in 2 flavors and a VSCode extension:
**Remix IDE** is an open source web and desktop application. It fosters a fast development cycle and has a rich set of plugins with intuitive GUIs. Remix is used for the **entire journey of contract development with [Solidity language](https://soliditylang.org/)** as well as a playground for learning and teaching [Ethereum](https://ethereum.org/). **Remix Online IDE**, see: [https://remix.ethereum.org](https://remix.ethereum.org)
Start developing using Remix on browser, visit: [https://remix.ethereum.org](https://remix.ethereum.org) :point_right: Supported browsers: Firefox v100.0.1 & Chrome v101.0.4951.64. No support for Remix's use on tablets or smartphones or telephones.
For desktop version, see releases: [https://github.com/ethereum/remix-desktop/releases](https://github.com/ethereum/remix-desktop/releases) **Remix Desktop IDE**, see releases: [https://github.com/ethereum/remix-desktop/releases](https://github.com/ethereum/remix-desktop/releases)
![Remix screenshot](https://github.com/ethereum/remix-project/raw/master/apps/remix-ide/remix_screenshot.png) ![Remix screenshot](https://github.com/ethereum/remix-project/raw/master/apps/remix-ide/remix-screenshot-400h.png)
:point_right: **Remix libraries** work as a core of native plugins of Remix IDE. Read more about libraries [here](libs/README.md) **VSCode extension**, see: [Ethereum-Remix](https://marketplace.visualstudio.com/items?itemName=RemixProject.ethereum-remix)
## Remix libraries
Remix libraries are essential for Remix IDE's native plugins. Read more about libraries [here](libs/README.md)
## Offline Usage ## Offline Usage
@ -30,7 +35,7 @@ Note: It contains the latest supported version of Solidity available at the time
## Setup ## Setup
* Install **NPM** and **Node.js**. See [Guide](https://docs.npmjs.com/downloading-and-installing-node-js-and-npm) <br/> * Install **Yarn** and **Node.js**. See [Guide for NodeJs](https://docs.npmjs.com/downloading-and-installing-node-js-and-npm) and [Yarn install](https://classic.yarnpkg.com/lang/en/docs/install)<br/>
*Supported versions:* *Supported versions:*
```bash ```bash
"engines": { "engines": {
@ -40,7 +45,7 @@ Note: It contains the latest supported version of Solidity available at the time
``` ```
* Install [Nx CLI](https://nx.dev/react/cli/overview) globally to enable running **nx executable commands**. * Install [Nx CLI](https://nx.dev/react/cli/overview) globally to enable running **nx executable commands**.
```bash ```bash
npm install -g @nrwl/cli yarn global add @nrwl/cli
``` ```
* Clone the github repository (`wget` need to be installed first): * Clone the github repository (`wget` need to be installed first):
@ -50,8 +55,8 @@ git clone https://github.com/ethereum/remix-project.git
* Build `remix-project`: * Build `remix-project`:
```bash ```bash
cd remix-project cd remix-project
npm install yarn install
npm run build:libs // Build remix libs yarn run build:libs // Build remix libs
nx build nx build
nx serve nx serve
``` ```
@ -63,12 +68,12 @@ Go to your `text editor` and start developing. Browser will automatically refres
## Production Build ## Production Build
To generate react production builds for remix-project. To generate react production builds for remix-project.
```bash ```bash
npm run build:production yarn run build:production
``` ```
Build can be found in `remix-project/dist/apps/remix-ide` directory. Build can be found in `remix-project/dist/apps/remix-ide` directory.
```bash ```bash
npm run serve:production yarn run serve:production
``` ```
Production build will be served by default to `http://localhost:8080/` or `http://127.0.0.1:8080/` Production build will be served by default to `http://localhost:8080/` or `http://127.0.0.1:8080/`
@ -133,78 +138,138 @@ For example, to run unit tests of `remix-analyzer`, use `nx test remix-analyzer`
To run the Selenium tests via Nightwatch: To run the Selenium tests via Nightwatch:
- Install Selenium for first time: `npm run selenium-install` - Install Selenium for first time: `yarn run selenium-install`
- Run a selenium server: `npm run selenium` - Run a selenium server: `yarn run selenium`
- Build & Serve Remix: `nx serve` - Build & Serve Remix: `nx serve`
- Run all the end-to-end tests: - Run all the end-to-end tests:
for Firefox: `npm run nightwatch_local_firefox`, or for Firefox: `yarn run nightwatch_local_firefox`, or
for Google Chrome: `npm run nightwatch_local_chrome` for Google Chrome: `yarn run nightwatch_local_chrome`
- Run a specific test case instead, use one of following commands: - Run a specific test case instead, use a command like this:
- npm run nightwatch_local_ballot - yarn run nightwatch_local_ballot
- npm run nightwatch_local_usingWorker
- npm run nightwatch_local_libraryDeployment
- npm run nightwatch_local_solidityImport
- npm run nightwatch_local_recorder The package.json file contains a list of all the tests you can run.
- npm run nightwatch_local_transactionExecution **NOTE:**
- npm run nightwatch_local_staticAnalysis
- npm run nightwatch_local_signingMessage
- npm run nightwatch_local_specialFunctions - **The `ballot` tests suite** requires to run `ganache-cli` locally.
- npm run nightwatch_local_solidityUnitTests - **The `remixd` tests suite** requires to run `remixd` locally.
- npm run nightwatch_local_remixd # remixd needs to be run - **The `gist` tests suite** requires specifying a github access token in **.env file**.
```
gist_token = <token> // token should have permission to create a gist
```
- npm run nightwatch_local_terminal ### Using 'select_test' for locally running specific tests
- npm run nightwatch_local_gist There is a script to allow selecting the browser and a specific test to run:
- npm run nightwatch_local_workspace ```
yarn run select_test
```
- npm run nightwatch_local_defaultLayout You need to have
- npm run nightwatch_local_pluginManager - selenium running
- npm run nightwatch_local_publishContract - the IDE running
- npm run nightwatch_local_generalSettings - optionally have remixd or ganache running
- npm run nightwatch_local_fileExplorer ### Splitting tests with groups
- npm run nightwatch_local_debugger Groups can be used to group tests in a test file together. The advantage is you can avoid running long test files when you want to focus on a specific set of tests within a test file.x
- npm run nightwatch_local_editor These groups only apply to the test file, not across all test files. So for example group1 in the ballot is not related to group1 in another test file.
- npm run nightwatch_local_compiler Running a group only runs the tests marked as belonging to the group + all the tests in the test file that do not have a group tag. This way you can have tests that run for all groups, for example to peform common actions.
- npm run nightwatch_local_txListener There is no need to number the groups in a certain order. The number of the group is arbitrary.
- npm run nightwatch_local_fileManager A test can have multiple group tags, this means that this test will run in different groups.
- npm run nightwatch_local_runAndDeploy You should write your tests so they can be executed in groups and not depend on other groups.
**NOTE:**
- **The `ballot` tests suite** requires to run `ganache-cli` locally. To do this you need to:
- **The `remixd` tests suite** requires to run `remixd` locally. - Add a group to tag to a test, they are formatted as #group followed by a number: so it becomes #group1, #group220, #group4. Any number will do. You don't have to do it in specific order.
- **The `gist` tests suite** requires specifying a github access token in **.env file**.
``` ```
gist_token = <token> // token should have permission to create a gist 'Should generate test file #group1': function (browser: NightwatchBrowser) {
browser.waitForElementPresent('*[data-id="verticalIconsKindfilePanel"]')
``` ```
- add '@disable': true to the test file you want to split:
```
module.exports = {
'@disabled': true,
before: function (browser: NightwatchBrowser, done: VoidFunction) {
init(browser, done) // , 'http://localhost:8080', false)
},
```
- change package json to locally run all group tests:
```
"nightwatch_local_debugger": "yarn run build:e2e && nightwatch --config dist/apps/remix-ide-e2e/nightwatch.js dist/apps/remix-ide-e2e/src/tests/debugger_*.spec.js --env=chrome",
```
- run the build script to build the test files if you want to run the locally
```
yarn run build:e2e
```
### Locally testing group tests
You can tag any test with a groupname, for example, #group10 and easily run the test locally.
- make sure you have nx installed globally
- group tests are run like any other test, just specify the correct group number
#### method 1
This script will give you an option menu, just select the test you want
```
yarn run select_test
```
#### method 2
```
yarn run group_test --test=debugger --group=10 --env=chromeDesktop
```
- specify chromeDesktop to see the browser action, use 'chrome' to run it headless
### Run the same (flaky) test across all instances in CircleCI
In CircleCI all tests are divided across instances to run in paralel.
You can also run 1 or more tests simultaneously across all instances.
This way the pipeline can easily be restarted to check if a test is flaky.
For example:
```
'Static Analysis run with remixd #group3 #flaky': function (browser) {
```
Now group3 of this test will be executed in firefox and chrome 80 times.
If you mark more groups in other tests they will also be executed.
**CONFIGURATION**
It's important to set a parameter in the .circleci/config.yml, set it to false then the normal tests will run.
Set it to true to run only tests marked with flaky.
```
parameters:
run_flaky_tests:
type: boolean
default: true
```
## Important Links ## Important Links
- Official documentation: https://remix-ide.readthedocs.io/en/latest/ - Official documentation: https://remix-ide.readthedocs.io/en/latest/

@ -2,7 +2,7 @@ import { NightwatchBrowser } from 'nightwatch'
import EventEmitter from 'events' import EventEmitter from 'events'
class VerifyContracts extends EventEmitter { class VerifyContracts extends EventEmitter {
command (this: NightwatchBrowser, compiledContractNames: string[], opts = { wait: 1000, version: null }): NightwatchBrowser { command (this: NightwatchBrowser, compiledContractNames: string[], opts = { wait: 1000, version: null, runs: '200' }): NightwatchBrowser {
this.api.perform((done) => { this.api.perform((done) => {
verifyContracts(this.api, compiledContractNames, opts, () => { verifyContracts(this.api, compiledContractNames, opts, () => {
done() done()
@ -13,13 +13,13 @@ class VerifyContracts extends EventEmitter {
} }
} }
function verifyContracts (browser: NightwatchBrowser, compiledContractNames: string[], opts: { wait: number, version?: string }, callback: VoidFunction) { function verifyContracts (browser: NightwatchBrowser, compiledContractNames: string[], opts: { wait: number, version?: string, runs?: string }, callback: VoidFunction) {
browser browser
.clickLaunchIcon('solidity') .clickLaunchIcon('solidity')
.pause(opts.wait) .pause(opts.wait)
.pause(5000) .pause(5000)
.waitForElementPresent('*[data-id="compiledContracts"] option', 60000) .waitForElementPresent('*[data-id="compiledContracts"] option', 60000)
.perform((done) => { .perform(async (done) => {
if (opts.version) { if (opts.version) {
browser browser
.click('*[data-id="compilation-details"]') .click('*[data-id="compilation-details"]')
@ -36,10 +36,28 @@ function verifyContracts (browser: NightwatchBrowser, compiledContractNames: str
done() done()
callback() callback()
}) })
} else { } if (opts.runs) {
compiledContractNames.forEach((name) => { browser
browser.waitForElementContainsText('[data-id="compiledContracts"]', name, 60000) .click('*[data-id="compilation-details"]')
.waitForElementVisible('*[data-id="remixui_treeviewitem_metadata"]')
.pause(2000)
.click('*[data-id="remixui_treeviewitem_metadata"]')
.waitForElementVisible('*[data-id="treeViewDivtreeViewItemsettings"]')
.pause(2000)
.click('*[data-id="treeViewDivtreeViewItemsettings"]')
.waitForElementVisible('*[data-id="treeViewDivtreeViewItemoptimizer"]')
.click('*[data-id="treeViewDivtreeViewItemoptimizer"]')
.waitForElementVisible('*[data-id="treeViewDivruns"]')
.assert.containsText('*[data-id="treeViewDivruns"]', `${opts.runs}`)
.click('[data-id="workspacesModalDialog-modal-footer-ok-react"]')
.perform(() => {
done()
callback()
}) })
} else {
for (const index in compiledContractNames) {
await browser.waitForElementContainsText('[data-id="compiledContracts"]', compiledContractNames[index], 60000)
}
done() done()
callback() callback()
} }

@ -24,7 +24,7 @@ do
*) echo "invalid option $REPLY";; *) echo "invalid option $REPLY";;
esac esac
done done
npm run build:e2e yarn run build:e2e
PS3='Select a test or command: ' PS3='Select a test or command: '
TESTFILES=( $(grep -IRiL "\'@disabled\': \?true" "dist/apps/remix-ide-e2e/src/tests" | grep "\.spec\|\.test\|plugin_api" | sort ) ) TESTFILES=( $(grep -IRiL "\'@disabled\': \?true" "dist/apps/remix-ide-e2e/src/tests" | grep "\.spec\|\.test\|plugin_api" | sort ) )
@ -42,6 +42,6 @@ do
done done
else else
# run the selected test # run the selected test
npm run build:e2e && nightwatch --config dist/apps/remix-ide-e2e/nightwatch.js $opt --env=$BROWSER yarn run build:e2e && nightwatch --config dist/apps/remix-ide-e2e/nightwatch.js $opt --env=$BROWSER
fi fi
done done

@ -122,6 +122,24 @@ module.exports = {
}) })
// Test in Udapp UI , treeViewDiv0 shows returned value on method click // Test in Udapp UI , treeViewDiv0 shows returned value on method click
.assert.containsText('*[data-id="treeViewDiv0"]', 'bytes32: winnerName_ 0x48656c6c6f20576f726c64210000000000000000000000000000000000000000') .assert.containsText('*[data-id="treeViewDiv0"]', 'bytes32: winnerName_ 0x48656c6c6f20576f726c64210000000000000000000000000000000000000000')
},
'Compile Ballot using config file': function (browser: NightwatchBrowser) {
browser
.addFile('cf.json', {content: configFile})
.clickLaunchIcon('solidity')
.waitForElementVisible('*[data-id="scConfigExpander"]')
.click('*[data-id="scConfigExpander"]')
.waitForElementVisible('*[data-id="scFileConfiguration"]', 10000)
.click('*[data-id="scFileConfiguration"]')
.waitForElementVisible('*[data-id="scConfigChangeFilePath"]', 10000)
.click('*[data-id="scConfigChangeFilePath"]')
.waitForElementVisible('*[data-id="scConfigFilePathInput"]', 10000)
.clearValue('*[data-id="scConfigFilePathInput"]')
.setValue('*[data-id="scConfigFilePathInput"]', 'cf.json')
.sendKeys('*[data-id$="scConfigFilePathInput"]', browser.Keys.ENTER)
.openFile('Untitled.sol')
.verifyContracts(['Ballot'], {wait: 2000, runs: '300'})
.end() .end()
} }
} }
@ -190,6 +208,7 @@ const stateCheck = {
immutable: false immutable: false
} }
} }
const ballotABI = `[ const ballotABI = `[
{ {
"inputs": [ "inputs": [
@ -356,3 +375,22 @@ const ballotABI = `[
"type": "function" "type": "function"
} }
]` ]`
const configFile = `
{
"language": "Solidity",
"settings": {
"optimizer": {
"enabled": true,
"runs": 300
},
"outputSelection": {
"*": {
"": ["ast"],
"*": ["abi", "metadata", "devdoc", "userdoc", "storageLayout", "evm.legacyAssembly", "evm.bytecode", "evm.deployedBytecode", "evm.methodIdentifiers", "evm.gasEstimates", "evm.assembly"]
}
},
"evmVersion": "byzantium"
}
}
`

@ -46,7 +46,7 @@ module.exports = {
browser browser
.addFile('test_updateConfiguration.js', { content: updateConfiguration }) .addFile('test_updateConfiguration.js', { content: updateConfiguration })
.executeScript('remix.exeCurrent()') .executeScript('remix.exeCurrent()')
.pause(5000) .pause(15000)
.addFile('test_updateConfiguration.sol', { content: simpleContract }) .addFile('test_updateConfiguration.sol', { content: simpleContract })
.verifyContracts(['StorageTestUpdateConfiguration'], { wait: 5000, version: '0.6.8+commit.0bbfe453' }) .verifyContracts(['StorageTestUpdateConfiguration'], { wait: 5000, version: '0.6.8+commit.0bbfe453' })
}, },
@ -168,3 +168,4 @@ contract DoesNotCompile {
function fStackLimit(uint u1, uint u2, uint u3, uint u4, uint u5, uint u6, uint u7, uint u8, uint u9, uint u10, uint u11, uint u12) public { function fStackLimit(uint u1, uint u2, uint u3, uint u4, uint u5, uint u6, uint u7, uint u8, uint u9, uint u10, uint u11, uint u12) public {
} }
}` }`

@ -71,7 +71,7 @@ module.exports = {
.addFile('mkdirFile.js', { content: executeMkdir }) .addFile('mkdirFile.js', { content: executeMkdir })
.executeScript('remix.exeCurrent()') .executeScript('remix.exeCurrent()')
.pause(2000) .pause(2000)
.waitForElementPresent('[data-id="treeViewLitreeViewItemTest_Folder"]', 60000) .waitForElementPresent('[data-id="treeViewLitreeViewItemTest_Folder"]', 80000)
}, },
'Should execute `readdir` api from file manager external api #group3': function (browser: NightwatchBrowser) { 'Should execute `readdir` api from file manager external api #group3': function (browser: NightwatchBrowser) {

@ -23,7 +23,7 @@ module.exports = {
.pause(1000) .pause(1000)
.click('button[data-id="landingPageImportFromGitHubButton"]') .click('button[data-id="landingPageImportFromGitHubButton"]')
.waitForElementVisible('*[data-id="homeTabModalDialogModalTitle-react"]') .waitForElementVisible('*[data-id="homeTabModalDialogModalTitle-react"]')
.assert.containsText('*[data-id="homeTabModalDialogModalTitle-react"]', 'Import from Github') .assert.containsText('*[data-id="homeTabModalDialogModalTitle-react"]', 'Import from GitHub')
.waitForElementVisible('*[data-id="homeTabModalDialogModalBody-react"]') .waitForElementVisible('*[data-id="homeTabModalDialogModalBody-react"]')
.assert.containsText('*[data-id="homeTabModalDialogModalBody-react"]', 'Enter the github URL you would like to load.') .assert.containsText('*[data-id="homeTabModalDialogModalBody-react"]', 'Enter the github URL you would like to load.')
.waitForElementVisible('input[data-id="homeTabModalDialogCustomPromptText"]') .waitForElementVisible('input[data-id="homeTabModalDialogCustomPromptText"]')
@ -41,7 +41,7 @@ module.exports = {
.assert.containsText('*[data-shared="tooltipPopup"] span', 'not found ' + testData.invalidURL) .assert.containsText('*[data-shared="tooltipPopup"] span', 'not found ' + testData.invalidURL)
}, },
'Import From Github For Valid URL #group2': function (browser: NightwatchBrowser) { 'Import From GitHub For Valid URL #group2': function (browser: NightwatchBrowser) {
browser browser
.waitForElementVisible('*[data-id="remixIdeIconPanel"]', 10000) .waitForElementVisible('*[data-id="remixIdeIconPanel"]', 10000)
.clickLaunchIcon('filePanel') .clickLaunchIcon('filePanel')
@ -62,7 +62,7 @@ module.exports = {
browser.assert.ok(content.indexOf('library Roles {') !== -1, 'content does contain "library Roles {"') browser.assert.ok(content.indexOf('library Roles {') !== -1, 'content does contain "library Roles {"')
}) })
}, },
'Import JSON From Github For Valid URL #group2': function (browser: NightwatchBrowser) { 'Import JSON From GitHub For Valid URL #group2': function (browser: NightwatchBrowser) {
browser browser
.click('div[title="home"]') .click('div[title="home"]')
.click('button[data-id="landingPageImportFromGitHubButton"]') .click('button[data-id="landingPageImportFromGitHubButton"]')

@ -230,7 +230,12 @@ module.exports = {
}, },
'Should get current files #group7': async function (browser: NightwatchBrowser) { 'Should get current files #group7': async function (browser: NightwatchBrowser) {
await clickAndCheckLog(browser, 'fileManager:readdir', { contracts: { isDirectory: true }, scripts: { isDirectory: true }, tests: { isDirectory: true }, 'README.txt': { isDirectory: false } }, null, '/') await clickAndCheckLog(browser, 'fileManager:readdir', {
contracts: { isDirectory: true },
scripts: { isDirectory: true },
tests: { isDirectory: true },
'README.txt': { isDirectory: false }
}, null, '/')
}, },
'Should throw error on current file #group7': async function (browser: NightwatchBrowser) { 'Should throw error on current file #group7': async function (browser: NightwatchBrowser) {
await clickAndCheckLog(browser, 'fileManager:getCurrentFile', 'Error from IDE : Error: No such file or directory No file selected', null, null) await clickAndCheckLog(browser, 'fileManager:getCurrentFile', 'Error from IDE : Error: No such file or directory No file selected', null, null)
@ -285,7 +290,12 @@ module.exports = {
'Should create workspace #group2': async function (browser: NightwatchBrowser) { 'Should create workspace #group2': async function (browser: NightwatchBrowser) {
await clickAndCheckLog(browser, 'filePanel:createWorkspace', null, null, 'testspace') await clickAndCheckLog(browser, 'filePanel:createWorkspace', null, null, 'testspace')
await clickAndCheckLog(browser, 'filePanel:getCurrentWorkspace', { name: 'testspace', isLocalhost: false, absolutePath: '.workspaces/testspace' }, null, null) await clickAndCheckLog(browser, 'filePanel:getCurrentWorkspace', { name: 'testspace', isLocalhost: false, absolutePath: '.workspaces/testspace' }, null, null)
await clickAndCheckLog(browser, 'fileManager:readdir', { contracts: { isDirectory: true }, scripts: { isDirectory: true }, tests: { isDirectory: true }, 'README.txt': { isDirectory: false } }, null, null) await clickAndCheckLog(browser, 'fileManager:readdir', {
contracts: { isDirectory: true },
scripts: { isDirectory: true },
tests: { isDirectory: true },
'README.txt': { isDirectory: false }
}, null, null)
}, },
'Should get all workspaces #group2': async function (browser: NightwatchBrowser) { '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', ['default_workspace', 'emptyworkspace', 'testspace'], null, null)
@ -409,7 +419,7 @@ module.exports = {
.addFile('test_modal.js', { content: testModalToasterApi }) .addFile('test_modal.js', { content: testModalToasterApi })
.executeScript('remix.execute(\'test_modal.js\')') .executeScript('remix.execute(\'test_modal.js\')')
.useCss() .useCss()
.waitForElementVisible('*[data-id="test_id_1_ModalDialogModalBody-react"]') .waitForElementVisible('*[data-id="test_id_1_ModalDialogModalBody-react"]', 60000)
.assert.containsText('*[data-id="test_id_1_ModalDialogModalBody-react"]', 'message 1') .assert.containsText('*[data-id="test_id_1_ModalDialogModalBody-react"]', 'message 1')
.modalFooterOKClick('test_id_1_') .modalFooterOKClick('test_id_1_')
// check the script runner notifications // check the script runner notifications

@ -29,5 +29,31 @@ module.exports = {
.waitForElementVisible('*[data-id="ganache-providerModalDialogModalBody-react"]') .waitForElementVisible('*[data-id="ganache-providerModalDialogModalBody-react"]')
.modalFooterOKClick('ganache-provider') .modalFooterOKClick('ganache-provider')
.waitForElementContainsText('*[data-id="settingsNetworkEnv"]', 'Custom (') .waitForElementContainsText('*[data-id="settingsNetworkEnv"]', 'Custom (')
},
'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"]')
.waitForElementVisible('*[data-id="foundry-providerModalDialogModalBody-react"]')
.execute(() => {
(document.querySelector('*[data-id="foundry-providerModalDialogModalBody-react"] input') as any).focus()
}, [], () => {})
.clearValue('*[data-id="foundry-providerModalDialogModalBody-react"] input')
.setValue('*[data-id="foundry-providerModalDialogModalBody-react"] input', 'http://127.0.0.1:8084')
.modalFooterOKClick('foundry-provider')
.waitForElementContainsText('*[data-id="foundry-providerModalDialogModalBody-react"]', 'Error while connecting to the provider')
.modalFooterOKClick('foundry-provider')
.waitForElementNotVisible('*[data-id="foundry-providerModalDialogModalBody-react"]')
.waitForElementVisible('*[data-id="PermissionHandler-modal-footer-ok-react"]')
.click('*[data-id="PermissionHandler-modal-footer-ok-react"]')
.waitForElementNotVisible('*[data-id="PermissionHandler-modal-footer-ok-react"]')
.pause(1000)
},
'Should switch to foundry provider, use the default foundry URL and succeed to connect': function (browser: NightwatchBrowser) {
browser.click('*[data-id="Foundry Provider"]')
.waitForElementVisible('*[data-id="foundry-providerModalDialogModalBody-react"]')
.modalFooterOKClick('foundry-provider')
.waitForElementContainsText('*[data-id="settingsNetworkEnv"]', 'Custom (')
} }
} }

@ -19,17 +19,22 @@ module.exports = {
.openFile('contracts/3_Ballot.sol') .openFile('contracts/3_Ballot.sol')
.verifyContracts(['Ballot']) .verifyContracts(['Ballot'])
.click('#publishOnIpfs') .click('#publishOnIpfs')
.pause(2000)
.waitForElementVisible('[data-id="publishToStorageModalDialogModalBody-react"]', 60000)
.click('[data-id="publishToStorage-modal-footer-ok-react"]')
.pause(8000) .pause(8000)
.waitForElementVisible('[data-id="publishToStorageModalDialogModalBody-react"]', 60000) .waitForElementVisible('[data-id="publishToStorageModalDialogModalBody-react"]', 60000)
.getText('[data-id="publishToStorageModalDialogModalBody-react"]', (result) => { .getText('[data-id="publishToStorageModalDialogModalBody-react"]', (result) => {
const value = <string>(result.value) const value = <string>(result.value)
browser.perform((done) => { browser.perform((done) => {
if (value.indexOf('Metadata of "ballot" was published successfully.') === -1) browser.assert.fail('ipfs deploy failed') if (value.indexOf('Metadata and sources of "ballot" were published successfully.') === -1) browser.assert.fail('ipfs deploy failed')
done() done()
}) })
}) })
.click('[data-id="publishToStorage-modal-footer-ok-react"]') .click('[data-id="publishToStorage-modal-footer-ok-react"]')
.openFile('ipfs/QmSUodhSvoorFL5m5CNqve8YvmuBpjCq17NbTf4GUX8ydw')
.openFile('ipfs/QmXYUS1ueS22EqNVRaKuZa31EgHLjKZ8uTM8vWhQLxa3pw')
}, },
/* Disableing the test untill refactoring and the new swarm usage /* Disableing the test untill refactoring and the new swarm usage
@ -41,7 +46,7 @@ module.exports = {
const value = <string>(result.value) const value = <string>(result.value)
browser.perform((done) => { browser.perform((done) => {
if (value.indexOf('Metadata of "ballot" was published successfully.') === -1) browser.assert.fail('swarm deploy failed') if (value.indexOf('Metadata and sources of "ballot" were published successfully.') === -1) browser.assert.fail('swarm deploy failed')
if (value.indexOf('bzz') === -1) browser.assert.fail('swarm deploy failed') if (value.indexOf('bzz') === -1) browser.assert.fail('swarm deploy failed')
done() done()
}) })
@ -61,11 +66,13 @@ module.exports = {
.waitForElementVisible('*[data-id="Deploy - transact (not payable)"]') .waitForElementVisible('*[data-id="Deploy - transact (not payable)"]')
.click('*[data-id="Deploy - transact (not payable)"]') .click('*[data-id="Deploy - transact (not payable)"]')
.pause(5000) .pause(5000)
.waitForElementVisible('[data-id="udappModalDialogModalBody-react"]') .waitForElementVisible('[data-id="udappModalDialogModalBody-react"]', 60000)
.modalFooterOKClick('udapp')
.pause(8000)
.getText('[data-id="udappModalDialogModalBody-react"]', (result) => { .getText('[data-id="udappModalDialogModalBody-react"]', (result) => {
const value = typeof result.value === 'string' ? result.value : null const value = typeof result.value === 'string' ? result.value : null
if (value.indexOf('Metadata of "storage" was published successfully.') === -1) browser.assert.fail('ipfs deploy failed') if (value.indexOf('Metadata and sources of "storage" were published successfully.') === -1) browser.assert.fail('ipfs deploy failed')
}) })
.modalFooterOKClick('udapp') .modalFooterOKClick('udapp')
}, },

@ -17,7 +17,7 @@ module.exports = {
.pause(5000) .pause(5000)
.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
.click('[data-id="udapp_arrow"]') .click('[data-id="udappRecorderTitleExpander"]')
.click('[data-id="runtransaction"]') .click('[data-id="runtransaction"]')
.clickInstance(0) .clickInstance(0)
.clickInstance(1) .clickInstance(1)

@ -33,11 +33,11 @@ module.exports = {
.clearValue('*[id="search_include"]').pause(2000) .clearValue('*[id="search_include"]').pause(2000)
.setValue('*[id="search_include"]', '**').sendKeys('*[id="search_include"]', browser.Keys.ENTER).pause(4000) .setValue('*[id="search_include"]', '**').sendKeys('*[id="search_include"]', browser.Keys.ENTER).pause(4000)
.elements('css selector', '.search_plugin_search_line', (res) => { .elements('css selector', '.search_plugin_search_line', (res) => {
Array.isArray(res.value) && browser.assert.equal(res.value.length, 48) Array.isArray(res.value) && browser.assert.equal(res.value.length, 62)
}) })
.setValue('*[id="search_exclude"]', ',contracts/**').sendKeys('*[id="search_exclude"]', browser.Keys.ENTER).pause(4000) .setValue('*[id="search_exclude"]', ',contracts/**').sendKeys('*[id="search_exclude"]', browser.Keys.ENTER).pause(4000)
.elements('css selector', '.search_plugin_search_line', (res) => { .elements('css selector', '.search_plugin_search_line', (res) => {
Array.isArray(res.value) && browser.assert.equal(res.value.length, 42) Array.isArray(res.value) && browser.assert.equal(res.value.length, 56)
}) })
.clearValue('*[id="search_include"]').setValue('*[id="search_include"]', '*.sol, *.js, *.txt') .clearValue('*[id="search_include"]').setValue('*[id="search_include"]', '*.sol, *.js, *.txt')
.clearValue('*[id="search_exclude"]').setValue('*[id="search_exclude"]', '.*/**/*') .clearValue('*[id="search_exclude"]').setValue('*[id="search_exclude"]', '.*/**/*')
@ -78,7 +78,7 @@ module.exports = {
.clearValue('*[id="search_input"]') .clearValue('*[id="search_input"]')
.setValue('*[id="search_input"]', 'contract').sendKeys('*[id="search_input"]', browser.Keys.ENTER).pause(4000) .setValue('*[id="search_input"]', 'contract').sendKeys('*[id="search_input"]', browser.Keys.ENTER).pause(4000)
.elements('css selector', '.search_plugin_search_line', (res) => { .elements('css selector', '.search_plugin_search_line', (res) => {
Array.isArray(res.value) && browser.assert.equal(res.value.length, 13) Array.isArray(res.value) && browser.assert.equal(res.value.length, 15)
}) })
}, },
'Should replace text': function (browser: NightwatchBrowser) { 'Should replace text': function (browser: NightwatchBrowser) {

@ -30,7 +30,7 @@ module.exports = {
.assert.containsText('#compileTabView .error pre', 'not found Untitled11.sol') .assert.containsText('#compileTabView .error pre', 'not found Untitled11.sol')
}, },
'Test Github Import - from master branch #group1': function (browser: NightwatchBrowser) { 'Test GitHub Import - from master branch #group1': function (browser: NightwatchBrowser) {
browser browser
.setSolidityCompilerVersion('soljson-v0.8.0+commit.c7dfd78e.js') // open-zeppelin moved to pragma ^0.8.0 (master branch) .setSolidityCompilerVersion('soljson-v0.8.0+commit.c7dfd78e.js') // open-zeppelin moved to pragma ^0.8.0 (master branch)
.addFile('Untitled4.sol', sources[3]['Untitled4.sol']) .addFile('Untitled4.sol', sources[3]['Untitled4.sol'])
@ -38,7 +38,7 @@ module.exports = {
.verifyContracts(['test7', 'ERC20'], { wait: 10000 }) .verifyContracts(['test7', 'ERC20'], { wait: 10000 })
}, },
'Test Github Import - from other branch #group2': function (browser: NightwatchBrowser) { 'Test GitHub Import - from other branch #group2': function (browser: NightwatchBrowser) {
browser browser
.setSolidityCompilerVersion('soljson-v0.5.0+commit.1d4f565a.js') // switch back to 0.5.0 : release-v2.3.0 branch is not solidity 0.6 compliant .setSolidityCompilerVersion('soljson-v0.5.0+commit.1d4f565a.js') // switch back to 0.5.0 : release-v2.3.0 branch is not solidity 0.6 compliant
.addFile('Untitled5.sol', sources[4]['Untitled5.sol']) .addFile('Untitled5.sol', sources[4]['Untitled5.sol'])
@ -46,7 +46,7 @@ module.exports = {
.verifyContracts(['test8', 'ERC20', 'SafeMath'], { wait: 10000 }) .verifyContracts(['test8', 'ERC20', 'SafeMath'], { wait: 10000 })
}, },
'Test Github Import - no branch specified #group2': function (browser: NightwatchBrowser) { 'Test GitHub Import - no branch specified #group2': function (browser: NightwatchBrowser) {
browser browser
.setSolidityCompilerVersion('soljson-v0.8.0+commit.c7dfd78e.js') // open-zeppelin moved to pragma ^0.8.0 (master branch) .setSolidityCompilerVersion('soljson-v0.8.0+commit.c7dfd78e.js') // open-zeppelin moved to pragma ^0.8.0 (master branch)
.clickLaunchIcon('filePanel') .clickLaunchIcon('filePanel')
@ -56,7 +56,7 @@ module.exports = {
.verifyContracts(['test10', 'ERC20'], { wait: 10000 }) .verifyContracts(['test10', 'ERC20'], { wait: 10000 })
}, },
'Test Github Import - raw URL #group4': function (browser: NightwatchBrowser) { 'Test GitHub Import - raw URL #group4': function (browser: NightwatchBrowser) {
browser browser
.clickLaunchIcon('filePanel') .clickLaunchIcon('filePanel')
.click('li[data-id="treeViewLitreeViewItemREADME.txt"') .click('li[data-id="treeViewLitreeViewItemREADME.txt"')
@ -65,7 +65,7 @@ module.exports = {
.verifyContracts(['test11', 'ERC20'], { wait: 10000 }) .verifyContracts(['test11', 'ERC20'], { wait: 10000 })
}, },
'Test switch to a github import from a solidity warning #group3': function (browser: NightwatchBrowser) { 'Test switch to a GitHub import from a solidity warning #group3': function (browser: NightwatchBrowser) {
browser browser
.setSolidityCompilerVersion('soljson-v0.7.4+commit.3f05b770.js') .setSolidityCompilerVersion('soljson-v0.7.4+commit.3f05b770.js')
.clickLaunchIcon('filePanel') .clickLaunchIcon('filePanel')

@ -291,7 +291,7 @@ const deployWithEthersJs = `
// 'web3Provider' is a remix global variable object // 'web3Provider' is a remix global variable object
const signer = (new ethers.providers.Web3Provider(web3Provider)).getSigner() const signer = (new ethers.providers.Web3Provider(web3Provider)).getSigner()
let factory = new ethers.ContractFactory(metadata.abi, metadata.data.bytecode.object, signer); let factory = new ethers.ContractFactory(metadata.abi, metadata.data.bytecode.object, signer)
let contract = await factory.deploy(...constructorArgs); let contract = await factory.deploy(...constructorArgs);
@ -320,7 +320,7 @@ describe("Storage with lib", function () {
// Make sure contract is compiled and artifacts are generated // Make sure contract is compiled and artifacts are generated
const metadata = JSON.parse(await remix.call('fileManager', 'getFile', 'contracts/artifacts/Storage.json')) const metadata = JSON.parse(await remix.call('fileManager', 'getFile', 'contracts/artifacts/Storage.json'))
const signer = (new ethers.providers.Web3Provider(web3Provider)).getSigner() const signer = (new ethers.providers.Web3Provider(web3Provider)).getSigner()
let Storage = new ethers.ContractFactory(metadata.abi, metadata.data.bytecode.object, signer); let Storage = new ethers.ContractFactory(metadata.abi, metadata.data.bytecode.object, signer)
let storage = await Storage.deploy(); let storage = await Storage.deploy();
console.log('storage contract Address: ' + storage.address); console.log('storage contract Address: ' + storage.address);
await storage.deployed() await storage.deployed()
@ -330,7 +330,7 @@ describe("Storage with lib", function () {
it("test updating and retrieving updated value", async function () { it("test updating and retrieving updated value", async function () {
const metadata = JSON.parse(await remix.call('fileManager', 'getFile', 'contracts/artifacts/Storage.json')) const metadata = JSON.parse(await remix.call('fileManager', 'getFile', 'contracts/artifacts/Storage.json'))
const signer = (new ethers.providers.Web3Provider(web3Provider)).getSigner() const signer = (new ethers.providers.Web3Provider(web3Provider)).getSigner()
let Storage = new ethers.ContractFactory(metadata.abi, metadata.data.bytecode.object, signer); let Storage = new ethers.ContractFactory(metadata.abi, metadata.data.bytecode.object, signer)
let storage = await Storage.deploy(); let storage = await Storage.deploy();
await storage.deployed() await storage.deployed()
const setValue = await storage.store(56); const setValue = await storage.store(56);
@ -341,7 +341,7 @@ describe("Storage with lib", function () {
it("fail test updating and retrieving updated value", async function () { it("fail test updating and retrieving updated value", async function () {
const metadata = JSON.parse(await remix.call('fileManager', 'getFile', 'contracts/artifacts/Storage.json')) const metadata = JSON.parse(await remix.call('fileManager', 'getFile', 'contracts/artifacts/Storage.json'))
const signer = (new ethers.providers.Web3Provider(web3Provider)).getSigner() const signer = (new ethers.providers.Web3Provider(web3Provider)).getSigner()
let Storage = new ethers.ContractFactory(metadata.abi, metadata.data.bytecode.object, signer); let Storage = new ethers.ContractFactory(metadata.abi, metadata.data.bytecode.object, signer)
let storage = await Storage.deploy(); let storage = await Storage.deploy();
await storage.deployed() await storage.deployed()
const setValue = await storage.store(56); const setValue = await storage.store(56);

@ -81,6 +81,7 @@ module.exports = {
.refresh() .refresh()
.pause(5000) .pause(5000)
.clickLaunchIcon('solidity') .clickLaunchIcon('solidity')
.click('*[data-id="scConfigExpander"]')
.assert.containsText('#versionSelector option[data-id="selected"]', '0.7.4+commit.3f05b770') .assert.containsText('#versionSelector option[data-id="selected"]', '0.7.4+commit.3f05b770')
.assert.containsText('#evmVersionSelector option[data-id="selected"]', 'istanbul') .assert.containsText('#evmVersionSelector option[data-id="selected"]', 'istanbul')
.assert.containsText('#compilierLanguageSelector option[data-id="selected"]', 'Yul') .assert.containsText('#compilierLanguageSelector option[data-id="selected"]', 'Yul')
@ -96,6 +97,7 @@ module.exports = {
.pause(5000) .pause(5000)
.clickLaunchIcon('solidity') .clickLaunchIcon('solidity')
.pause(5000) .pause(5000)
.click('*[data-id="scConfigExpander"]')
.assert.containsText('#versionSelector option[data-id="selected"]', 'custom') .assert.containsText('#versionSelector option[data-id="selected"]', 'custom')
// default values // default values
.assert.containsText('#evmVersionSelector option[data-id="selected"]', 'default') .assert.containsText('#evmVersionSelector option[data-id="selected"]', 'default')

@ -50,11 +50,42 @@ module.exports = {
.assert.elementPresent('*[data-id="treeViewLitreeViewItemcontracts/3_Ballot.sol"]') .assert.elementPresent('*[data-id="treeViewLitreeViewItemcontracts/3_Ballot.sol"]')
.assert.elementPresent('*[data-id="treeViewLitreeViewItemscripts"]') .assert.elementPresent('*[data-id="treeViewLitreeViewItemscripts"]')
.assert.elementPresent('*[data-id="treeViewLitreeViewItemscripts/deploy_with_web3.ts"]') .assert.elementPresent('*[data-id="treeViewLitreeViewItemscripts/deploy_with_web3.ts"]')
// check js and ts files are not transformed
.click('*[data-id="treeViewLitreeViewItemscripts/deploy_with_web3.ts"]')
.getEditorValue((content) => {
browser.assert.ok(content.indexOf(`import { deploy } from './web3-lib'`) !== -1,
'Incorrect content')
})
.assert.elementPresent('*[data-id="treeViewLitreeViewItemscripts/deploy_with_ethers.ts"]') .assert.elementPresent('*[data-id="treeViewLitreeViewItemscripts/deploy_with_ethers.ts"]')
.assert.elementPresent('*[data-id="treeViewLitreeViewItemscripts/web3.ts"]') .click('*[data-id="treeViewLitreeViewItemscripts/deploy_with_ethers.ts"]')
.assert.elementPresent('*[data-id="treeViewLitreeViewItemscripts/ethers.ts"]') .pause(100)
.getEditorValue((content) => {
browser.assert.ok(content.indexOf(`import { deploy } from './ethers-lib'`) !== -1,
'Incorrect content')
})
.assert.elementPresent('*[data-id="treeViewLitreeViewItemscripts/web3-lib.ts"]')
.click('*[data-id="treeViewLitreeViewItemscripts/web3-lib.ts"]')
.pause(2000)
.getEditorValue((content) => {
browser.assert.ok(content.indexOf(`export const deploy = async (contractName: string, args: Array<any>, from?: string, gas?: number): Promise<Options> => {`) !== -1,
'Incorrect content')
})
.assert.elementPresent('*[data-id="treeViewLitreeViewItemscripts/ethers-lib.ts"]')
.click('*[data-id="treeViewLitreeViewItemscripts/ethers-lib.ts"]')
.pause(100)
.getEditorValue((content) => {
browser.assert.ok(content.indexOf(`export const deploy = async (contractName: string, args: Array<any>, accountIndex?: number): Promise<ethers.Contract> => {`) !== -1,
'Incorrect content')
})
.assert.elementPresent('*[data-id="treeViewLitreeViewItemtests"]') .assert.elementPresent('*[data-id="treeViewLitreeViewItemtests"]')
.assert.elementPresent('*[data-id="treeViewLitreeViewItemtests/storage.test.js"]') .assert.elementPresent('*[data-id="treeViewLitreeViewItemtests/storage.test.js"]')
.click('*[data-id="treeViewLitreeViewItemtests/storage.test.js"]')
.pause(100)
.getEditorValue((content) => {
browser.assert.ok(content.indexOf(`const { expect } = require("chai");`) !== -1,
'Incorrect content')
})
.assert.elementPresent('*[data-id="treeViewLitreeViewItemtests/Ballot_test.sol"]') .assert.elementPresent('*[data-id="treeViewLitreeViewItemtests/Ballot_test.sol"]')
.assert.elementPresent('*[data-id="treeViewLitreeViewItemREADME.txt"]') .assert.elementPresent('*[data-id="treeViewLitreeViewItemREADME.txt"]')
}, },
@ -70,13 +101,12 @@ module.exports = {
.click('select[id="wstemplate"] option[value=blank]') .click('select[id="wstemplate"] option[value=blank]')
.waitForElementPresent('[data-id="fileSystemModalDialogModalFooter-react"] .modal-ok') .waitForElementPresent('[data-id="fileSystemModalDialogModalFooter-react"] .modal-ok')
.execute(function () { (document.querySelector('[data-id="fileSystemModalDialogModalFooter-react"] .modal-ok') as HTMLElement).click() }) .execute(function () { (document.querySelector('[data-id="fileSystemModalDialogModalFooter-react"] .modal-ok') as HTMLElement).click() })
.pause(1000) .pause(100)
.assert.elementPresent('*[data-id="treeViewUltreeViewMenu"]') .assert.elementPresent('*[data-id="treeViewUltreeViewMenu"]')
.execute(function () { .execute(function () {
const fileList = document.querySelector('*[data-id="treeViewUltreeViewMenu"]') const fileList = document.querySelector('*[data-id="treeViewUltreeViewMenu"]')
return fileList.getElementsByTagName('li').length; return fileList.getElementsByTagName('li').length;
}, [], function(result){ }, [], function(result){
// check there are no files in FE
browser.assert.equal(result.value, 0, 'Incorrect number of files'); browser.assert.equal(result.value, 0, 'Incorrect number of files');
}); });
}, },
@ -92,18 +122,91 @@ module.exports = {
.click('select[id="wstemplate"] option[value=ozerc20]') .click('select[id="wstemplate"] option[value=ozerc20]')
.waitForElementPresent('[data-id="fileSystemModalDialogModalFooter-react"] .modal-ok') .waitForElementPresent('[data-id="fileSystemModalDialogModalFooter-react"] .modal-ok')
.execute(function () { (document.querySelector('[data-id="fileSystemModalDialogModalFooter-react"] .modal-ok') as HTMLElement).click() }) .execute(function () { (document.querySelector('[data-id="fileSystemModalDialogModalFooter-react"] .modal-ok') as HTMLElement).click() })
.pause(1000) .pause(100)
.assert.elementPresent('*[data-id="treeViewLitreeViewItemcontracts"]') .assert.elementPresent('*[data-id="treeViewLitreeViewItemcontracts"]')
.assert.elementPresent('*[data-id="treeViewLitreeViewItemcontracts/SampleERC20.sol"]') .assert.elementPresent('*[data-id="treeViewLitreeViewItemcontracts/SampleERC20.sol"]')
.assert.elementPresent('*[data-id="treeViewLitreeViewItemscripts"]') .assert.elementPresent('*[data-id="treeViewLitreeViewItemscripts"]')
.assert.elementPresent('*[data-id="treeViewLitreeViewItemscripts/deploy_with_web3.ts"]') .assert.elementPresent('*[data-id="treeViewLitreeViewItemscripts/deploy_with_web3.ts"]')
// check js and ts files are not transformed
.click('*[data-id="treeViewLitreeViewItemscripts/deploy_with_web3.ts"]')
.getEditorValue((content) => {
browser.assert.ok(content.indexOf(`import { deploy } from './web3-lib'`) !== -1,
'Incorrect content')
})
.assert.elementPresent('*[data-id="treeViewLitreeViewItemscripts/deploy_with_ethers.ts"]') .assert.elementPresent('*[data-id="treeViewLitreeViewItemscripts/deploy_with_ethers.ts"]')
.assert.elementPresent('*[data-id="treeViewLitreeViewItemscripts/web3.ts"]') .click('*[data-id="treeViewLitreeViewItemscripts/deploy_with_ethers.ts"]')
.assert.elementPresent('*[data-id="treeViewLitreeViewItemscripts/ethers.ts"]') .pause(100)
.getEditorValue((content) => {
browser.assert.ok(content.indexOf(`import { deploy } from './ethers-lib'`) !== -1,
'Incorrect content')
})
.assert.elementPresent('*[data-id="treeViewLitreeViewItemscripts/web3-lib.ts"]')
.click('*[data-id="treeViewLitreeViewItemscripts/web3-lib.ts"]')
.pause(100)
.getEditorValue((content) => {
browser.assert.ok(content.indexOf(`export const deploy = async (contractName: string, args: Array<any>, from?: string, gas?: number): Promise<Options> => {`) !== -1,
'Incorrect content')
})
.assert.elementPresent('*[data-id="treeViewLitreeViewItemscripts/ethers-lib.ts"]')
.click('*[data-id="treeViewLitreeViewItemscripts/ethers-lib.ts"]')
.pause(100)
.getEditorValue((content) => {
browser.assert.ok(content.indexOf(`export const deploy = async (contractName: string, args: Array<any>, accountIndex?: number): Promise<ethers.Contract> => {`) !== -1,
'Incorrect content')
})
.assert.elementPresent('*[data-id="treeViewLitreeViewItemtests"]') .assert.elementPresent('*[data-id="treeViewLitreeViewItemtests"]')
.assert.elementPresent('*[data-id="treeViewLitreeViewItemtests/SampleERC20_test.sol"]') .assert.elementPresent('*[data-id="treeViewLitreeViewItemtests/SampleERC20_test.sol"]')
}, },
'Should create ERC721 workspace with files': function (browser: NightwatchBrowser) {
browser
.click('*[data-id="workspaceCreate"]')
.waitForElementVisible('*[data-id="modalDialogCustomPromptTextCreate"]')
.waitForElementVisible('[data-id="fileSystemModalDialogModalFooter-react"] > span')
// eslint-disable-next-line dot-notation
.execute(function () { document.querySelector('*[data-id="modalDialogCustomPromptTextCreate"]')['value'] = 'workspace_erc721' })
.click('select[id="wstemplate"]')
.click('select[id="wstemplate"] option[value=ozerc721]')
.waitForElementPresent('[data-id="fileSystemModalDialogModalFooter-react"] .modal-ok')
.execute(function () { (document.querySelector('[data-id="fileSystemModalDialogModalFooter-react"] .modal-ok') as HTMLElement).click() })
.pause(100)
.assert.elementPresent('*[data-id="treeViewLitreeViewItemcontracts"]')
.assert.elementPresent('*[data-id="treeViewLitreeViewItemcontracts/SampleERC721.sol"]')
.assert.elementPresent('*[data-id="treeViewLitreeViewItemscripts"]')
.assert.elementPresent('*[data-id="treeViewLitreeViewItemscripts/deploy_with_web3.ts"]')
// check js and ts files are not transformed
.click('*[data-id="treeViewLitreeViewItemscripts/deploy_with_web3.ts"]')
.getEditorValue((content) => {
browser.assert.ok(content.indexOf(`import { deploy } from './web3-lib'`) !== -1,
'Incorrect content')
})
.assert.elementPresent('*[data-id="treeViewLitreeViewItemscripts/deploy_with_ethers.ts"]')
.click('*[data-id="treeViewLitreeViewItemscripts/deploy_with_ethers.ts"]')
.pause(100)
.getEditorValue((content) => {
browser.assert.ok(content.indexOf(`import { deploy } from './ethers-lib'`) !== -1,
'Incorrect content')
})
.assert.elementPresent('*[data-id="treeViewLitreeViewItemscripts/web3-lib.ts"]')
.click('*[data-id="treeViewLitreeViewItemscripts/web3-lib.ts"]')
.pause(100)
.getEditorValue((content) => {
browser.assert.ok(content.indexOf(`export const deploy = async (contractName: string, args: Array<any>, from?: string, gas?: number): Promise<Options> => {`) !== -1,
'Incorrect content')
})
.assert.elementPresent('*[data-id="treeViewLitreeViewItemscripts/ethers-lib.ts"]')
.click('*[data-id="treeViewLitreeViewItemscripts/ethers-lib.ts"]')
.pause(100)
.getEditorValue((content) => {
browser.assert.ok(content.indexOf(`export const deploy = async (contractName: string, args: Array<any>, accountIndex?: number): Promise<ethers.Contract> => {`) !== -1,
'Incorrect content')
})
.assert.elementPresent('*[data-id="treeViewLitreeViewItemtests"]')
.assert.elementPresent('*[data-id="treeViewLitreeViewItemtests/SampleERC721_test.sol"]')
},
// WORKSPACE TEMPLATES E2E END // WORKSPACE TEMPLATES E2E END
'Should create two workspace and switch to the first one': function (browser: NightwatchBrowser) { 'Should create two workspace and switch to the first one': function (browser: NightwatchBrowser) {

@ -11,7 +11,7 @@ declare module 'nightwatch' {
testContracts(fileName: string, contractCode: NightwatchContractContent, compiledContractNames: string[]): NightwatchBrowser, testContracts(fileName: string, contractCode: NightwatchContractContent, compiledContractNames: string[]): NightwatchBrowser,
setEditorValue(value: string, callback?: () => void): NightwatchBrowser, setEditorValue(value: string, callback?: () => void): NightwatchBrowser,
addFile(name: string, content: NightwatchContractContent): NightwatchBrowser, addFile(name: string, content: NightwatchContractContent): NightwatchBrowser,
verifyContracts(compiledContractNames: string[], opts?: { wait: number, version?: string }): NightwatchBrowser, verifyContracts(compiledContractNames: string[], opts?: { wait: number, version?: string, runs?: string }): NightwatchBrowser,
selectAccount(account?: string): NightwatchBrowser, selectAccount(account?: string): NightwatchBrowser,
clickFunction(fnFullName: string, expectedInput?: NightwatchClickFunctionExpectedInput): NightwatchBrowser, clickFunction(fnFullName: string, expectedInput?: NightwatchClickFunctionExpectedInput): NightwatchBrowser,
testFunction(txHash: string, expectedInput: NightwatchTestFunctionExpectedInput): NightwatchBrowser, testFunction(txHash: string, expectedInput: NightwatchTestFunctionExpectedInput): NightwatchBrowser,

@ -1,4 +1,4 @@
{ {
"presets": ["@babel/preset-env", "@babel/preset-react"], "presets": ["@babel/preset-env", "@babel/preset-react"],
"plugins": ["@babel/plugin-proposal-class-properties"] "plugins": ["@babel/plugin-proposal-class-properties", "@babel/plugin-transform-runtime"]
} }

@ -3,5 +3,6 @@ node_modules
reports reports
npm-debug.log* npm-debug.log*
package-lock.json package-lock.json
yarn.lock
remix remix
.DS_Store .DS_Store

@ -9,8 +9,8 @@ branches:
- master - master
- remix_live - remix_live
script: script:
- npm install - yarn install
- npm run lint && npm run test && npm run make-mock-compiler && npm run build - yarn run lint && yarn run test && yarn run make-mock-compiler && yarn run build
- wget http://selenium-release.storage.googleapis.com/3.5/selenium-server-standalone-3.5.3.jar - wget http://selenium-release.storage.googleapis.com/3.5/selenium-server-standalone-3.5.3.jar
- wget http://chromedriver.storage.googleapis.com/2.30/chromedriver_linux64.zip - wget http://chromedriver.storage.googleapis.com/2.30/chromedriver_linux64.zip
- unzip chromedriver_linux64.zip - unzip chromedriver_linux64.zip

@ -26,7 +26,7 @@ Install **npm** and **node.js** (see https://docs.npmjs.com/getting-started/inst
Remix-ide has been published as an npm module: Remix-ide has been published as an npm module:
```bash ```bash
npm install remix-ide -g yarn global add remix-ide
remix-ide remix-ide
``` ```
Or if you want to clone the github repository (`wget` need to be installed first) : Or if you want to clone the github repository (`wget` need to be installed first) :
@ -36,12 +36,12 @@ git clone https://github.com/ethereum/remix-ide.git
git clone https://github.com/ethereum/remix.git # only if you plan to link remix and remix-ide repositories and develop on it. git clone https://github.com/ethereum/remix.git # only if you plan to link remix and remix-ide repositories and develop on it.
cd remix # only if you plan to link remix and remix-ide repositories and develop on it. cd remix # only if you plan to link remix and remix-ide repositories and develop on it.
npm install # only if you plan to link remix and remix-ide repositories and develop on it. yarn install # only if you plan to link remix and remix-ide repositories and develop on it.
npm run bootstrap # only if you plan to link remix and remix-ide repositories and develop on it. yarn run bootstrap # only if you plan to link remix and remix-ide repositories and develop on it.
cd remix-ide cd remix-ide
npm install yarn install
npm run setupremix # only if you plan to link remix and remix-ide repositories and develop on it. yarn run setupremix # only if you plan to link remix and remix-ide repositories and develop on it.
npm start npm start
``` ```
@ -112,40 +112,40 @@ nvm --version
Register new unit test files in `test/index.js`. Register new unit test files in `test/index.js`.
The tests are written using [tape](https://www.npmjs.com/package/tape). The tests are written using [tape](https://www.npmjs.com/package/tape).
Run the unit tests via: `npm test` Run the unit tests via: `yarn test`
For local headless browser tests run `npm run test-browser` For local headless browser tests run `yarn run test-browser`
(requires Selenium to be installed - can be done with `npm run selenium-install`) (requires Selenium to be installed - can be done with `yarn run selenium-install`)
Running unit tests via `npm test` requires at least node v7.0.0 Running unit tests via `yarn test` requires at least node v7.0.0
## Browser Testing ## Browser Testing
To run the Selenium tests via Nightwatch: To run the Selenium tests via Nightwatch:
- Build Remix IDE and serve it: `npm run build && npm run serve` # starts web server at localhost:8080 - Build Remix IDE and serve it: `yarn run build && yarn run serve` # starts web server at localhost:8080
- Make sure Selenium is installed `npm run selenium-install` # don't need to repeat - Make sure Selenium is installed `yarn run selenium-install` # don't need to repeat
- Run a selenium server `npm run selenium` - Run a selenium server `yarn run selenium`
- Run all the tests `npm run nightwatch_local_firefox` or `npm run nightwatch_local_chrome` - Run all the tests `yarn run nightwatch_local_firefox` or `yarn run nightwatch_local_chrome`
- Or run a specific test case: - Or run a specific test case:
- npm run nightwatch_local_ballot - yarn run nightwatch_local_ballot
- npm run nightwatch_local_libraryDeployment - yarn run nightwatch_local_libraryDeployment
- npm run nightwatch_local_solidityImport - yarn run nightwatch_local_solidityImport
- npm run nightwatch_local_recorder - yarn run nightwatch_local_recorder
- npm run nightwatch_local_transactionExecution - yarn run nightwatch_local_transactionExecution
- npm run nightwatch_local_staticAnalysis - yarn run nightwatch_local_staticAnalysis
- npm run nightwatch_local_signingMessage - yarn run nightwatch_local_signingMessage
- npm run nightwatch_local_console - yarn run nightwatch_local_console
- npm run nightwatch_local_remixd # remixd needs to be run - yarn run nightwatch_local_remixd # remixd needs to be run
**NOTE:** **NOTE:**
- **the `ballot` tests suite** requires to run `ganache-cli` locally. - **the `ballot` tests suite** requires to run `ganache-cli` locally.

@ -6,14 +6,14 @@ BUILD_ID=${CIRCLE_BUILD_NUM:-${TRAVIS_JOB_NUMBER}}
echo "$BUILD_ID" echo "$BUILD_ID"
TEST_EXITCODE=0 TEST_EXITCODE=0
npm run ganache-cli & yarn run ganache-cli &
npm run serve:production & yarn run serve:production &
echo 'sharing folder: ' $PWD '/apps/remix-ide/contracts' & echo 'sharing folder: ' $PWD '/apps/remix-ide/contracts' &
npm run remixd & yarn run remixd &
sleep 5 sleep 5
npm run build:e2e yarn run build:e2e
TESTFILES=$(grep -IRiL "\'@disabled\': \?true" "dist/apps/remix-ide-e2e/src/tests" | grep "\.spec\|\.test" | sort | circleci tests split ) TESTFILES=$(grep -IRiL "\'@disabled\': \?true" "dist/apps/remix-ide-e2e/src/tests" | grep "\.spec\|\.test" | sort | circleci tests split )
for TESTFILE in $TESTFILES; do for TESTFILE in $TESTFILES; do

@ -15,13 +15,13 @@ BUILD_ID=${CIRCLE_BUILD_NUM:-${TRAVIS_JOB_NUMBER}}
echo "$BUILD_ID" echo "$BUILD_ID"
TEST_EXITCODE=0 TEST_EXITCODE=0
npm run ganache-cli & yarn run ganache-cli &
npm run serve & yarn run serve &
setupRemixd setupRemixd
sleep 5 sleep 5
npm run nightwatch_parallel || TEST_EXITCODE=1 yarn run nightwatch_parallel || TEST_EXITCODE=1
TESTFILES=$(circleci tests glob "./apps/remix-ide/test-browser/tests/**/*.test.js" | circleci tests split ) TESTFILES=$(circleci tests glob "./apps/remix-ide/test-browser/tests/**/*.test.js" | circleci tests split )
for TESTFILE in $TESTFILES; do for TESTFILE in $TESTFILES; do
./node_modules/.bin/nightwatch --config ./apps/remix-ide/nightwatch.js --env chrome $TESTFILE || TEST_EXITCODE=1 ./node_modules/.bin/nightwatch --config ./apps/remix-ide/nightwatch.js --env chrome $TESTFILE || TEST_EXITCODE=1

@ -6,13 +6,13 @@ BUILD_ID=${CIRCLE_BUILD_NUM:-${TRAVIS_JOB_NUMBER}}
echo "$BUILD_ID" echo "$BUILD_ID"
TEST_EXITCODE=0 TEST_EXITCODE=0
npm run ganache-cli & yarn run ganache-cli &
npm run serve:production & yarn run serve:production &
npx nx serve remix-ide-e2e-src-local-plugin & npx nx serve remix-ide-e2e-src-local-plugin &
sleep 5 sleep 5
npm run build:e2e yarn run build:e2e
TESTFILES=$(grep -IRiL "\'@disabled\': \?true" "dist/apps/remix-ide-e2e/src/tests" | grep "plugin_api" | sort | circleci tests split ) TESTFILES=$(grep -IRiL "\'@disabled\': \?true" "dist/apps/remix-ide-e2e/src/tests" | grep "plugin_api" | sort | circleci tests split )
for TESTFILE in $TESTFILES; do for TESTFILE in $TESTFILES; do

@ -2,7 +2,7 @@
set -e set -e
npm run build:e2e yarn run build:e2e
TESTFILES=$(grep -IRiL "\'@disabled\': \?true" "dist/apps/remix-ide-e2e/src/tests" | grep "\.flaky" | sort ) TESTFILES=$(grep -IRiL "\'@disabled\': \?true" "dist/apps/remix-ide-e2e/src/tests" | grep "\.flaky" | sort )
# count test files # count test files
@ -18,10 +18,10 @@ BUILD_ID=${CIRCLE_BUILD_NUM:-${TRAVIS_JOB_NUMBER}}
echo "$BUILD_ID" echo "$BUILD_ID"
TEST_EXITCODE=0 TEST_EXITCODE=0
npm run ganache-cli & yarn run ganache-cli &
npm run serve:production & yarn run serve:production &
echo 'sharing folder: ' $PWD '/apps/remix-ide/contracts' & echo 'sharing folder: ' $PWD '/apps/remix-ide/contracts' &
npm run remixd & yarn run remixd &
npx nx serve remix-ide-e2e-src-local-plugin & npx nx serve remix-ide-e2e-src-local-plugin &
sleep 5 sleep 5

@ -13,7 +13,7 @@ KEYS=$(jq -r '.projects | keys' workspace.json | tr -d '[],"')
then then
echo ${row} echo ${row}
fi fi
done) | circleci tests split | { while read i;do npm run lint $i; done } done) | circleci tests split | { while read i;do yarn run lint $i; done }
echo "$TEST_EXITCODE" echo "$TEST_EXITCODE"
if [ "$TEST_EXITCODE" -eq 1 ] if [ "$TEST_EXITCODE" -eq 1 ]

@ -5,8 +5,14 @@ const { globSource } = IpfsHttpClient
const folder = process.cwd() + '/dist/apps/remix-ide'; const folder = process.cwd() + '/dist/apps/remix-ide';
(async () => { (async () => {
const host = 'ipfs.remixproject.org' const host = 'ipfs.infura.io'
const ipfs = IpfsHttpClient({ host, port: 443, protocol: 'https' }) const projectId = process.argv[2]
const projectSecret = process.argv[3]
const auth = 'Basic ' + Buffer.from(projectId + ':' + projectSecret).toString('base64')
const ipfs = IpfsHttpClient({ port: 5001, host, protocol: 'https', headers: {
authorization: auth
} })
try { try {
let result = await ipfs.add(globSource(folder, { recursive: true}), { pin: false }) let result = await ipfs.add(globSource(folder, { recursive: true}), { pin: false })
const hash = result.cid.toString() const hash = result.cid.toString()

@ -33,9 +33,9 @@ Publish to Gist
The icon marked **C.** above. Publishes all files from the browser folder to a gist. Only file in the root of **browser** will be published. Files in subfolders will not be publish to the Gist. The icon marked **C.** above. Publishes all files from the browser folder to a gist. Only file in the root of **browser** will be published. Files in subfolders will not be publish to the Gist.
Gist API has changed in 2018 and **requires** users to be authenticated to be able to publish a gist. Gist API has changed in 2018 and **requires** users to be authenticated to be able to publish a gist.
Click [this link](https://github.com/settings/tokens) to Github tokens setup and select Generate new token. Then check the **Create gists** checkbox and generate a new token. Click [this link](https://github.com/settings/tokens) to GitHub tokens setup and select Generate new token. Then check the **Create gists** checkbox and generate a new token.
Take the token and paste it in Remix's **Settings** module in the **Github Access Token** section. And then click Save. Now you should be able to use the feature. Take the token and paste it in Remix's **Settings** module in the **GitHub Access Token** section. And then click Save. Now you should be able to use the feature.
Create a folder Create a folder
--------------- ---------------

@ -5,6 +5,10 @@ So if you've found the documentation to Remix but don't know where to find Remix
- An online version is available at [https://remix.ethereum.org](https://remix.ethereum.org). This version is stable and is updated at almost every release. - An online version is available at [https://remix.ethereum.org](https://remix.ethereum.org). This version is stable and is updated at almost every release.
- An alpha online version is available at [https://remix-alpha.ethereum.org](https://remix-alpha.ethereum.org). This is not a stable version. - An alpha online version is available at [https://remix-alpha.ethereum.org](https://remix-alpha.ethereum.org). This is not a stable version.
- npm `remix-ide` package `npm install remix-ide -g`. `remix-ide` create a new instance of `Remix IDE` available at [http://127.0.0.1:8080](http://127.0.0.1:8080) and make the current folder available to Remix IDE by automatically starting `remixd`. - npm `remix-ide` package `yarn global add remix-ide`. `remix-ide` create a new instance of `Remix IDE` available at [http://127.0.0.1:8080](http://127.0.0.1:8080) and make the current folder available to Remix IDE by automatically starting `remixd`.
see [Connection to `remixd`](https://remix-ide.readthedocs.io/en/latest/remixd.html) for more information about sharing local file with `Remix IDE`. see [Connection to `remixd`](https://remix-ide.readthedocs.io/en/latest/remixd.html) for more information about sharing local file with `Remix IDE`.
- Github release: [https://github.com/ethereum/remix-ide/releases](https://github.com/ethereum/remix-ide/releases) . The source code is packaged at every release but still need to be built using `npm run build`.
- GitHub release: [https://github.com/ethereum/remix-ide/releases](https://github.com/ethereum/remix-ide/releases) . The source code is packaged at every release but still need to be built using `npm run build`.
- GitHub release: [https://github.com/ethereum/remix-ide/releases](https://github.com/ethereum/remix-ide/releases) . The source code is packaged at every release but still need to be built using `yarn run build`.

@ -1,4 +1,4 @@
Remix Github Tutorials Remix GitHub Tutorials
======================= =======================
There are a series of tutorials in our github repo [remix-workshops](https://github.com/ethereum/remix-workshops). There are a series of tutorials in our github repo [remix-workshops](https://github.com/ethereum/remix-workshops).

@ -8,10 +8,7 @@ The code of `remixd` is
[here](https://github.com/ethereum/remixd) . [here](https://github.com/ethereum/remixd) .
`remixd` can be globally installed using the following command: `remixd` can be globally installed using the following command:
`npm install -g remixd` `yarn global add @remix-project/remixd`
Or just install it in the directory of your choice by removing the -g flag:
`npm install remixd`
Then from the terminal, the command `remixd -s <absolute-path-to-the-shared-folder> --remix-ide <your-remix-ide-URL-instance>` will start `remixd` and will share the given folder with remix-ide. Then from the terminal, the command `remixd -s <absolute-path-to-the-shared-folder> --remix-ide <your-remix-ide-URL-instance>` will start `remixd` and will share the given folder with remix-ide.

@ -9,7 +9,6 @@ This document includes:
- git checkout origin/master - git checkout origin/master
- git checkout -b bumpVersion - git checkout -b bumpVersion
- update package.json version - update package.json version
- remove package-lock.json version and generate a new one with `npm install`
- merge PR - merge PR
- git fetch origin master - git fetch origin master
- git checkout origin/master - git checkout origin/master
@ -18,9 +17,9 @@ This document includes:
- github-changes -o ethereum -r remix-ide -a --only-pulls --use-commit-body --only-merges --between-tags previous_version...next_version - github-changes -o ethereum -r remix-ide -a --only-pulls --use-commit-body --only-merges --between-tags previous_version...next_version
- publish a release in github using the changelog - publish a release in github using the changelog
- rm -rf node_modules - rm -rf node_modules
- npm install - yarn install
- remove all soljson.js files in root folder - remove all soljson.js files in root folder
- npm run build - yarn run build
- npm publish - npm publish
- after remix_live is updated, drop the zip (from https://github.com/ethereum/remix-live/) to the release. - after remix_live is updated, drop the zip (from https://github.com/ethereum/remix-live/) to the release.
@ -29,7 +28,6 @@ This document includes:
- git checkout origin/master - git checkout origin/master
- git checkout -b bumpVersion - git checkout -b bumpVersion
- update package.json version to the new version "vx.x.x-beta.1" - update package.json version to the new version "vx.x.x-beta.1"
- remove package-lock/json version and generate a new one with `npm install`
- merge PR - merge PR
- git fetch origin master - git fetch origin master
- git checkout origin/master - git checkout origin/master

Binary file not shown.

After

Width:  |  Height:  |  Size: 118 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 418 KiB

@ -27,6 +27,7 @@ import { NotificationPlugin } from './app/plugins/notification'
import { Blockchain } from './blockchain/blockchain.js' import { Blockchain } from './blockchain/blockchain.js'
import { HardhatProvider } from './app/tabs/hardhat-provider' import { HardhatProvider } from './app/tabs/hardhat-provider'
import { GanacheProvider } from './app/tabs/ganache-provider' import { GanacheProvider } from './app/tabs/ganache-provider'
import { FoundryProvider } from './app/tabs/foundry-provider'
const isElectron = require('is-electron') const isElectron = require('is-electron')
@ -177,6 +178,7 @@ class AppComponent {
const web3Provider = new Web3ProviderModule(blockchain) const web3Provider = new Web3ProviderModule(blockchain)
const hardhatProvider = new HardhatProvider(blockchain) const hardhatProvider = new HardhatProvider(blockchain)
const ganacheProvider = new GanacheProvider(blockchain) const ganacheProvider = new GanacheProvider(blockchain)
const foundryProvider = new FoundryProvider(blockchain)
// ----------------- convert offset to line/column service ----------- // ----------------- convert offset to line/column service -----------
const offsetToLineColumnConverter = new OffsetToLineColumnConverter() const offsetToLineColumnConverter = new OffsetToLineColumnConverter()
Registry.getInstance().put({ Registry.getInstance().put({
@ -233,6 +235,7 @@ class AppComponent {
storagePlugin, storagePlugin,
hardhatProvider, hardhatProvider,
ganacheProvider, ganacheProvider,
foundryProvider,
this.walkthroughService, this.walkthroughService,
search search
]) ])

@ -1,5 +1,6 @@
'use strict' 'use strict'
import React from 'react' // eslint-disable-line import React from 'react' // eslint-disable-line
import { resolve } from 'path'
import { EditorUI } from '@remix-ui/editor' // eslint-disable-line import { EditorUI } from '@remix-ui/editor' // eslint-disable-line
import { Plugin } from '@remixproject/engine' import { Plugin } from '@remixproject/engine'
import * as packageJson from '../../../../../package.json' import * as packageJson from '../../../../../package.json'
@ -140,16 +141,62 @@ class Editor extends Plugin {
this.on('sidePanel', 'pluginDisabled', (name) => { this.on('sidePanel', 'pluginDisabled', (name) => {
this.clearAllDecorationsFor(name) this.clearAllDecorationsFor(name)
}) })
this.on('fileManager', 'fileClosed', (name) => {
if (name === this.currentFile) {
this.currentFile = null
this.renderComponent()
}
})
this.on('theme', 'themeLoaded', (theme) => { this.on('theme', 'themeLoaded', (theme) => {
this.currentThemeType = theme.quality this.currentThemeType = theme.quality
this.renderComponent() this.renderComponent()
}) })
this.on('fileManager', 'currentFileChanged', async (name) => {
if (name.endsWith('.ts')) {
// extract the import, resolve their content
// and add the imported files to Monaco through the `addModel`
// so Monaco can provide auto completion
let content = await this.call('fileManager', 'readFile', name)
const paths = name.split('/')
paths.pop()
const fromPath = paths.join('/') // get current execution context path
for (const match of content.matchAll(/import\s+.*\s+from\s+(?:"(.*?)"|'(.*?)')/g)) {
let path = match[2]
if (path.startsWith('./') || path.startsWith('../')) path = resolve(fromPath, path)
if (path.startsWith('/')) path = path.substring(1)
if (!path.endsWith('.ts')) path = path + '.ts'
if (await this.call('fileManager', 'exists', path)) {
content = await this.call('fileManager', 'readFile', path)
this.emit('addModel', content, 'typescript', path, false)
}
}
}
})
this.on('fileManager', 'noFileSelected', async () => {
this.currentFile = null
this.renderComponent()
})
this.on('fileManager', 'currentFileChanged', async (name) => {
if (name.endsWith('.ts')) {
// extract the import, resolve their content
// and add the imported files to Monaco through the `addModel`
// so Monaco can provide auto completion
let content = await this.call('fileManager', 'readFile', name)
const paths = name.split('/')
paths.pop()
const fromPath = paths.join('/') // get current execution context path
for (const match of content.matchAll(/import\s+.*\s+from\s+(?:"(.*?)"|'(.*?)')/g)) {
let path = match[2]
if (path.startsWith('./') || path.startsWith('../')) path = resolve(fromPath, path)
if (path.startsWith('/')) path = path.substring(1)
if (!path.endsWith('.ts')) path = path + '.ts'
if (await this.call('fileManager', 'exists', path)) {
content = await this.call('fileManager', 'readFile', path)
this.emit('addModel', content, 'typescript', path, false)
}
}
}
})
this.on('fileManager', 'noFileSelected', async () => {
this.currentFile = null
this.renderComponent()
})
try { try {
this.currentThemeType = (await this.call('theme', 'currentTheme')).quality this.currentThemeType = (await this.call('theme', 'currentTheme')).quality
} catch (e) { } catch (e) {

@ -4,7 +4,7 @@ import * as packageJson from '../../../../../package.json'
import Registry from '../state/registry' import Registry from '../state/registry'
import { EventEmitter } from 'events' import { EventEmitter } from 'events'
import { RemixAppManager } from '../../../../../libs/remix-ui/plugin-manager/src/types' import { RemixAppManager } from '../../../../../libs/remix-ui/plugin-manager/src/types'
import { fileChangedToastMsg, storageFullMessage } from '@remix-ui/helper' import { fileChangedToastMsg, recursivePasteToastMsg, storageFullMessage } from '@remix-ui/helper'
import helper from '../../lib/helper.js' import helper from '../../lib/helper.js'
/* /*
@ -275,7 +275,7 @@ class FileManager extends Plugin {
const provider = this.fileProviderOf(src) const provider = this.fileProviderOf(src)
if (provider.isSubDirectory(src, dest)) { if (provider.isSubDirectory(src, dest)) {
this.call('notification', 'toast', 'File(s) to paste is an ancestor of the destination folder') this.call('notification', 'toast', recursivePasteToastMsg())
} else { } else {
await this.inDepthCopy(src, dest) await this.inDepthCopy(src, dest)
} }

@ -135,16 +135,12 @@ class FileProvider {
if (!await window.remixFileSystem.exists(currentCheck)) { if (!await window.remixFileSystem.exists(currentCheck)) {
try { try {
await window.remixFileSystem.mkdir(currentCheck) await window.remixFileSystem.mkdir(currentCheck)
this.event.emit('folderAdded', this._normalizePath(currentCheck))
} catch (error) { } catch (error) {
console.log(error) console.log(error)
} }
} }
} }
currentCheck = ''
for (const value of paths) {
currentCheck = currentCheck + '/' + value
this.event.emit('folderAdded', this._normalizePath(currentCheck))
}
if (cb) cb() if (cb) cb()
} }

@ -57,9 +57,6 @@ export class Layout extends Plugin {
this.panels.main.active = false this.panels.main.active = false
this.event.emit('change', null) this.event.emit('change', null)
}) })
this.on('tabs', 'tabCountChanged', async count => {
if (!count) await this.call('manager', 'activatePlugin', 'home')
})
this.on('manager', 'activate', (profile: Profile) => { this.on('manager', 'activate', (profile: Profile) => {
switch (profile.name) { switch (profile.name) {
case 'filePanel': case 'filePanel':

@ -24,7 +24,7 @@ export class TabProxy extends Plugin {
this.themeQuality = 'dark' this.themeQuality = 'dark'
} }
onActivation () { async onActivation () {
this.on('theme', 'themeChanged', (theme) => { this.on('theme', 'themeChanged', (theme) => {
this.themeQuality = theme.quality this.themeQuality = theme.quality
// update invert for all icons // update invert for all icons
@ -169,6 +169,13 @@ export class TabProxy extends Plugin {
this.on('manager', 'pluginDeactivated', (profile) => { this.on('manager', 'pluginDeactivated', (profile) => {
this.removeTab(profile.name) this.removeTab(profile.name)
}) })
try {
this.themeQuality = (await this.call('theme', 'currentTheme') ).quality
} catch (e) {
console.log('theme plugin has an issue: ', e)
}
this.renderComponent()
} }
focus (name) { focus (name) {
@ -208,6 +215,7 @@ export class TabProxy extends Plugin {
} }
renameTab (oldName, newName) { renameTab (oldName, newName) {
// The new tab is being added by FileManager
this.removeTab(oldName) this.removeTab(oldName)
} }
@ -275,7 +283,12 @@ export class TabProxy extends Plugin {
delete this._handlers[name] delete this._handlers[name]
let previous = currentFileTab let previous = currentFileTab
this.loadedTabs = this.loadedTabs.filter((tab, index) => { this.loadedTabs = this.loadedTabs.filter((tab, index) => {
if (!previous && tab.name === name) previous = this.loadedTabs[index - 1] if (!previous && tab.name === name) {
if(index - 1 >= 0 && this.loadedTabs[index - 1])
previous = this.loadedTabs[index - 1]
else if (index + 1 && this.loadedTabs[index + 1])
previous = this.loadedTabs[index + 1]
}
return tab.name !== name return tab.name !== name
}) })
this.renderComponent() this.renderComponent()
@ -292,7 +305,15 @@ export class TabProxy extends Plugin {
} }
updateComponent(state) { updateComponent(state) {
return <TabsUI tabs={state.loadedTabs} onSelect={state.onSelect} onClose={state.onClose} onZoomIn={state.onZoomIn} onZoomOut={state.onZoomOut} onReady={state.onReady} themeQuality={state.themeQuality} /> return <TabsUI
tabs={state.loadedTabs}
onSelect={state.onSelect}
onClose={state.onClose}
onZoomIn={state.onZoomIn}
onZoomOut={state.onZoomOut}
onReady={state.onReady}
themeQuality={state.themeQuality}
/>
} }
renderComponent () { renderComponent () {

@ -18,7 +18,7 @@ export class ConfigPlugin extends Plugin {
const queryParams = new QueryParams() const queryParams = new QueryParams()
const params = queryParams.get() const params = queryParams.get()
const config = Registry.getInstance().get('config').api const config = Registry.getInstance().get('config').api
let param = params[name] || config.get(name) || config.get('settings/' + name) const param = params[name] || config.get(name) || config.get('settings/' + name)
if (param === 'true') return true if (param === 'true') return true
if (param === 'false') return false if (param === 'false') return false
return param return param

@ -134,22 +134,29 @@ export class RemixdHandle extends WebsocketPlugin {
} }
function remixdDialog () { function remixdDialog () {
const commandText = 'remixd -s <path-to-the-shared-folder> -u <remix-ide-instance-URL>' const commandText = 'remixd'
const fullCommandText = 'remixd -s <path-to-the-shared-folder> -u <remix-ide-instance-URL>'
return (<> return (<>
<div className=''> <div className=''>
<div className='mb-2 text-break'> <div className='mb-2 text-break'>
Access your local file system from Remix IDE using <a target="_blank" href="https://www.npmjs.com/package/@remix-project/remixd">Remixd NPM package</a>.<br/><br/> Access your local file system from Remix IDE using <a target="_blank" href="https://www.npmjs.com/package/@remix-project/remixd">Remixd NPM package</a>.
Remixd needs to be running in the background to load the files in localhost workspace. For more info, please check the <a target="_blank" href="https://remix-ide.readthedocs.io/en/latest/remixd.html">Remixd tutorial</a>.
</div> </div>
<div className='mb-2 text-break'> <div className='mb-2 text-break'>
If you are just looking for the remixd command, here it is: Remixd <a target="_blank" href="https://remix-ide.readthedocs.io/en/latest/remixd.html">documentation</a>.
<br></br><br></br><b>{commandText}</b>
<CopyToClipboard data-id='remixdCopyCommand' content={commandText}></CopyToClipboard>
</div> </div>
<div className='mb-2 text-break'> <div className='mb-2 text-break'>
When connected, a session will be started between <em>{window.location.origin}</em> and your local file system at <i>ws://127.0.0.1:65520</i>. The remixd command is:
The shared folder will be in the "File Explorers" workspace named "localhost". <br/><b>{commandText}</b>
<br/>Read more about other <a target="_blank" href="https://remix-ide.readthedocs.io/en/latest/remixd.html#ports-usage">Remixd ports usage</a> </div>
<div className='mb-2 text-break'>
The remixd command without options uses the terminal's current directory as the shared directory and the shared Remix domain can only be https://remix.ethereum.org, https://remix-alpha.ethereum.org, or https://remix-beta.ethereum.org
</div>
<div className='mb-2 text-break'>
Example command with flags: <br/>
<b>{fullCommandText}</b>
</div>
<div className='mb-2 text-break'>
For info about ports, see <a target="_blank" href="https://remix-ide.readthedocs.io/en/latest/remixd.html#ports-usage">Remixd ports usage</a>
</div> </div>
<div className='mb-2 text-break'> <div className='mb-2 text-break'>
This feature is still in Alpha. We recommend to keep a backup of the shared folder. This feature is still in Alpha. We recommend to keep a backup of the shared folder.

@ -56,6 +56,10 @@ class CompileTab extends CompilerApiMixin(ViewPlugin) { // implements ICompilerA
this.renderComponent() this.renderComponent()
} }
onFileRemoved () {
this.renderComponent()
}
onNoFileSelected () { onNoFileSelected () {
this.renderComponent() this.renderComponent()
} }

@ -0,0 +1,33 @@
import * as packageJson from '../../../../../package.json'
import { Plugin } from '@remixproject/engine'
import { AppModal, AlertModal, ModalTypes } from '@remix-ui/app'
import React from 'react' // eslint-disable-line
import { Blockchain } from '../../blockchain/blockchain'
import { ethers } from 'ethers'
import { AbstractProvider } from './abstract-provider'
const profile = {
name: 'foundry-provider',
displayName: 'Foundry Provider',
kind: 'provider',
description: 'Anvil',
methods: ['sendAsync'],
version: packageJson.version
}
export class FoundryProvider extends AbstractProvider {
constructor (blockchain) {
super(profile, blockchain, 'http://127.0.0.1:8545')
}
body (): JSX.Element {
return (
<div> Note: To run Anvil on your system, run
<div className="border p-1">curl -L https://foundry.paradigm.xyz | bash</div>
<div className="border p-1">anvil</div>
For more info, visit: <a href="https://github.com/foundry-rs/foundry" target="_blank">Foundry Documentation</a>
<div>Anvil JSON-RPC Endpoint:</div>
</div>
)
}
}

@ -23,7 +23,7 @@ export class GanacheProvider extends AbstractProvider {
body (): JSX.Element { body (): JSX.Element {
return ( return (
<div> Note: To run Ganache on your system, run <div> Note: To run Ganache on your system, run
<div className="border p-1">npm install -g ganache</div> <div className="border p-1">yarn global add ganache</div>
<div className="border p-1">ganache</div> <div className="border p-1">ganache</div>
For more info, visit: <a href="https://github.com/trufflesuite/ganache" target="_blank">Ganache Documentation</a> For more info, visit: <a href="https://github.com/trufflesuite/ganache" target="_blank">Ganache Documentation</a>
<div>Ganache JSON-RPC Endpoint:</div> <div>Ganache JSON-RPC Endpoint:</div>

@ -127,6 +127,20 @@ export class RunTab extends ViewPlugin {
} }
}) })
await this.call('blockchain', 'addProvider', {
name: 'Foundry Provider',
provider: {
async sendAsync (payload, callback) {
try {
const result = await udapp.call('foundry-provider', 'sendAsync', payload)
callback(null, result)
} catch (e) {
callback(e)
}
}
}
})
await this.call('blockchain', 'addProvider', { await this.call('blockchain', 'addProvider', {
name: 'Wallet Connect', name: 'Wallet Connect',
provider: { provider: {

@ -7,7 +7,7 @@ const _paq = window._paq = window._paq || []
const requiredModules = [ // services + layout views + system views const requiredModules = [ // services + layout views + system views
'manager', 'config', 'compilerArtefacts', 'compilerMetadata', 'contextualListener', 'editor', 'offsetToLineColumnConverter', 'network', 'theme', 'manager', 'config', 'compilerArtefacts', 'compilerMetadata', 'contextualListener', 'editor', 'offsetToLineColumnConverter', 'network', 'theme',
'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-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']
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)
@ -19,7 +19,7 @@ const sensitiveCalls = {
} }
export function isNative(name) { export function isNative(name) {
const nativePlugins = ['vyper', 'workshops', 'debugger', 'remixd', 'menuicons', 'solidity', 'hardhat-provider', 'solidityStaticAnalysis', 'solidityUnitTesting', 'layout', 'notification', 'hardhat-provider', 'ganache-provider'] const nativePlugins = ['vyper', 'workshops', 'debugger', 'remixd', 'menuicons', 'solidity', 'solidity-logic', 'solidityStaticAnalysis', 'solidityUnitTesting', 'layout', 'notification', 'hardhat-provider', 'ganache-provider']
return nativePlugins.includes(name) || requiredModules.includes(name) return nativePlugins.includes(name) || requiredModules.includes(name)
} }

@ -54,7 +54,7 @@ Related links:
- How the backend (if any) works / will work (could be a smart contract). - How the backend (if any) works / will work (could be a smart contract).
- How the frontend works / will work. - How the frontend works / will work.
- What is the general vision of the UX design for this particular story. - What is the general vision of the UX design for this particular story.
Later progress and discussion is updated directly on the issue or pull request (Github). Later progress and discussion is updated directly on the issue or pull request (GitHub).
--- ---
@ -72,7 +72,7 @@ Before starting coding, we should ensure all devs / contributors are aware of:
# Story / Bug fix # Story / Bug fix
- Prioritised list of PRs / issues are tracked in a Github Project, Remix IDE issues are managed by a prioritized backlog. - Prioritised list of PRs / issues are tracked in a GitHub Project, Remix IDE issues are managed by a prioritized backlog.
- Every story can be executed by a single developer or a group of 2 or more developers (depending on the size and complexity) - Every story can be executed by a single developer or a group of 2 or more developers (depending on the size and complexity)
- Each dev should take the part he/she feels the most confortable with. - Each dev should take the part he/she feels the most confortable with.
- Later progress and discussion is updated directly on the issue or pull request (github). - Later progress and discussion is updated directly on the issue or pull request (github).

@ -18,7 +18,8 @@ export const CompilerApiMixin = (Base) => class extends Base {
onCurrentFileChanged: (fileName: string) => void onCurrentFileChanged: (fileName: string) => void
// onResetResults: () => void // onResetResults: () => void
onSetWorkspace: (workspace: any) => void onSetWorkspace: (isLocalhost: boolean, workspaceName: string) => void
onFileRemoved: (path: string) => void
onNoFileSelected: () => void onNoFileSelected: () => void
onCompilationFinished: (compilationDetails: { contractMap: { file: string } | Record<string, any>, contractsDetails: Record<string, any> }) => void onCompilationFinished: (compilationDetails: { contractMap: { file: string } | Record<string, any>, contractsDetails: Record<string, any> }) => void
onSessionSwitched: () => void onSessionSwitched: () => void
@ -237,12 +238,16 @@ export const CompilerApiMixin = (Base) => class extends Base {
this.on('filePanel', 'setWorkspace', (workspace) => { this.on('filePanel', 'setWorkspace', (workspace) => {
this.resetResults() this.resetResults()
if (this.onSetWorkspace) this.onSetWorkspace(workspace.isLocalhost) if (this.onSetWorkspace) this.onSetWorkspace(workspace.isLocalhost, workspace.name)
})
this.on('fileManager', 'fileRemoved', (path) => {
if (this.onFileRemoved) this.onFileRemoved(path)
}) })
this.on('remixd', 'rootFolderChanged', () => { this.on('remixd', 'rootFolderChanged', () => {
this.resetResults() this.resetResults()
if (this.onSetWorkspace) this.onSetWorkspace(true) if (this.onSetWorkspace) this.onSetWorkspace(true, 'localhost')
}) })
this.on('editor', 'sessionSwitched', () => { this.on('editor', 'sessionSwitched', () => {
@ -282,36 +287,34 @@ export const CompilerApiMixin = (Base) => class extends Base {
type: 'warning' type: 'warning'
}) })
} else this.statusChanged({ key: 'succeed', title: 'compilation successful', type: 'success' }) } else this.statusChanged({ key: 'succeed', title: 'compilation successful', type: 'success' })
// Store the contracts
this.compilationDetails.contractsDetails = {}
this.compiler.visitContracts((contract) => {
this.compilationDetails.contractsDetails[contract.name] = parseContracts(
contract.name,
contract.object,
this.compiler.getSource(contract.file)
)
})
} else { } else {
const count = (data.errors ? data.errors.filter(error => error.severity === 'error').length : 0 + (data.error ? 1 : 0)) const count = (data.errors ? data.errors.filter(error => error.severity === 'error').length : 0 + (data.error ? 1 : 0))
this.statusChanged({ key: count, title: `compilation failed with ${count} error${count > 1 ? 's' : ''}`, type: 'error' }) this.statusChanged({ key: count, title: `compilation failed with ${count} error${count > 1 ? 's' : ''}`, type: 'error' })
} }
// Update contract Selection // Store the contracts and Update contract Selection
this.compilationDetails.contractMap = {} if (success) {
if (success) this.compiler.visitContracts((contract) => { this.compilationDetails.contractMap[contract.name] = contract }) this.compilationDetails = await this.visitsContractApi(source, data)
this.compilationDetails.target = source.target } else {
this.compilationDetails = {
contractMap: {},
contractsDetails: {},
target: source.target
}
}
if (this.onCompilationFinished) this.onCompilationFinished(this.compilationDetails) if (this.onCompilationFinished) this.onCompilationFinished(this.compilationDetails)
// set annotations // set annotations
if (data.errors) { if (data.errors) {
for (const error of data.errors) { for (const error of data.errors) {
let pos = helper.getPositionDetails(error.formattedMessage) let pos = helper.getPositionDetails(error.formattedMessage)
if (pos.errFile) { const file = pos.errFile
if (file) {
pos = { pos = {
row: pos.errLine, row: pos.errLine,
column: pos.errCol, column: pos.errCol,
text: error.formattedMessage, text: error.formattedMessage,
type: error.severity type: error.severity
} }
await this.call('editor', 'addAnnotation', pos, pos.errFile) await this.call('editor', 'addAnnotation', pos, file)
} }
} }
} }
@ -332,11 +335,40 @@ export const CompilerApiMixin = (Base) => class extends Base {
// ctrl+s or command+s // ctrl+s or command+s
if ((e.metaKey || e.ctrlKey) && !e.shiftKey && e.keyCode === 83 && this.currentFile !== '') { if ((e.metaKey || e.ctrlKey) && !e.shiftKey && e.keyCode === 83 && this.currentFile !== '') {
e.preventDefault() e.preventDefault()
if(await this.getAppParameter('hardhat-compilation')) this.compileTabLogic.runCompiler('hardhat') if (this.currentFile && (this.currentFile.endsWith('.sol') || this.currentFile.endsWith('.yul'))) {
else if(await this.getAppParameter('truffle-compilation')) this.compileTabLogic.runCompiler('truffle') if(await this.getAppParameter('hardhat-compilation')) this.compileTabLogic.runCompiler('hardhat')
else this.compileTabLogic.runCompiler(undefined) else if(await this.getAppParameter('truffle-compilation')) this.compileTabLogic.runCompiler('truffle')
else this.compileTabLogic.runCompiler(undefined)
}
} }
} }
window.document.addEventListener('keydown', this.data.eventHandlers.onKeyDown) window.document.addEventListener('keydown', this.data.eventHandlers.onKeyDown)
} }
async visitsContractApi (source, data): Promise<{ contractMap: { file: string } | Record<string, any>, contractsDetails: Record<string, any>, target?: string }> {
return new Promise((resolve) => {
if (!data.contracts || (data.contracts && Object.keys(data.contracts).length === 0)) {
return resolve({
contractMap: {},
contractsDetails: {},
target: source.target
})
}
const contractMap = {}
const contractsDetails = {}
this.compiler.visitContracts((contract) => {
contractMap[contract.name] = contract
contractsDetails[contract.name] = parseContracts(
contract.name,
contract.object,
this.compiler.getSource(contract.file)
)
})
return resolve({
contractMap,
contractsDetails,
target: source.target
})
})
}
} }

@ -6,31 +6,14 @@ import { CompilerApiMixin } from './compiler-api'
import { ICompilerApi } from '@remix-project/remix-lib-ts' import { ICompilerApi } from '@remix-project/remix-lib-ts'
import { CompileTabLogic } from '@remix-ui/solidity-compiler' import { CompileTabLogic } from '@remix-ui/solidity-compiler'
const profile = {
name: 'solidity',
displayName: 'Solidity compiler',
icon: 'assets/img/solidity.webp',
description: 'Compile solidity contracts',
kind: 'compiler',
permission: true,
location: 'sidePanel',
documentation: 'https://remix-ide.readthedocs.io/en/latest/solidity_editor.html',
version: '0.0.1',
methods: ['getCompilationResult', 'compile', 'compileWithParameters', 'setCompilerConfig', 'compileFile', 'getCompilerState']
}
const defaultAppParameters = {
hideWarnings: false,
autoCompile: false,
includeNightlies: false
}
const defaultCompilerParameters = { const defaultCompilerParameters = {
runs: '200', runs: '200',
optimize: false, optimize: false,
version: 'soljson-v0.8.7+commit.e28d00a7', version: 'soljson-v0.8.7+commit.e28d00a7',
evmVersion: null, // compiler default evmVersion: null, // compiler default
language: 'Solidity' language: 'Solidity',
useFileConfiguration: false,
configFilePath: "compiler_config.json"
} }
export class CompilerClientApi extends CompilerApiMixin(PluginClient) implements ICompilerApi { export class CompilerClientApi extends CompilerApiMixin(PluginClient) implements ICompilerApi {
constructor () { constructor () {
@ -48,7 +31,9 @@ export class CompilerClientApi extends CompilerApiMixin(PluginClient) implements
optimize: localStorage.getItem('optimize') === 'true', optimize: localStorage.getItem('optimize') === 'true',
version: localStorage.getItem('version') || defaultCompilerParameters.version, version: localStorage.getItem('version') || defaultCompilerParameters.version,
evmVersion: localStorage.getItem('evmVersion') || defaultCompilerParameters.evmVersion, // default evmVersion: localStorage.getItem('evmVersion') || defaultCompilerParameters.evmVersion, // default
language: localStorage.getItem('language') || defaultCompilerParameters.language language: localStorage.getItem('language') || defaultCompilerParameters.language,
useFileConfiguration: localStorage.getItem('useFileConfiguration') === 'true',
configFilePath: localStorage.getItem('configFilePath') || defaultCompilerParameters.configFilePath
} }
return params return params
} }

@ -10,7 +10,7 @@
### Installation ### Installation
`@remix-project/remix-analyzer` is an NPM package and can be installed using NPM as: `@remix-project/remix-analyzer` is an NPM package and can be installed using NPM as:
`npm install @remix-project/remix-analyzer` `yarn add @remix-project/remix-analyzer`
### How to use ### How to use

@ -1,6 +1,6 @@
{ {
"name": "@remix-project/remix-analyzer", "name": "@remix-project/remix-analyzer",
"version": "0.5.22", "version": "0.5.23",
"description": "Tool to perform static analysis on Solidity smart contracts", "description": "Tool to perform static analysis on Solidity smart contracts",
"main": "src/index.js", "main": "src/index.js",
"types": "src/index.d.ts", "types": "src/index.d.ts",
@ -22,8 +22,8 @@
"@ethereumjs/block": "^3.5.1", "@ethereumjs/block": "^3.5.1",
"@ethereumjs/tx": "^3.3.2", "@ethereumjs/tx": "^3.3.2",
"@ethereumjs/vm": "^5.5.3", "@ethereumjs/vm": "^5.5.3",
"@remix-project/remix-astwalker": "^0.0.43", "@remix-project/remix-astwalker": "^0.0.44",
"@remix-project/remix-lib": "^0.5.13", "@remix-project/remix-lib": "^0.5.14",
"async": "^2.6.2", "async": "^2.6.2",
"ethereumjs-util": "^7.0.10", "ethereumjs-util": "^7.0.10",
"ethers": "^5.4.2", "ethers": "^5.4.2",
@ -52,5 +52,5 @@
"typescript": "^3.7.5" "typescript": "^3.7.5"
}, },
"typings": "src/index.d.ts", "typings": "src/index.d.ts",
"gitHead": "2be5108c51f2ac9226e4598c512ec6d3c63f5c78" "gitHead": "3f311aaf25f5796f70711006bb783ee4087ffc71"
} }

@ -10,7 +10,7 @@
### Installation ### Installation
`@remix-project/remix-astwalker` is an NPM package and can be installed using NPM as: `@remix-project/remix-astwalker` is an NPM package and can be installed using NPM as:
`npm install @remix-project/remix-astwalker` `yarn add @remix-project/remix-astwalker`
### How to use ### How to use

@ -1,6 +1,6 @@
{ {
"name": "@remix-project/remix-astwalker", "name": "@remix-project/remix-astwalker",
"version": "0.0.43", "version": "0.0.44",
"description": "Tool to walk through Solidity AST", "description": "Tool to walk through Solidity AST",
"main": "src/index.js", "main": "src/index.js",
"scripts": { "scripts": {
@ -37,7 +37,7 @@
"@ethereumjs/block": "^3.5.1", "@ethereumjs/block": "^3.5.1",
"@ethereumjs/tx": "^3.3.2", "@ethereumjs/tx": "^3.3.2",
"@ethereumjs/vm": "^5.5.3", "@ethereumjs/vm": "^5.5.3",
"@remix-project/remix-lib": "^0.5.13", "@remix-project/remix-lib": "^0.5.14",
"@types/tape": "^4.2.33", "@types/tape": "^4.2.33",
"async": "^2.6.2", "async": "^2.6.2",
"ethereumjs-util": "^7.0.10", "ethereumjs-util": "^7.0.10",
@ -54,5 +54,5 @@
"tap-spec": "^5.0.0" "tap-spec": "^5.0.0"
}, },
"typings": "src/index.d.ts", "typings": "src/index.d.ts",
"gitHead": "2be5108c51f2ac9226e4598c512ec6d3c63f5c78" "gitHead": "3f311aaf25f5796f70711006bb783ee4087ffc71"
} }

@ -10,7 +10,7 @@
### Installation ### Installation
`@remix-project/remix-debug` is an NPM package and can be installed using NPM as: `@remix-project/remix-debug` is an NPM package and can be installed using NPM as:
`npm install @remix-project/remix-debug` `yarn add @remix-project/remix-debug`
### How to use ### How to use

@ -1,6 +1,6 @@
{ {
"name": "@remix-project/remix-debug", "name": "@remix-project/remix-debug",
"version": "0.5.13", "version": "0.5.14",
"description": "Tool to debug Ethereum transactions", "description": "Tool to debug Ethereum transactions",
"contributors": [ "contributors": [
{ {
@ -22,9 +22,9 @@
"@ethereumjs/common": "^2.5.0", "@ethereumjs/common": "^2.5.0",
"@ethereumjs/tx": "^3.3.2", "@ethereumjs/tx": "^3.3.2",
"@ethereumjs/vm": "^5.5.3", "@ethereumjs/vm": "^5.5.3",
"@remix-project/remix-astwalker": "^0.0.43", "@remix-project/remix-astwalker": "^0.0.44",
"@remix-project/remix-lib": "^0.5.13", "@remix-project/remix-lib": "^0.5.14",
"@remix-project/remix-simulator": "^0.2.13", "@remix-project/remix-simulator": "^0.2.14",
"ansi-gray": "^0.1.1", "ansi-gray": "^0.1.1",
"async": "^2.6.2", "async": "^2.6.2",
"color-support": "^1.1.3", "color-support": "^1.1.3",
@ -68,5 +68,5 @@
}, },
"homepage": "https://github.com/ethereum/remix-project/tree/master/libs/remix-debug#readme", "homepage": "https://github.com/ethereum/remix-project/tree/master/libs/remix-debug#readme",
"typings": "src/index.d.ts", "typings": "src/index.d.ts",
"gitHead": "2be5108c51f2ac9226e4598c512ec6d3c63f5c78" "gitHead": "3f311aaf25f5796f70711006bb783ee4087ffc71"
} }

@ -10,7 +10,7 @@
### Installation ### Installation
`@remix-project/remix-lib` is an NPM package and can be installed using NPM as: `@remix-project/remix-lib` is an NPM package and can be installed using NPM as:
`npm install @remix-project/remix-lib` `yarn add @remix-project/remix-lib`
### How to use ### How to use

@ -1,6 +1,6 @@
{ {
"name": "@remix-project/remix-lib", "name": "@remix-project/remix-lib",
"version": "0.5.13", "version": "0.5.14",
"description": "Library to various Remix tools", "description": "Library to various Remix tools",
"contributors": [ "contributors": [
{ {
@ -54,5 +54,5 @@
}, },
"homepage": "https://github.com/ethereum/remix-project/tree/master/libs/remix-lib#readme", "homepage": "https://github.com/ethereum/remix-project/tree/master/libs/remix-lib#readme",
"typings": "src/index.d.ts", "typings": "src/index.d.ts",
"gitHead": "2be5108c51f2ac9226e4598c512ec6d3c63f5c78" "gitHead": "3f311aaf25f5796f70711006bb783ee4087ffc71"
} }

@ -24,7 +24,8 @@ export interface ICompilerApi {
onCurrentFileChanged: (fileName: string) => void onCurrentFileChanged: (fileName: string) => void
// onResetResults: () => void, // onResetResults: () => void,
onSetWorkspace: (workspace: any) => void onSetWorkspace: (isLocalhost: boolean, workspaceName: string) => void
onFileRemoved: (path: string) => void
onNoFileSelected: () => void onNoFileSelected: () => void
onCompilationFinished: (contractsDetails: any, contractMap: any) => void onCompilationFinished: (contractsDetails: any, contractMap: any) => void
onSessionSwitched: () => void onSessionSwitched: () => void

@ -10,7 +10,7 @@
### Installation ### Installation
`@remix-project/remix-simulator` is an NPM package and can be installed using NPM as: `@remix-project/remix-simulator` is an NPM package and can be installed using NPM as:
`npm install @remix-project/remix-simulator` `yarn add @remix-project/remix-simulator`
### How to use ### How to use

@ -1,6 +1,6 @@
{ {
"name": "@remix-project/remix-simulator", "name": "@remix-project/remix-simulator",
"version": "0.2.13", "version": "0.2.14",
"description": "Ethereum IDE and tools for the web", "description": "Ethereum IDE and tools for the web",
"contributors": [ "contributors": [
{ {
@ -18,7 +18,7 @@
"@ethereumjs/common": "^2.5.0", "@ethereumjs/common": "^2.5.0",
"@ethereumjs/tx": "^3.3.2", "@ethereumjs/tx": "^3.3.2",
"@ethereumjs/vm": "^5.5.3", "@ethereumjs/vm": "^5.5.3",
"@remix-project/remix-lib": "^0.5.13", "@remix-project/remix-lib": "^0.5.14",
"ansi-gray": "^0.1.1", "ansi-gray": "^0.1.1",
"async": "^3.1.0", "async": "^3.1.0",
"body-parser": "^1.18.2", "body-parser": "^1.18.2",
@ -67,5 +67,5 @@
}, },
"homepage": "https://github.com/ethereum/remix-project/tree/master/libs/remix-simulator#readme", "homepage": "https://github.com/ethereum/remix-project/tree/master/libs/remix-simulator#readme",
"typings": "src/index.d.ts", "typings": "src/index.d.ts",
"gitHead": "2be5108c51f2ac9226e4598c512ec6d3c63f5c78" "gitHead": "3f311aaf25f5796f70711006bb783ee4087ffc71"
} }

@ -40,7 +40,6 @@ export class Blocks {
return cb(new Error('block not found')) return cb(new Error('block not found'))
} }
console.log(block.transactions)
const transactions = block.transactions.map((t) => { const transactions = block.transactions.map((t) => {
const hash = '0x' + t.hash().toString('hex') const hash = '0x' + t.hash().toString('hex')
const tx = this.vmContext.txByHash[hash] const tx = this.vmContext.txByHash[hash]
@ -95,7 +94,6 @@ export class Blocks {
eth_getBlockByHash (payload, cb) { eth_getBlockByHash (payload, cb) {
const block = this.vmContext.blocks[payload.params[0]] const block = this.vmContext.blocks[payload.params[0]]
console.log(block.transactions)
const transactions = block.transactions.map((t) => { const transactions = block.transactions.map((t) => {
const hash = '0x' + t.hash().toString('hex') const hash = '0x' + t.hash().toString('hex')
const tx = this.vmContext.txByHash[hash] const tx = this.vmContext.txByHash[hash]

@ -12,7 +12,7 @@
`@remix-project/remix-solidity` is an NPM package and can be installed using NPM as: `@remix-project/remix-solidity` is an NPM package and can be installed using NPM as:
`npm install @remix-project/remix-solidity` `yarn add @remix-project/remix-solidity`
### How to use ### How to use

@ -1,6 +1,6 @@
{ {
"name": "@remix-project/remix-solidity", "name": "@remix-project/remix-solidity",
"version": "0.4.13", "version": "0.5.0",
"description": "Tool to load and run Solidity compiler", "description": "Tool to load and run Solidity compiler",
"main": "src/index.js", "main": "src/index.js",
"types": "src/index.d.ts", "types": "src/index.d.ts",
@ -18,7 +18,7 @@
"@ethereumjs/block": "^3.5.1", "@ethereumjs/block": "^3.5.1",
"@ethereumjs/tx": "^3.3.2", "@ethereumjs/tx": "^3.3.2",
"@ethereumjs/vm": "^5.5.3", "@ethereumjs/vm": "^5.5.3",
"@remix-project/remix-lib": "^0.5.13", "@remix-project/remix-lib": "^0.5.14",
"async": "^2.6.2", "async": "^2.6.2",
"eslint-scope": "^5.0.0", "eslint-scope": "^5.0.0",
"ethereumjs-util": "^7.0.10", "ethereumjs-util": "^7.0.10",
@ -61,5 +61,5 @@
}, },
"homepage": "https://github.com/ethereum/remix-project/tree/master/libs/remix-solidity#readme", "homepage": "https://github.com/ethereum/remix-project/tree/master/libs/remix-solidity#readme",
"typings": "src/index.d.ts", "typings": "src/index.d.ts",
"gitHead": "2be5108c51f2ac9226e4598c512ec6d3c63f5c78" "gitHead": "3f311aaf25f5796f70711006bb783ee4087ffc71"
} }

@ -46,3 +46,9 @@ export function getValidLanguage (val: string): Language {
} }
return null return null
} }
export function compilerInputForConfigFile(sources: Source, opts)
{
opts.sources = sources
return JSON.stringify(opts)
}

@ -2,7 +2,7 @@
import { update } from 'solc/abi' import { update } from 'solc/abi'
import * as webworkify from 'webworkify-webpack' import * as webworkify from 'webworkify-webpack'
import compilerInput from './compiler-input' import compilerInput, { compilerInputForConfigFile } from './compiler-input'
import EventManager from '../lib/eventManager' import EventManager from '../lib/eventManager'
import txHelper from './helper' import txHelper from './helper'
import { import {
@ -31,6 +31,8 @@ export class Compiler {
language: 'Solidity', language: 'Solidity',
compilationStartTime: null, compilationStartTime: null,
target: null, target: null,
useFileConfiguration: false,
configFileContent: '',
lastCompilationResult: { lastCompilationResult: {
data: null, data: null,
source: null source: null
@ -111,11 +113,17 @@ export class Compiler {
return { error: 'Deferred import' } return { error: 'Deferred import' }
} }
let result: CompilationResult = {} let result: CompilationResult = {}
let input let input = ""
try { try {
if (source && source.sources) { if (source && source.sources) {
const { optimize, runs, evmVersion, language } = this.state const { optimize, runs, evmVersion, language, useFileConfiguration, configFileContent } = this.state
input = compilerInput(source.sources, { optimize, runs, evmVersion, language })
if (useFileConfiguration) {
input = compilerInputForConfigFile(source.sources, JSON.parse(configFileContent))
} else {
input = compilerInput(source.sources, { optimize, runs, evmVersion, language })
}
result = JSON.parse(compiler.compile(input, { import: missingInputsCallback })) result = JSON.parse(compiler.compile(input, { import: missingInputsCallback }))
} }
} catch (exception) { } catch (exception) {
@ -183,11 +191,17 @@ export class Compiler {
return { error: 'Deferred import' } return { error: 'Deferred import' }
} }
let result: CompilationResult = {} let result: CompilationResult = {}
let input: string let input = ""
try { try {
if (source && source.sources) { if (source && source.sources) {
const { optimize, runs, evmVersion, language } = this.state const { optimize, runs, evmVersion, language, useFileConfiguration, configFileContent } = this.state
input = compilerInput(source.sources, { optimize, runs, evmVersion, language })
if (useFileConfiguration) {
input = compilerInputForConfigFile(source.sources, JSON.parse(configFileContent))
} else {
input = compilerInput(source.sources, { optimize, runs, evmVersion, language })
}
result = JSON.parse(remoteCompiler.compile(input, { import: missingInputsCallback })) result = JSON.parse(remoteCompiler.compile(input, { import: missingInputsCallback }))
} }
} catch (exception) { } catch (exception) {
@ -289,12 +303,26 @@ export class Compiler {
this.state.compileJSON = (source: SourceWithTarget) => { this.state.compileJSON = (source: SourceWithTarget) => {
if (source && source.sources) { if (source && source.sources) {
const { optimize, runs, evmVersion, language } = this.state const { optimize, runs, evmVersion, language, useFileConfiguration, configFileContent } = this.state
jobs.push({ sources: source }) jobs.push({ sources: source })
let input = ""
try {
if (useFileConfiguration) {
input = compilerInputForConfigFile(source.sources, JSON.parse(configFileContent))
} else {
input = compilerInput(source.sources, { optimize, runs, evmVersion, language })
}
} catch (exception) {
this.onCompilationFinished({ error: { formattedMessage: exception.message } }, [], source, "", this.state.currentVersion)
return
}
this.state.worker.postMessage({ this.state.worker.postMessage({
cmd: 'compile', cmd: 'compile',
job: jobs.length - 1, job: jobs.length - 1,
input: compilerInput(source.sources, { optimize, runs, evmVersion, language }) input: input
}) })
} }
} }

@ -164,6 +164,8 @@ export interface CompilerState {
language: Language, language: Language,
compilationStartTime: number| null, compilationStartTime: number| null,
target: string | null, target: string | null,
useFileConfiguration: boolean,
configFileContent: string,
lastCompilationResult: { lastCompilationResult: {
data: CompilationResult | null, data: CompilationResult | null,
source: SourceWithTarget | null | undefined source: SourceWithTarget | null | undefined

@ -12,11 +12,11 @@ To know more about Remix IDE `Solidity Unit Testing Plugin`, visit [Remix IDE of
### Installation ### Installation
* As a dev dependency: * As a dev dependency:
`npm install --save-dev @remix-project/remix-tests` `yarn add --dev @remix-project/remix-tests`
* As a global NPM module to use as CLI: * As a global NPM module to use as CLI:
`npm -g install @remix-project/remix-tests` `yarn global add @remix-project/remix-tests`
To confirm installation, run: To confirm installation, run:
``` ```

@ -1,6 +1,6 @@
{ {
"name": "@remix-project/remix-tests", "name": "@remix-project/remix-tests",
"version": "0.2.13", "version": "0.2.14",
"description": "Tool to test Solidity smart contracts", "description": "Tool to test Solidity smart contracts",
"main": "src/index.js", "main": "src/index.js",
"types": "./src/index.d.ts", "types": "./src/index.d.ts",
@ -39,9 +39,9 @@
"@ethereumjs/common": "^2.5.0", "@ethereumjs/common": "^2.5.0",
"@ethereumjs/tx": "^3.3.2", "@ethereumjs/tx": "^3.3.2",
"@ethereumjs/vm": "^5.5.3", "@ethereumjs/vm": "^5.5.3",
"@remix-project/remix-lib": "^0.5.13", "@remix-project/remix-lib": "^0.5.14",
"@remix-project/remix-simulator": "^0.2.13", "@remix-project/remix-simulator": "^0.2.14",
"@remix-project/remix-solidity": "^0.4.13", "@remix-project/remix-solidity": "^0.5.0",
"ansi-gray": "^0.1.1", "ansi-gray": "^0.1.1",
"async": "^2.6.0", "async": "^2.6.0",
"axios": ">=0.21.1", "axios": ">=0.21.1",
@ -79,5 +79,5 @@
"typescript": "^3.3.1" "typescript": "^3.3.1"
}, },
"typings": "src/index.d.ts", "typings": "src/index.d.ts",
"gitHead": "2be5108c51f2ac9226e4598c512ec6d3c63f5c78" "gitHead": "3f311aaf25f5796f70711006bb783ee4087ffc71"
} }

@ -11,7 +11,7 @@ describe('testRunner: remix-tests CLI', () => {
const dirContent = result.stdout.toString() const dirContent = result.stdout.toString()
// Install dependencies if 'node_modules' is not already present // Install dependencies if 'node_modules' is not already present
if(!dirContent.includes('node_modules')) { if(!dirContent.includes('node_modules')) {
execSync('npm install', { cwd: resolve(__dirname + '/../../../dist/libs/remix-tests') }) execSync('yarn install', { cwd: resolve(__dirname + '/../../../dist/libs/remix-tests') })
} }
} }

File diff suppressed because it is too large Load Diff

@ -75,6 +75,25 @@ export interface EditorUIProps {
export const EditorUI = (props: EditorUIProps) => { export const EditorUI = (props: EditorUIProps) => {
const [, setCurrentBreakpoints] = useState({}) const [, setCurrentBreakpoints] = useState({})
const defaultEditorValue = `
\t\t\t\t\t\t\t ____ _____ __ __ ___ __ __ ___ ____ _____
\t\t\t\t\t\t\t| _ \\ | ____| | \\/ | |_ _| \\ \\/ / |_ _| | _ \\ | ____|
\t\t\t\t\t\t\t| |_) | | _| | |\\/| | | | \\ / | | | | | | | _|
\t\t\t\t\t\t\t| _ < | |___ | | | | | | / \\ | | | |_| | | |___
\t\t\t\t\t\t\t|_| \\_\\ |_____| |_| |_| |___| /_/\\_\\ |___| |____/ |_____|\n\n
\t\t\t\t\t\t\tKeyboard Shortcuts:\n
\t\t\t\t\t\t\t\tCTRL + S: Compile the current contract\n
\t\t\t\t\t\t\t\tCtrl + Shift + F : Open the File Explorer\n
\t\t\t\t\t\t\t\tCtrl + Shift + A : Open the Plugin Manager\n
\t\t\t\t\t\t\t\tCTRL + SHIFT + S: Compile the current contract & Run an associated script\n\n
\t\t\t\t\t\t\tImportant Links:\n
\t\t\t\t\t\t\t\tOfficial website about the Remix Project: https://remix-project.org/\n
\t\t\t\t\t\t\t\tOfficial documentation: https://remix-ide.readthedocs.io/en/latest/\n
\t\t\t\t\t\t\t\tGithub: https://github.com/ethereum/remix-project\n
\t\t\t\t\t\t\t\tGitter: https://gitter.im/ethereum/remix\n
\t\t\t\t\t\t\t\tMedium: https://medium.com/remix-ide\n
\t\t\t\t\t\t\t\tTwitter: https://twitter.com/ethereumremix\n
`
const editorRef = useRef(null) const editorRef = useRef(null)
const monacoRef = useRef(null) const monacoRef = useRef(null)
const currentFileRef = useRef('') const currentFileRef = useRef('')
@ -422,7 +441,8 @@ export const EditorUI = (props: EditorUIProps) => {
language={editorModelsState[props.currentFile] ? editorModelsState[props.currentFile].language : 'text'} language={editorModelsState[props.currentFile] ? editorModelsState[props.currentFile].language : 'text'}
onMount={handleEditorDidMount} onMount={handleEditorDidMount}
beforeMount={handleEditorWillMount} beforeMount={handleEditorWillMount}
options={{ glyphMargin: true }} options={{ glyphMargin: true, readOnly: true}}
defaultValue={defaultEditorValue}
/> />
<div className="contextview"> <div className="contextview">
<RemixUiEditorContextView <RemixUiEditorContextView

@ -1,132 +1,180 @@
import { remixTypes } from './remix-plugin-types'
export const loadTypes = async (monaco) => { export const loadTypes = async (monaco) => {
// ethers.js // ethers.js
// @ts-ignore // @ts-ignore
const ethersAbi = await import('raw-loader!@ethersproject/abi/lib/index.d.ts') const ethersAbi = await import('raw-loader!@ethersproject/abi/lib/index.d.ts')
monaco.languages.typescript.typescriptDefaults.addExtraLib(ethersAbi.default, `file:///node_modules/@types/@ethersproject/abi/index.d.ts`) ethersAbi.default = ethersAbi.default.replace(/@ethersproject\//g, '@ethersproject_')
monaco.languages.typescript.typescriptDefaults.addExtraLib(ethersAbi.default, `file:///node_modules/@types/@ethersproject_abi/index.d.ts`)
// @ts-ignore // @ts-ignore
const ethersAbstract = await import('raw-loader!@ethersproject/abstract-provider/lib/index.d.ts') const ethersAbstract = await import('raw-loader!@ethersproject/abstract-provider/lib/index.d.ts')
monaco.languages.typescript.typescriptDefaults.addExtraLib(ethersAbstract.default, `file:///node_modules/@types/@ethersproject/abstract-provider/index.d.ts`) ethersAbstract.default = ethersAbstract.default.replace(/@ethersproject\//g, '@ethersproject_')
monaco.languages.typescript.typescriptDefaults.addExtraLib(ethersAbstract.default, `file:///node_modules/@types/@ethersproject_abstract-provider/index.d.ts`)
// @ts-ignore // @ts-ignore
const ethersSigner = await import('raw-loader!@ethersproject/abstract-signer/lib/index.d.ts') const ethersSigner = await import('raw-loader!@ethersproject/abstract-signer/lib/index.d.ts')
monaco.languages.typescript.typescriptDefaults.addExtraLib(ethersSigner.default, `file:///node_modules/@types/@ethersproject/abstract-signer/index.d.ts`) ethersSigner.default = ethersSigner.default.replace(/@ethersproject\//g, '@ethersproject_')
monaco.languages.typescript.typescriptDefaults.addExtraLib(ethersSigner.default, `file:///node_modules/@types/@ethersproject_abstract-signer/index.d.ts`)
// @ts-ignore // @ts-ignore
const ethersAddress = await import('raw-loader!@ethersproject/address/lib/index.d.ts') const ethersAddress = await import('raw-loader!@ethersproject/address/lib/index.d.ts')
monaco.languages.typescript.typescriptDefaults.addExtraLib(ethersAddress.default, `file:///node_modules/@types/@ethersproject/address/index.d.ts`) ethersAddress.default = ethersAddress.default.replace(/@ethersproject\//g, '@ethersproject_')
monaco.languages.typescript.typescriptDefaults.addExtraLib(ethersAddress.default, `file:///node_modules/@types/@ethersproject_address/index.d.ts`)
// @ts-ignore // @ts-ignore
const ethersBase64 = await import('raw-loader!@ethersproject/base64/lib/index.d.ts') const ethersBase64 = await import('raw-loader!@ethersproject/base64/lib/index.d.ts')
monaco.languages.typescript.typescriptDefaults.addExtraLib(ethersBase64.default, `file:///node_modules/@types/@ethersproject/base64/index.d.ts`) ethersBase64.default = ethersBase64.default.replace(/@ethersproject\//g, '@ethersproject_')
monaco.languages.typescript.typescriptDefaults.addExtraLib(ethersBase64.default, `file:///node_modules/@types/@ethersproject_base64/index.d.ts`)
// @ts-ignore // @ts-ignore
const ethersBasex = await import('raw-loader!@ethersproject/basex/lib/index.d.ts') const ethersBasex = await import('raw-loader!@ethersproject/basex/lib/index.d.ts')
monaco.languages.typescript.typescriptDefaults.addExtraLib(ethersBasex.default, `file:///node_modules/@types/@ethersproject/basex/index.d.ts`) ethersBasex.default = ethersBasex.default.replace(/@ethersproject\//g, '@ethersproject_')
monaco.languages.typescript.typescriptDefaults.addExtraLib(ethersBasex.default, `file:///node_modules/@types/@ethersproject_basex/index.d.ts`)
// @ts-ignore // @ts-ignore
const ethersBignumber = await import('raw-loader!@ethersproject/bignumber/lib/index.d.ts') const ethersBignumber = await import('raw-loader!@ethersproject/bignumber/lib/index.d.ts')
monaco.languages.typescript.typescriptDefaults.addExtraLib(ethersBignumber.default, `file:///node_modules/@types/@ethersproject/bignumber/index.d.ts`) ethersBignumber.default = ethersBignumber.default.replace(/@ethersproject\//g, '@ethersproject_')
monaco.languages.typescript.typescriptDefaults.addExtraLib(ethersBignumber.default, `file:///node_modules/@types/@ethersproject_bignumber/index.d.ts`)
// @ts-ignore // @ts-ignore
const ethersBytes = await import('raw-loader!@ethersproject/bytes/lib/index.d.ts') const ethersBytes = await import('raw-loader!@ethersproject/bytes/lib/index.d.ts')
monaco.languages.typescript.typescriptDefaults.addExtraLib(ethersBytes.default, `file:///node_modules/@types/@ethersproject/bytes/index.d.ts`) ethersBytes.default = ethersBytes.default.replace(/@ethersproject\//g, '@ethersproject_')
monaco.languages.typescript.typescriptDefaults.addExtraLib(ethersBytes.default, `file:///node_modules/@types/@ethersproject_bytes/index.d.ts`)
// @ts-ignore // @ts-ignore
const ethersConstants = await import('raw-loader!@ethersproject/constants/lib/index.d.ts') const ethersConstants = await import('raw-loader!@ethersproject/constants/lib/index.d.ts')
monaco.languages.typescript.typescriptDefaults.addExtraLib(ethersConstants.default, `file:///node_modules/@types/@ethersproject/constants/index.d.ts`) ethersConstants.default = ethersConstants.default.replace(/@ethersproject\//g, '@ethersproject_')
monaco.languages.typescript.typescriptDefaults.addExtraLib(ethersConstants.default, `file:///node_modules/@types/@ethersproject_constants/index.d.ts`)
// @ts-ignore // @ts-ignore
const ethersContracts = await import('raw-loader!@ethersproject/contracts/lib/index.d.ts') const ethersContracts = await import('raw-loader!@ethersproject/contracts/lib/index.d.ts')
monaco.languages.typescript.typescriptDefaults.addExtraLib(ethersContracts.default, `file:///node_modules/@types/@ethersproject/contracts/index.d.ts`) ethersContracts.default = ethersContracts.default.replace(/@ethersproject\//g, '@ethersproject_')
monaco.languages.typescript.typescriptDefaults.addExtraLib(ethersContracts.default, `file:///node_modules/@types/@ethersproject_contracts/index.d.ts`)
// @ts-ignore // @ts-ignore
const ethersHash = await import('raw-loader!@ethersproject/hash/lib/index.d.ts') const ethersHash = await import('raw-loader!@ethersproject/hash/lib/index.d.ts')
monaco.languages.typescript.typescriptDefaults.addExtraLib(ethersHash.default, `file:///node_modules/@types/@ethersproject/lib/index.d.ts`) ethersHash.default = ethersHash.default.replace(/@ethersproject\//g, '@ethersproject_')
monaco.languages.typescript.typescriptDefaults.addExtraLib(ethersHash.default, `file:///node_modules/@types/@ethersproject_hash/index.d.ts`)
// @ts-ignore // @ts-ignore
const ethersHdnode = await import('raw-loader!@ethersproject/hdnode/lib/index.d.ts') const ethersHdnode = await import('raw-loader!@ethersproject/hdnode/lib/index.d.ts')
monaco.languages.typescript.typescriptDefaults.addExtraLib(ethersHdnode.default, `file:///node_modules/@types/@ethersproject/hdnode/index.d.ts`) ethersHdnode.default = ethersHdnode.default.replace(/@ethersproject\//g, '@ethersproject_')
monaco.languages.typescript.typescriptDefaults.addExtraLib(ethersHdnode.default, `file:///node_modules/@types/@ethersproject_hdnode/index.d.ts`)
// @ts-ignore // @ts-ignore
const ethersJsonWallets = await import('raw-loader!@ethersproject/json-wallets/lib/index.d.ts') const ethersJsonWallets = await import('raw-loader!@ethersproject/json-wallets/lib/index.d.ts')
monaco.languages.typescript.typescriptDefaults.addExtraLib(ethersJsonWallets.default, `file:///node_modules/@types/@ethersproject/json-wallets/index.d.ts`) ethersJsonWallets.default = ethersJsonWallets.default.replace(/@ethersproject\//g, '@ethersproject_')
monaco.languages.typescript.typescriptDefaults.addExtraLib(ethersJsonWallets.default, `file:///node_modules/@types/@ethersproject_json-wallets/index.d.ts`)
// @ts-ignore // @ts-ignore
const ethersKeccak256 = await import('raw-loader!@ethersproject/keccak256/lib/index.d.ts') const ethersKeccak256 = await import('raw-loader!@ethersproject/keccak256/lib/index.d.ts')
monaco.languages.typescript.typescriptDefaults.addExtraLib(ethersKeccak256.default, `file:///node_modules/@types/@ethersproject/keccak256/index.d.ts`) ethersKeccak256.default = ethersKeccak256.default.replace(/@ethersproject\//g, '@ethersproject_')
monaco.languages.typescript.typescriptDefaults.addExtraLib(ethersKeccak256.default, `file:///node_modules/@types/@ethersproject_keccak256/index.d.ts`)
// @ts-ignore // @ts-ignore
const ethersLogger = await import('raw-loader!@ethersproject/logger/lib/index.d.ts') const ethersLogger = await import('raw-loader!@ethersproject/logger/lib/index.d.ts')
monaco.languages.typescript.typescriptDefaults.addExtraLib(ethersLogger.default, `file:///node_modules/@types/@ethersproject/logger/index.d.ts`) ethersLogger.default = ethersLogger.default.replace(/@ethersproject\//g, '@ethersproject_')
monaco.languages.typescript.typescriptDefaults.addExtraLib(ethersLogger.default, `file:///node_modules/@types/@ethersproject_logger/index.d.ts`)
// @ts-ignore // @ts-ignore
const ethersNetworks = await import('raw-loader!@ethersproject/networks/lib/index.d.ts') const ethersNetworks = await import('raw-loader!@ethersproject/networks/lib/index.d.ts')
monaco.languages.typescript.typescriptDefaults.addExtraLib(ethersNetworks.default, `file:///node_modules/@types/@ethersproject/networks/index.d.ts`) ethersNetworks.default = ethersNetworks.default.replace(/@ethersproject\//g, '@ethersproject_')
monaco.languages.typescript.typescriptDefaults.addExtraLib(ethersNetworks.default, `file:///node_modules/@types/@ethersproject_networks/index.d.ts`)
// @ts-ignore // @ts-ignore
const ethersPbkdf2 = await import('raw-loader!@ethersproject/pbkdf2/lib/index.d.ts') const ethersPbkdf2 = await import('raw-loader!@ethersproject/pbkdf2/lib/index.d.ts')
monaco.languages.typescript.typescriptDefaults.addExtraLib(ethersPbkdf2.default, `file:///node_modules/@types/@ethersproject/pbkdf2/index.d.ts`) ethersPbkdf2.default = ethersPbkdf2.default.replace(/@ethersproject\//g, '@ethersproject_')
monaco.languages.typescript.typescriptDefaults.addExtraLib(ethersPbkdf2.default, `file:///node_modules/@types/@ethersproject_pbkdf2/index.d.ts`)
// @ts-ignore // @ts-ignore
const ethersProperties = await import('raw-loader!@ethersproject/properties/lib/index.d.ts') const ethersProperties = await import('raw-loader!@ethersproject/properties/lib/index.d.ts')
monaco.languages.typescript.typescriptDefaults.addExtraLib(ethersProperties.default, `file:///node_modules/@types/@ethersproject/properties/index.d.ts`) ethersProperties.default = ethersProperties.default.replace(/@ethersproject\//g, '@ethersproject_')
monaco.languages.typescript.typescriptDefaults.addExtraLib(ethersProperties.default, `file:///node_modules/@types/@ethersproject_properties/index.d.ts`)
// @ts-ignore // @ts-ignore
const ethersProviders = await import('raw-loader!@ethersproject/providers/lib/index.d.ts') const ethersProviders = await import('raw-loader!@ethersproject/providers/lib/index.d.ts')
monaco.languages.typescript.typescriptDefaults.addExtraLib(ethersProviders.default, `file:///node_modules/@types/@ethersproject/providers/index.d.ts`) ethersProviders.default = ethersProviders.default.replace(/@ethersproject\//g, '@ethersproject_')
monaco.languages.typescript.typescriptDefaults.addExtraLib(ethersProviders.default, `file:///node_modules/@types/@ethersproject_providers/index.d.ts`)
// @ts-ignore // @ts-ignore
const ethersRandom = await import('raw-loader!@ethersproject/random/lib/index.d.ts') const ethersRandom = await import('raw-loader!@ethersproject/random/lib/index.d.ts')
monaco.languages.typescript.typescriptDefaults.addExtraLib(ethersRandom.default, `file:///node_modules/@types/@ethersproject/random/index.d.ts`) ethersRandom.default = ethersRandom.default.replace(/@ethersproject\//g, '@ethersproject_')
monaco.languages.typescript.typescriptDefaults.addExtraLib(ethersRandom.default, `file:///node_modules/@types/@ethersproject_random/index.d.ts`)
// @ts-ignore // @ts-ignore
const ethersRlp = await import('raw-loader!@ethersproject/rlp/lib/index.d.ts') const ethersRlp = await import('raw-loader!@ethersproject/rlp/lib/index.d.ts')
monaco.languages.typescript.typescriptDefaults.addExtraLib(ethersRlp.default, `file:///node_modules/@types/@ethersproject/rlp/index.d.ts`) ethersRlp.default = ethersRlp.default.replace(/@ethersproject\//g, '@ethersproject_')
monaco.languages.typescript.typescriptDefaults.addExtraLib(ethersRlp.default, `file:///node_modules/@types/@ethersproject_rlp/index.d.ts`)
// @ts-ignore // @ts-ignore
const ethersSha2 = await import('raw-loader!@ethersproject/sha2/lib/index.d.ts') const ethersSha2 = await import('raw-loader!@ethersproject/sha2/lib/index.d.ts')
monaco.languages.typescript.typescriptDefaults.addExtraLib(ethersSha2.default, `file:///node_modules/@types/@ethersproject/sha2/index.d.ts`) ethersSha2.default = ethersSha2.default.replace(/@ethersproject\//g, '@ethersproject_')
monaco.languages.typescript.typescriptDefaults.addExtraLib(ethersSha2.default, `file:///node_modules/@types/@ethersproject_sha2/index.d.ts`)
// @ts-ignore // @ts-ignore
const ethersSingningkey = await import('raw-loader!@ethersproject/signing-key/lib/index.d.ts') const ethersSingningkey = await import('raw-loader!@ethersproject/signing-key/lib/index.d.ts')
monaco.languages.typescript.typescriptDefaults.addExtraLib(ethersSingningkey.default, `file:///node_modules/@types/@ethersproject/signing-key/index.d.ts`) ethersSingningkey.default = ethersSingningkey.default.replace(/@ethersproject\//g, '@ethersproject_')
monaco.languages.typescript.typescriptDefaults.addExtraLib(ethersSingningkey.default, `file:///node_modules/@types/@ethersproject_signing-key/index.d.ts`)
// @ts-ignore // @ts-ignore
const ethersSolidity = await import('raw-loader!@ethersproject/solidity/lib/index.d.ts') const ethersSolidity = await import('raw-loader!@ethersproject/solidity/lib/index.d.ts')
monaco.languages.typescript.typescriptDefaults.addExtraLib(ethersSolidity.default, `file:///node_modules/@types/@ethersproject/solidity/index.d.ts`) ethersSolidity.default = ethersSolidity.default.replace(/@ethersproject\//g, '@ethersproject_')
monaco.languages.typescript.typescriptDefaults.addExtraLib(ethersSolidity.default, `file:///node_modules/@types/@ethersproject_solidity/index.d.ts`)
// @ts-ignore // @ts-ignore
const ethersStrings = await import('raw-loader!@ethersproject/strings/lib/index.d.ts') const ethersStrings = await import('raw-loader!@ethersproject/strings/lib/index.d.ts')
monaco.languages.typescript.typescriptDefaults.addExtraLib(ethersStrings.default, `file:///node_modules/@types/@ethersproject/strings/index.d.ts`) ethersStrings.default = ethersStrings.default.replace(/@ethersproject\//g, '@ethersproject_')
monaco.languages.typescript.typescriptDefaults.addExtraLib(ethersStrings.default, `file:///node_modules/@types/@ethersproject_strings/index.d.ts`)
// @ts-ignore // @ts-ignore
const ethersTransactions = await import('raw-loader!@ethersproject/transactions/lib/index.d.ts') const ethersTransactions = await import('raw-loader!@ethersproject/transactions/lib/index.d.ts')
monaco.languages.typescript.typescriptDefaults.addExtraLib(ethersTransactions.default, `file:///node_modules/@types/@ethersproject/transactions/index.d.ts`) ethersTransactions.default = ethersTransactions.default.replace(/@ethersproject\//g, '@ethersproject_')
monaco.languages.typescript.typescriptDefaults.addExtraLib(ethersTransactions.default, `file:///node_modules/@types/@ethersproject_transactions/index.d.ts`)
// @ts-ignore // @ts-ignore
const ethersUnits = await import('raw-loader!@ethersproject/units/lib/index.d.ts') const ethersUnits = await import('raw-loader!@ethersproject/units/lib/index.d.ts')
monaco.languages.typescript.typescriptDefaults.addExtraLib(ethersUnits.default, `file:///node_modules/@types/@ethersproject/units/index.d.ts`) ethersUnits.default = ethersUnits.default.replace(/@ethersproject\//g, '@ethersproject_')
monaco.languages.typescript.typescriptDefaults.addExtraLib(ethersUnits.default, `file:///node_modules/@types/@ethersproject_units/index.d.ts`)
// @ts-ignore // @ts-ignore
const ethersWallet = await import('raw-loader!@ethersproject/wallet/lib/index.d.ts') const ethersWallet = await import('raw-loader!@ethersproject/wallet/lib/index.d.ts')
monaco.languages.typescript.typescriptDefaults.addExtraLib(ethersWallet.default, `file:///node_modules/@types/@ethersproject/wallet/index.d.ts`) ethersWallet.default = ethersWallet.default.replace(/@ethersproject\//g, '@ethersproject_')
monaco.languages.typescript.typescriptDefaults.addExtraLib(ethersWallet.default, `file:///node_modules/@types/@ethersproject_wallet/index.d.ts`)
// @ts-ignore // @ts-ignore
const ethersWeb = await import('raw-loader!@ethersproject/web/lib/index.d.ts') const ethersWeb = await import('raw-loader!@ethersproject/web/lib/index.d.ts')
monaco.languages.typescript.typescriptDefaults.addExtraLib(ethersWeb.default, `file:///node_modules/@types/@ethersproject/web/index.d.ts`) ethersWeb.default = ethersWeb.default.replace(/@ethersproject\//g, '@ethersproject_')
monaco.languages.typescript.typescriptDefaults.addExtraLib(ethersWeb.default, `file:///node_modules/@types/@ethersproject_web/index.d.ts`)
// @ts-ignore // @ts-ignore
const ethersWordlists = await import('raw-loader!@ethersproject/wordlists/lib/index.d.ts') const ethersWordlists = await import('raw-loader!@ethersproject/wordlists/lib/index.d.ts')
monaco.languages.typescript.typescriptDefaults.addExtraLib(ethersWordlists.default, `file:///node_modules/@types/@ethersproject/wordlists/index.d.ts`) ethersWordlists.default = ethersWordlists.default.replace(/@ethersproject\//g, '@ethersproject_')
monaco.languages.typescript.typescriptDefaults.addExtraLib(ethersWordlists.default, `file:///node_modules/@types/@ethersproject_wordlists/index.d.ts`)
// @ts-ignore
const versionEthers = await import('raw-loader!ethers/lib/_version.d.ts')
versionEthers.default = versionEthers.default.replace(/@ethersproject\//g, '@ethersproject_')
monaco.languages.typescript.typescriptDefaults.addExtraLib(versionEthers.default, `file:///node_modules/@types/_version-ethers-lib/index.d.ts`)
// @ts-ignore
const utilEthers = await import('raw-loader!ethers/lib/utils.d.ts')
utilEthers.default = utilEthers.default.replace(/@ethersproject\//g, '@ethersproject_')
monaco.languages.typescript.typescriptDefaults.addExtraLib(utilEthers.default, `file:///node_modules/@types/utils-ethers-lib/index.d.ts`)
// @ts-ignore // @ts-ignore
const ethers = await import('raw-loader!ethers/lib/ethers.d.ts') const ethers = await import('raw-loader!ethers/lib/ethers.d.ts')
monaco.languages.typescript.typescriptDefaults.addExtraLib(ethers.default, `file:///node_modules/@types/ethers/ethers.d.ts`) ethers.default = ethers.default.replace(/.\/utils/g, 'utils-ethers-lib')
ethers.default = ethers.default.replace(/.\/_version/g, '_version-ethers-lib')
ethers.default = ethers.default.replace(/.\/ethers/g, 'ethers-lib')
ethers.default = ethers.default.replace(/@ethersproject\//g, '@ethersproject_')
monaco.languages.typescript.typescriptDefaults.addExtraLib(ethers.default, `file:///node_modules/@types/ethers-lib/index.d.ts`)
// @ts-ignore // @ts-ignore
const indexEthers = await import('raw-loader!ethers/lib/index.d.ts') const indexEthers = await import('raw-loader!ethers/lib/index.d.ts')
indexEthers.default = indexEthers.default.replace(/.\/ethers/g, 'ethers-lib')
indexEthers.default = indexEthers.default.replace(/@ethersproject\//g, '@ethersproject_')
monaco.languages.typescript.typescriptDefaults.addExtraLib(indexEthers.default, `file:///node_modules/@types/ethers/index.d.ts`) monaco.languages.typescript.typescriptDefaults.addExtraLib(indexEthers.default, `file:///node_modules/@types/ethers/index.d.ts`)
// Web3 // Web3
@ -151,6 +199,10 @@ export const loadTypes = async (monaco) => {
const indexWeb3Personal = await import('raw-loader!web3-eth-personal/types/index.d.ts') const indexWeb3Personal = await import('raw-loader!web3-eth-personal/types/index.d.ts')
monaco.languages.typescript.typescriptDefaults.addExtraLib(indexWeb3Personal.default, `file:///node_modules/@types/web3-eth-personal/index.d.ts`) monaco.languages.typescript.typescriptDefaults.addExtraLib(indexWeb3Personal.default, `file:///node_modules/@types/web3-eth-personal/index.d.ts`)
// @ts-ignore
const indexWeb3Contract = await import('raw-loader!web3-eth-contract/types/index.d.ts')
monaco.languages.typescript.typescriptDefaults.addExtraLib(indexWeb3Contract.default, `file:///node_modules/@types/web3-eth-contract/index.d.ts`)
// @ts-ignore // @ts-ignore
const indexWeb3Net = await import('raw-loader!web3-net/types/index.d.ts') const indexWeb3Net = await import('raw-loader!web3-net/types/index.d.ts')
monaco.languages.typescript.typescriptDefaults.addExtraLib(indexWeb3Net.default, `file:///node_modules/@types/web3-net/index.d.ts`) monaco.languages.typescript.typescriptDefaults.addExtraLib(indexWeb3Net.default, `file:///node_modules/@types/web3-net/index.d.ts`)
@ -162,20 +214,13 @@ export const loadTypes = async (monaco) => {
// @ts-ignore // @ts-ignore
const indexWeb3Util = await import('raw-loader!web3-utils/types/index.d.ts') const indexWeb3Util = await import('raw-loader!web3-utils/types/index.d.ts')
monaco.languages.typescript.typescriptDefaults.addExtraLib(indexWeb3Util.default, `file:///node_modules/@types/web3-utils/index.d.ts`) monaco.languages.typescript.typescriptDefaults.addExtraLib(indexWeb3Util.default, `file:///node_modules/@types/web3-utils/index.d.ts`)
/*
// remix // remix
const indexRemixApi = remixTypes + `\n
// @ts-ignore declare global {
const indexRemixApi = await import('./remix-plugin-types') const remix: PluginClient;
monaco.languages.typescript.typescriptDefaults.addExtraLib(indexRemixApi.types) const web3Provider;
*/ }
`
monaco.languages.typescript.typescriptDefaults.addExtraLib(` monaco.languages.typescript.typescriptDefaults.addExtraLib(indexRemixApi)
import { PluginClient } from 'packages/plugin/core/src/lib/client'
declare const remix: PluginClient
`)
console.log('loaded monaco types') console.log('loaded monaco types')
} }

@ -78,3 +78,9 @@ export const storageFullMessage = () => (
</span> </span>
</div> </div>
) )
export const recursivePasteToastMsg = () => (
<div>
File(s) to paste is an ancestor of the destination folder
</div>
)

@ -56,15 +56,15 @@ export const joinPath = (...paths) => {
export const getPathIcon = (path: string) => { export const getPathIcon = (path: string) => {
return path.endsWith('.txt') return path.endsWith('.txt')
? 'far fa-file-alt' : path.endsWith('.md') ? 'far fa-file-alt' : path.endsWith('.md')
? 'far fa-file-alt' : path.endsWith('.sol') ? 'fab fa-markdown' : path.endsWith('.sol')
? 'fak fa-solidity-mono' : path.endsWith('.js') ? 'fak fa-solidity-mono' : path.endsWith('.js')
? 'fab fa-js' : path.endsWith('.json') ? 'fab fa-js' : path.endsWith('.json')
? 'fas fa-brackets-curly' : path.endsWith('.vy') ? 'small fas fa-brackets-curly' : path.endsWith('.vy')
? 'fak fa-vyper-mono' : path.endsWith('.lex') ? 'small fak fa-vyper2' : path.endsWith('.lex')
? 'fak fa-lexon' : path.endsWith('ts') ? 'fak fa-lexon' : path.endsWith('ts')
? 'fad fa-brackets-curly' : path.endsWith('.contract') ? 'small fak fa-ts-logo' : path.endsWith('.tsc')
? 'fab fa-ethereum' : path.endsWith('.cairo') ? 'fad fa-brackets-curly' : path.endsWith('.cairo')
? 'fab fa-ethereum' : 'far fa-file' // TODO: add cairo icon ? 'small fak fa-cairo' : 'far fa-file'
} }
export const isNumeric = (value) => { export const isNumeric = (value) => {

@ -0,0 +1,12 @@
.RSSFeed-item img {
width: 100%;
}
.RSSFeed-item .truncate {
max-height: 500px;
overflow: hidden;
}
.RSSFeed-item .more-button {
}

@ -0,0 +1,42 @@
import React, { useState, useEffect } from "react";
import Parser from "rss-parser";
import './rssFeed.css';
interface RSSFeedProps {
feedUrl: string,
maxItems: number,
}
export function RSSFeed({ feedUrl, maxItems }: RSSFeedProps) {
const [feed, setFeed] = useState(null);
useEffect(() => {
const fetchData = async () => {
const parser = new Parser()
const feed = await parser.parseURL(feedUrl);
for (const item of feed.items) {
item.content = item['content:encoded']
item.date = new Date(item.pubDate).toLocaleDateString('en-US', {
month: 'short',
day: 'numeric'
})
}
setFeed(feed);
};
fetchData();
}, [feedUrl]);
return (<>
{feed && feed.items.slice(0, maxItems).map((item: any, index: any) => (
<div className='RSSFeed-item' key={index}>
<a target="_blank" href={item.link}><h3>{item.title}</h3></a>
<p>Author: {item.creator}</p>
<h4>{item.date}</h4>
<div className="truncate" dangerouslySetInnerHTML={{ __html: item.content }} />
<a className="more-button btn mb-3" target="_blank" href={item.link}>READ MORE</a>
<hr></hr>
</div>
))}
</>)
}

@ -4,8 +4,8 @@ import './remix-ui-home-tab.css'
import { ModalDialog } from '@remix-ui/modal-dialog' // eslint-disable-line import { ModalDialog } from '@remix-ui/modal-dialog' // eslint-disable-line
import { Toaster } from '@remix-ui/toaster' // eslint-disable-line import { Toaster } from '@remix-ui/toaster' // eslint-disable-line
import PluginButton from './components/pluginButton' // eslint-disable-line import PluginButton from './components/pluginButton' // eslint-disable-line
import { QueryParams } from '@remix-project/remix-lib'
import { ThemeContext, themes } from './themeContext' import { ThemeContext, themes } from './themeContext'
import { RSSFeed } from './components/rssFeed'
declare global { declare global {
interface Window { interface Window {
_paq: any _paq: any
@ -54,13 +54,16 @@ export const RemixUiHomeTab = (props: RemixUiHomeTabProps) => {
contentImport.import( contentImport.import(
state.importSource, state.importSource,
(loadingMsg) => dispatch({ tooltip: loadingMsg }), (loadingMsg) => dispatch({ tooltip: loadingMsg }),
(error, content, cleanUrl, type, url) => { async (error, content, cleanUrl, type, url) => {
if (error) { if (error) {
toast(error.message || error) toast(error.message || error)
} else { } else {
try { try {
workspace.addExternal(type + '/' + cleanUrl, content, url) if (await workspace.exists(type + '/' + cleanUrl)) toast('File already exists in workspace')
plugin.call('menuicons', 'select', 'filePanel') else {
workspace.addExternal(type + '/' + cleanUrl, content, url)
plugin.call('menuicons', 'select', 'filePanel')
}
} catch (e) { } catch (e) {
toast(e.message) toast(e.message)
} }
@ -108,14 +111,8 @@ export const RemixUiHomeTab = (props: RemixUiHomeTabProps) => {
scriptTwitter.src = 'https://platform.twitter.com/widgets.js' scriptTwitter.src = 'https://platform.twitter.com/widgets.js'
scriptTwitter.async = true scriptTwitter.async = true
document.body.appendChild(scriptTwitter) document.body.appendChild(scriptTwitter)
// to retrieve medium publications
const scriptMedium = document.createElement('script')
scriptMedium.src = 'https://www.twilik.com/assets/retainable/rss-embed/retainable-rss-embed.js'
scriptMedium.async = true
document.body.appendChild(scriptMedium)
return () => { return () => {
document.body.removeChild(scriptTwitter) document.body.removeChild(scriptTwitter)
document.body.removeChild(scriptMedium)
} }
}, []) }, [])
@ -285,7 +282,7 @@ export const RemixUiHomeTab = (props: RemixUiHomeTabProps) => {
<p className="mt-3 mb-0"><label>LOAD FROM:</label></p> <p className="mt-3 mb-0"><label>LOAD FROM:</label></p>
<div className="btn-group"> <div className="btn-group">
<button className="btn mr-1 btn-secondary" data-id="landingPageImportFromGistButton" onClick={() => importFromGist()}>Gist</button> <button className="btn mr-1 btn-secondary" data-id="landingPageImportFromGistButton" onClick={() => importFromGist()}>Gist</button>
<button className="btn mx-1 btn-secondary" data-id="landingPageImportFromGitHubButton" onClick={() => showFullMessage('Github', 'github URL', ['https://github.com/0xcert/ethereum-erc721/src/contracts/tokens/nf-token-metadata.sol', 'https://github.com/OpenZeppelin/openzeppelin-solidity/blob/67bca857eedf99bf44a4b6a0fc5b5ed553135316/contracts/access/Roles.sol'])}>GitHub</button> <button className="btn mx-1 btn-secondary" data-id="landingPageImportFromGitHubButton" onClick={() => showFullMessage('GitHub', 'github URL', ['https://github.com/0xcert/ethereum-erc721/src/contracts/tokens/nf-token-metadata.sol', 'https://github.com/OpenZeppelin/openzeppelin-solidity/blob/67bca857eedf99bf44a4b6a0fc5b5ed553135316/contracts/access/Roles.sol'])}>GitHub</button>
<button className="btn mx-1 btn-secondary" onClick={() => showFullMessage('Ipfs', 'ipfs URL', ['ipfs://<ipfs-hash>'])}>Ipfs</button> <button className="btn mx-1 btn-secondary" onClick={() => showFullMessage('Ipfs', 'ipfs URL', ['ipfs://<ipfs-hash>'])}>Ipfs</button>
<button className="btn mx-1 btn-secondary" onClick={() => showFullMessage('Https', 'http/https raw content', ['https://raw.githubusercontent.com/OpenZeppelin/openzeppelin-contracts/master/contracts/token/ERC20/ERC20.sol'])}>https</button> <button className="btn mx-1 btn-secondary" onClick={() => showFullMessage('Https', 'http/https raw content', ['https://raw.githubusercontent.com/OpenZeppelin/openzeppelin-contracts/master/contracts/token/ERC20/ERC20.sol'])}>https</button>
</div> </div>
@ -341,17 +338,7 @@ export const RemixUiHomeTab = (props: RemixUiHomeTabProps) => {
> >
<div id="remixIDE_MediumBlock" className="p-2 mx-1 mt-3 mb-0 remixui_home_remixHomeMedia" style={ { maxHeight: maxHeight } }> <div id="remixIDE_MediumBlock" className="p-2 mx-1 mt-3 mb-0 remixui_home_remixHomeMedia" style={ { maxHeight: maxHeight } }>
<div id="medium-widget" className="px-3 remixui_home_media" hidden={state.showMediaPanel !== 'medium'} style={ { maxHeight: '10000px' } }> <div id="medium-widget" className="px-3 remixui_home_media" hidden={state.showMediaPanel !== 'medium'} style={ { maxHeight: '10000px' } }>
<div <RSSFeed feedUrl='https://rss.remixproject.org/' maxItems={10} />
id="retainable-rss-embed"
data-rss="https://medium.com/feed/remix-ide"
data-maxcols="1"
data-layout="grid"
data-poststyle="external"
data-readmore="More..."
data-buttonclass="btn mb-3"
data-offset="-100"
>
</div>
</div> </div>
</div> </div>
<div id="remixIDE_TwitterBlock" className="p-2 mx-1 mt-3 mb-0 remixui_home_remixHomeMedia" hidden={state.showMediaPanel !== 'twitter'} style={ { maxHeight: maxHeight, marginRight: '28px' } } > <div id="remixIDE_TwitterBlock" className="p-2 mx-1 mt-3 mb-0 remixui_home_remixHomeMedia" hidden={state.showMediaPanel !== 'twitter'} style={ { maxHeight: maxHeight, marginRight: '28px' } } >

@ -6,6 +6,7 @@ import { publishToSwarm } from './publishOnSwarm'
export const PublishToStorage = (props: RemixUiPublishToStorageProps) => { export const PublishToStorage = (props: RemixUiPublishToStorageProps) => {
const { api, storage, contract, resetStorage } = props const { api, storage, contract, resetStorage } = props
const [modalShown, setModalShown] = useState(false)
const [state, setState] = useState({ const [state, setState] = useState({
modal: { modal: {
title: '', title: '',
@ -22,15 +23,13 @@ export const PublishToStorage = (props: RemixUiPublishToStorageProps) => {
const storageService = async () => { const storageService = async () => {
if ((contract.metadata === undefined || contract.metadata.length === 0)) { if ((contract.metadata === undefined || contract.metadata.length === 0)) {
modal('Publish To Storage', '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.') modal('Publish To Storage', '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.')
} else { } else {
if (storage === 'swarm') { if (storage === 'swarm') {
try { try {
const result = await publishToSwarm(contract, api) const result = await publishToSwarm(contract, api)
modal(`Published ${contract.name}'s Metadata`, publishMessage(result.uploaded)) modal(`Published ${contract.name}'s Metadata and Sources`, publishMessage(result.uploaded))
// triggered each time there's a new verified publish (means hash correspond)
api.writeFile('swarm/' + result.item.hash, result.item.content)
} catch (err) { } catch (err) {
let parseError = err let parseError = err
try { try {
@ -40,14 +39,32 @@ export const PublishToStorage = (props: RemixUiPublishToStorageProps) => {
modal('Swarm Publish Failed', publishMessageFailed(storage, parseError)) modal('Swarm Publish Failed', publishMessageFailed(storage, parseError))
} }
} else { } else {
try { if (!api.config.get('settings/ipfs-url') && !modalShown) {
const result = await publishToIPFS(contract, api) modal('IPFS Settings', <div>You have not set your own custom IPFS settings.<br></br>
<br></br>
modal(`Published ${contract.name}'s Metadata`, publishMessage(result.uploaded)) We wont be providing a public endpoint anymore for publishing your contracts to IPFS.<br></br>Instead of that, 4 options are now available:<br></br>
// triggered each time there's a new verified publish (means hash correspond) <br></br>
api.writeFile('ipfs/' + result.item.hash, result.item.content) <ul className='pl-3'>
} catch (err) { <li>
modal('IPFS Publish Failed', publishMessageFailed(storage, err)) DEFAULT OPTION:
Use the public INFURA node. This will not guarantee your data will persist.
</li>
<li>
Use your own INFURA IPFS node. This requires a subscription. <a href='https://infura.io/product/ipfs' target={'_blank'}>Learn more</a>
</li>
<li>
Use any external IPFS which doesnt require any authentification.
</li>
<li>
Use your own local ipfs node (which usually runs under http://localhost:5001)
</li>
</ul>
You can update your IPFS settings in the SETTINGS tab.
<br></br>
Now the default option will be used.
</div>, async () => await ipfs(contract, api))
} else {
await ipfs(contract, api)
} }
} }
} }
@ -58,18 +75,29 @@ export const PublishToStorage = (props: RemixUiPublishToStorageProps) => {
} }
}, [storage]) }, [storage])
const ipfs = async (contract, api) => {
try {
const result = await publishToIPFS(contract, api)
modal(`Published ${contract.name}'s Metadata and Sources`, publishMessage(result.uploaded))
} catch (err) {
modal('IPFS Publish Failed', publishMessageFailed(storage, err))
}
setModalShown(true)
}
const publishMessage = (uploaded) => ( const publishMessage = (uploaded) => (
<span> Metadata of "{contract.name.toLowerCase()}" was published successfully. <br /> <span> Metadata and sources of "{contract.name.toLowerCase()}" were published successfully. <br />
<pre> <pre>
<div> <div>
{ uploaded.map((value, index) => <div key={index}><b>{ value.filename }</b> : <pre>{ value.output.url }</pre></div>) } {uploaded.map((value, index) => <div key={index}><b>{value.filename}</b> : <pre>{value.output.url}</pre></div>)}
</div> </div>
</pre> </pre>
</span> </span>
) )
const publishMessageFailed = (storage, err) => ( const publishMessageFailed = (storage, err) => (
<span>Failed to publish metadata file to { storage }, please check the { storage } gateways is available. <br /> <span>Failed to publish metadata file and sources to {storage}, please check the {storage} gateways is available. <br />
{err} {err}
</span> </span>
) )
@ -81,7 +109,7 @@ export const PublishToStorage = (props: RemixUiPublishToStorageProps) => {
resetStorage() resetStorage()
} }
const modal = async (title: string, message: string | JSX.Element) => { const modal = async (title: string, message: string | JSX.Element, okFn: any = () => { }) => {
await setState(prevState => { await setState(prevState => {
return { return {
...prevState, ...prevState,
@ -89,7 +117,8 @@ export const PublishToStorage = (props: RemixUiPublishToStorageProps) => {
...prevState.modal, ...prevState.modal,
hide: false, hide: false,
message, message,
title title,
okFn
} }
} }
}) })
@ -98,13 +127,13 @@ export const PublishToStorage = (props: RemixUiPublishToStorageProps) => {
return ( return (
<ModalDialog <ModalDialog
id={props.id || 'publishToStorage'} id={props.id || 'publishToStorage'}
title={ state.modal.title } title={state.modal.title}
message={ state.modal.message } message={state.modal.message}
hide={ state.modal.hide } hide={state.modal.hide}
okLabel='OK' okLabel='OK'
okFn={() => {}} okFn={state.modal.okFn}
handleHide={ handleHideModal }> handleHide={handleHideModal}>
{ (typeof state.modal.message !== 'string') && state.modal.message } {(typeof state.modal.message !== 'string') && state.modal.message}
</ModalDialog> </ModalDialog>
) )
} }

@ -79,7 +79,7 @@ export const publishToSwarm = async (contract, api) => {
// publish the list of sources in order, fail if any failed // publish the list of sources in order, fail if any failed
await Promise.all(sources.map(async (item) => { await Promise.all(sources.map(async (item) => {
try { try {
const result = await swarmVerifiedPublish(beeNodes, postageStampId, item.content, item.hash) const result = await swarmVerifiedPublish(beeNodes, postageStampId, item.content, item.hash, api)
try { try {
item.hash = result.url.match('bzz-raw://(.+)')[1] item.hash = result.url.match('bzz-raw://(.+)')[1]
@ -96,9 +96,9 @@ export const publishToSwarm = async (contract, api) => {
} }
})) }))
const metadataContent = JSON.stringify(metadata) const metadataContent = JSON.stringify(metadata, null, '\t')
try { try {
const result = await swarmVerifiedPublish(beeNodes, postageStampId, metadataContent, '') const result = await swarmVerifiedPublish(beeNodes, postageStampId, metadataContent, '', api)
try { try {
contract.metadataHash = result.url.match('bzz-raw://(.+)')[1] contract.metadataHash = result.url.match('bzz-raw://(.+)')[1]
@ -121,7 +121,7 @@ export const publishToSwarm = async (contract, api) => {
return { uploaded, item } return { uploaded, item }
} }
const swarmVerifiedPublish = async (beeNodes: Bee[], postageStampId: string, content, expectedHash): Promise<Record<string, any>> => { const swarmVerifiedPublish = async (beeNodes: Bee[], postageStampId: string, content, expectedHash, api): Promise<Record<string, any>> => {
try { try {
const results = await uploadToBeeNodes(beeNodes, postageStampId, content) const results = await uploadToBeeNodes(beeNodes, postageStampId, content)
const hash = hashFromResults(results) const hash = hashFromResults(results)
@ -129,6 +129,7 @@ const swarmVerifiedPublish = async (beeNodes: Bee[], postageStampId: string, con
if (expectedHash && hash !== expectedHash) { if (expectedHash && hash !== expectedHash) {
return { message: 'hash mismatch between solidity bytecode and uploaded content.', url: 'bzz-raw://' + hash, hash } return { message: 'hash mismatch between solidity bytecode and uploaded content.', url: 'bzz-raw://' + hash, hash }
} else { } else {
api.writeFile('swarm/' + hash, content)
return { message: 'ok', url: 'bzz-raw://' + hash, hash } return { message: 'ok', url: 'bzz-raw://' + hash, hash }
} }
} catch (error) { } catch (error) {

@ -1,12 +1,26 @@
import IpfsClient from 'ipfs-mini' import IpfsHttpClient from 'ipfs-http-client'
const ipfsNodes = [
new IpfsClient({ host: 'ipfs.remixproject.org', port: 443, protocol: 'https' }),
new IpfsClient({ host: 'ipfs.infura.io', port: 5001, protocol: 'https' }), let ipfsNodes = []
new IpfsClient({ host: '127.0.0.1', port: 5001, protocol: 'http' })
]
export const publishToIPFS = async (contract, api) => { export const publishToIPFS = async (contract, api) => {
ipfsNodes = [
IpfsHttpClient({ host: 'ipfs.infura.io', port: 5001, protocol: 'https' })
]
if (api.config.get('settings/ipfs-url')) {
const auth = api.config.get('settings/ipfs-project-id') ? 'Basic ' + Buffer.from(api.config.get('settings/ipfs-project-id') + ':' + api.config.get('settings/ipfs-project-secret')).toString('base64') : null
const ipfs = IpfsHttpClient({
host: api.config.get('settings/ipfs-url'),
port: api.config.get('settings/ipfs-port'),
protocol: api.config.get('settings/ipfs-protocol'),
headers: {
Authorization: auth
}
})
ipfsNodes.push(ipfs)
}
// gather list of files to publish // gather list of files to publish
const sources = [] const sources = []
let metadata let metadata
@ -58,13 +72,12 @@ export const publishToIPFS = async (contract, api) => {
console.log(error) console.log(error)
reject(error) reject(error)
}) })
}) })
})) }))
// publish the list of sources in order, fail if any failed // publish the list of sources in order, fail if any failed
await Promise.all(sources.map(async (item) => { await Promise.all(sources.map(async (item) => {
try { try {
const result = await ipfsVerifiedPublish(item.content, item.hash) const result = await ipfsVerifiedPublish(item.content, item.hash, api)
try { try {
item.hash = result.url.match('dweb:/ipfs/(.+)')[1] item.hash = result.url.match('dweb:/ipfs/(.+)')[1]
} catch (e) { } catch (e) {
@ -76,10 +89,10 @@ export const publishToIPFS = async (contract, api) => {
throw new Error(error) throw new Error(error)
} }
})) }))
const metadataContent = JSON.stringify(metadata) const metadataContent = JSON.stringify(metadata, null, '\t')
try { try {
const result = await ipfsVerifiedPublish(metadataContent, '') const result = await ipfsVerifiedPublish(metadataContent, '', api)
try { try {
contract.metadataHash = result.url.match('dweb:/ipfs/(.+)')[1] contract.metadataHash = result.url.match('dweb:/ipfs/(.+)')[1]
@ -101,14 +114,15 @@ export const publishToIPFS = async (contract, api) => {
return { uploaded, item } return { uploaded, item }
} }
const ipfsVerifiedPublish = async (content, expectedHash) => { const ipfsVerifiedPublish = async (content, expectedHash, api) => {
try { try {
const results = await severalGatewaysPush(content) const results = await severalGatewaysPush(content)
const hash: any = (results as any).path
if (expectedHash && results !== expectedHash) { if (expectedHash && hash !== expectedHash) {
return { message: 'hash mismatch between solidity bytecode and uploaded content.', url: 'dweb:/ipfs/' + results, hash: results } return { message: 'hash mismatch between solidity bytecode and uploaded content.', url: 'dweb:/ipfs/' + hash, hash }
} else { } else {
return { message: 'ok', url: 'dweb:/ipfs/' + results, hash: results } api.writeFile('ipfs/' + hash, content)
return { message: 'ok', url: 'dweb:/ipfs/' + hash, hash }
} }
} catch (error) { } catch (error) {
throw new Error(error) throw new Error(error)

@ -19,7 +19,7 @@ export function EnvironmentUI (props: EnvironmentProps) {
Environment Environment
</label> </label>
<div className="udapp_environment"> <div className="udapp_environment">
<select id="selectExEnvOptions" data-id="settingsSelectEnvOptions" className="form-control udapp_select custom-select" value={props.selectedEnv} onChange={(e) => { handleChangeExEnv(e.target.value) }}> <select id="selectExEnvOptions" data-id="settingsSelectEnvOptions" className="form-control udapp_select custom-select" value={props.selectedEnv || ''} onChange={(e) => { handleChangeExEnv(e.target.value) }}>
{ {
props.providers.providerList.map((provider, index) => props.providers.providerList.map((provider, index) =>
<option id={provider.id} key={index} data-id={provider.dataId} <option id={provider.id} key={index} data-id={provider.dataId}

@ -11,8 +11,8 @@ export function InstanceContainerUI (props: InstanceContainerProps) {
} }
return ( return (
<div className="udapp_instanceContainer border-0 list-group-item"> <div className="udapp_instanceContainer mt-3 border-0 list-group-item">
<div className="d-flex justify-content-between align-items-center pl-2 ml-1 mb-2" <label className="udapp_deployedContracts d-flex justify-content-between align-items-center pl-2 mb-1"
title="Autogenerated generic user interfaces for interaction with deployed contracts"> title="Autogenerated generic user interfaces for interaction with deployed contracts">
Deployed Contracts Deployed Contracts
{ instanceList.length > 0 { instanceList.length > 0
@ -20,7 +20,7 @@ export function InstanceContainerUI (props: InstanceContainerProps) {
title="Clear instances list and reset recorder" aria-hidden="true"> title="Clear instances list and reset recorder" aria-hidden="true">
</i> : null </i> : null
} }
</div> </label>
{ instanceList.length > 0 { instanceList.length > 0
? <div> { props.instances.instanceList.map((instance, index) => { ? <div> { props.instances.instanceList.map((instance, index) => {
return <UniversalDappUI return <UniversalDappUI

@ -1,27 +1,9 @@
// eslint-disable-next-line no-use-before-define // eslint-disable-next-line no-use-before-define
import React from 'react' import React, {useState} from 'react'
import { TreeView, TreeViewItem } from '@remix-ui/tree-view'
import { RecorderProps } from '../types' import { RecorderProps } from '../types'
export function RecorderUI (props: RecorderProps) { export function RecorderUI (props: RecorderProps) {
const card = (title: string, recorderCount: number) => { const [toggleExpander, setToggleExpander] = useState<boolean>(false)
return (
<div className="d-flex justify-content-between align-items-center" onClick={() => {}}>
<div className="pr-1 d-flex flex-row">
<div>{title}</div>
<div>
<div className="d-flex flex-column">
<div className="ml-2 badge badge-pill badge-primary" title="The number of recorded transactions">{recorderCount}</div>
</div>
</div>
</div>
<div>
<div><i className="udapp_arrow fas fa-angle-down" data-id="udapp_arrow"></i></div>
</div>
</div>
)
}
const triggerRecordButton = () => { const triggerRecordButton = () => {
props.storeScenario(props.scenarioPrompt) props.storeScenario(props.scenarioPrompt)
} }
@ -30,24 +12,35 @@ export function RecorderUI (props: RecorderProps) {
props.runCurrentScenario(props.gasEstimationPrompt, props.passphrasePrompt, props.mainnetPrompt, props.logBuilder) props.runCurrentScenario(props.gasEstimationPrompt, props.passphrasePrompt, props.mainnetPrompt, props.logBuilder)
} }
const toggleClass = () => {
setToggleExpander(!toggleExpander)
}
return ( return (
<div className="udapp_cardContainer list-group-item border-0"> <div className="udapp_cardContainer list-group-item border border-bottom">
<TreeView> <div className="udapp_recorderSection d-flex justify-content-between" onClick={toggleClass}>
<TreeViewItem label={card('Transactions recorded', props.count)} showIcon={false} labelClass="ml-n1"> <div className="d-flex">
<div className="d-flex flex-column"> <label className="mt-1 udapp_recorderSectionLabel">Transactions recorded</label>
<div className="udapp_recorderDescription mt-2"> <div className="ml-2 mb-2 badge badge-pill badge-primary" title="The number of recorded transactions">{props.count}</div>
All transactions (deployed contracts and function executions) in this environment can be saved and replayed in </div>
another environment. e.g Transactions created in Javascript VM can be replayed in the Injected Web3. <div>
</div> <span data-id='udappRecorderTitleExpander' onClick={toggleClass}>
<div className="udapp_transactionActions"> <i className={!toggleExpander ? 'fas fa-angle-right' : 'fas fa-angle-down'} aria-hidden="true"></i>
<i className="fas fa-save savetransaction udapp_recorder udapp_icon" </span>
onClick={triggerRecordButton} title="Save Transactions" aria-hidden="true"> </div>
</i> </div>
<i className="fas fa-play runtransaction udapp_runTxs udapp_icon" title="Run Transactions" data-id="runtransaction" aria-hidden="true" onClick={handleClickRunButton}></i> <div className={`border-bottom flex-column ${toggleExpander ? "d-flex" : "d-none"}`}>
</div> <div className="p-2 mt-2">
</div> All transactions (deployed contracts and function executions) can be saved and replayed in
</TreeViewItem> another environment. e.g Transactions created in Javascript VM can be replayed in the Injected Web3.
</TreeView> </div>
<div className="mb-2 udapp_transactionActions">
<i className="fas fa-save savetransaction udapp_recorder udapp_icon"
onClick={triggerRecordButton} title="Save Transactions" aria-hidden="true">
</i>
<i className="fas fa-play runtransaction udapp_runTxs udapp_icon" title="Run Transactions" data-id="runtransaction" aria-hidden="true" onClick={handleClickRunButton}></i>
</div>
</div>
</div> </div>
) )
} }

@ -206,9 +206,9 @@ export function UniversalDappUI (props: UdappProps) {
return ( return (
<div className={`instance udapp_instance udapp_run-instance border-dark ${toggleExpander ? 'udapp_hidesub' : 'bg-light'}`} id={`instance${address}`} data-shared="universalDappUiInstance"> <div className={`instance udapp_instance udapp_run-instance border-dark ${toggleExpander ? 'udapp_hidesub' : 'bg-light'}`} id={`instance${address}`} data-shared="universalDappUiInstance">
<div className="udapp_title alert alert-secondary"> <div className="udapp_title alert alert-secondary">
<button data-id={`universalDappUiTitleExpander${props.index}`} className="btn udapp_titleExpander" onClick={toggleClass}> <span data-id={`universalDappUiTitleExpander${props.index}`} className="btn udapp_titleExpander" onClick={toggleClass}>
<i className={`fas ${toggleExpander ? 'fa-angle-right' : 'fa-angle-down'}`} aria-hidden="true"></i> <i className={`fas ${toggleExpander ? 'fa-angle-right' : 'fa-angle-down'}`} aria-hidden="true"></i>
</button> </span>
<div className="input-group udapp_nameNbuts"> <div className="input-group udapp_nameNbuts">
<div className="udapp_titleText input-group-prepend"> <div className="udapp_titleText input-group-prepend">
<span className="input-group-text udapp_spanTitleText"> <span className="input-group-text udapp_spanTitleText">

@ -2,11 +2,4 @@
padding : 0 24px 16px; padding : 0 24px 16px;
margin : 0; margin : 0;
background : none; background : none;
}
.udapp_arrow {
font-weight : bold;
cursor : pointer;
font-size : 14px;
}
.udapp_arrow:hover {
} }

@ -60,6 +60,9 @@
text-align: center; text-align: center;
padding: 0 14px 16px; padding: 0 14px 16px;
} }
.udapp_deployedContracts {
font-size: 1rem;
}
.udapp_pendingTxsContainer { .udapp_pendingTxsContainer {
display: flex; display: flex;
flex-direction: column; flex-direction: column;
@ -70,9 +73,6 @@
.udapp_container { .udapp_container {
padding: 0 24px 16px; padding: 0 24px 16px;
} }
.udapp_recorderDescription {
margin: 0 15px 15px 0;
}
.udapp_contractNames { .udapp_contractNames {
width: 100%; width: 100%;
border: 1px solid border: 1px solid
@ -121,7 +121,14 @@
.udapp_ataddressinput { .udapp_ataddressinput {
padding: .25rem; padding: .25rem;
} }
.udapp_create { .udapp_recorderSection:hover {
cursor: pointer;
}.udapp_recorderSectionLabel:hover {
cursor: pointer;
}
.udapp_recorderSectionLabel {
cursor: pointer;
font-size: 1rem;
} }
.udapp_input { .udapp_input {
font-size: 10px !important; font-size: 10px !important;
@ -269,7 +276,7 @@
color: var(--primary); color: var(--primary);
} }
.udapp_titleExpander { .udapp_titleExpander {
padding: 5px 7px; margin-top: 2px;
} }
.udapp_nameNbuts { .udapp_nameNbuts {
display: contents; display: contents;

@ -227,7 +227,8 @@ export interface ContractGUIProps {
clickCallBack: (inputs: { name: string, type: string }[], input: string) => void, clickCallBack: (inputs: { name: string, type: string }[], input: string) => void,
widthClass?: string, widthClass?: string,
evmBC: any, evmBC: any,
lookupOnly: boolean lookupOnly: boolean,
disabled?: boolean
} }
export interface MainnetProps { export interface MainnetProps {
network: Network, network: Network,

@ -344,8 +344,10 @@ export const SearchProvider = ({
console.log(e) console.log(e)
} }
} }
setTimeout(async () => {
await fetchWorkspace()
}, 500)
fetchWorkspace()
return () => { return () => {
plugin.off('fileManager', 'fileChanged') plugin.off('fileManager', 'fileChanged')

@ -3,8 +3,8 @@ export const textSecondary = 'text-secondary'
export const textDark = 'text-dark' export const textDark = 'text-dark'
export const warnText = 'Be sure the endpoint is opened before enabling it. \nThis mode allows a user to provide a passphrase in the Remix interface without having to unlock the account. Although this is very convenient, you should completely trust the backend you are connected to (Geth, Parity, ...). Remix never persists any passphrase'.split('\n').map(s => s.trim()).join(' ') export const warnText = 'Be sure the endpoint is opened before enabling it. \nThis mode allows a user to provide a passphrase in the Remix interface without having to unlock the account. Although this is very convenient, you should completely trust the backend you are connected to (Geth, Parity, ...). Remix never persists any passphrase'.split('\n').map(s => s.trim()).join(' ')
export const gitAccessTokenTitle = 'Github Access Token' export const gitAccessTokenTitle = 'GitHub Access Token'
export const gitAccessTokenText = 'Manage the access token used to publish to Gist and retrieve Github contents.' export const gitAccessTokenText = 'Manage the access token used to publish to Gist and retrieve GitHub contents.'
export const gitAccessTokenText2 = 'Go to github token page (link below) to create a new token and save it in Remix. Make sure this token has only \'create gist\' permission.' export const gitAccessTokenText2 = 'Go to github token page (link below) to create a new token and save it in Remix. Make sure this token has only \'create gist\' permission.'
export const gitAccessTokenLink = 'https://github.com/settings/tokens' export const gitAccessTokenLink = 'https://github.com/settings/tokens'
export const etherscanTokenTitle = 'EtherScan Access Token' export const etherscanTokenTitle = 'EtherScan Access Token'
@ -17,6 +17,7 @@ export const enablePersonalModeText = ' Enable Personal Mode for web3 provider.
export const matomoAnalytics = 'Enable Matomo Analytics. We do not collect personally identifiable information (PII). The info is used to improve the site’s UX & UI. See more about ' export const matomoAnalytics = 'Enable Matomo Analytics. We do not collect personally identifiable information (PII). The info is used to improve the site’s UX & UI. See more about '
export const swarmSettingsTitle = 'Swarm Settings' export const swarmSettingsTitle = 'Swarm Settings'
export const swarmSettingsText = 'Swarm Settings' export const swarmSettingsText = 'Swarm Settings'
export const ipfsSettingsText = 'IPFS Settings'
export const labels = { export const labels = {
'gist': { 'gist': {
'link': gitAccessTokenLink, 'link': gitAccessTokenLink,

@ -1,10 +1,10 @@
import React, { useState, useReducer, useEffect, useCallback } from 'react' // eslint-disable-line import React, { useState, useReducer, useEffect, useCallback } from 'react' // eslint-disable-line
import { CopyToClipboard } from '@remix-ui/clipboard' // eslint-disable-line import { CopyToClipboard } from '@remix-ui/clipboard' // eslint-disable-line
import { enablePersonalModeText, ethereunVMText, labels, generateContractMetadataText, matomoAnalytics, textDark, textSecondary, warnText, wordWrapText, swarmSettingsTitle } from './constants' import { enablePersonalModeText, ethereunVMText, labels, generateContractMetadataText, matomoAnalytics, textDark, textSecondary, warnText, wordWrapText, swarmSettingsTitle, ipfsSettingsText } from './constants'
import './remix-ui-settings.css' import './remix-ui-settings.css'
import { ethereumVM, generateContractMetadat, personal, textWrapEventAction, useMatomoAnalytics, saveTokenToast, removeTokenToast, saveSwarmSettingsToast } from './settingsAction' import { ethereumVM, generateContractMetadat, personal, textWrapEventAction, useMatomoAnalytics, saveTokenToast, removeTokenToast, saveSwarmSettingsToast, saveIpfsSettingsToast } from './settingsAction'
import { initialState, toastInitialState, toastReducer, settingReducer } from './settingsReducer' import { initialState, toastInitialState, toastReducer, settingReducer } from './settingsReducer'
import { Toaster } from '@remix-ui/toaster'// eslint-disable-line import { Toaster } from '@remix-ui/toaster'// eslint-disable-line
import { RemixUiThemeModule, ThemeModule} from '@remix-ui/theme-module' import { RemixUiThemeModule, ThemeModule} from '@remix-ui/theme-module'
@ -26,6 +26,12 @@ export const RemixUiSettings = (props: RemixUiSettingsProps) => {
const [privateBeeAddress, setPrivateBeeAddress] = useState('') const [privateBeeAddress, setPrivateBeeAddress] = useState('')
const [postageStampId, setPostageStampId] = useState('') const [postageStampId, setPostageStampId] = useState('')
const [resetState, refresh] = useState(0) const [resetState, refresh] = useState(0)
const [ipfsUrl, setipfsUrl] = useState('')
const [ipfsPort, setipfsPort] = useState('')
const [ipfsProtocol, setipfsProtocol] = useState('')
const [ipfsProjectId, setipfsProjectId] = useState('')
const [ipfsProjectSecret, setipfsProjectSecret] = useState('')
const initValue = () => { const initValue = () => {
const metadataConfig = props.config.get('settings/generate-contract-metadata') const metadataConfig = props.config.get('settings/generate-contract-metadata')
@ -59,6 +65,29 @@ export const RemixUiSettings = (props: RemixUiSettingsProps) => {
if (configPostageStampId) { if (configPostageStampId) {
setPostageStampId(configPostageStampId) setPostageStampId(configPostageStampId)
} }
const configipfsUrl = props.config.get('settings/ipfs-url')
if (configipfsUrl) {
setipfsUrl(configipfsUrl)
}
const configipfsPort = props.config.get('settings/ipfs-port')
if (configipfsPort) {
setipfsPort(configipfsPort)
}
const configipfsProtocol = props.config.get('settings/ipfs-protocol')
if (configipfsProtocol) {
setipfsProtocol(configipfsProtocol)
}
const configipfsProjectId = props.config.get('settings/ipfs-project-id')
if (configipfsProjectId) {
setipfsProjectId(configipfsProjectId)
}
const configipfsProjectSecret = props.config.get('settings/ipfs-project-secret')
if (configipfsProjectSecret) {
setipfsProjectSecret(configipfsProjectSecret)
}
}, [themeName, state.message]) }, [themeName, state.message])
useEffect(() => { useEffect(() => {
@ -188,7 +217,7 @@ export const RemixUiSettings = (props: RemixUiSettingsProps) => {
<div className="d-flex justify-content-end pt-2"> <div className="d-flex justify-content-end pt-2">
<CopyToClipboard content={tokenValue[type]} data-id='copyToClipboardCopyIcon' /> <CopyToClipboard content={tokenValue[type]} data-id='copyToClipboardCopyIcon' />
<input className="btn btn-sm btn-primary ml-2" id="savegisttoken" data-id="settingsTabSaveGistToken" onClick={() => saveToken(type)} value="Save" type="button" disabled={tokenValue === ''}></input> <input className="btn btn-sm btn-primary ml-2" id="savegisttoken" data-id="settingsTabSaveGistToken" onClick={() => saveToken(type)} value="Save" type="button" disabled={tokenValue === ''}></input>
<button className="btn btn-sm btn-secondary ml-2" id="removegisttoken" data-id="settingsTabRemoveGistToken" title="Delete Github access token" onClick={() => removeToken(type)}>Remove</button> <button className="btn btn-sm btn-secondary ml-2" id="removegisttoken" data-id="settingsTabRemoveGistToken" title="Delete GitHub access token" onClick={() => removeToken(type)}>Remove</button>
</div> </div>
</div></div> </div></div>
</div> </div>
@ -237,6 +266,83 @@ export const RemixUiSettings = (props: RemixUiSettingsProps) => {
</div> </div>
) )
// ipfs settings
const handleSaveIpfsProjectId = useCallback(
(event) => {
setipfsProjectId(event.target.value)
}
, [ipfsProjectId]
)
const handleSaveIpfsSecret = useCallback(
(event) => {
setipfsProjectSecret(event.target.value)
}
, [ipfsProjectSecret]
)
const handleSaveIpfsUrl = useCallback(
(event) => {
setipfsUrl(event.target.value)
}
, [ipfsUrl]
)
const handleSaveIpfsPort = useCallback(
(event) => {
setipfsPort(event.target.value)
}
, [ipfsPort]
)
const handleSaveIpfsProtocol = useCallback(
(event) => {
setipfsProtocol(event.target.value)
}
, [ipfsProtocol]
)
const saveIpfsSettings = () => {
saveIpfsSettingsToast(props.config, dispatchToast, ipfsUrl, ipfsProtocol, ipfsPort, ipfsProjectId, ipfsProjectSecret)
}
const ipfsSettings = () => (
<div className="border-top">
<div className="card-body pt-3 pb-2">
<h6 className="card-title">{ ipfsSettingsText }</h6>
<div className="pt-2 mb-1"><label>IPFS HOST:</label>
<div className="text-secondary mb-0 h6">
<input placeholder='e.g. ipfs.infura.io' id="settingsIpfsUrl" data-id="settingsIpfsUrl" className="form-control" onChange={handleSaveIpfsUrl} value={ ipfsUrl } />
</div>
</div>
<div className=""><label>IPFS PROTOCOL:</label>
<div className="text-secondary mb-0 h6">
<input placeholder='e.g. https' id="settingsIpfsProtocol" data-id="settingsIpfsProtocol" className="form-control" onChange={handleSaveIpfsProtocol} value={ ipfsProtocol } />
</div>
</div>
<div className=""><label>IPFS PORT:</label>
<div className="text-secondary mb-0 h6">
<input placeholder='e.g. 5001' id="settingsIpfsPort" data-id="settingsIpfsPort" className="form-control" onChange={handleSaveIpfsPort} value={ ipfsPort } />
</div>
</div>
<div className=""><label>IPFS PROJECT ID [ INFURA ]:</label>
<div className="text-secondary mb-0 h6">
<input id="settingsIpfsProjectId" data-id="settingsIpfsProjectId" className="form-control" onChange={handleSaveIpfsProjectId} value={ ipfsProjectId } />
</div>
</div>
<div className=""><label>IPFS PROJECT SECRET [ INFURA ]:</label>
<div className="text-secondary mb-0 h6">
<input id="settingsIpfsProjectSecret" data-id="settingsIpfsProjectSecret" className="form-control" type="password" onChange={handleSaveIpfsSecret} value={ ipfsProjectSecret } />
</div>
</div>
<div className="d-flex justify-content-end pt-2">
<input className="btn btn-sm btn-primary ml-2" id="saveIpfssettings" data-id="settingsTabSaveIpfsSettings" onClick={() => saveIpfsSettings()} value="Save" type="button"></input>
</div>
</div>
</div>)
return ( return (
<div> <div>
{state.message ? <Toaster message= {state.message}/> : null} {state.message ? <Toaster message= {state.message}/> : null}
@ -244,6 +350,7 @@ export const RemixUiSettings = (props: RemixUiSettingsProps) => {
{token('gist')} {token('gist')}
{token('etherscan')} {token('etherscan')}
{swarmSettings()} {swarmSettings()}
{ipfsSettings()}
<RemixUiThemeModule themeModule={props._deps.themeModule} /> <RemixUiThemeModule themeModule={props._deps.themeModule} />
</div> </div>
) )

@ -56,3 +56,12 @@ export const saveSwarmSettingsToast = (config, dispatch, privateBeeAddress, post
config.set('settings/swarm-postage-stamp-id', postageStampId) config.set('settings/swarm-postage-stamp-id', postageStampId)
dispatch({ type: 'save', payload: { message: 'Swarm settings have been saved' } }) dispatch({ type: 'save', payload: { message: 'Swarm settings have been saved' } })
} }
export const saveIpfsSettingsToast = (config, dispatch, ipfsURL, ipfsProtocol, ipfsPort, ipfsProjectId, ipfsProjectSecret) => {
config.set('settings/ipfs-url', ipfsURL)
config.set('settings/ipfs-protocol', ipfsProtocol)
config.set('settings/ipfs-port', ipfsPort)
config.set('settings/ipfs-project-id', ipfsProjectId)
config.set('settings/ipfs-project-secret', ipfsProjectSecret)
dispatch({ type: 'save', payload: { message: 'IPFS settings have been saved' } })
}

@ -9,8 +9,10 @@ import { resetEditorMode, listenToEvents } from './actions/compiler'
import { OverlayTrigger, Tooltip } from 'react-bootstrap' // eslint-disable-line import { OverlayTrigger, Tooltip } from 'react-bootstrap' // eslint-disable-line
import { getValidLanguage } from '@remix-project/remix-solidity' import { getValidLanguage } from '@remix-project/remix-solidity'
import { CopyToClipboard } from '@remix-ui/clipboard' import { CopyToClipboard } from '@remix-ui/clipboard'
import { configFileContent } from './compilerConfiguration'
import './css/style.css' import './css/style.css'
const defaultPath = "compiler_config.json"
declare global { declare global {
interface Window { interface Window {
@ -21,10 +23,24 @@ declare global {
const _paq = window._paq = window._paq || [] //eslint-disable-line const _paq = window._paq = window._paq || [] //eslint-disable-line
export const CompilerContainer = (props: CompilerContainerProps) => { export const CompilerContainer = (props: CompilerContainerProps) => {
const { api, compileTabLogic, tooltip, modal, compiledFileName, updateCurrentVersion, configurationSettings, isHardhatProject, isTruffleProject } = props // eslint-disable-line const {
api,
compileTabLogic,
tooltip,
modal,
compiledFileName,
updateCurrentVersion,
configurationSettings,
isHardhatProject,
isTruffleProject,
workspaceName,
configFilePath,
setConfigFilePath,
} = props // eslint-disable-line
const [state, setState] = useState({ const [state, setState] = useState({
hideWarnings: false, hideWarnings: false,
autoCompile: false, autoCompile: false,
useFileConfiguration: false,
matomoAutocompileOnce: true, matomoAutocompileOnce: true,
optimize: false, optimize: false,
compileTimeout: null, compileTimeout: null,
@ -37,15 +53,56 @@ export const CompilerContainer = (props: CompilerContainerProps) => {
compiledFileName: '', compiledFileName: '',
includeNightlies: false, includeNightlies: false,
language: 'Solidity', language: 'Solidity',
evmVersion: '' evmVersion: '',
createFileOnce: true
}) })
const [showFilePathInput, setShowFilePathInput] = useState<boolean>(false)
const [toggleExpander, setToggleExpander] = useState<boolean>(false)
const [disableCompileButton, setDisableCompileButton] = useState<boolean>(false) const [disableCompileButton, setDisableCompileButton] = useState<boolean>(false)
const compileIcon = useRef(null) const compileIcon = useRef(null)
const promptMessageInput = useRef(null) const promptMessageInput = useRef(null)
const configFilePathInput = useRef(null)
const [hhCompilation, sethhCompilation] = useState(false) const [hhCompilation, sethhCompilation] = useState(false)
const [truffleCompilation, setTruffleCompilation] = useState(false) const [truffleCompilation, setTruffleCompilation] = useState(false)
const [compilerContainer, dispatch] = useReducer(compilerReducer, compilerInitialState) const [compilerContainer, dispatch] = useReducer(compilerReducer, compilerInitialState)
useEffect(() => {
if (workspaceName) {
api.setAppParameter('configFilePath', defaultPath)
if (state.useFileConfiguration) {
api.fileExists(defaultPath).then((exists) => {
if (!exists && state.useFileConfiguration) createNewConfigFile()
})
}
setShowFilePathInput(false)
}
}, [workspaceName])
useEffect(() => {
if (state.useFileConfiguration) {
api.fileExists(defaultPath).then((exists) => {
if (!exists) createNewConfigFile()
})
setToggleExpander(true)
}
}, [state.useFileConfiguration])
useEffect(() => {
const listener = (event) => {
if (configFilePathInput.current !== event.target) {
setShowFilePathInput(false)
return;
}
};
document.addEventListener("mousedown", listener);
document.addEventListener("touchstart", listener);
return () => {
document.removeEventListener("mousedown", listener);
document.removeEventListener("touchstart", listener);
}
})
useEffect(() => { useEffect(() => {
fetchAllVersion((allversions, selectedVersion, isURL) => { fetchAllVersion((allversions, selectedVersion, isURL) => {
setState(prevState => { setState(prevState => {
@ -72,6 +129,12 @@ export const CompilerContainer = (props: CompilerContainerProps) => {
const autocompile = await api.getAppParameter('autoCompile') as boolean || false const autocompile = await api.getAppParameter('autoCompile') as boolean || false
const hideWarnings = await api.getAppParameter('hideWarnings') as boolean || false const hideWarnings = await api.getAppParameter('hideWarnings') as boolean || false
const includeNightlies = await api.getAppParameter('includeNightlies') as boolean || false const includeNightlies = await api.getAppParameter('includeNightlies') as boolean || false
const useFileConfiguration = await api.getAppParameter('useFileConfiguration') as boolean || false
let configFilePathSaved = await api.getAppParameter('configFilePath')
if (!configFilePathSaved || configFilePathSaved == '') configFilePathSaved = defaultPath
setConfigFilePath(configFilePathSaved)
setState(prevState => { setState(prevState => {
const params = api.getCompilerParameters() const params = api.getCompilerParameters()
const optimize = params.optimize const optimize = params.optimize
@ -84,6 +147,7 @@ export const CompilerContainer = (props: CompilerContainerProps) => {
hideWarnings: hideWarnings, hideWarnings: hideWarnings,
autoCompile: autocompile, autoCompile: autocompile,
includeNightlies: includeNightlies, includeNightlies: includeNightlies,
useFileConfiguration: useFileConfiguration,
optimize: optimize, optimize: optimize,
runs: runs, runs: runs,
evmVersion: (evmVersion !== null) && (evmVersion !== 'null') && (evmVersion !== undefined) && (evmVersion !== 'undefined') ? evmVersion : 'default', evmVersion: (evmVersion !== null) && (evmVersion !== 'null') && (evmVersion !== undefined) && (evmVersion !== 'undefined') ? evmVersion : 'default',
@ -140,12 +204,74 @@ export const CompilerContainer = (props: CompilerContainerProps) => {
} }
}, [compilerContainer.editor.mode]) }, [compilerContainer.editor.mode])
useEffect(() => {
compileTabLogic.setUseFileConfiguration(state.useFileConfiguration)
if (state.useFileConfiguration) compileTabLogic.setConfigFilePath(configFilePath)
}, [state.useFileConfiguration])
useEffect(() => { useEffect(() => {
if (configurationSettings) { if (configurationSettings) {
setConfiguration(configurationSettings) setConfiguration(configurationSettings)
} }
}, [configurationSettings]) }, [configurationSettings])
const toggleConfigType = () => {
if (state.useFileConfiguration)
if (state.createFileOnce) {
api.fileExists(defaultPath).then((exists) => {
if (!exists || state.useFileConfiguration ) createNewConfigFile()
})
setState(prevState => {
return { ...prevState, createFileOnce: false }
})
}
setState(prevState => {
api.setAppParameter('useFileConfiguration', !state.useFileConfiguration)
return { ...prevState, useFileConfiguration: !state.useFileConfiguration }
})
}
const openFile = async () => {
api.open(configFilePath)
}
const createNewConfigFile = async () => {
let filePath = configFilePathInput.current && configFilePathInput.current.value !== '' ? configFilePathInput.current.value : configFilePath
if (filePath === '') filePath = defaultPath
if (!filePath.endsWith('.json')) filePath = filePath + '.json'
await api.writeFile(filePath, configFileContent)
api.setAppParameter('configFilePath', filePath)
setConfigFilePath(filePath)
compileTabLogic.setConfigFilePath(filePath)
setShowFilePathInput(false)
}
const handleConfigPathChange = async () => {
if (configFilePathInput.current.value !== '') {
if (!configFilePathInput.current.value.endsWith('.json')) configFilePathInput.current.value += '.json'
if (await api.fileExists(configFilePathInput.current.value)) {
api.setAppParameter('configFilePath', configFilePathInput.current.value)
setConfigFilePath(configFilePathInput.current.value)
compileTabLogic.setConfigFilePath(configFilePathInput.current.value)
setShowFilePathInput(false)
} else {
modal(
'New configuration file', `The file "${configFilePathInput.current.value}" you entered does not exist. Do you want to create a new one?`,
'Create',
async () => await createNewConfigFile(),
'Cancel',
() => {
setShowFilePathInput(false)
}
)
}
}
}
const _retrieveVersion = (version?) => { const _retrieveVersion = (version?) => {
if (!version) version = state.selectedVersion if (!version) version = state.selectedVersion
if (version === 'builtin') version = state.defaultVersion if (version === 'builtin') version = state.defaultVersion
@ -300,6 +426,9 @@ export const CompilerContainer = (props: CompilerContainerProps) => {
compileIcon.current.classList.remove('remixui_spinningIcon') compileIcon.current.classList.remove('remixui_spinningIcon')
compileIcon.current.classList.remove('remixui_bouncingIcon') compileIcon.current.classList.remove('remixui_bouncingIcon')
if (!state.autoCompile || (state.autoCompile && state.matomoAutocompileOnce)) { if (!state.autoCompile || (state.autoCompile && state.matomoAutocompileOnce)) {
if (state.useFileConfiguration)
_paq.push(['trackEvent', 'compiler', 'compiled_with_config_file'])
_paq.push(['trackEvent', 'compiler', 'compiled_with_version', _retrieveVersion()]) _paq.push(['trackEvent', 'compiler', 'compiled_with_version', _retrieveVersion()])
if (state.autoCompile && state.matomoAutocompileOnce) { if (state.autoCompile && state.matomoAutocompileOnce) {
setState(prevState => { setState(prevState => {
@ -537,14 +666,18 @@ export const CompilerContainer = (props: CompilerContainerProps) => {
onChangeRuns(settings.runs) onChangeRuns(settings.runs)
} }
const toggleConfigurations = () => {
setToggleExpander(!toggleExpander)
}
return ( return (
<section> <section>
<article> <article>
<header className='remixui_compilerSection border-bottom'> <div className='pt-0 remixui_compilerSection'>
<div className="mb-2"> <div className="mb-1">
<label className="remixui_compilerLabel form-check-label" htmlFor="versionSelector"> <label className="remixui_compilerLabel form-check-label" htmlFor="versionSelector">
Compiler Compiler
<button className="far fa-plus-square border-0 p-0 mx-2 btn-sm" onClick={promptCompiler} title="Add a custom compiler with URL"></button> <button className="far fa-plus btn-light border-0 p-0 mx-2 btn-sm" onClick={promptCompiler} title="Add a custom compiler with URL"></button>
</label> </label>
<select value={ state.selectedVersion || state.defaultVersion } onChange={(e) => handleLoadVersion(e.target.value) } className="custom-select" id="versionSelector" disabled={state.allversions.length <= 0}> <select value={ state.selectedVersion || state.defaultVersion } onChange={(e) => handleLoadVersion(e.target.value) } className="custom-select" id="versionSelector" disabled={state.allversions.length <= 0}>
{ state.allversions.length <= 0 && <option disabled data-id={state.selectedVersion === state.defaultVersion ? 'selected' : ''}>{ state.defaultVersion }</option> } { state.allversions.length <= 0 && <option disabled data-id={state.selectedVersion === state.defaultVersion ? 'selected' : ''}>{ state.defaultVersion }</option> }
@ -558,50 +691,17 @@ export const CompilerContainer = (props: CompilerContainerProps) => {
} }
</select> </select>
</div> </div>
<div className="mb-2 remixui_nightlyBuilds custom-control custom-checkbox"> <div className="mb-2 flex-row-reverse remixui_nightlyBuilds custom-control custom-checkbox">
<input className="mr-2 custom-control-input" id="nightlies" type="checkbox" onChange={handleNightliesChange} checked={state.includeNightlies} /> <input className="mr-2 custom-control-input" id="nightlies" type="checkbox" onChange={handleNightliesChange} checked={state.includeNightlies} />
<label htmlFor="nightlies" data-id="compilerNightliesBuild" className="form-check-label custom-control-label">Include nightly builds</label> <label htmlFor="nightlies" data-id="compilerNightliesBuild" className="form-check-label custom-control-label">Include nightly builds</label>
</div> </div>
<div className="mb-2"> <div className="mt-2 remixui_compilerConfig custom-control custom-checkbox">
<label className="remixui_compilerLabel form-check-label" htmlFor="compilierLanguageSelector">Language</label> <input className="remixui_autocompile custom-control-input" type="checkbox" onChange={handleAutoCompile} data-id="compilerContainerAutoCompile" id="autoCompile" title="Auto compile" checked={state.autoCompile} />
<select onChange={(e) => handleLanguageChange(e.target.value)} value={state.language} className="custom-select" id="compilierLanguageSelector" title="Available since v0.5.7"> <label className="form-check-label custom-control-label" htmlFor="autoCompile">Auto compile</label>
<option data-id={state.language === 'Solidity' ? 'selected' : ''} value='Solidity'>Solidity</option>
<option data-id={state.language === 'Yul' ? 'selected' : ''} value='Yul'>Yul</option>
</select>
</div>
<div className="mb-2">
<label className="remixui_compilerLabel form-check-label" htmlFor="evmVersionSelector">EVM Version</label>
<select value={state.evmVersion} onChange={(e) => handleEvmVersionChange(e.target.value)} className="custom-select" id="evmVersionSelector">
{compileTabLogic.evmVersions.map((version, index) => (<option key={index} data-id={state.evmVersion === version ? 'selected' : ''} value={version}>{version}</option>))}
</select>
</div> </div>
<div className="mt-3"> <div className="mt-1 mb-2 remixui_compilerConfig custom-control custom-checkbox">
<p className="mt-2 remixui_compilerLabel">Compiler Configuration</p> <input className="remixui_autocompile custom-control-input" onChange={handleHideWarningsChange} id="hideWarningsBox" type="checkbox" title="Hide warnings" checked={state.hideWarnings} />
<div className="mt-2 remixui_compilerConfig custom-control custom-checkbox"> <label className="form-check-label custom-control-label" htmlFor="hideWarningsBox">Hide warnings</label>
<input className="remixui_autocompile custom-control-input" type="checkbox" onChange={handleAutoCompile} data-id="compilerContainerAutoCompile" id="autoCompile" title="Auto compile" checked={state.autoCompile} />
<label className="form-check-label custom-control-label" htmlFor="autoCompile">Auto compile</label>
</div>
<div className="mt-2 remixui_compilerConfig custom-control custom-checkbox">
<div className="justify-content-between align-items-center d-flex">
<input onChange={(e) => { handleOptimizeChange(e.target.checked) }} className="custom-control-input" id="optimize" type="checkbox" checked={state.optimize} />
<label className="form-check-label custom-control-label" htmlFor="optimize">Enable optimization</label>
<input
min="1"
className="custom-select ml-2 remixui_runs"
id="runs"
placeholder="200"
value={state.runs}
type="number"
title="Estimated number of times each opcode of the deployed code will be executed across the life-time of the contract."
onChange={(e) => onChangeRuns(e.target.value)}
disabled={!state.optimize}
/>
</div>
</div>
<div className="mt-2 remixui_compilerConfig custom-control custom-checkbox">
<input className="remixui_autocompile custom-control-input" onChange={handleHideWarningsChange} id="hideWarningsBox" type="checkbox" title="Hide warnings" checked={state.hideWarnings} />
<label className="form-check-label custom-control-label" htmlFor="hideWarningsBox">Hide warnings</label>
</div>
</div> </div>
{ {
isHardhatProject && isHardhatProject &&
@ -635,12 +735,88 @@ export const CompilerContainer = (props: CompilerContainerProps) => {
</a> </a>
</div> </div>
} }
</div>
<div className="d-flex px-4 remixui_compilerConfigSection justify-content-between" onClick={toggleConfigurations}>
<div className="d-flex">
<label className="mt-1 remixui_compilerConfigSection">Advanced Configurations</label>
</div>
<div> <div>
<button id="compileBtn" data-id="compilerContainerCompileBtn" className="btn btn-primary btn-block d-block w-100 text-break remixui_disabled mb-1 mt-3" onClick={compile} disabled={disableCompileButton}> <span data-id='scConfigExpander' onClick={toggleConfigurations}>
<i className={!toggleExpander ? 'fas fa-angle-right' : 'fas fa-angle-down'} aria-hidden="true"></i>
</span>
</div>
</div>
<div className={`px-4 pb-4 border-bottom flex-column ${toggleExpander ? "d-flex" : "d-none"}`}>
<div className="d-flex pb-1 remixui_compilerConfig custom-control custom-radio">
<input className="custom-control-input" type="radio" name="configradio" value="manual" onChange={toggleConfigType} checked={!state.useFileConfiguration} id="scManualConfig" />
<label className="form-check-label custom-control-label" htmlFor="scManualConfig">Compiler configuration</label>
</div>
<div className={`flex-column 'd-flex'}`}>
<div className="mb-2 ml-4">
<label className="remixui_compilerLabel form-check-label" htmlFor="compilierLanguageSelector">Language</label>
<select onChange={(e) => handleLanguageChange(e.target.value)} disabled={state.useFileConfiguration} value={state.language} className="custom-select" id="compilierLanguageSelector" title="Available since v0.5.7">
<option data-id={state.language === 'Solidity' ? 'selected' : ''} value='Solidity'>Solidity</option>
<option data-id={state.language === 'Yul' ? 'selected' : ''} value='Yul'>Yul</option>
</select>
</div>
<div className="mb-2 ml-4">
<label className="remixui_compilerLabel form-check-label" htmlFor="evmVersionSelector">EVM Version</label>
<select value={state.evmVersion} onChange={(e) => handleEvmVersionChange(e.target.value)} disabled={state.useFileConfiguration} className="custom-select" id="evmVersionSelector">
{compileTabLogic.evmVersions.map((version, index) => (<option key={index} data-id={state.evmVersion === version ? 'selected' : ''} value={version}>{version}</option>))}
</select>
</div>
<div className="mt-1 mt-3 border-dark pb-3 ml-4 remixui_compilerConfig custom-control custom-checkbox">
<div className="justify-content-between align-items-center d-flex">
<input onChange={(e) => { handleOptimizeChange(e.target.checked) }} disabled={state.useFileConfiguration} className="custom-control-input" id="optimize" type="checkbox" checked={state.optimize} />
<label className="form-check-label custom-control-label" htmlFor="optimize">Enable optimization</label>
<input
min="1"
className="custom-select ml-2 remixui_runs"
id="runs"
placeholder="200"
value={state.runs}
type="number"
title="Estimated number of times each opcode of the deployed code will be executed across the life-time of the contract."
onChange={(e) => onChangeRuns(e.target.value)}
disabled={!state.optimize || state.useFileConfiguration}
/>
</div>
</div>
</div>
<div className="d-flex pb-1 remixui_compilerConfig custom-control custom-radio">
<input className="custom-control-input" type="radio" name="configradio" value="file" onChange={toggleConfigType} checked={state.useFileConfiguration} id="scFileConfig" />
<label className="form-check-label custom-control-label" htmlFor="scFileConfig" data-id="scFileConfiguration">Use configuration file</label>
</div>
<div className={`pt-2 ml-4 ml-2 align-items-start justify-content-between d-flex`}>
{ (!showFilePathInput && state.useFileConfiguration) && <span
title="Click to open the config file."
onClick={configFilePath === '' ? () => {} : openFile}
className="py-2 remixui_compilerConfigPath"
>{configFilePath === '' ? 'No file selected.' : configFilePath}</span> }
{ (!showFilePathInput && !state.useFileConfiguration) && <span className="py-2 text-secondary">{configFilePath}</span> }
<input
ref={configFilePathInput}
className={`py-0 my-0 form-control ${showFilePathInput ? "d-flex" : "d-none"}`}
placeholder={"Enter the new path"}
title="If the file you entered does not exist you will be able to create one in the next step."
disabled={!state.useFileConfiguration}
data-id="scConfigFilePathInput"
onKeyPress={event => {
if (event.key === 'Enter') {
handleConfigPathChange()
}
}}
/>
{ !showFilePathInput && <button disabled={!state.useFileConfiguration} data-id="scConfigChangeFilePath" className="btn-secondary" onClick={() => {setShowFilePathInput(true)}}>Change</button> }
</div>
</div>
<div className="px-4">
<button id="compileBtn" data-id="compilerContainerCompileBtn" className="btn btn-primary btn-block d-block w-100 text-break remixui_disabled mb-1 mt-3" onClick={compile} disabled={(configFilePath === '' && state.useFileConfiguration) || disableCompileButton}>
<OverlayTrigger overlay={ <OverlayTrigger overlay={
<Tooltip id="overlay-tooltip-compile"> <Tooltip id="overlay-tooltip-compile">
<div className="text-left"> <div className="text-left">
<div><b>Ctrl+S</b> for compiling</div> { !(configFilePath === '' && state.useFileConfiguration) && <div><b>Ctrl+S</b> for compiling</div> }
{ (configFilePath === '' && state.useFileConfiguration) && <div> No config file selected</div> }
</div> </div>
</Tooltip> </Tooltip>
}> }>
@ -651,11 +827,12 @@ export const CompilerContainer = (props: CompilerContainerProps) => {
</OverlayTrigger> </OverlayTrigger>
</button> </button>
<div className='d-flex align-items-center'> <div className='d-flex align-items-center'>
<button id="compileAndRunBtn" data-id="compilerContainerCompileAndRunBtn" className="btn btn-secondary btn-block d-block w-100 text-break remixui_solidityCompileAndRunButton d-inline-block remixui_disabled mb-1 mt-3" onClick={compileAndRun} disabled={disableCompileButton}> <button id="compileAndRunBtn" data-id="compilerContainerCompileAndRunBtn" className="btn btn-secondary btn-block d-block w-100 text-break remixui_solidityCompileAndRunButton d-inline-block remixui_disabled mb-1 mt-3" onClick={compileAndRun} disabled={(configFilePath === '' && state.useFileConfiguration) || disableCompileButton}>
<OverlayTrigger overlay={ <OverlayTrigger overlay={
<Tooltip id="overlay-tooltip-compile-run"> <Tooltip id="overlay-tooltip-compile-run">
<div className="text-left"> <div className="text-left">
<div><b>Ctrl+Shift+S</b> for compiling and script execution</div> { !(configFilePath === '' && state.useFileConfiguration) && <div><b>Ctrl+Shift+S</b> for compiling and script execution</div> }
{ (configFilePath === '' && state.useFileConfiguration) && <div> No config file selected</div> }
</div> </div>
</Tooltip> </Tooltip>
}> }>
@ -665,33 +842,32 @@ export const CompilerContainer = (props: CompilerContainerProps) => {
</OverlayTrigger> </OverlayTrigger>
</button> </button>
<OverlayTrigger overlay={ <OverlayTrigger overlay={
<Tooltip id="overlay-tooltip-compile-run-doc"> <Tooltip id="overlay-tooltip-compile-run-doc">
<div className="text-left p-2"> <div className="text-left p-2">
<div>Choose the script to execute right after compilation by adding the `dev-run-script` natspec tag, as in:</div> <div>Choose the script to execute right after compilation by adding the `dev-run-script` natspec tag, as in:</div>
<pre> <pre>
<code> <code>
/**<br /> /**<br />
* @title ContractName<br /> * @title ContractName<br />
* @dev ContractDescription<br /> * @dev ContractDescription<br />
* @custom:dev-run-script file_path<br /> * @custom:dev-run-script file_path<br />
*/<br /> */<br />
contract ContractName {'{}'}<br /> contract ContractName {'{}'}<br />
</code> </code>
</pre> </pre>
Click to know more Click to know more
</div> </div>
</Tooltip> </Tooltip>
}> }>
<a href="https://remix-ide.readthedocs.io/en/latest/running_js_scripts.html#compile-a-contract-and-run-a-script-on-the-fly" target="_blank" ><i className="pl-2 ml-2 mt-3 mb-1 fas fa-info text-dark"></i></a> <a href="https://remix-ide.readthedocs.io/en/latest/running_js_scripts.html#compile-a-contract-and-run-a-script-on-the-fly" target="_blank" ><i className="pl-2 ml-2 mt-3 mb-1 fas fa-info text-dark"></i></a>
</OverlayTrigger> </OverlayTrigger>
<CopyToClipboard tip="Copy tag to use in contract NatSpec" getContent={() => '@custom:dev-run-script file_path'} direction='top'> <CopyToClipboard tip="Copy tag to use in contract NatSpec" getContent={() => '@custom:dev-run-script file_path'} direction='top'>
<button className="btn remixui_copyButton ml-2 mt-3 mb-1 text-dark"> <button className="btn remixui_copyButton ml-2 mt-3 mb-1 text-dark">
<i className="remixui_copyIcon far fa-copy" aria-hidden="true"></i> <i className="remixui_copyIcon far fa-copy" aria-hidden="true"></i>
</button> </button>
</CopyToClipboard> </CopyToClipboard>
</div> </div>
</div> </div>
</header>
</article> </article>
</section> </section>
) )

@ -0,0 +1,18 @@
export const configFileContent = `
{
"language": "Solidity",
"settings": {
"optimizer": {
"enabled": true,
"runs": 200
},
"outputSelection": {
"*": {
"": ["ast"],
"*": ["abi", "metadata", "devdoc", "userdoc", "storageLayout", "evm.legacyAssembly", "evm.bytecode", "evm.deployedBytecode", "evm.methodIdentifiers", "evm.gasEstimates", "evm.assembly"]
}
},
"evmVersion": "byzantium"
}
}
`

@ -72,6 +72,15 @@
.remixui_compilerSection { .remixui_compilerSection {
padding: 12px 24px 16px; padding: 12px 24px 16px;
} }
.remixui_compilerConfigSection:hover {
cursor: pointer;
}
.remixui_compilerConfigSection {
font-size: 1rem;
}
.remixui_compilerConfigPath {
cursor: pointer;
}
.remixui_compilerLabel { .remixui_compilerLabel {
margin-bottom: 2px; margin-bottom: 2px;
font-size: 11px; font-size: 11px;

@ -18,11 +18,13 @@ export class CompileTabLogic {
public compilerImport public compilerImport
public event public event
public evmVersions: Array<string> public evmVersions: Array<string>
public useFileConfiguration: boolean
public configFilePath: string
constructor (public api: ICompilerApi, public contentImport) { constructor (public api: ICompilerApi, public contentImport) {
this.event = new EventEmitter() this.event = new EventEmitter()
this.compiler = new Compiler((url, cb) => api.resolveContentAndSave(url).then((result) => cb(null, result)).catch((error) => cb(error.message))) this.compiler = new Compiler((url, cb) => api.resolveContentAndSave(url).then((result) => cb(null, result)).catch((error) => cb(error.message)))
this.evmVersions = ['default', 'london', 'istanbul', 'petersburg', 'constantinople', 'byzantium', 'spuriousDragon', 'tangerineWhistle', 'homestead'] this.evmVersions = ['default', 'berlin', 'london', 'istanbul', 'petersburg', 'constantinople', 'byzantium', 'spuriousDragon', 'tangerineWhistle', 'homestead']
} }
init () { init () {
@ -52,12 +54,21 @@ export class CompileTabLogic {
} }
} }
setOptimize (newOptimizeValue) { setOptimize (newOptimizeValue: boolean) {
this.optimize = newOptimizeValue this.optimize = newOptimizeValue
this.api.setCompilerParameters({ optimize: this.optimize }) this.api.setCompilerParameters({ optimize: this.optimize })
this.compiler.set('optimize', this.optimize) this.compiler.set('optimize', this.optimize)
} }
setUseFileConfiguration (useFileConfiguration: boolean) {
this.useFileConfiguration = useFileConfiguration
this.compiler.set('useFileConfiguration', useFileConfiguration)
}
setConfigFilePath (path) {
this.configFilePath = path
}
setRuns (runs) { setRuns (runs) {
this.runs = runs this.runs = runs
this.api.setCompilerParameters({ runs: this.runs }) this.api.setCompilerParameters({ runs: this.runs })
@ -95,6 +106,11 @@ export class CompileTabLogic {
const sources = { [target]: { content } } const sources = { [target]: { content } }
this.event.emit('removeAnnotations') this.event.emit('removeAnnotations')
this.event.emit('startingCompilation') this.event.emit('startingCompilation')
if (this.configFilePath) {
this.api.readFile(this.configFilePath).then( contentConfig => {
this.compiler.set('configFileContent', contentConfig)
})
}
// setTimeout fix the animation on chrome... (animation triggered by 'staringCompilation') // setTimeout fix the animation on chrome... (animation triggered by 'staringCompilation')
setTimeout(() => { this.compiler.compile(sources, target); resolve(true) }, 100) setTimeout(() => { this.compiler.compile(sources, target); resolve(true) }, 100)
}).catch((error) => { }).catch((error) => {

@ -13,7 +13,9 @@ export const SolidityCompiler = (props: SolidityCompilerProps) => {
const [state, setState] = useState({ const [state, setState] = useState({
isHardhatProject: false, isHardhatProject: false,
isTruffleProject: false, isTruffleProject: false,
workspaceName: '',
currentFile, currentFile,
configFilePath: 'compiler_config.json',
loading: false, loading: false,
compileTabLogic: null, compileTabLogic: null,
compiler: null, compiler: null,
@ -63,13 +65,20 @@ export const SolidityCompiler = (props: SolidityCompilerProps) => {
}) })
} }
api.onSetWorkspace = async (isLocalhost: boolean) => { api.onSetWorkspace = async (isLocalhost: boolean, workspaceName: string) => {
const isHardhat = isLocalhost && await compileTabLogic.isHardhatProject() const isHardhat = isLocalhost && await compileTabLogic.isHardhatProject()
const isTruffle = await compileTabLogic.isTruffleProject() const isTruffle = await compileTabLogic.isTruffleProject()
setState(prevState => { setState(prevState => {
return { ...prevState, currentFile, isHardhatProject: isHardhat, isTruffleProject: isTruffle } return { ...prevState, currentFile, isHardhatProject: isHardhat, workspaceName: workspaceName, isTruffleProject: isTruffle }
}) })
} }
api.onFileRemoved = (path: string) => {
if (path === state.configFilePath)
setState(prevState => {
return { ...prevState, configFilePath: '' }
})
}
api.onNoFileSelected = () => { api.onNoFileSelected = () => {
setState(prevState => { setState(prevState => {
@ -102,6 +111,13 @@ export const SolidityCompiler = (props: SolidityCompilerProps) => {
setBadgeStatus({ ...badgeStatus, [currentFile]: data }) setBadgeStatus({ ...badgeStatus, [currentFile]: data })
} }
const setConfigFilePath = (path: string) => {
setState(prevState => {
return { ...prevState, configFilePath: path }
})
}
const toast = (message: string) => { const toast = (message: string) => {
setState(prevState => { setState(prevState => {
return { ...prevState, toasterMsg: message } return { ...prevState, toasterMsg: message }
@ -150,7 +166,20 @@ export const SolidityCompiler = (props: SolidityCompilerProps) => {
return ( return (
<> <>
<div id="compileTabView"> <div id="compileTabView">
<CompilerContainer api={api} isHardhatProject={state.isHardhatProject} isTruffleProject={state.isTruffleProject} compileTabLogic={compileTabLogic} tooltip={toast} modal={modal} compiledFileName={currentFile} updateCurrentVersion={updateCurrentVersion} configurationSettings={configurationSettings} /> <CompilerContainer
api={api}
isHardhatProject={state.isHardhatProject}
workspaceName={state.workspaceName}
isTruffleProject={state.isTruffleProject}
compileTabLogic={compileTabLogic}
tooltip={toast}
modal={modal}
compiledFileName={currentFile}
updateCurrentVersion={updateCurrentVersion}
configurationSettings={configurationSettings}
configFilePath={state.configFilePath}
setConfigFilePath={setConfigFilePath}
/>
{ contractsFile[currentFile] && contractsFile[currentFile].contractsDetails && <ContractSelection api={api} contractsDetails={contractsFile[currentFile].contractsDetails} contractList={contractsFile[currentFile].contractList} modal={modal} /> } { contractsFile[currentFile] && contractsFile[currentFile].contractsDetails && <ContractSelection api={api} contractsDetails={contractsFile[currentFile].contractsDetails} contractList={contractsFile[currentFile].contractList} modal={modal} /> }
{ compileErrors[currentFile] && { compileErrors[currentFile] &&
<div className="remixui_errorBlobs p-4" data-id="compiledErrors"> <div className="remixui_errorBlobs p-4" data-id="compiledErrors">

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

Loading…
Cancel
Save