desktopmerge
filip mertens 1 year ago
parent e42a392286
commit e9c806d750
  1. 46
      .circleci/config.yml
  2. 12
      .eslintrc.json
  3. 4
      .husky/pre-commit
  4. 13
      .prettierignore
  5. 7
      .prettierrc.json
  6. 18
      CONTRIBUTING.md
  7. 23
      README.md
  8. 59
      apps/circuit-compiler/project.json
  9. 17
      apps/circuit-compiler/src/app/app.tsx
  10. 224
      apps/circuit-compiler/src/app/services/circomPluginClient.ts
  11. 0
      apps/circuit-compiler/src/css/app.css
  12. 11
      apps/circuit-compiler/src/example/simple.circom
  13. 15
      apps/circuit-compiler/src/index.html
  14. 8
      apps/circuit-compiler/src/main.tsx
  15. 7
      apps/circuit-compiler/src/polyfills.ts
  16. 17
      apps/circuit-compiler/src/profile.json
  17. 24
      apps/circuit-compiler/tsconfig.app.json
  18. 17
      apps/circuit-compiler/tsconfig.json
  19. 92
      apps/circuit-compiler/webpack.config.js
  20. 12
      apps/debugger/src/app/app.tsx
  21. 1
      apps/debugger/src/app/debugger.ts
  22. 11
      apps/debugger/src/main.tsx
  23. 67
      apps/debugger/webpack.config.js
  24. 30
      apps/doc-gen/src/app/App.tsx
  25. 25
      apps/doc-gen/src/app/hooks/useLocalStorage.tsx
  26. 23
      apps/doc-gen/src/app/views/ErrorView.tsx
  27. 10
      apps/doc-gen/src/main.tsx
  28. 25
      apps/doc-gen/webpack.config.js
  29. 7
      apps/doc-viewer/src/app/App.tsx
  30. 4
      apps/doc-viewer/src/main.tsx
  31. 32
      apps/doc-viewer/webpack.config.js
  32. 20
      apps/etherscan/src/app/AppContext.tsx
  33. 83
      apps/etherscan/src/app/RemixPlugin.tsx
  34. 90
      apps/etherscan/src/app/app.tsx
  35. 60
      apps/etherscan/src/app/components/HeaderWithSettings.tsx
  36. 30
      apps/etherscan/src/app/components/SubmitButton.tsx
  37. 5
      apps/etherscan/src/app/hooks/useLocalStorage.tsx
  38. 10
      apps/etherscan/src/app/layouts/Default.tsx
  39. 16
      apps/etherscan/src/app/routes.tsx
  40. 68
      apps/etherscan/src/app/utils/networks.ts
  41. 2
      apps/etherscan/src/app/utils/verify.ts
  42. 106
      apps/etherscan/src/app/views/CaptureKeyView.tsx
  43. 16
      apps/etherscan/src/app/views/ErrorView.tsx
  44. 22
      apps/etherscan/src/app/views/HomeView.tsx
  45. 140
      apps/etherscan/src/app/views/ReceiptsView.tsx
  46. 325
      apps/etherscan/src/app/views/VerifyView.tsx
  47. 14
      apps/etherscan/src/main.tsx
  48. 64
      apps/etherscan/webpack.config.js
  49. 64
      apps/remix-ide-e2e/nightwatch.ts
  50. 10
      apps/remix-ide-e2e/package.json
  51. 6
      apps/remix-ide-e2e/src/commands/checkAnnotations.ts
  52. 6
      apps/remix-ide-e2e/src/commands/checkAnnotationsNotPresent.ts
  53. 39
      apps/remix-ide-e2e/src/commands/clickFunction.ts
  54. 4
      apps/remix-ide-e2e/src/commands/createContract.ts
  55. 86
      apps/remix-ide-e2e/src/commands/testConstantFunction.ts
  56. 104
      apps/remix-ide-e2e/src/local-plugin/src/app/app.tsx
  57. 8
      apps/remix-ide-e2e/src/local-plugin/src/app/logger.tsx
  58. 2
      apps/remix-ide-e2e/src/tests/blockchain.test.ts
  59. 52
      apps/remix-ide-e2e/src/tests/contract_flattener.test.ts
  60. 132
      apps/remix-ide-e2e/src/tests/editor.test.ts
  61. 87
      apps/remix-ide-e2e/src/tests/editorHoverContext.test.ts
  62. 88
      apps/remix-ide-e2e/src/tests/editorReferences.test.ts
  63. 27
      apps/remix-ide-e2e/src/tests/fileManager_api.test.ts
  64. 345
      apps/remix-ide-e2e/src/tests/proxy_oz_v4.test.ts
  65. 47
      apps/remix-ide-e2e/src/tests/proxy_oz_v5.test.ts
  66. 2
      apps/remix-ide-e2e/src/tests/remixd.test.ts
  67. 64
      apps/remix-ide-e2e/src/tests/sol2uml.test.ts
  68. 6
      apps/remix-ide-e2e/src/tests/solidityImport.test.ts
  69. 12
      apps/remix-ide-e2e/src/tests/solidityUnittests.test.ts
  70. 53
      apps/remix-ide-e2e/src/tests/staticAnalysis.test.ts
  71. 8
      apps/remix-ide-e2e/src/tests/terminal.test.ts
  72. 16
      apps/remix-ide-e2e/src/tests/transactionExecution.test.ts
  73. 15
      apps/remix-ide-e2e/src/tests/url.test.ts
  74. 2
      apps/remix-ide-e2e/src/tests/workspace.test.ts
  75. 186
      apps/remix-ide-e2e/src/types/index.d.ts
  76. 760
      apps/remix-ide-e2e/yarn.lock
  77. 4
      apps/remix-ide/background.js
  78. 94
      apps/remix-ide/ci/download_e2e_assets.js
  79. 12
      apps/remix-ide/ci/lint-targets.js
  80. 32
      apps/remix-ide/ci/splice_tests.js
  81. 2
      apps/remix-ide/project.json
  82. 4
      apps/remix-ide/release-process.md
  83. 214
      apps/remix-ide/src/app.js
  84. 26
      apps/remix-ide/src/app/components/hidden-panel.tsx
  85. 96
      apps/remix-ide/src/app/components/main-panel.tsx
  86. 276
      apps/remix-ide/src/app/components/preload.tsx
  87. 17
      apps/remix-ide/src/app/components/side-panel.tsx
  88. 70
      apps/remix-ide/src/app/components/vertical-icons.tsx
  89. 19
      apps/remix-ide/src/app/editor/editor.js
  90. 148
      apps/remix-ide/src/app/files/fileManager.ts
  91. 104
      apps/remix-ide/src/app/files/fileSystem.ts
  92. 308
      apps/remix-ide/src/app/files/filesystems/fileSystemUtility.ts
  93. 156
      apps/remix-ide/src/app/files/filesystems/indexedDB.ts
  94. 98
      apps/remix-ide/src/app/files/filesystems/localStorage.ts
  95. 14
      apps/remix-ide/src/app/panels/layout.ts
  96. 482
      apps/remix-ide/src/app/plugins/code-format.ts
  97. 64
      apps/remix-ide/src/app/plugins/code-format/index.ts
  98. 210
      apps/remix-ide/src/app/plugins/code-format/parser.ts
  99. 28
      apps/remix-ide/src/app/plugins/contractFlattener.tsx
  100. 118
      apps/remix-ide/src/app/plugins/file-decorator.ts
  101. Some files were not shown because too many files have changed in this diff Show More

@ -5,7 +5,7 @@ parameters:
type: boolean type: boolean
default: false default: false
orbs: orbs:
browser-tools: circleci/browser-tools@1.4.1 browser-tools: circleci/browser-tools@1.4.4
win: circleci/windows@5.0 win: circleci/windows@5.0
jobs: jobs:
build: build:
@ -286,8 +286,9 @@ jobs:
- browser-tools/install-browser-tools: - browser-tools/install-browser-tools:
install-firefox: false install-firefox: false
install-chrome: true install-chrome: true
install-chromedriver: false
install-geckodriver: false install-geckodriver: false
install-chromedriver: true - install-chromedriver-custom-linux
- run: google-chrome --version - run: google-chrome --version
- run: chromedriver --version - run: chromedriver --version
- run: rm LICENSE.chromedriver 2> /dev/null || true - run: rm LICENSE.chromedriver 2> /dev/null || true
@ -310,6 +311,11 @@ jobs:
- run: ls -la ./dist/apps/remix-ide/assets/js - run: ls -la ./dist/apps/remix-ide/assets/js
- run: yarn run selenium-install || yarn run selenium-install - run: yarn run selenium-install || yarn run selenium-install
- when:
condition:
equal: [ "chrome", << parameters.browser >> ]
steps:
- run: cp ~/bin/chromedriver /home/circleci/remix-project/node_modules/selenium-standalone/.selenium/chromedriver/latest-x64/
- run: - run:
name: Start Selenium name: Start Selenium
command: yarn run selenium command: yarn run selenium
@ -344,7 +350,8 @@ jobs:
install-firefox: false install-firefox: false
install-chrome: true install-chrome: true
install-geckodriver: false install-geckodriver: false
install-chromedriver: true install-chromedriver: false
- install-chromedriver-custom-linux
- run: google-chrome --version - run: google-chrome --version
- run: chromedriver --version - run: chromedriver --version
- run: rm LICENSE.chromedriver 2> /dev/null || true - run: rm LICENSE.chromedriver 2> /dev/null || true
@ -355,6 +362,7 @@ jobs:
- run: unzip ./persist/plugin-<< parameters.plugin >>.zip - run: unzip ./persist/plugin-<< parameters.plugin >>.zip
- run: yarn install --cwd ./apps/remix-ide-e2e --modules-folder ../../node_modules - run: yarn install --cwd ./apps/remix-ide-e2e --modules-folder ../../node_modules
- run: yarn run selenium-install || yarn run selenium-install - run: yarn run selenium-install || yarn run selenium-install
- run: cp ~/bin/chromedriver /home/circleci/remix-project/node_modules/selenium-standalone/.selenium/chromedriver/latest-x64/
- run: - run:
name: Start Selenium name: Start Selenium
command: yarn run selenium command: yarn run selenium
@ -522,3 +530,35 @@ workflows:
only: remix_beta only: remix_beta
# VS Code Extension Version: 1.5.1 # VS Code Extension Version: 1.5.1
commands:
install-chromedriver-custom-linux:
description: Custom script to install chromedriver with better version support for linux
steps:
- run:
name: install-chromedriver-custom-linux
command: |
google-chrome --version > version.txt
VERSION=$(grep -Eo '[0-9]+\.' < version.txt | head -1)
# CHROMEDRIVER_URL=$(curl -s 'https://googlechromelabs.github.io/chrome-for-testing/last-known-good-versions-with-downloads.json' | jq '.channels.Stable.downloads.chromedriver[] | select(.platform == "linux64") | .url' | tr -d '"')
CHROMEDRIVER_URL=$(curl -s 'https://googlechromelabs.github.io/chrome-for-testing/known-good-versions-with-downloads.json' | jq --arg v "$VERSION" '.versions[] | select(.version | startswith($v)) | .downloads.chromedriver[] | select(.platform == "linux64") | .url' | tail -n1 | tr -d '"')
echo $CHROMEDRIVER_URL
ZIPFILEPATH="/tmp/chromedriver.zip"
echo "Downloading from $CHROMEDRIVER_URL"
curl -f --silent $CHROMEDRIVER_URL > "$ZIPFILEPATH"
BINFILEPATH="$HOME/bin/chromedriver-linux"
echo "Extracting to $BINFILEPATH"
unzip -p "$ZIPFILEPATH" chromedriver-linux64/chromedriver > "$BINFILEPATH"
echo Setting execute flag
chmod +x "$BINFILEPATH"
echo Updating symlink
ln -nfs "$BINFILEPATH" ~/bin/chromedriver
echo Removing ZIP file
rm "$ZIPFILEPATH"
rm version.txt
echo Done
chromedriver -v

@ -47,7 +47,13 @@
"eslint-disable-next-line no-empty": "off", "eslint-disable-next-line no-empty": "off",
"no-empty": "off", "no-empty": "off",
"jsx-a11y/anchor-is-valid": "off", "jsx-a11y/anchor-is-valid": "off",
"@typescript-eslint/no-inferrable-types": "off" "@typescript-eslint/no-inferrable-types": "off",
"@typescript-eslint/no-unused-vars": "off",
"@typescript-eslint/no-explicit-any": "off",
"react-hooks/exhaustive-deps": "off",
"array-callback-return": "off",
"prefer-spread": "off",
"indent": ["error", 2]
} }
}, },
{ {
@ -58,7 +64,9 @@
"extends": [ "extends": [
"plugin:@nrwl/nx/javascript" "plugin:@nrwl/nx/javascript"
], ],
"rules": {} "rules": {
"indent": ["error", 2]
}
} }
], ],
"globals": { "globals": {

@ -0,0 +1,4 @@
#!/usr/bin/env sh
. "$(dirname -- "$0")/_/husky.sh"

@ -0,0 +1,13 @@
**/.yarn/*
# Ignore node_modules
./node_modules
# Ignore e2e files
./apps/remix-ide-e2e/*
# Ignore build artefacts
./dist
# Ignore all json files
**/*.json

@ -0,0 +1,7 @@
{
"tabWidth": 2,
"useTabs": false,
"printWidth": 180,
"semi": false,
"singleQuote": true
}

@ -93,3 +93,21 @@ But in some cases, the `id` prop may not be static. For example,
</h6> </h6>
``` ```
You can't be sure there is a match key in locale file or not. So it will be better to provide a `defaultMessage` prop. You can't be sure there is a match key in locale file or not. So it will be better to provide a `defaultMessage` prop.
### Should I update the non-english locale json files?
You probably will have this question when you are updating the english locale json files.
Well, that depends.
If you update an old json file, then you don't need to update it in other languages, because crowdin will do it for you.
But if you add a new json file, then you have to add it in other languages, and import it in `index.js` of all languages. Because crowdin will not update `index.js`, you have to do it manually.
### How to contribute on translations?
Remix is using crowdin to manage translations. If you want to contribute on that, you can do it on crowdin. Check the link below.
https://crowdin.com/project/remix-translation
There are many languages, just get into your language, and you will see a folder named `Remix UI`, where you can do the translations.
Not only can you do the translations, you can also review it. If you agree or disagree with some translations, you can vote yes or no. If you vote no, you can comment to explain why you vote no, and give your translation.

@ -12,7 +12,7 @@
[![GitHub contributors](https://img.shields.io/github/contributors/ethereum/remix-project?style=flat&logo=github)](https://github.com/ethereum/remix-project/blob/master/CONTRIBUTING.md) [![GitHub contributors](https://img.shields.io/github/contributors/ethereum/remix-project?style=flat&logo=github)](https://github.com/ethereum/remix-project/blob/master/CONTRIBUTING.md)
[![Awesome Remix](https://img.shields.io/badge/Awesome--Remix-resources-green?logo=awesomelists)](https://github.com/ethereum/awesome-remix) [![Awesome Remix](https://img.shields.io/badge/Awesome--Remix-resources-green?logo=awesomelists)](https://github.com/ethereum/awesome-remix)
![GitHub](https://img.shields.io/github/license/ethereum/remix-project) ![GitHub](https://img.shields.io/github/license/ethereum/remix-project)
[![Gitter Chat](https://img.shields.io/badge/Gitter%20-chat-brightgreen?style=plastic&logo=gitter)](https://gitter.im/ethereum/remix) [![Discord](https://img.shields.io/badge/join-discord-brightgreen.svg?style=flat&logo=discord)](https://discord.gg/q4vS2GVn)
[![Twitter Follow](https://img.shields.io/twitter/follow/ethereumremix?style=flat&logo=twitter&color=green)](https://twitter.com/ethereumremix) [![Twitter Follow](https://img.shields.io/twitter/follow/ethereumremix?style=flat&logo=twitter&color=green)](https://twitter.com/ethereumremix)
</div> </div>
@ -63,14 +63,13 @@ yarn global add nx
```bash ```bash
git clone https://github.com/ethereum/remix-project.git git clone https://github.com/ethereum/remix-project.git
``` ```
* Build `remix-project`: * Build and Run `remix-project`:
```bash
cd remix-project 1. Move to project directory: `cd remix-project`
yarn install 2. Install dependencies: `yarn install` or simply run `yarn`
yarn run build:libs // Build remix libs 3. Build Remix libraries: `yarn run build:libs`
nx build 4. Build Remix project: `yarn build`
nx serve 5. Build and run project server: `yarn serve`. Optionally, run `yarn serve:hot` to enable hot module reload for frontend updates.
```
Open `http://127.0.0.1:8080` in your browser to load Remix IDE locally. Open `http://127.0.0.1:8080` in your browser to load Remix IDE locally.
@ -151,7 +150,7 @@ To run the Selenium tests via Nightwatch:
- Install Selenium for the first time: `yarn run selenium-install` - Install Selenium for the first time: `yarn run selenium-install`
- Run a selenium server: `yarn run selenium` - Run a selenium server: `yarn run selenium`
- Build & Serve Remix: `nx serve` - Build & Serve Remix: `yarn serve`
- Run all the end-to-end tests: - Run all the end-to-end tests:
for Firefox: `yarn run nightwatch_local_firefox`, or for Firefox: `yarn run nightwatch_local_firefox`, or
@ -283,7 +282,9 @@ parameters:
## Important Links ## Important Links
- Official website: https://remix-project.org
- Official documentation: https://remix-ide.readthedocs.io/en/latest/ - Official documentation: https://remix-ide.readthedocs.io/en/latest/
- Curated list of Remix resources, tutorials etc.: https://github.com/ethereum/awesome-remix - Curated list of Remix resources: https://github.com/ethereum/awesome-remix
- Medium: https://medium.com/remix-ide - Medium: https://medium.com/remix-ide
- Twitter: https://twitter.com/ethereumremix - Twitter: https://twitter.com/ethereumremix
- Join Discord: https://discord.gg/q4vS2GVn

@ -0,0 +1,59 @@
{
"name": "circuit-compiler",
"$schema": "../../node_modules/nx/schemas/project-schema.json",
"sourceRoot": "apps/circuit-compiler/src",
"projectType": "application",
"implicitDependencies": ["remixd"],
"targets": {
"build": {
"executor": "@nrwl/webpack:webpack",
"outputs": ["{options.outputPath}"],
"defaultConfiguration": "development",
"options": {
"compiler": "babel",
"outputPath": "dist/apps/circuit-compiler",
"index": "apps/circuit-compiler/src/index.html",
"baseHref": "./",
"main": "apps/circuit-compiler/src/main.tsx",
"polyfills": "apps/circuit-compiler/src/polyfills.ts",
"tsConfig": "apps/circuit-compiler/tsconfig.app.json",
"assets": ["apps/circuit-compiler/src/profile.json"],
"styles": ["apps/circuit-compiler/src/css/app.css"],
"scripts": [],
"webpackConfig": "apps/circuit-compiler/webpack.config.js"
},
"configurations": {
"development": {
},
"production": {
"fileReplacements": [
{
"replace": "apps/circuit-compiler/src/environments/environment.ts",
"with": "apps/circuit-compiler/src/environments/environment.prod.ts"
}
]
}
}
},
"serve": {
"executor": "@nrwl/webpack:dev-server",
"defaultConfiguration": "development",
"options": {
"buildTarget": "circuit-compiler:build",
"hmr": true,
"baseHref": "/"
},
"configurations": {
"development": {
"buildTarget": "circuit-compiler:build:development",
"port": 2023
},
"production": {
"buildTarget": "circuit-compiler:build:production"
}
}
}
},
"tags": []
}

@ -0,0 +1,17 @@
import React, { useEffect } from 'react'
import { CircomPluginClient } from './services/circomPluginClient'
function App() {
useEffect(() => {
new CircomPluginClient()
}, [])
return (
<div className="App">
</div>
)
}
export default App

@ -0,0 +1,224 @@
import {PluginClient} from '@remixproject/plugin'
import {createClient} from '@remixproject/plugin-webview'
import EventManager from 'events'
import pathModule from 'path'
import {parse} from 'circom_wasm'
export class CircomPluginClient extends PluginClient {
public internalEvents: EventManager
constructor() {
super()
createClient(this)
this.internalEvents = new EventManager()
this.methods = ['init', 'parse']
this.onload()
}
init(): void {
console.log('initializing circom plugin...')
}
onActivation(): void {
// @ts-ignore
this.on('editor', 'contentChanged', (path: string, fileContent) => {
if (path.endsWith('.circom')) {
this.parse(path, fileContent)
}
})
}
async parse(path: string, fileContent: string): Promise<void> {
let buildFiles = {
[path]: fileContent
}
buildFiles = await this.resolveDependencies(path, fileContent, buildFiles)
const parsedOutput = parse(path, buildFiles)
try {
const result = JSON.parse(parsedOutput)
if (result.length === 0) {
// @ts-ignore
await this.call('editor', 'clearErrorMarkers', [path])
} else {
const markers = []
for (const report of result) {
for (const label in report.labels) {
if (report.labels[label].file_id === '0') {
// @ts-ignore
const startPosition: {lineNumber: number; column: number} =
await this.call(
'editor',
// @ts-ignore
'getPositionAt',
report.labels[label].range.start
)
// @ts-ignore
const endPosition: {lineNumber: number; column: number} =
await this.call(
'editor',
// @ts-ignore
'getPositionAt',
report.labels[label].range.end
)
markers.push({
message: report.message,
severity: report.type.toLowerCase(),
position: {
start: {
line: startPosition.lineNumber,
column: startPosition.column
},
end: {
line: endPosition.lineNumber,
column: endPosition.column
}
},
file: path
})
}
}
}
if (markers.length > 0) {
// @ts-ignore
await this.call('editor', 'addErrorMarker', markers)
} else {
// @ts-ignore
await this.call('editor', 'clearErrorMarkers', [path])
}
}
} catch (e) {
console.log(e)
}
}
async resolveDependencies(
filePath: string,
fileContent: string,
output = {},
depPath: string = '',
blackPath: string[] = []
): Promise<Record<string, string>> {
// extract all includes
const includes = (fileContent.match(/include ['"].*['"]/g) || []).map(
(include) => include.replace(/include ['"]/g, '').replace(/['"]/g, '')
)
await Promise.all(
includes.map(async (include) => {
// fix for endless recursive includes
if (blackPath.includes(include)) return
let dependencyContent = ''
let path = include
// @ts-ignore
const pathExists = await this.call('fileManager', 'exists', path)
if (pathExists) {
// fetch file content if include import (path) exists within same level as current file opened in editor
dependencyContent = await this.call('fileManager', 'readFile', path)
} else {
// if include import (path) does not exist, try to construct relative path using the original file path (current file opened in editor)
let relativePath = pathModule.resolve(
filePath.slice(0, filePath.lastIndexOf('/')),
include
)
if (relativePath.indexOf('/') === 0)
relativePath = relativePath.slice(1)
const relativePathExists = await this.call(
'fileManager',
// @ts-ignore
'exists',
relativePath
)
if (relativePathExists) {
// fetch file content if include import exists as a relative path
dependencyContent = await this.call(
'fileManager',
'readFile',
relativePath
)
} else {
if (depPath) {
// if depPath is provided, try to resolve include import from './deps' folder in remix
path = pathModule.resolve(
depPath.slice(0, depPath.lastIndexOf('/')),
include
)
if (path.indexOf('/') === 0) path = path.slice(1)
dependencyContent = await this.call(
'contentImport',
'resolveAndSave',
path,
null
)
} else {
if (include.startsWith('circomlib')) {
// try to resolve include import from github if it is a circomlib dependency
const splitInclude = include.split('/')
const version = splitInclude[1].match(/v[0-9]+.[0-9]+.[0-9]+/g)
if (version && version[0]) {
path = `https://raw.githubusercontent.com/iden3/circomlib/${
version[0]
}/circuits/${splitInclude.slice(2).join('/')}`
dependencyContent = await this.call(
'contentImport',
'resolveAndSave',
path,
null
)
} else {
path = `https://raw.githubusercontent.com/iden3/circomlib/master/circuits/${splitInclude
.slice(1)
.join('/')}`
dependencyContent = await this.call(
'contentImport',
'resolveAndSave',
path,
null
)
}
} else {
// If all import cases are not true, use the default import to try fetching from node_modules and unpkg
dependencyContent = await this.call(
'contentImport',
'resolveAndSave',
path,
null
)
}
}
}
}
// extract all includes from the dependency content
const dependencyIncludes = (
dependencyContent.match(/include ['"].*['"]/g) || []
).map((include) =>
include.replace(/include ['"]/g, '').replace(/['"]/g, '')
)
blackPath.push(include)
// recursively resolve all dependencies of the dependency
if (dependencyIncludes.length > 0) {
await this.resolveDependencies(
filePath,
dependencyContent,
output,
path,
blackPath
)
output[include] = dependencyContent
} else {
output[include] = dependencyContent
}
})
)
return output
}
}

@ -0,0 +1,11 @@
pragma circom 2.0.0;
template Multiplier2() {
signal input a;
signal input b;
signal output c;
c <== a*b;
}
component main = Multiplier2();

@ -0,0 +1,15 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<title>Circuit - Compiler</title>
<base href="./" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<link rel="stylesheet" href="https://use.fontawesome.com/releases/v5.8.1/css/all.css" integrity="sha384-50oBUHEmvpQ+1lW4y57PTFmhCaXp0ML5d60M1M7uH2+nqUivzIebhndOJK28anvf" crossorigin="anonymous"/>
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.2.3/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-rbsA2VBKQhggwzxH7pPCaAqO46MgnOM80zW1RWuH61DGLwZJEdK2Kadq2F9CUG65" crossorigin="anonymous">
</head>
<body>
<div id="root"></div>
</body>
</html>

@ -0,0 +1,8 @@
import React from 'react'
import ReactDOM from 'react-dom'
import App from './app/app'
ReactDOM.render(
<App />,
document.getElementById('root')
)

@ -0,0 +1,7 @@
/**
* Polyfill stable language features. These imports will be optimized by `@babel/preset-env`.
*
* See: https://github.com/zloirock/core-js#babel
*/
import 'core-js/stable';
import 'regenerator-runtime/runtime';

@ -0,0 +1,17 @@
{
"name": "circuit-compiler",
"kind": "provider",
"displayName": "Circuit Compiler",
"events": [],
"version": "2.0.0",
"methods": ["init", "parse"],
"canActivate": [],
"url": "",
"description": "Enables circuit compilation and computing a witness for ZK proofs",
"icon": "https://docs.circom.io/assets/images/favicon.png",
"location": "hiddenPanel",
"documentation": "",
"repo": "https://github.com/ethereum/remix-project/tree/master/apps/circuit-compiler",
"maintainedBy": "Remix",
"authorContact": ""
}

@ -0,0 +1,24 @@
{
"extends": "./tsconfig.json",
"compilerOptions": {
"outDir": "../../dist/out-tsc",
"types": ["node"]
},
"files": [
"../../node_modules/@nrwl/react/typings/cssmodule.d.ts",
"../../node_modules/@nrwl/react/typings/image.d.ts"
],
"exclude": [
"jest.config.ts",
"**/*.spec.ts",
"**/*.test.ts",
"**/*.spec.tsx",
"**/*.test.tsx",
"**/*.spec.js",
"**/*.test.js",
"**/*.spec.jsx",
"**/*.test.jsx"
],
"include": ["**/*.js", "**/*.jsx", "**/*.ts", "**/*.tsx"]
}

@ -0,0 +1,17 @@
{
"extends": "../../tsconfig.base.json",
"compilerOptions": {
"jsx": "react-jsx",
"allowJs": true,
"esModuleInterop": true,
"allowSyntheticDefaultImports": true
},
"files": [],
"include": [],
"references": [
{
"path": "./tsconfig.app.json"
}
]
}

@ -0,0 +1,92 @@
const { composePlugins, withNx } = require('@nrwl/webpack')
const webpack = require('webpack')
const TerserPlugin = require("terser-webpack-plugin")
const CssMinimizerPlugin = require("css-minimizer-webpack-plugin")
// Nx plugins for webpack.
module.exports = composePlugins(withNx(), (config) => {
// Update the webpack config as needed here.
// e.g. `config.plugins.push(new MyPlugin())`
// add fallback for node modules
config.resolve.fallback = {
...config.resolve.fallback,
"crypto": require.resolve("crypto-browserify"),
"stream": require.resolve("stream-browserify"),
"path": require.resolve("path-browserify"),
"http": require.resolve("stream-http"),
"https": require.resolve("https-browserify"),
"constants": require.resolve("constants-browserify"),
"os": false, //require.resolve("os-browserify/browser"),
"timers": false, // require.resolve("timers-browserify"),
"zlib": require.resolve("browserify-zlib"),
"fs": false,
"module": false,
"tls": false,
"net": false,
"readline": false,
"child_process": false,
"buffer": require.resolve("buffer/"),
"vm": require.resolve('vm-browserify'),
}
// add externals
config.externals = {
...config.externals,
solc: 'solc',
}
// add public path
config.output.publicPath = './'
// add copy & provide plugin
config.plugins.push(
new webpack.ProvidePlugin({
Buffer: ['buffer', 'Buffer'],
url: ['url', 'URL'],
process: 'process/browser',
})
)
// set the define plugin to load the WALLET_CONNECT_PROJECT_ID
config.plugins.push(
new webpack.DefinePlugin({
WALLET_CONNECT_PROJECT_ID: JSON.stringify(process.env.WALLET_CONNECT_PROJECT_ID),
})
)
// souce-map loader
config.module.rules.push({
test: /\.js$/,
use: ["source-map-loader"],
enforce: "pre"
})
config.ignoreWarnings = [/Failed to parse source map/] // ignore source-map-loader warnings
// set minimizer
config.optimization.minimizer = [
new TerserPlugin({
parallel: true,
terserOptions: {
ecma: 2015,
compress: false,
mangle: false,
format: {
comments: false,
},
},
extractComments: false,
}),
new CssMinimizerPlugin(),
];
config.watchOptions = {
ignored: /node_modules/
}
config.experiments.syncWebAssembly = true
return config;
});

@ -1,8 +1,8 @@
import React, { useState, useEffect } from 'react'; import React, {useState, useEffect} from 'react'
import { DebuggerUI } from '@remix-ui/debugger-ui' // eslint-disable-line import {DebuggerUI} from '@remix-ui/debugger-ui' // eslint-disable-line
import { DebuggerClientApi } from './debugger' import {DebuggerClientApi} from './debugger'
const remix = new DebuggerClientApi() const remix = new DebuggerClientApi()
@ -11,7 +11,7 @@ export const App = () => {
<div className="debugger"> <div className="debugger">
<DebuggerUI debuggerAPI={remix} /> <DebuggerUI debuggerAPI={remix} />
</div> </div>
); )
}; }
export default App; export default App

@ -28,4 +28,3 @@ export class DebuggerClientApi extends DebuggerApiMixin(PluginClient) {
onStartDebugging: (debuggerBackend: any) => void // called when debug starts onStartDebugging: (debuggerBackend: any) => void // called when debug starts
onStopDebugging: () => void // called when debug stops onStopDebugging: () => void // called when debug stops
} }

@ -1,9 +1,6 @@
import React from 'react'; import React from 'react'
import ReactDOM from 'react-dom'; import ReactDOM from 'react-dom'
import App from './app/app'; import App from './app/app'
ReactDOM.render( ReactDOM.render(<App />, document.getElementById('root'))
<App />,
document.getElementById('root')
);

@ -1,8 +1,7 @@
const { composePlugins, withNx } = require('@nrwl/webpack') const {composePlugins, withNx} = require('@nrwl/webpack')
const webpack = require('webpack') const webpack = require('webpack')
const TerserPlugin = require("terser-webpack-plugin") const TerserPlugin = require('terser-webpack-plugin')
const CssMinimizerPlugin = require("css-minimizer-webpack-plugin") const CssMinimizerPlugin = require('css-minimizer-webpack-plugin')
// Nx plugins for webpack. // Nx plugins for webpack.
module.exports = composePlugins(withNx(), (config) => { module.exports = composePlugins(withNx(), (config) => {
@ -12,56 +11,52 @@ module.exports = composePlugins(withNx(), (config) => {
// add fallback for node modules // add fallback for node modules
config.resolve.fallback = { config.resolve.fallback = {
...config.resolve.fallback, ...config.resolve.fallback,
"crypto": require.resolve("crypto-browserify"), crypto: require.resolve('crypto-browserify'),
"stream": require.resolve("stream-browserify"), stream: require.resolve('stream-browserify'),
"path": require.resolve("path-browserify"), path: require.resolve('path-browserify'),
"http": require.resolve("stream-http"), http: require.resolve('stream-http'),
"https": require.resolve("https-browserify"), https: require.resolve('https-browserify'),
"constants": require.resolve("constants-browserify"), constants: require.resolve('constants-browserify'),
"os": false, //require.resolve("os-browserify/browser"), os: false, //require.resolve("os-browserify/browser"),
"timers": false, // require.resolve("timers-browserify"), timers: false, // require.resolve("timers-browserify"),
"zlib": require.resolve("browserify-zlib"), zlib: require.resolve('browserify-zlib'),
"fs": false, fs: false,
"module": false, module: false,
"tls": false, tls: false,
"net": false, net: false,
"readline": false, readline: false,
"child_process": false, child_process: false,
"buffer": require.resolve("buffer/"), buffer: require.resolve('buffer/'),
"vm": require.resolve('vm-browserify'), vm: require.resolve('vm-browserify')
} }
// add externals // add externals
config.externals = { config.externals = {
...config.externals, ...config.externals,
solc: 'solc', solc: 'solc'
} }
// add public path // add public path
config.output.publicPath = '/' config.output.publicPath = '/'
// add copy & provide plugin // add copy & provide plugin
config.plugins.push( config.plugins.push(
new webpack.ProvidePlugin({ new webpack.ProvidePlugin({
Buffer: ['buffer', 'Buffer'], Buffer: ['buffer', 'Buffer'],
url: ['url', 'URL'], url: ['url', 'URL'],
process: 'process/browser', process: 'process/browser'
}) })
) )
// souce-map loader // souce-map loader
config.module.rules.push({ config.module.rules.push({
test: /\.js$/, test: /\.js$/,
use: ["source-map-loader"], use: ['source-map-loader'],
enforce: "pre" enforce: 'pre'
}) })
config.ignoreWarnings = [/Failed to parse source map/] // ignore source-map-loader warnings config.ignoreWarnings = [/Failed to parse source map/] // ignore source-map-loader warnings
// set minimizer // set minimizer
config.optimization.minimizer = [ config.optimization.minimizer = [
new TerserPlugin({ new TerserPlugin({
@ -71,17 +66,17 @@ module.exports = composePlugins(withNx(), (config) => {
compress: false, compress: false,
mangle: false, mangle: false,
format: { format: {
comments: false, comments: false
}, }
}, },
extractComments: false, extractComments: false
}), }),
new CssMinimizerPlugin(), new CssMinimizerPlugin()
]; ]
config.watchOptions = { config.watchOptions = {
ignored: /node_modules/ ignored: /node_modules/
} }
return config; return config
}); })

@ -4,13 +4,13 @@ import './App.css'
import { DocGenClient } from './docgen-client' import { DocGenClient } from './docgen-client'
import { Build } from './docgen/site' import { Build } from './docgen/site'
export const client = new DocGenClient() export const client = new DocGenClient()
const App = () => { const App = () => {
// eslint-disable-next-line @typescript-eslint/no-unused-vars // eslint-disable-next-line @typescript-eslint/no-unused-vars
const [themeType, setThemeType] = useState<string>('dark'); const [themeType, setThemeType] = useState<string>('dark')
const [hasBuild, setHasBuild] = useState<boolean>(false); const [hasBuild, setHasBuild] = useState<boolean>(false)
const [fileName, setFileName] = useState<string>(''); const [fileName, setFileName] = useState<string>('')
useEffect(() => { useEffect(() => {
const watchThemeSwitch = async () => { const watchThemeSwitch = async () => {
@ -21,19 +21,27 @@ const App = () => {
setHasBuild(true) setHasBuild(true)
setFileName(fileName) setFileName(fileName)
}) })
client.eventEmitter.on('docsGenerated', (docs: string[]) => { client.eventEmitter.on('docsGenerated', (docs: string[]) => {})
})
} }
watchThemeSwitch() watchThemeSwitch()
}, []) }, [])
return ( return (
<div className="p-3"> <div className="p-3">
<h5 className="h-5 mb-3">Compile a Solidity contract and generate its documentation as Markdown. (Right-click on a contract in the File Explorer and select "Generate Docs" from the context menu.).</h5> <h5 className="h-5 mb-3">
{fileName && <div className="border-bottom border-top px-2 py-3 justify-center align-items-center d-flex"> Compile a Solidity contract and generate its documentation as Markdown. (Right-click on a contract in the File
<h6>File: {fileName}</h6> Explorer and select "Generate Docs" from the context menu.).
</div>} </h5>
{hasBuild && <button className="btn btn-primary btn-block mt-4" onClick={() => client.generateDocs()}>Generate Docs</button>} {fileName && (
<div className="border-bottom border-top px-2 py-3 justify-center align-items-center d-flex">
<h6>File: {fileName}</h6>
</div>
)}
{hasBuild && (
<button className="btn btn-primary btn-block mt-4" onClick={() => client.generateDocs()}>
Generate Docs
</button>
)}
</div> </div>
) )
} }

@ -1,4 +1,4 @@
import { useState } from "react"; import { useState } from 'react'
export function useLocalStorage(key: string, initialValue: any) { export function useLocalStorage(key: string, initialValue: any) {
// State to store our value // State to store our value
@ -6,32 +6,31 @@ export function useLocalStorage(key: string, initialValue: any) {
const [storedValue, setStoredValue] = useState(() => { const [storedValue, setStoredValue] = useState(() => {
try { try {
// Get from local storage by key // Get from local storage by key
const item = window.localStorage.getItem(key); const item = window.localStorage.getItem(key)
// Parse stored json or if none return initialValue // Parse stored json or if none return initialValue
return item ? JSON.parse(item) : initialValue; return item ? JSON.parse(item) : initialValue
} catch (error) { } catch (error) {
// If error also return initialValue // If error also return initialValue
console.log(error); console.log(error)
return initialValue; return initialValue
} }
}); })
// Return a wrapped version of useState's setter function that ... // Return a wrapped version of useState's setter function that ...
// ... persists the new value to localStorage. // ... persists the new value to localStorage.
const setValue = (value: any) => { const setValue = (value: any) => {
try { try {
// Allow value to be a function so we have same API as useState // Allow value to be a function so we have same API as useState
const valueToStore = const valueToStore = value instanceof Function ? value(storedValue) : value
value instanceof Function ? value(storedValue) : value;
// Save state // Save state
setStoredValue(valueToStore); setStoredValue(valueToStore)
// Save to local storage // Save to local storage
window.localStorage.setItem(key, JSON.stringify(valueToStore)); window.localStorage.setItem(key, JSON.stringify(valueToStore))
} catch (error) { } catch (error) {
// A more advanced implementation would handle the error case // A more advanced implementation would handle the error case
console.log(error); console.log(error)
} }
}; }
return [storedValue, setValue]; return [storedValue, setValue]
} }

@ -1,31 +1,28 @@
import React from "react"; import React from 'react'
export const ErrorView: React.FC = () => { export const ErrorView: React.FC = () => {
return ( return (
<div <div
style={{ style={{
width: "100%", width: '100%',
display: "flex", display: 'flex',
flexDirection: "column", flexDirection: 'column',
alignItems: "center", alignItems: 'center',
}} }}
> >
<img <img
style={{ paddingBottom: "2em" }} style={{ paddingBottom: '2em' }}
width="250" width="250"
src="https://res.cloudinary.com/key-solutions/image/upload/v1580400635/solid/error-png.png" src="https://res.cloudinary.com/key-solutions/image/upload/v1580400635/solid/error-png.png"
alt="Error page" alt="Error page"
/> />
<h5>Sorry, something unexpected happened. </h5> <h5>Sorry, something unexpected happened. </h5>
<h5> <h5>
Please raise an issue:{" "} Please raise an issue:{' '}
<a <a style={{ color: 'red' }} href="https://github.com/Machinalabs/remix-ethdoc-plugin/issues">
style={{ color: "red" }}
href="https://github.com/Machinalabs/remix-ethdoc-plugin/issues"
>
Here Here
</a> </a>
</h5> </h5>
</div> </div>
); )
}; }

@ -1,11 +1,11 @@
import React from "react"; import React from 'react'
import ReactDOM from "react-dom"; import ReactDOM from 'react-dom'
import App from "./app/App"; import App from './app/App'
// import { Routes } from "./routes"; // import { Routes } from "./routes";
ReactDOM.render( ReactDOM.render(
<React.StrictMode> <React.StrictMode>
<App /> <App />
</React.StrictMode>, </React.StrictMode>,
document.getElementById("root") document.getElementById('root'),
); )

@ -1,19 +1,19 @@
const { composePlugins, withNx } = require('@nrwl/webpack') const { composePlugins, withNx } = require('@nrwl/webpack')
const { withReact } = require('@nrwl/react') const { withReact } = require('@nrwl/react')
const webpack = require('webpack') const webpack = require('webpack')
const TerserPlugin = require("terser-webpack-plugin") const TerserPlugin = require('terser-webpack-plugin')
const CssMinimizerPlugin = require("css-minimizer-webpack-plugin") const CssMinimizerPlugin = require('css-minimizer-webpack-plugin')
// Nx plugins for webpack. // Nx plugins for webpack.
module.exports = composePlugins(withNx(), withReact(), (config) => { module.exports = composePlugins(withNx(), withReact(), config => {
// Update the webpack config as needed here. // Update the webpack config as needed here.
// e.g. `config.plugins.push(new MyPlugin())` // e.g. `config.plugins.push(new MyPlugin())`
// add fallback for node modules // add fallback for node modules
config.resolve.fallback = { config.resolve.fallback = {
...config.resolve.fallback, ...config.resolve.fallback,
"path": require.resolve("path-browserify"), path: require.resolve('path-browserify'),
"fs": false, fs: false,
} }
// add externals // add externals
@ -24,10 +24,9 @@ module.exports = composePlugins(withNx(), withReact(), (config) => {
config.module.rules.push({ config.module.rules.push({
test: /\.hbs$/, test: /\.hbs$/,
type: 'asset/source' type: 'asset/source',
}) })
// add public path // add public path
config.output.publicPath = '/' config.output.publicPath = '/'
@ -38,16 +37,14 @@ module.exports = composePlugins(withNx(), withReact(), (config) => {
url: ['url', 'URL'], url: ['url', 'URL'],
process: 'process/browser', process: 'process/browser',
}), }),
new webpack.DefinePlugin({ new webpack.DefinePlugin({}),
}),
) )
// souce-map loader // souce-map loader
config.module.rules.push({ config.module.rules.push({
test: /\.js$/, test: /\.js$/,
use: ["source-map-loader"], use: ['source-map-loader'],
enforce: "pre" enforce: 'pre',
}) })
config.ignoreWarnings = [/Failed to parse source map/] // ignore source-map-loader warnings config.ignoreWarnings = [/Failed to parse source map/] // ignore source-map-loader warnings
@ -67,7 +64,7 @@ module.exports = composePlugins(withNx(), withReact(), (config) => {
extractComments: false, extractComments: false,
}), }),
new CssMinimizerPlugin(), new CssMinimizerPlugin(),
]; ]
return config; return config
}) })

@ -1,5 +1,5 @@
import React, { useEffect, useState } from "react" import React, {useEffect, useState} from 'react'
import { DocViewer } from "./docviewer" import {DocViewer} from './docviewer'
import ReactMarkdown from 'react-markdown' import ReactMarkdown from 'react-markdown'
import remarkGfm from 'remark-gfm' import remarkGfm from 'remark-gfm'
@ -11,12 +11,11 @@ export default function App() {
client.eventEmitter.on('contentsReady', (fileContents: string) => { client.eventEmitter.on('contentsReady', (fileContents: string) => {
setContents(fileContents) setContents(fileContents)
}) })
}, []) }, [])
return ( return (
<> <>
<div className="m-5 p-2"> <div className="m-5 p-2">
<ReactMarkdown children={contents} remarkPlugins={[remarkGfm]}/> <ReactMarkdown children={contents} remarkPlugins={[remarkGfm]} />
</div> </div>
</> </>
) )

@ -6,5 +6,5 @@ ReactDOM.render(
<React.StrictMode> <React.StrictMode>
<App /> <App />
</React.StrictMode>, </React.StrictMode>,
document.getElementById("root") document.getElementById('root')
); )

@ -1,8 +1,8 @@
const { composePlugins, withNx } = require('@nrwl/webpack') const {composePlugins, withNx} = require('@nrwl/webpack')
const { withReact } = require('@nrwl/react') const {withReact} = require('@nrwl/react')
const webpack = require('webpack') const webpack = require('webpack')
const TerserPlugin = require("terser-webpack-plugin") const TerserPlugin = require('terser-webpack-plugin')
const CssMinimizerPlugin = require("css-minimizer-webpack-plugin") const CssMinimizerPlugin = require('css-minimizer-webpack-plugin')
// Nx plugins for webpack. // Nx plugins for webpack.
module.exports = composePlugins(withNx(), withReact(), (config) => { module.exports = composePlugins(withNx(), withReact(), (config) => {
@ -12,7 +12,7 @@ module.exports = composePlugins(withNx(), withReact(), (config) => {
// add externals // add externals
config.externals = { config.externals = {
...config.externals, ...config.externals,
solc: 'solc', solc: 'solc'
} }
// add public path // add public path
@ -23,18 +23,16 @@ module.exports = composePlugins(withNx(), withReact(), (config) => {
new webpack.ProvidePlugin({ new webpack.ProvidePlugin({
Buffer: ['buffer', 'Buffer'], Buffer: ['buffer', 'Buffer'],
url: ['url', 'URL'], url: ['url', 'URL'],
process: 'process/browser', process: 'process/browser'
}),
new webpack.DefinePlugin({
}), }),
new webpack.DefinePlugin({})
) )
// souce-map loader // souce-map loader
config.module.rules.push({ config.module.rules.push({
test: /\.js$/, test: /\.js$/,
use: ["source-map-loader"], use: ['source-map-loader'],
enforce: "pre" enforce: 'pre'
}) })
config.ignoreWarnings = [/Failed to parse source map/] // ignore source-map-loader warnings config.ignoreWarnings = [/Failed to parse source map/] // ignore source-map-loader warnings
@ -48,13 +46,13 @@ module.exports = composePlugins(withNx(), withReact(), (config) => {
compress: false, compress: false,
mangle: false, mangle: false,
format: { format: {
comments: false, comments: false
}, }
}, },
extractComments: false, extractComments: false
}), }),
new CssMinimizerPlugin(), new CssMinimizerPlugin()
]; ]
return config; return config
}) })

@ -1,24 +1,24 @@
import React from "react" import React from 'react'
import { PluginClient } from "@remixproject/plugin" import {PluginClient} from '@remixproject/plugin'
import { Receipt, ThemeType } from "./types" import {Receipt, ThemeType} from './types'
export const AppContext = React.createContext({ export const AppContext = React.createContext({
apiKey: "", apiKey: '',
setAPIKey: (value: string) => { setAPIKey: (value: string) => {
console.log("Set API Key from Context") console.log('Set API Key from Context')
}, },
clientInstance: {} as PluginClient, clientInstance: {} as PluginClient,
receipts: [] as Receipt[], receipts: [] as Receipt[],
setReceipts: (receipts: Receipt[]) => { setReceipts: (receipts: Receipt[]) => {
console.log("Calling Set Receipts") console.log('Calling Set Receipts')
}, },
contracts: [] as string[], contracts: [] as string[],
setContracts: (contracts: string[]) => { setContracts: (contracts: string[]) => {
console.log("Calling Set Contract Names") console.log('Calling Set Contract Names')
}, },
themeType: "dark" as ThemeType, themeType: 'dark' as ThemeType,
setThemeType: (themeType: ThemeType) => { setThemeType: (themeType: ThemeType) => {
console.log("Calling Set Theme Type") console.log('Calling Set Theme Type')
}, }
}) })

@ -1,40 +1,59 @@
import { PluginClient } from '@remixproject/plugin'; import {PluginClient} from '@remixproject/plugin'
import { verify, EtherScanReturn } from './utils/verify'; import {verify, EtherScanReturn} from './utils/verify'
import { getReceiptStatus, getEtherScanApi, getNetworkName, getProxyContractReceiptStatus } from './utils'; import {getReceiptStatus, getEtherScanApi, getNetworkName, getProxyContractReceiptStatus} from './utils'
export class RemixClient extends PluginClient { export class RemixClient extends PluginClient {
loaded() {
return this.onload()
}
loaded() { async verify(
return this.onload() apiKey: string,
} contractAddress: string,
contractArguments: string,
async verify (apiKey: string, contractAddress: string, contractArguments: string, contractName: string, compilationResultParam: any, chainRef?: number | string, isProxyContract?: boolean, expectedImplAddress?: string) { contractName: string,
const result = await verify(apiKey, contractAddress, contractArguments, contractName, compilationResultParam, chainRef, isProxyContract, expectedImplAddress, this, compilationResultParam: any,
(value: EtherScanReturn) => {}, (value: string) => {}) chainRef?: number | string,
return result isProxyContract?: boolean,
} expectedImplAddress?: string
) {
const result = await verify(
apiKey,
contractAddress,
contractArguments,
contractName,
compilationResultParam,
chainRef,
isProxyContract,
expectedImplAddress,
this,
(value: EtherScanReturn) => {},
(value: string) => {}
)
return result
}
async receiptStatus (receiptGuid: string, apiKey: string, isProxyContract: boolean) { async receiptStatus(receiptGuid: string, apiKey: string, isProxyContract: boolean) {
try { try {
const { network, networkId } = await getNetworkName(this) const {network, networkId} = await getNetworkName(this)
if (network === "vm") { if (network === 'vm') {
throw new Error("Cannot check the receipt status in the selected network") throw new Error('Cannot check the receipt status in the selected network')
} }
const etherscanApi = getEtherScanApi(networkId) const etherscanApi = getEtherScanApi(networkId)
let receiptStatus let receiptStatus
if (isProxyContract) receiptStatus = await getProxyContractReceiptStatus(receiptGuid, apiKey, etherscanApi) if (isProxyContract) receiptStatus = await getProxyContractReceiptStatus(receiptGuid, apiKey, etherscanApi)
else receiptStatus = await getReceiptStatus(receiptGuid, apiKey, etherscanApi) else receiptStatus = await getReceiptStatus(receiptGuid, apiKey, etherscanApi)
return { return {
message: receiptStatus.result, message: receiptStatus.result,
succeed: receiptStatus.status === '0' ? false : true succeed: receiptStatus.status === '0' ? false : true
} }
} catch (e: any){ } catch (e: any) {
return { return {
status: 'error', status: 'error',
message: e.message, message: e.message,
succeed: false succeed: false
}
} }
} }
}
} }

@ -1,22 +1,19 @@
import React, { useState, useEffect, useRef } from "react" import React, {useState, useEffect, useRef} from 'react'
import { import {CompilationFileSources, CompilationResult} from '@remixproject/plugin-api'
CompilationFileSources,
CompilationResult,
} from "@remixproject/plugin-api"
import { RemixClient } from "./RemixPlugin"; import {RemixClient} from './RemixPlugin'
import { createClient } from "@remixproject/plugin-webview"; import {createClient} from '@remixproject/plugin-webview'
import { AppContext } from "./AppContext" import {AppContext} from './AppContext'
import { DisplayRoutes } from "./routes" import {DisplayRoutes} from './routes'
import { useLocalStorage } from "./hooks/useLocalStorage" import {useLocalStorage} from './hooks/useLocalStorage'
import { getReceiptStatus, getEtherScanApi, getNetworkName, getProxyContractReceiptStatus } from "./utils" import {getReceiptStatus, getEtherScanApi, getNetworkName, getProxyContractReceiptStatus} from './utils'
import { Receipt, ThemeType } from "./types" import {Receipt, ThemeType} from './types'
import "./App.css" import './App.css'
export const getNewContractNames = (compilationResult: CompilationResult) => { export const getNewContractNames = (compilationResult: CompilationResult) => {
const compiledContracts = compilationResult.contracts const compiledContracts = compilationResult.contracts
@ -31,11 +28,11 @@ export const getNewContractNames = (compilationResult: CompilationResult) => {
} }
const App = () => { const App = () => {
const [apiKey, setAPIKey] = useLocalStorage("apiKey", "") const [apiKey, setAPIKey] = useLocalStorage('apiKey', '')
const [clientInstance, setClientInstance] = useState(undefined as any) const [clientInstance, setClientInstance] = useState(undefined as any)
const [receipts, setReceipts] = useLocalStorage("receipts", []) const [receipts, setReceipts] = useLocalStorage('receipts', [])
const [contracts, setContracts] = useState([] as string[]) const [contracts, setContracts] = useState([] as string[])
const [themeType, setThemeType] = useState("dark" as ThemeType) const [themeType, setThemeType] = useState('dark' as ThemeType)
const timer = useRef(null) const timer = useRef(null)
const clientInstanceRef = useRef(clientInstance) const clientInstanceRef = useRef(clientInstance)
@ -49,26 +46,15 @@ const App = () => {
const loadClient = async () => { const loadClient = async () => {
await client.onload() await client.onload()
setClientInstance(client) setClientInstance(client)
client.on("solidity", client.on('solidity', 'compilationFinished', (fileName: string, source: CompilationFileSources, languageVersion: string, data: CompilationResult) => {
"compilationFinished", const newContractsNames = getNewContractNames(data)
(
fileName: string, const newContractsToSave: string[] = [...contractsRef.current, ...newContractsNames]
source: CompilationFileSources,
languageVersion: string, const uniqueContracts: string[] = [...new Set(newContractsToSave)]
data: CompilationResult
) => { setContracts(uniqueContracts)
const newContractsNames = getNewContractNames(data) })
const newContractsToSave: string[] = [
...contractsRef.current,
...newContractsNames,
]
const uniqueContracts: string[] = [...new Set(newContractsToSave)]
setContracts(uniqueContracts)
}
)
//const currentTheme = await client.call("theme", "currentTheme") //const currentTheme = await client.call("theme", "currentTheme")
//setThemeType(currentTheme.quality) //setThemeType(currentTheme.quality)
@ -82,7 +68,7 @@ const App = () => {
useEffect(() => { useEffect(() => {
let receiptsNotVerified: Receipt[] = receipts.filter((item: Receipt) => { let receiptsNotVerified: Receipt[] = receipts.filter((item: Receipt) => {
return item.status === "Pending in queue" || item.status === "Max rate limit reached" return item.status === 'Pending in queue' || item.status === 'Max rate limit reached'
}) })
if (receiptsNotVerified.length > 0) { if (receiptsNotVerified.length > 0) {
@ -91,41 +77,31 @@ const App = () => {
timer.current = null timer.current = null
} }
timer.current = setInterval(async () => { timer.current = setInterval(async () => {
const { network, networkId } = await getNetworkName(clientInstanceRef.current) const {network, networkId} = await getNetworkName(clientInstanceRef.current)
if (!clientInstanceRef.current) { if (!clientInstanceRef.current) {
return return
} }
if (network === "vm") { if (network === 'vm') {
return return
} }
let newReceipts = receipts let newReceipts = receipts
for (const item of receiptsNotVerified) { for (const item of receiptsNotVerified) {
await new Promise(r => setTimeout(r, 500)) // avoid api rate limit exceed. await new Promise((r) => setTimeout(r, 500)) // avoid api rate limit exceed.
let status let status
if (item.isProxyContract) { if (item.isProxyContract) {
status = await getProxyContractReceiptStatus( status = await getProxyContractReceiptStatus(item.guid, apiKey, getEtherScanApi(networkId))
item.guid,
apiKey,
getEtherScanApi(networkId)
)
if (status.status === '1') { if (status.status === '1') {
status.message = status.result status.message = status.result
status.result = 'Successfully Updated' status.result = 'Successfully Updated'
} }
} else } else status = await getReceiptStatus(item.guid, apiKey, getEtherScanApi(networkId))
status = await getReceiptStatus( if (status.result === 'Pass - Verified' || status.result === 'Already Verified' || status.result === 'Successfully Updated') {
item.guid,
apiKey,
getEtherScanApi(networkId)
)
if (status.result === "Pass - Verified" || status.result === "Already Verified" ||
status.result === "Successfully Updated") {
newReceipts = newReceipts.map((currentReceipt: Receipt) => { newReceipts = newReceipts.map((currentReceipt: Receipt) => {
if (currentReceipt.guid === item.guid) { if (currentReceipt.guid === item.guid) {
let res = { const res = {
...currentReceipt, ...currentReceipt,
status: status.result, status: status.result
} }
if (currentReceipt.isProxyContract) res.message = status.message if (currentReceipt.isProxyContract) res.message = status.message
return res return res
@ -135,7 +111,7 @@ const App = () => {
} }
} }
receiptsNotVerified = newReceipts.filter((item: Receipt) => { receiptsNotVerified = newReceipts.filter((item: Receipt) => {
return item.status === "Pending in queue" || item.status === "Max rate limit reached" return item.status === 'Pending in queue' || item.status === 'Max rate limit reached'
}) })
if (timer.current && receiptsNotVerified.length === 0) { if (timer.current && receiptsNotVerified.length === 0) {
clearInterval(timer.current) clearInterval(timer.current)
@ -157,7 +133,7 @@ const App = () => {
contracts, contracts,
setContracts, setContracts,
themeType, themeType,
setThemeType, setThemeType
}} }}
> >
<DisplayRoutes /> <DisplayRoutes />

@ -1,8 +1,8 @@
import React from "react" import React from 'react'
import { NavLink } from "react-router-dom" import {NavLink} from 'react-router-dom'
import { CustomTooltip } from '@remix-ui/helper' import {CustomTooltip} from '@remix-ui/helper'
import { AppContext } from "../AppContext" import {AppContext} from '../AppContext'
interface Props { interface Props {
title?: string title?: string
@ -13,79 +13,67 @@ interface IconProps {
from: string from: string
} }
const HomeIcon: React.FC<IconProps> = ({ from }: IconProps) => { const HomeIcon: React.FC<IconProps> = ({from}: IconProps) => {
return ( return (
<NavLink <NavLink
data-id="home" data-id="home"
to={{ to={{
pathname: "/" pathname: '/'
}} }}
className={({ isActive }) => isActive ? "btn p-0 m-0" : "btn text-dark p-0 m-0"} className={({isActive}) => (isActive ? 'border border-secondary shadow-none btn p-1 m-0' : 'border-0 shadow-none btn p-1 m-0')}
state={ from } style={({isActive}) => (!isActive ? {width: '1.8rem', filter: 'contrast(0.5)'} : {width: '1.8rem'})}
state={from}
> >
<CustomTooltip <CustomTooltip tooltipText="Home" tooltipId="etherscan-nav-home" placement="bottom">
tooltipText='Home'
tooltipId='etherscan-nav-home'
placement='bottom'
>
<i className="fas fa-home"></i> <i className="fas fa-home"></i>
</CustomTooltip> </CustomTooltip>
</NavLink> </NavLink>
) )
} }
const ReceiptsIcon: React.FC<IconProps> = ({ from }: IconProps) => { const ReceiptsIcon: React.FC<IconProps> = ({from}: IconProps) => {
return ( return (
<NavLink <NavLink
data-id="receipts" data-id="receipts"
to={{ to={{
pathname: "/receipts" pathname: '/receipts'
}} }}
className={({ isActive }) => isActive ? "btn p-0 m-0 mx-2" : "btn text-dark p-0 m-0 mx-2"} className={({isActive}) => (isActive ? 'border border-secondary shadow-none btn p-1 m-0' : 'border-0 shadow-none btn p-1 m-0')}
state={ from } style={({isActive}) => (!isActive ? {width: '1.8rem', filter: 'contrast(0.5)'} : {width: '1.8rem'})}
state={from}
> >
<CustomTooltip <CustomTooltip tooltipText="Receipts" tooltipId="etherscan-nav-receipts" placement="bottom">
tooltipText='Receipts'
tooltipId='etherscan-nav-receipts'
placement='bottom'
>
<i className="fas fa-receipt"></i> <i className="fas fa-receipt"></i>
</CustomTooltip> </CustomTooltip>
</NavLink> </NavLink>
) )
} }
const SettingsIcon: React.FC<IconProps> = ({ from }: IconProps) => { const SettingsIcon: React.FC<IconProps> = ({from}: IconProps) => {
return ( return (
<NavLink <NavLink
data-id="settings" data-id="settings"
to={{ to={{
pathname: "/settings" pathname: '/settings'
}} }}
className={({ isActive }) => isActive ? "btn p-0 m-0" : "btn text-dark p-0 m-0"} className={({isActive}) => (isActive ? 'border border-secondary shadow-none btn p-1 m-0' : 'border-0 shadow-none btn p-1 m-0')}
state= {from} style={({isActive}) => (!isActive ? {width: '1.8rem', filter: 'contrast(0.5)'} : {width: '1.8rem'})}
state={from}
> >
<CustomTooltip <CustomTooltip tooltipText="Settings" tooltipId="etherscan-nav-settings" placement="bottom">
tooltipText='Settings'
tooltipId='etherscan-nav-settings'
placement='bottom'
>
<i className="fas fa-cog"></i> <i className="fas fa-cog"></i>
</CustomTooltip> </CustomTooltip>
</NavLink> </NavLink>
) )
} }
export const HeaderWithSettings: React.FC<Props> = ({ export const HeaderWithSettings: React.FC<Props> = ({title = '', from}) => {
title = "",
from,
}) => {
return ( return (
<AppContext.Consumer> <AppContext.Consumer>
{() => ( {() => (
<div className="d-flex justify-content-between"> <div className="d-flex justify-content-between">
<h6 className="d-inline">{title}</h6> <h6 className="d-inline">{title}</h6>
<div> <div className="nav">
<HomeIcon from={from} /> <HomeIcon from={from} />
<ReceiptsIcon from={from} /> <ReceiptsIcon from={from} />
<SettingsIcon from={from} /> <SettingsIcon from={from} />

@ -1,5 +1,5 @@
import React from "react" import React from 'react'
import { CustomTooltip } from '@remix-ui/helper' import {CustomTooltip} from '@remix-ui/helper'
interface Props { interface Props {
text: string text: string
@ -8,35 +8,21 @@ interface Props {
disable?: boolean disable?: boolean
} }
export const SubmitButton: React.FC<Props> = ({ export const SubmitButton: React.FC<Props> = ({text, dataId, isSubmitting = false, disable = true}) => {
text,
dataId,
isSubmitting = false,
disable = true
}) => {
return ( return (
<div> <div>
<button <button data-id={dataId} type="submit" className="btn btn-primary btn-block p-1 text-decoration-none" disabled={disable}>
data-id={dataId}
type="submit"
className="btn btn-primary btn-block p-1 text-decoration-none"
disabled={disable}
>
<CustomTooltip <CustomTooltip
tooltipText={disable ? "Fill in the valid value(s) and select a supported network" : "Click to proceed"} tooltipText={disable ? 'Fill in the valid value(s) and select a supported network' : 'Click to proceed'}
tooltipId={'etherscan-submit-button-'+ dataId} tooltipId={'etherscan-submit-button-' + dataId}
tooltipTextClasses="border bg-light text-dark p-1 pr-3" tooltipTextClasses="border bg-light text-dark p-1 pr-3"
placement='bottom' placement="bottom"
> >
<div> <div>
{!isSubmitting && text} {!isSubmitting && text}
{isSubmitting && ( {isSubmitting && (
<div> <div>
<span <span className="spinner-border spinner-border-sm mr-1" role="status" aria-hidden="true" />
className="spinner-border spinner-border-sm mr-1"
role="status"
aria-hidden="true"
/>
Verifying... Please wait Verifying... Please wait
</div> </div>
)} )}

@ -1,4 +1,4 @@
import { useState } from "react" import {useState} from 'react'
export function useLocalStorage(key: string, initialValue: any) { export function useLocalStorage(key: string, initialValue: any) {
// State to store our value // State to store our value
@ -21,8 +21,7 @@ export function useLocalStorage(key: string, initialValue: any) {
const setValue = (value: any) => { const setValue = (value: any) => {
try { try {
// Allow value to be a function so we have same API as useState // Allow value to be a function so we have same API as useState
const valueToStore = const valueToStore = value instanceof Function ? value(storedValue) : value
value instanceof Function ? value(storedValue) : value
// Save state // Save state
setStoredValue(valueToStore) setStoredValue(valueToStore)
// Save to local storage // Save to local storage

@ -1,17 +1,13 @@
import React, { PropsWithChildren } from "react" import React, {PropsWithChildren} from 'react'
import { HeaderWithSettings } from "../components" import {HeaderWithSettings} from '../components'
interface Props { interface Props {
from: string from: string
title?: string title?: string
} }
export const DefaultLayout: React.FC<PropsWithChildren<Props>> = ({ export const DefaultLayout: React.FC<PropsWithChildren<Props>> = ({children, from, title}) => {
children,
from,
title
}) => {
return ( return (
<div> <div>
<HeaderWithSettings from={from} title={title} /> <HeaderWithSettings from={from} title={title} />

@ -1,13 +1,8 @@
import React from "react" import React from 'react'
import { import {HashRouter as Router, Route, Routes, RouteProps} from 'react-router-dom'
HashRouter as Router,
Route,
Routes,
RouteProps,
} from "react-router-dom"
import { ErrorView, HomeView, ReceiptsView, CaptureKeyView } from "./views" import {ErrorView, HomeView, ReceiptsView, CaptureKeyView} from './views'
import { DefaultLayout } from "./layouts" import {DefaultLayout} from './layouts'
interface Props extends RouteProps { interface Props extends RouteProps {
component: any // TODO: new (props: any) => React.Component component: any // TODO: new (props: any) => React.Component
@ -25,8 +20,7 @@ export const DisplayRoutes = () => (
</DefaultLayout> </DefaultLayout>
} }
/> />
<Route path="/error" <Route path="/error" element={<ErrorView />} />
element={<ErrorView />} />
<Route <Route
path="/receipts" path="/receipts"
element={ element={

@ -1,36 +1,38 @@
export const scanAPIurls = { export const scanAPIurls = {
// all mainnet // all mainnet
1: "https://api.etherscan.io/api", 1: 'https://api.etherscan.io/api',
56: "https://api.bscscan.com/api", 56: 'https://api.bscscan.com/api',
137: "https://api.polygonscan.com/api", 137: 'https://api.polygonscan.com/api',
250: "https://api.ftmscan.com/api", 250: 'https://api.ftmscan.com/api',
42161: "https://api.arbiscan.io/api", 42161: 'https://api.arbiscan.io/api',
43114: "https://api.snowtrace.io/api", 43114: 'https://api.snowtrace.io/api',
1285: "https://api-moonriver.moonscan.io/api", 1285: 'https://api-moonriver.moonscan.io/api',
1284: "https://api-moonbeam.moonscan.io/api", 1284: 'https://api-moonbeam.moonscan.io/api',
25: "https://api.cronoscan.com/api", 25: 'https://api.cronoscan.com/api',
199: "https://api.bttcscan.com/api", 199: 'https://api.bttcscan.com/api',
10: "https://api-optimistic.etherscan.io/api", 10: 'https://api-optimistic.etherscan.io/api',
42220: "https://api.celoscan.io/api", 42220: 'https://api.celoscan.io/api',
288: "https://api.bobascan.com/api", 288: 'https://api.bobascan.com/api',
100: "https://api.gnosisscan.io/api", 100: 'https://api.gnosisscan.io/api',
1101: "https://api-zkevm.polygonscan.com/api", 1101: 'https://api-zkevm.polygonscan.com/api',
59144: 'https://api.lineascan.build/api',
// all testnet // all testnet
5: "https://api-goerli.etherscan.io/api", 5: 'https://api-goerli.etherscan.io/api',
11155111: "https://api-sepolia.etherscan.io/api", 11155111: 'https://api-sepolia.etherscan.io/api',
97: "https://api-testnet.bscscan.com/api", 97: 'https://api-testnet.bscscan.com/api',
80001: "https://api-testnet.polygonscan.com/api", 80001: 'https://api-testnet.polygonscan.com/api',
4002: "https://api-testnet.ftmscan.com/api", 4002: 'https://api-testnet.ftmscan.com/api',
421611: "https://api-testnet.arbiscan.io/api", 421611: 'https://api-testnet.arbiscan.io/api',
42170: "https://api-nova.arbiscan.io/api", 42170: 'https://api-nova.arbiscan.io/api',
43113: "https://api-testnet.snowtrace.io/api", 43113: 'https://api-testnet.snowtrace.io/api',
1287: "https://api-moonbase.moonscan.io/api", 1287: 'https://api-moonbase.moonscan.io/api',
338: "https://api-testnet.cronoscan.com/api", 338: 'https://api-testnet.cronoscan.com/api',
1028: "https://api-testnet.bttcscan.com/api", 1028: 'https://api-testnet.bttcscan.com/api',
420: "https://api-goerli-optimistic.etherscan.io/api", 420: 'https://api-goerli-optimistic.etherscan.io/api',
44787: "https://api-alfajores.celoscan.io/api", 44787: 'https://api-alfajores.celoscan.io/api',
2888: "https://api-testnet.bobascan.com/api", 2888: 'https://api-testnet.bobascan.com/api',
84531: "https://api-goerli.basescan.org/api", 84531: 'https://api-goerli.basescan.org/api',
1442: "https://api-testnet-zkevm.polygonscan.com/api" 1442: 'https://api-testnet-zkevm.polygonscan.com/api',
59140: 'https://api-testnet.lineascan.build/api',
} }

@ -133,7 +133,7 @@ export const verify = async (
const returnValue = { const returnValue = {
guid: result, guid: result,
status: receiptStatus.result, status: receiptStatus.result,
message: `Verification process started correctly. Receipt GUID ${result}`, message: `Verification request submitted successfully. Use this receipt GUID ${result} to track the status of your submission`,
succeed: true, succeed: true,
isProxyContract isProxyContract
} }

@ -1,67 +1,63 @@
import React from "react" import React, {useState} from 'react'
import { Formik, ErrorMessage, Field } from "formik" import {Formik, ErrorMessage, Field} from 'formik'
import { useNavigate, useLocation } from "react-router-dom" import {useNavigate, useLocation} from 'react-router-dom'
import { AppContext } from "../AppContext" import {AppContext} from '../AppContext'
import { SubmitButton } from "../components" import {SubmitButton} from '../components'
export const CaptureKeyView: React.FC = () => { export const CaptureKeyView: React.FC = () => {
const location = useLocation() const location = useLocation()
const navigate = useNavigate() const navigate = useNavigate()
const [msg, setMsg] = useState('')
return ( return (
<AppContext.Consumer> <AppContext.Consumer>
{({ apiKey, clientInstance, setAPIKey }) => { {({apiKey, clientInstance, setAPIKey}) => {
if (!apiKey && clientInstance && clientInstance.call) { if (!apiKey) setMsg('Please provide a 34-character API key to continue')
clientInstance.call('sidePanel' as any, 'currentFocus').then((current) => { return (
if (current === 'etherscan') clientInstance.call('notification' as any, 'toast', 'Please add API key to continue') <div>
}) <Formik
} initialValues={{apiKey}}
return <Formik validate={(values) => {
initialValues={{ apiKey }} const errors = {} as any
validate={(values) => { if (!values.apiKey) {
const errors = {} as any errors.apiKey = 'Required'
if (!values.apiKey) { } else if (values.apiKey.length !== 34) {
errors.apiKey = "Required" errors.apiKey = 'API key should be 34 characters long'
} }
return errors return errors
}} }}
onSubmit={(values) => { onSubmit={(values) => {
const apiKey = values.apiKey const apiKey = values.apiKey
if (apiKey.length === 34) { if (apiKey.length === 34) {
setAPIKey(values.apiKey) setAPIKey(values.apiKey)
clientInstance.call('notification' as any, 'toast', 'API key saved successfully!!!') navigate(location && location.state ? location.state : '/')
navigate((location && location.state ? location.state : '/')) }
} else clientInstance.call('notification' as any, 'toast', 'API key should be 34 characters long') }}
}} >
> {({errors, touched, handleSubmit}) => (
{({ errors, touched, handleSubmit }) => ( <form onSubmit={handleSubmit}>
<form onSubmit={handleSubmit}> <div className="form-group mb-2">
<div className="form-group mb-2"> <label htmlFor="apikey">API Key</label>
<label htmlFor="apikey">API Key</label> <Field
<Field className={errors.apiKey && touched.apiKey ? 'form-control form-control-sm is-invalid' : 'form-control form-control-sm'}
className={ type="password"
errors.apiKey && touched.apiKey name="apiKey"
? "form-control form-control-sm is-invalid" placeholder="e.g. GM1T20XY6JGSAPWKDCYZ7B2FJXKTJRFVGZ"
: "form-control form-control-sm" />
} <ErrorMessage className="invalid-feedback" name="apiKey" component="div" />
type="password" </div>
name="apiKey"
placeholder="e.g. GM1T20XY6JGSAPWKDCYZ7B2FJXKTJRFVGZ"
/>
<ErrorMessage
className="invalid-feedback"
name="apiKey"
component="div"
/>
</div>
<div> <div>
<SubmitButton text="Save" dataId="save-api-key" disable={false} /> <SubmitButton text="Save" dataId="save-api-key" disable={errors && errors.apiKey ? true : false} />
</div> </div>
</form> </form>
)} )}
</Formik> </Formik>
<div data-id="api-key-result" className="text-primary mt-4 text-center" style={{fontSize: '0.8em'}} dangerouslySetInnerHTML={{__html: msg}} />
</div>
)
}} }}
</AppContext.Consumer> </AppContext.Consumer>
) )

@ -1,21 +1,13 @@
import React from "react" import React from 'react'
export const ErrorView: React.FC = () => { export const ErrorView: React.FC = () => {
return ( return (
<div className="d-flex w-100 flex-column align-items-center"> <div className="d-flex w-100 flex-column align-items-center">
<img <img className="pb-4" width="250" src="https://res.cloudinary.com/key-solutions/image/upload/v1580400635/solid/error-png.png" alt="Error page" />
className="pb-4"
width="250"
src="https://res.cloudinary.com/key-solutions/image/upload/v1580400635/solid/error-png.png"
alt="Error page"
/>
<h5>Sorry, something unexpected happened.</h5> <h5>Sorry, something unexpected happened.</h5>
<h5> <h5>
Please raise an issue:{" "} Please raise an issue:{' '}
<a <a className="text-danger" href="https://github.com/ethereum/remix-project/issues">
className="text-danger"
href="https://github.com/ethereum/remix-project/issues"
>
Here Here
</a> </a>
</h5> </h5>

@ -1,25 +1,20 @@
import React from "react" import React from 'react'
import { Navigate } from "react-router-dom" import {Navigate} from 'react-router-dom'
import { AppContext } from "../AppContext" import {AppContext} from '../AppContext'
import { Receipt } from "../types" import {Receipt} from '../types'
import { VerifyView } from "./VerifyView" import {VerifyView} from './VerifyView'
export const HomeView: React.FC = () => { export const HomeView: React.FC = () => {
return ( return (
<AppContext.Consumer> <AppContext.Consumer>
{({ apiKey, clientInstance, setReceipts, receipts, contracts }) => { {({apiKey, clientInstance, setReceipts, receipts, contracts}) => {
if (!apiKey && clientInstance && clientInstance.call) {
clientInstance.call('sidePanel' as any, 'currentFocus').then((current) => {
if (current === 'etherscan') clientInstance.call('notification' as any, 'toast', 'Please add API key to continue')
})
}
return !apiKey ? ( return !apiKey ? (
<Navigate <Navigate
to={{ to={{
pathname: "/settings" pathname: '/settings'
}} }}
/> />
) : ( ) : (
@ -33,8 +28,7 @@ export const HomeView: React.FC = () => {
}} }}
/> />
) )
} }}
}
</AppContext.Consumer> </AppContext.Consumer>
) )
} }

@ -1,13 +1,13 @@
import React, { useState } from "react" import React, {useState} from 'react'
import { Formik, ErrorMessage, Field } from "formik" import {Formik, ErrorMessage, Field} from 'formik'
import { getEtherScanApi, getNetworkName, getReceiptStatus, getProxyContractReceiptStatus } from "../utils" import {getEtherScanApi, getNetworkName, getReceiptStatus, getProxyContractReceiptStatus} from '../utils'
import { Receipt } from "../types" import {Receipt} from '../types'
import { AppContext } from "../AppContext" import {AppContext} from '../AppContext'
import { SubmitButton } from "../components" import {SubmitButton} from '../components'
import { Navigate } from "react-router-dom" import {Navigate} from 'react-router-dom'
import { Button } from "react-bootstrap" import {Button} from 'react-bootstrap'
import { CustomTooltip } from '@remix-ui/helper' import {CustomTooltip} from '@remix-ui/helper'
interface FormValues { interface FormValues {
receiptGuid: string receiptGuid: string
@ -17,38 +17,25 @@ export const ReceiptsView: React.FC = () => {
const [results, setResults] = useState({succeed: false, message: ''}) const [results, setResults] = useState({succeed: false, message: ''})
const [isProxyContractReceipt, setIsProxyContractReceipt] = useState(false) const [isProxyContractReceipt, setIsProxyContractReceipt] = useState(false)
const onGetReceiptStatus = async ( const onGetReceiptStatus = async (values: FormValues, clientInstance: any, apiKey: string) => {
values: FormValues,
clientInstance: any,
apiKey: string
) => {
try { try {
const { network, networkId } = await getNetworkName(clientInstance) const {network, networkId} = await getNetworkName(clientInstance)
if (network === "vm") { if (network === 'vm') {
setResults({ setResults({
succeed: false, succeed: false,
message: "Cannot verify in the selected network" message: 'Cannot verify in the selected network'
}) })
return return
} }
const etherscanApi = getEtherScanApi(networkId) const etherscanApi = getEtherScanApi(networkId)
let result let result
if (isProxyContractReceipt) { if (isProxyContractReceipt) {
result = await getProxyContractReceiptStatus( result = await getProxyContractReceiptStatus(values.receiptGuid, apiKey, etherscanApi)
values.receiptGuid,
apiKey,
etherscanApi
)
if (result.status === '1') { if (result.status === '1') {
result.message = result.result result.message = result.result
result.result = 'Successfully Updated' result.result = 'Successfully Updated'
} }
} else } else result = await getReceiptStatus(values.receiptGuid, apiKey, etherscanApi)
result = await getReceiptStatus(
values.receiptGuid,
apiKey,
etherscanApi
)
setResults({ setResults({
succeed: result.status === '1' ? true : false, succeed: result.status === '1' ? true : false,
message: result.result || (result.status === '0' ? 'Verification failed' : result.message) message: result.result || (result.status === '0' ? 'Verification failed' : result.message)
@ -63,51 +50,36 @@ export const ReceiptsView: React.FC = () => {
return ( return (
<AppContext.Consumer> <AppContext.Consumer>
{({ apiKey, clientInstance, receipts, setReceipts }) => { {({apiKey, clientInstance, receipts, setReceipts}) => {
if (!apiKey && clientInstance && clientInstance.call) {
clientInstance.call('sidePanel' as any, 'currentFocus').then((current) => {
if (current === 'etherscan') clientInstance.call('notification' as any, 'toast', 'Please add API key to continue')
})
}
return !apiKey ? ( return !apiKey ? (
<Navigate <Navigate
to={{ to={{
pathname: "/settings" pathname: '/settings'
}} }}
/> />
) : ( ) : (
<div> <div>
<Formik <Formik
initialValues={{ receiptGuid: "" }} initialValues={{receiptGuid: ''}}
validate={(values) => { validate={(values) => {
const errors = {} as any const errors = {} as any
if (!values.receiptGuid) { if (!values.receiptGuid) {
errors.receiptGuid = "Required" errors.receiptGuid = 'Required'
} }
return errors return errors
}} }}
onSubmit={(values) => onSubmit={(values) => onGetReceiptStatus(values, clientInstance, apiKey)}
onGetReceiptStatus(values, clientInstance, apiKey)
}
> >
{({ errors, touched, handleSubmit, handleChange }) => ( {({errors, touched, handleSubmit, handleChange}) => (
<form onSubmit={handleSubmit}> <form onSubmit={handleSubmit}>
<div className="form-group mb-2"> <div className="form-group mb-2">
<label htmlFor="receiptGuid">Receipt GUID</label> <label htmlFor="receiptGuid">Receipt GUID</label>
<Field <Field
className={ className={errors.receiptGuid && touched.receiptGuid ? 'form-control form-control-sm is-invalid' : 'form-control form-control-sm'}
errors.receiptGuid && touched.receiptGuid
? "form-control form-control-sm is-invalid"
: "form-control form-control-sm"
}
type="text" type="text"
name="receiptGuid" name="receiptGuid"
/> />
<ErrorMessage <ErrorMessage className="invalid-feedback" name="receiptGuid" component="div" />
className="invalid-feedback"
name="receiptGuid"
component="div"
/>
</div> </div>
<div className="d-flex mb-2 custom-control custom-checkbox"> <div className="d-flex mb-2 custom-control custom-checkbox">
@ -120,37 +92,44 @@ export const ReceiptsView: React.FC = () => {
handleChange(e) handleChange(e)
if (e.target.checked) setIsProxyContractReceipt(true) if (e.target.checked) setIsProxyContractReceipt(true)
else setIsProxyContractReceipt(false) else setIsProxyContractReceipt(false)
}} }}
/> />
<label className="form-check-label custom-control-label" htmlFor="isProxyReceipt">It's a proxy contract GUID</label> <label className="form-check-label custom-control-label" htmlFor="isProxyReceipt">
It's a proxy contract GUID
</label>
</div> </div>
<SubmitButton text="Check" disable = {!touched.receiptGuid || (touched.receiptGuid && errors.receiptGuid) ? true : false} /> <SubmitButton text="Check" disable={!touched.receiptGuid || (touched.receiptGuid && errors.receiptGuid) ? true : false} />
</form> </form>
)} )}
</Formik> </Formik>
<div <div
className={results['succeed'] ? "text-success mt-3 text-center" : "text-danger mt-3 text-center"} className={results['succeed'] ? 'text-success mt-3 text-center' : 'text-danger mt-3 text-center'}
dangerouslySetInnerHTML={{ __html: results.message ? results.message : '' }} dangerouslySetInnerHTML={{
__html: results.message ? results.message : ''
}}
/> />
<ReceiptsTable receipts={receipts} /><br/> <ReceiptsTable receipts={receipts} />
<CustomTooltip <br />
tooltipText="Clear the list of receipts" <CustomTooltip tooltipText="Clear the list of receipts" tooltipId="etherscan-clear-receipts" placement="bottom">
tooltipId='etherscan-clear-receipts' <Button
placement='bottom' className="btn-sm"
> onClick={() => {
<Button className="btn-sm" onClick={() => { setReceipts([]) }} >Clear</Button> setReceipts([])
}}
>
Clear
</Button>
</CustomTooltip> </CustomTooltip>
</div> </div>
) )
} }}
}
</AppContext.Consumer> </AppContext.Consumer>
) )
} }
const ReceiptsTable: React.FC<{ receipts: Receipt[] }> = ({ receipts }) => { const ReceiptsTable: React.FC<{receipts: Receipt[]}> = ({receipts}) => {
return ( return (
<div className="table-responsive"> <div className="table-responsive">
<h6>Receipts</h6> <h6>Receipts</h6>
@ -167,20 +146,23 @@ const ReceiptsTable: React.FC<{ receipts: Receipt[] }> = ({ receipts }) => {
receipts.map((item: Receipt, index) => { receipts.map((item: Receipt, index) => {
return ( return (
<tr key={item.guid}> <tr key={item.guid}>
<td className={(item.status === 'Pass - Verified' || item.status === 'Successfully Updated') <td
? 'text-success' : (item.status === 'Pending in queue' className={
? 'text-warning' : (item.status === 'Already Verified' item.status === 'Pass - Verified' || item.status === 'Successfully Updated'
? 'text-info': 'text-secondary'))}> ? 'text-success'
{item.status} : item.status === 'Pending in queue'
{item.status === 'Successfully Updated' && <CustomTooltip ? 'text-warning'
placement={'bottom'} : item.status === 'Already Verified'
tooltipClasses="text-wrap" ? 'text-info'
tooltipId="etherscan-receipt-proxy-status" : 'text-secondary'
tooltipText={item.message}
>
<i style={{ fontSize: 'small' }} className={'ml-1 fal fa-info-circle align-self-center'} aria-hidden="true"></i>
</CustomTooltip>
} }
>
{item.status}
{item.status === 'Successfully Updated' && (
<CustomTooltip placement={'bottom'} tooltipClasses="text-wrap" tooltipId="etherscan-receipt-proxy-status" tooltipText={item.message}>
<i style={{fontSize: 'small'}} className={'ml-1 fal fa-info-circle align-self-center'} aria-hidden="true"></i>
</CustomTooltip>
)}
</td> </td>
<td>{item.guid}</td> <td>{item.guid}</td>
</tr> </tr>

@ -1,16 +1,14 @@
import React, { useEffect, useRef, useState } from "react" import React, {useEffect, useRef, useState} from 'react'
import Web3 from 'web3' import Web3 from 'web3'
import { import {PluginClient} from '@remixproject/plugin'
PluginClient, import {CustomTooltip} from '@remix-ui/helper'
} from "@remixproject/plugin" import {Formik, ErrorMessage, Field} from 'formik'
import { CustomTooltip } from '@remix-ui/helper'
import { Formik, ErrorMessage, Field } from "formik"
import { SubmitButton } from "../components" import {SubmitButton} from '../components'
import { Receipt } from "../types" import {Receipt} from '../types'
import { verify } from "../utils/verify" import {verify} from '../utils/verify'
import { etherscanScripts } from "@remix-project/remix-ws-templates" import {etherscanScripts} from '@remix-project/remix-ws-templates'
interface Props { interface Props {
client: PluginClient client: PluginClient
@ -25,15 +23,10 @@ interface FormValues {
expectedImplAddress?: string expectedImplAddress?: string
} }
export const VerifyView: React.FC<Props> = ({ export const VerifyView: React.FC<Props> = ({apiKey, client, contracts, onVerifiedContract}) => {
apiKey, const [results, setResults] = useState('')
client, const [networkName, setNetworkName] = useState('Loading...')
contracts, const [selectedContract, setSelectedContract] = useState('')
onVerifiedContract,
}) => {
const [results, setResults] = useState("")
const [networkName, setNetworkName] = useState("Loading...")
const [selectedContract, setSelectedContract] = useState("")
const [showConstructorArgs, setShowConstructorArgs] = useState(false) const [showConstructorArgs, setShowConstructorArgs] = useState(false)
const [isProxyContract, setIsProxyContract] = useState(false) const [isProxyContract, setIsProxyContract] = useState(false)
const [constructorInputs, setConstructorInputs] = useState([]) const [constructorInputs, setConstructorInputs] = useState([])
@ -41,13 +34,13 @@ export const VerifyView: React.FC<Props> = ({
useEffect(() => { useEffect(() => {
if (client && client.on) { if (client && client.on) {
client.on("blockchain" as any, 'networkStatus', (result) => { client.on('blockchain' as any, 'networkStatus', (result) => {
setNetworkName(`${result.network.name} ${result.network.id !== '-' ? `(Chain id: ${result.network.id})` : '(Not supported)'}`) setNetworkName(`${result.network.name} ${result.network.id !== '-' ? `(Chain id: ${result.network.id})` : '(Not supported)'}`)
}) })
} }
return () => { return () => {
// To fix memory leak // To fix memory leak
if (client && client.off) client.off("blockchain" as any, 'networkStatus') if (client && client.off) client.off('blockchain' as any, 'networkStatus')
} }
}, [client]) }, [client])
@ -56,8 +49,8 @@ export const VerifyView: React.FC<Props> = ({
}, [contracts]) }, [contracts])
const updateConsFields = (contractName) => { const updateConsFields = (contractName) => {
client.call("compilerArtefacts" as any, "getArtefactsByContractName", contractName).then((result) => { client.call('compilerArtefacts' as any, 'getArtefactsByContractName', contractName).then((result) => {
const { artefact } = result const {artefact} = result
if (artefact && artefact.abi && artefact.abi[0] && artefact.abi[0].type && artefact.abi[0].type === 'constructor' && artefact.abi[0].inputs.length > 0) { if (artefact && artefact.abi && artefact.abi[0] && artefact.abi[0].type && artefact.abi[0].type === 'constructor' && artefact.abi[0].inputs.length > 0) {
setConstructorInputs(artefact.abi[0].inputs) setConstructorInputs(artefact.abi[0].inputs)
setShowConstructorArgs(true) setShowConstructorArgs(true)
@ -69,13 +62,10 @@ export const VerifyView: React.FC<Props> = ({
} }
const onVerifyContract = async (values: FormValues) => { const onVerifyContract = async (values: FormValues) => {
const compilationResult = (await client.call( const compilationResult = (await client.call('solidity', 'getCompilationResult')) as any
"solidity",
"getCompilationResult"
)) as any
if (!compilationResult) { if (!compilationResult) {
throw new Error("no compilation result available") throw new Error('no compilation result available')
} }
const constructorValues = [] const constructorValues = []
@ -83,9 +73,9 @@ export const VerifyView: React.FC<Props> = ({
if (key.startsWith('contractArgValue')) constructorValues.push(values[key]) if (key.startsWith('contractArgValue')) constructorValues.push(values[key])
} }
const web3 = new Web3() const web3 = new Web3()
const constructorTypes = constructorInputs.map(e => e.type) const constructorTypes = constructorInputs.map((e) => e.type)
let contractArguments = web3.eth.abi.encodeParameters(constructorTypes, constructorValues) let contractArguments = web3.eth.abi.encodeParameters(constructorTypes, constructorValues)
contractArguments = contractArguments.replace("0x", "") contractArguments = contractArguments.replace('0x', '')
verificationResult.current = await verify( verificationResult.current = await verify(
apiKey, apiKey,
@ -98,7 +88,7 @@ export const VerifyView: React.FC<Props> = ({
values.expectedImplAddress, values.expectedImplAddress,
client, client,
onVerifiedContract, onVerifiedContract,
setResults, setResults
) )
setResults(verificationResult.current['message']) setResults(verificationResult.current['message'])
} }
@ -107,199 +97,148 @@ export const VerifyView: React.FC<Props> = ({
<div> <div>
<Formik <Formik
initialValues={{ initialValues={{
contractName: "", contractName: '',
contractAddress: "" contractAddress: ''
}} }}
validate={(values) => { validate={(values) => {
const errors = {} as any const errors = {} as any
if (!values.contractName) { if (!values.contractName) {
errors.contractName = "Required" errors.contractName = 'Required'
} }
if (!values.contractAddress) { if (!values.contractAddress) {
errors.contractAddress = "Required" errors.contractAddress = 'Required'
} }
if (values.contractAddress.trim() === "" || !values.contractAddress.startsWith('0x') if (values.contractAddress.trim() === '' || !values.contractAddress.startsWith('0x') || values.contractAddress.length !== 42) {
|| values.contractAddress.length !== 42) { errors.contractAddress = 'Please enter a valid contract address'
errors.contractAddress = "Please enter a valid contract address"
} }
return errors return errors
}} }}
onSubmit={(values) => onVerifyContract(values)} onSubmit={(values) => onVerifyContract(values)}
> >
{({ errors, touched, handleSubmit, handleChange, isSubmitting }) => { {({errors, touched, handleSubmit, handleChange, isSubmitting}) => {
return (<form onSubmit={handleSubmit}> return (
<div className="form-group"> <form onSubmit={handleSubmit}>
<label htmlFor="network">Selected Network</label> <div className="form-group">
<CustomTooltip <label htmlFor="network">Selected Network</label>
tooltipText="Network is fetched from 'Deploy and Run Transactions' plugin's ENVIRONMENT field" <CustomTooltip
tooltipId='etherscan-impl-address2' tooltipText="Network is fetched from 'Deploy and Run Transactions' plugin's ENVIRONMENT field"
placement='bottom' tooltipId="etherscan-impl-address2"
> placement="bottom"
>
<Field className="form-control" type="text" name="network" value={networkName} disabled={true} />
</CustomTooltip>
</div>
<div className="form-group">
<label htmlFor="contractName">Contract Name</label>
<Field <Field
className="form-control" as="select"
type="text" className={errors.contractName && touched.contractName && contracts.length ? 'form-control is-invalid' : 'form-control'}
name="network" name="contractName"
value={networkName} onChange={async (e) => {
disabled={true} handleChange(e)
/> setSelectedContract(e.target.value)
</CustomTooltip> updateConsFields(e.target.value)
</div> }}
>
<div className="form-group"> <option disabled={true} value="">
<label htmlFor="contractName">Contract Name</label> {contracts.length ? 'Select a contract' : `--- No compiled contracts ---`}
<Field
as="select"
className={
errors.contractName && touched.contractName && contracts.length
? "form-control is-invalid"
: "form-control"
}
name="contractName"
onChange={async (e) => {
handleChange(e)
setSelectedContract(e.target.value)
updateConsFields(e.target.value)
}}
>
<option disabled={true} value="">
{ contracts.length ? 'Select a contract' : `--- No compiled contracts ---` }
</option>
{contracts.map((item) => (
<option key={item} value={item}>
{item}
</option> </option>
))} {contracts.map((item) => (
</Field> <option key={item} value={item}>
<ErrorMessage {item}
className="invalid-feedback" </option>
name="contractName" ))}
component="div" </Field>
/> <ErrorMessage className="invalid-feedback" name="contractName" component="div" />
</div> </div>
<div className={showConstructorArgs ? 'form-group d-block' : 'form-group d-none'}>
<div className={ showConstructorArgs ? 'form-group d-block': 'form-group d-none' } > <label>Constructor Arguments</label>
<label>Constructor Arguments</label>
{constructorInputs.map((item, index) => { {constructorInputs.map((item, index) => {
return ( return (
<div className="d-flex"> <div className="d-flex">
<Field <Field className="form-control m-1" type="text" key={`contractArgName${index}`} name={`contractArgName${index}`} value={item.name} disabled={true} />
className="form-control m-1" <CustomTooltip tooltipText={`value of ${item.name}`} tooltipId={`etherscan-constructor-value${index}`} placement="top">
type="text" <Field className="form-control m-1" type="text" key={`contractArgValue${index}`} name={`contractArgValue${index}`} placeholder={item.type} />
key={`contractArgName${index}`}
name={`contractArgName${index}`}
value={item.name}
disabled={true}
/>
<CustomTooltip
tooltipText={`value of ${item.name}`}
tooltipId={`etherscan-constructor-value${index}`}
placement='top'
>
<Field
className="form-control m-1"
type="text"
key={`contractArgValue${index}`}
name={`contractArgValue${index}`}
placeholder={item.type}
/>
</CustomTooltip> </CustomTooltip>
</div> </div>
)} )
)} })}
</div>
</div> <div className="form-group">
<label htmlFor="contractAddress">Contract Address</label>
<div className="form-group">
<label htmlFor="contractAddress">Contract Address</label>
<Field
className={
errors.contractAddress && touched.contractAddress
? "form-control is-invalid"
: "form-control"
}
type="text"
name="contractAddress"
placeholder="e.g. 0x11b79afc03baf25c631dd70169bb6a3160b2706e"
/>
<ErrorMessage
className="invalid-feedback"
name="contractAddress"
component="div"
/>
<div className="d-flex mb-2 custom-control custom-checkbox">
<Field <Field
className="custom-control-input" className={errors.contractAddress && touched.contractAddress ? 'form-control is-invalid' : 'form-control'}
type="checkbox"
name="isProxy"
id="isProxy"
onChange={async (e) => {
handleChange(e)
if (e.target.checked) setIsProxyContract(true)
else setIsProxyContract(false)
}}
/>
<label className="form-check-label custom-control-label" htmlFor="isProxy">It's a proxy contract address</label>
</div>
</div>
<div className={ isProxyContract ? 'form-group d-block': 'form-group d-none' }>
<label htmlFor="expectedImplAddress">Expected Implementation Address</label>
<CustomTooltip
tooltipText='Providing expected implementation address enforces a check to ensure the returned implementation contract address is same as address picked up by the verifier'
tooltipId='etherscan-impl-address'
placement='bottom'
>
<Field
className="form-control"
type="text" type="text"
name="expectedImplAddress" name="contractAddress"
placeholder="verified implementation contract address" placeholder="e.g. 0x11b79afc03baf25c631dd70169bb6a3160b2706e"
/> />
</CustomTooltip> <ErrorMessage className="invalid-feedback" name="contractAddress" component="div" />
<i style={{ fontSize: 'x-small' }} className={'ml-1 fal fa-info-circle align-self-center'} aria-hidden="true"></i> <div className="d-flex mb-2 custom-control custom-checkbox">
<label> &nbsp;Make sure contract is already verified on Etherscan</label> <Field
</div> className="custom-control-input"
type="checkbox"
<SubmitButton dataId="verify-contract" text="Verify" name="isProxy"
isSubmitting={isSubmitting} id="isProxy"
disable={ !contracts.length || onChange={async (e) => {
!touched.contractName || handleChange(e)
!touched.contractAddress || if (e.target.checked) setIsProxyContract(true)
(touched.contractName && errors.contractName) || else setIsProxyContract(false)
(touched.contractAddress && errors.contractAddress) || }}
(networkName === 'VM (Not supported)') />
? true <label className="form-check-label custom-control-label" htmlFor="isProxy">
: false} It's a proxy contract address
/> </label>
<br/> </div>
<CustomTooltip </div>
tooltipText='Generate the required TS scripts to verify a contract on Etherscan' <div className={isProxyContract ? 'form-group d-block' : 'form-group d-none'}>
tooltipId='etherscan-generate-scripts' <label htmlFor="expectedImplAddress">Expected Implementation Address</label>
placement='bottom' <CustomTooltip
> tooltipText="Providing expected implementation address enforces a check to ensure the returned implementation contract address is same as address picked up by the verifier"
<button tooltipId="etherscan-impl-address"
type="button" placement="bottom"
className="mr-2 mb-2 py-1 px-2 btn btn-secondary btn-block" >
onClick={async () => { <Field className="form-control" type="text" name="expectedImplAddress" placeholder="verified implementation contract address" />
etherscanScripts(client) </CustomTooltip>
}} <i style={{fontSize: 'x-small'}} className={'ml-1 fal fa-info-circle align-self-center'} aria-hidden="true"></i>
<label> &nbsp;Make sure contract is already verified on Etherscan</label>
</div>
<SubmitButton
dataId="verify-contract"
text="Verify"
isSubmitting={isSubmitting}
disable={
!contracts.length ||
!touched.contractName ||
!touched.contractAddress ||
(touched.contractName && errors.contractName) ||
(touched.contractAddress && errors.contractAddress) ||
networkName === 'VM (Not supported)'
? true
: false
}
/>
<br />
<CustomTooltip tooltipText="Generate the required TS scripts to verify a contract on Etherscan" tooltipId="etherscan-generate-scripts" placement="bottom">
<button
type="button"
className="mr-2 mb-2 py-1 px-2 btn btn-secondary btn-block"
onClick={async () => {
etherscanScripts(client)
}}
> >
Generate Verification Scripts Generate Verification Scripts
</button> </button>
</CustomTooltip> </CustomTooltip>
</form> </form>
) )
} }}
}
</Formik> </Formik>
<div <div
data-id="verify-result" data-id="verify-result"
className={verificationResult.current['succeed'] ? "text-success mt-4 text-center" : "text-danger mt-4 text-center"} className={verificationResult.current['succeed'] ? 'text-success mt-4 text-center' : 'text-danger mt-4 text-center'}
style={{fontSize: "0.8em"}} style={{fontSize: '0.8em'}}
dangerouslySetInnerHTML={{ __html: results }} dangerouslySetInnerHTML={{__html: results}}
/> />
{/* <div style={{ display: "block", textAlign: "center", marginTop: "1em" }}> {/* <div style={{ display: "block", textAlign: "center", marginTop: "1em" }}>
<Link to="/receipts">View Receipts</Link> <Link to="/receipts">View Receipts</Link>
</div> */} </div> */}

@ -1,7 +1,11 @@
import { StrictMode } from 'react'; import {StrictMode} from 'react'
import * as ReactDOM from 'react-dom'; import * as ReactDOM from 'react-dom'
import App from './app/app'
import App from './app/app'; ReactDOM.render(
<StrictMode>
ReactDOM.render(<StrictMode><App /></StrictMode>, document.getElementById('root')); <App />
</StrictMode>,
document.getElementById('root')
)

@ -1,7 +1,7 @@
const { composePlugins, withNx } = require('@nrwl/webpack') const {composePlugins, withNx} = require('@nrwl/webpack')
const webpack = require('webpack') const webpack = require('webpack')
const TerserPlugin = require("terser-webpack-plugin") const TerserPlugin = require('terser-webpack-plugin')
const CssMinimizerPlugin = require("css-minimizer-webpack-plugin") const CssMinimizerPlugin = require('css-minimizer-webpack-plugin')
const versionData = { const versionData = {
timestamp: Date.now(), timestamp: Date.now(),
@ -15,29 +15,29 @@ module.exports = composePlugins(withNx(), (config) => {
// add fallback for node modules // add fallback for node modules
config.resolve.fallback = { config.resolve.fallback = {
...config.resolve.fallback, ...config.resolve.fallback,
"crypto": require.resolve("crypto-browserify"), crypto: require.resolve('crypto-browserify'),
"stream": require.resolve("stream-browserify"), stream: require.resolve('stream-browserify'),
"path": require.resolve("path-browserify"), path: require.resolve('path-browserify'),
"http": require.resolve("stream-http"), http: require.resolve('stream-http'),
"https": require.resolve("https-browserify"), https: require.resolve('https-browserify'),
"constants": require.resolve("constants-browserify"), constants: require.resolve('constants-browserify'),
"os": false, //require.resolve("os-browserify/browser"), os: false, //require.resolve("os-browserify/browser"),
"timers": false, // require.resolve("timers-browserify"), timers: false, // require.resolve("timers-browserify"),
"zlib": require.resolve("browserify-zlib"), zlib: require.resolve('browserify-zlib'),
"fs": false, fs: false,
"module": false, module: false,
"tls": false, tls: false,
"net": false, net: false,
"readline": false, readline: false,
"child_process": false, child_process: false,
"buffer": require.resolve("buffer/"), buffer: require.resolve('buffer/'),
"vm": require.resolve('vm-browserify'), vm: require.resolve('vm-browserify')
} }
// add externals // add externals
config.externals = { config.externals = {
...config.externals, ...config.externals,
solc: 'solc', solc: 'solc'
} }
// add public path // add public path
@ -47,26 +47,24 @@ module.exports = composePlugins(withNx(), (config) => {
config.output.filename = `[name].plugin-etherscan.${versionData.timestamp}.js` config.output.filename = `[name].plugin-etherscan.${versionData.timestamp}.js`
config.output.chunkFilename = `[name].plugin-etherscan.${versionData.timestamp}.js` config.output.chunkFilename = `[name].plugin-etherscan.${versionData.timestamp}.js`
// add copy & provide plugin // add copy & provide plugin
config.plugins.push( config.plugins.push(
new webpack.ProvidePlugin({ new webpack.ProvidePlugin({
Buffer: ['buffer', 'Buffer'], Buffer: ['buffer', 'Buffer'],
url: ['url', 'URL'], url: ['url', 'URL'],
process: 'process/browser', process: 'process/browser'
}) })
) )
// souce-map loader // souce-map loader
config.module.rules.push({ config.module.rules.push({
test: /\.js$/, test: /\.js$/,
use: ["source-map-loader"], use: ['source-map-loader'],
enforce: "pre" enforce: 'pre'
}) })
config.ignoreWarnings = [/Failed to parse source map/] // ignore source-map-loader warnings config.ignoreWarnings = [/Failed to parse source map/] // ignore source-map-loader warnings
// set minimizer // set minimizer
config.optimization.minimizer = [ config.optimization.minimizer = [
new TerserPlugin({ new TerserPlugin({
@ -76,17 +74,17 @@ module.exports = composePlugins(withNx(), (config) => {
compress: false, compress: false,
mangle: false, mangle: false,
format: { format: {
comments: false, comments: false
}, }
}, },
extractComments: false, extractComments: false
}), }),
new CssMinimizerPlugin(), new CssMinimizerPlugin()
]; ]
config.watchOptions = { config.watchOptions = {
ignored: /node_modules/ ignored: /node_modules/
} }
return config; return config
}); })

@ -7,7 +7,7 @@ module.exports = {
globals_path: '', globals_path: '',
test_settings: { test_settings: {
default: { 'default': {
selenium_port: 4444, selenium_port: 4444,
selenium_host: 'localhost', selenium_host: 'localhost',
globals: { globals: {
@ -23,28 +23,29 @@ module.exports = {
exclude: ['dist/apps/remix-ide-e2e/src/tests/runAndDeploy.test.js', 'dist/apps/remix-ide-e2e/src/tests/pluginManager.test.ts'] exclude: ['dist/apps/remix-ide-e2e/src/tests/runAndDeploy.test.js', 'dist/apps/remix-ide-e2e/src/tests/pluginManager.test.ts']
}, },
chrome: { 'chrome': {
desiredCapabilities: { desiredCapabilities: {
browserName: 'chrome', 'browserName': 'chrome',
javascriptEnabled: true, 'javascriptEnabled': true,
acceptSslCerts: true, 'acceptSslCerts': true,
'goog:chromeOptions': { 'goog:chromeOptions': {
args: ['window-size=2560,1440', args: [
'start-fullscreen', 'window-size=2560,1440',
'--no-sandbox', 'start-fullscreen',
'--headless', '--no-sandbox',
'--verbose', '--headless',
"--user-agent=Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/104.0.0.0 Safari/537.36", '--verbose',
'--user-agent=Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/104.0.0.0 Safari/537.36'
] ]
} }
} }
}, },
chromeDesktop: { 'chromeDesktop': {
desiredCapabilities: { desiredCapabilities: {
browserName: 'chrome', 'browserName': 'chrome',
javascriptEnabled: true, 'javascriptEnabled': true,
acceptSslCerts: true, 'acceptSslCerts': true,
'goog:chromeOptions': { 'goog:chromeOptions': {
args: ['window-size=2560,1440', 'start-fullscreen', '--no-sandbox'] args: ['window-size=2560,1440', 'start-fullscreen', '--no-sandbox']
} }
@ -53,40 +54,33 @@ module.exports = {
'chrome-runAndDeploy': { 'chrome-runAndDeploy': {
desiredCapabilities: { desiredCapabilities: {
browserName: 'chrome', 'browserName': 'chrome',
javascriptEnabled: true, 'javascriptEnabled': true,
acceptSslCerts: true, 'acceptSslCerts': true,
'goog:chromeOptions': { 'goog:chromeOptions': {
args: ['window-size=2560,1440', 'start-fullscreen', '--no-sandbox', '--headless', '--verbose'] args: ['window-size=2560,1440', 'start-fullscreen', '--no-sandbox', '--headless', '--verbose']
} }
} }
}, },
firefoxDesktop: { 'firefoxDesktop': {
desiredCapabilities: { desiredCapabilities: {
browserName: 'firefox', 'browserName': 'firefox',
javascriptEnabled: true, 'javascriptEnabled': true,
acceptSslCerts: true, 'acceptSslCerts': true,
'moz:firefoxOptions': { 'moz:firefoxOptions': {
args: [ args: ['-width=2560', '-height=1440']
'-width=2560',
'-height=1440'
]
} }
} }
}, },
firefox: { 'firefox': {
desiredCapabilities: { desiredCapabilities: {
browserName: 'firefox', 'browserName': 'firefox',
javascriptEnabled: true, 'javascriptEnabled': true,
acceptSslCerts: true, 'acceptSslCerts': true,
'moz:firefoxOptions': { 'moz:firefoxOptions': {
args: [ args: ['-headless', '-width=2560', '-height=1440']
'-headless',
'-width=2560',
'-height=1440'
]
} }
} }
} }

@ -6,14 +6,14 @@
"npm": "^6.14.15" "npm": "^6.14.15"
}, },
"dependencies": { "dependencies": {
"@openzeppelin/contracts": "^4.8.3", "@openzeppelin/contracts": "^5.0.0",
"@openzeppelin/contracts-upgradeable": "^4.8.3", "@openzeppelin/contracts-upgradeable": "^5.0.0",
"@openzeppelin/upgrades-core": "^1.22.0", "@openzeppelin/upgrades-core": "^1.30.0",
"@openzeppelin/wizard": "^0.1.1", "@openzeppelin/wizard": "^0.4.0",
"@remix-project/remixd": "../../dist/libs/remixd", "@remix-project/remixd": "../../dist/libs/remixd",
"deep-equal": "^1.0.1", "deep-equal": "^1.0.1",
"ganache-cli": "^6.8.1", "ganache-cli": "^6.8.1",
"selenium-standalone": "^8.2.3", "selenium-standalone": "^9.0.3",
"tree-kill": "^1.2.2" "tree-kill": "^1.2.2"
}, },
"devDependencies": { "devDependencies": {

@ -1,9 +1,9 @@
import EventEmitter from 'events' import EventEmitter from 'events'
import { NightwatchBrowser } from 'nightwatch' import {NightwatchBrowser} from 'nightwatch'
class checkAnnotations extends EventEmitter { class checkAnnotations extends EventEmitter {
command (this: NightwatchBrowser, type: string, line: number): NightwatchBrowser { command(this: NightwatchBrowser, type: string): NightwatchBrowser {
this.api.assert.containsText(`.margin-view-overlays .${type} + div`, line.toString()).perform(() => this.emit('complete')) this.api.waitForElementPresent(`.glyph-margin-widgets .${type}`).perform(() => this.emit('complete'))
return this return this
} }
} }

@ -1,9 +1,9 @@
import EventEmitter from 'events' import EventEmitter from 'events'
import { NightwatchBrowser } from 'nightwatch' import {NightwatchBrowser} from 'nightwatch'
class checkAnnotationsNotPresent extends EventEmitter { class checkAnnotationsNotPresent extends EventEmitter {
command (this: NightwatchBrowser, type: string): NightwatchBrowser { command(this: NightwatchBrowser, type: string): NightwatchBrowser {
this.api.waitForElementNotPresent(`.margin-view-overlays .${type}`).perform(() => this.emit('complete')) this.api.waitForElementNotPresent(`.glyph-margin-widgets .${type}`).perform(() => this.emit('complete'))
return this return this
} }
} }

@ -1,20 +1,37 @@
import { NightwatchBrowser, NightwatchClickFunctionExpectedInput } from 'nightwatch' import {
NightwatchBrowser,
NightwatchClickFunctionExpectedInput
} from 'nightwatch'
import EventEmitter from 'events' import EventEmitter from 'events'
class ClickFunction extends EventEmitter { class ClickFunction extends EventEmitter {
command (this: NightwatchBrowser, fnFullName: string, expectedInput?: NightwatchClickFunctionExpectedInput): NightwatchBrowser { command(
this.api.waitForElementPresent('.instance button[data-title="' + fnFullName + '"]') this: NightwatchBrowser,
fnFullName: string,
expectedInput?: NightwatchClickFunctionExpectedInput
): NightwatchBrowser {
this.api
.waitForElementPresent('.instance *[data-title="' + fnFullName + '"]')
.perform(function (client, done) { .perform(function (client, done) {
client.execute(function () { client.execute(
document.querySelector('#runTabView').scrollTop = document.querySelector('#runTabView').scrollHeight function () {
}, [], function () { document.querySelector('#runTabView').scrollTop =
if (expectedInput) { document.querySelector('#runTabView').scrollHeight
client.setValue('#runTabView input[data-title="' + expectedInput.types + '"]', expectedInput.values, _ => _) },
[],
function () {
if (expectedInput) {
client.setValue(
'#runTabView input[data-title="' + expectedInput.types + '"]',
expectedInput.values,
(_) => _
)
}
done()
} }
done() )
})
}) })
.scrollAndClick('.instance button[data-title="' + fnFullName + '"]') .scrollAndClick('.instance *[data-title="' + fnFullName + '"]')
.pause(2000) .pause(2000)
.perform(() => { .perform(() => {
this.emit('complete') this.emit('complete')

@ -16,11 +16,11 @@ class CreateContract extends EventEmitter {
function createContract (browser: NightwatchBrowser, inputParams: string, callback: VoidFunction) { function createContract (browser: NightwatchBrowser, inputParams: string, callback: VoidFunction) {
if (inputParams) { if (inputParams) {
browser.setValue('.udapp_contractActionsContainerSingle > input', inputParams, function () { browser.setValue('.udapp_contractActionsContainerSingle > input', inputParams, function () {
browser.click('.udapp_contractActionsContainerSingle > button').pause(500).perform(function () { callback() }) browser.click('.udapp_contractActionsContainerSingle > div').pause(500).perform(function () { callback() })
}) })
} else { } else {
browser browser
.click('.udapp_contractActionsContainerSingle > button') .click('.udapp_contractActionsContainerSingle > div')
.pause(500) .pause(500)
.perform(function () { callback() }) .perform(function () { callback() })
} }

@ -1,36 +1,80 @@
import { NightwatchBrowser, NightwatchTestConstantFunctionExpectedInput } from 'nightwatch' import {
NightwatchBrowser,
NightwatchTestConstantFunctionExpectedInput
} from 'nightwatch'
import EventEmitter from 'events' import EventEmitter from 'events'
class TestConstantFunction extends EventEmitter { class TestConstantFunction extends EventEmitter {
command (this: NightwatchBrowser, address: string, fnFullName: string, expectedInput: NightwatchTestConstantFunctionExpectedInput | null, expectedOutput: string): NightwatchBrowser { command(
this: NightwatchBrowser,
address: string,
fnFullName: string,
expectedInput: NightwatchTestConstantFunctionExpectedInput | null,
expectedOutput: string
): NightwatchBrowser {
console.log('TestConstantFunction ' + address + ' fnFullName') console.log('TestConstantFunction ' + address + ' fnFullName')
this.api.perform((done) => { this.api.perform((done) => {
testConstantFunction(this.api, address, fnFullName, expectedInput, expectedOutput, () => { testConstantFunction(
done() this.api,
this.emit('complete') address,
}) fnFullName,
expectedInput,
expectedOutput,
() => {
done()
this.emit('complete')
}
)
}) })
return this return this
} }
} }
function testConstantFunction (browser: NightwatchBrowser, address: string, fnFullName: string, expectedInput: NightwatchTestConstantFunctionExpectedInput, expectedOutput: string, cb: VoidFunction) { function testConstantFunction(
browser.waitForElementPresent('.instance button[data-title="' + fnFullName + '"]').perform(function (client, done) { browser: NightwatchBrowser,
client.execute(function () { address: string,
document.querySelector('#runTabView').scrollTop = document.querySelector('#runTabView').scrollHeight fnFullName: string,
}, [], function () { expectedInput: NightwatchTestConstantFunctionExpectedInput,
if (expectedInput) { expectedOutput: string,
client.waitForElementPresent('#runTabView input[data-title="' + expectedInput.types + '"]') cb: VoidFunction
.setValue('#runTabView input[data-title="' + expectedInput.types + '"]', expectedInput.values) ) {
} browser
done() .waitForElementPresent('.instance *[data-title="' + fnFullName + '"]')
.perform(function (client, done) {
client.execute(
function () {
document.querySelector('#runTabView').scrollTop =
document.querySelector('#runTabView').scrollHeight
},
[],
function () {
if (expectedInput) {
client
.waitForElementPresent(
'#runTabView input[data-title="' + expectedInput.types + '"]'
)
.setValue(
'#runTabView input[data-title="' + expectedInput.types + '"]',
expectedInput.values
)
}
done()
}
)
}) })
}) .click(`#instance${address} *[data-title="${fnFullName}"]`)
.click(`#instance${address} button[data-title="${fnFullName}"]`)
.pause(1000) .pause(1000)
.waitForElementPresent('#instance' + address + ' .udapp_contractActionsContainer .udapp_value') .waitForElementPresent(
.scrollInto('#instance' + address + ' .udapp_contractActionsContainer .udapp_value') '#instance' + address + ' .udapp_contractActionsContainer .udapp_value'
.assert.containsText('#instance' + address + ' .udapp_contractActionsContainer', expectedOutput).perform(() => { )
.scrollInto(
'#instance' + address + ' .udapp_contractActionsContainer .udapp_value'
)
.assert.containsText(
'#instance' + address + ' .udapp_contractActionsContainer',
expectedOutput
)
.perform(() => {
cb() cb()
}) })
} }

@ -1,32 +1,43 @@
import React, {useEffect, useState} from 'react'
import React, { useEffect, useState } from 'react' import {RemixPlugin} from './Client'
import { RemixPlugin } from './Client' import {Logger} from './logger'
import { Logger } from './logger' import {filePanelProfile} from '@remixproject/plugin-api'
import { filePanelProfile } from '@remixproject/plugin-api' import {filSystemProfile} from '@remixproject/plugin-api'
import { filSystemProfile } from '@remixproject/plugin-api' import {dGitProfile} from '@remixproject/plugin-api'
import { dGitProfile } from '@remixproject/plugin-api' import {editorProfile} from '@remixproject/plugin-api'
import { editorProfile } from '@remixproject/plugin-api' import {settingsProfile} from '@remixproject/plugin-api'
import { settingsProfile } from '@remixproject/plugin-api' import {networkProfile} from '@remixproject/plugin-api'
import { networkProfile } from '@remixproject/plugin-api' import {udappProfile} from '@remixproject/plugin-api'
import { udappProfile } from '@remixproject/plugin-api' import {compilerProfile} from '@remixproject/plugin-api'
import { compilerProfile } from '@remixproject/plugin-api' import {contentImportProfile} from '@remixproject/plugin-api'
import { contentImportProfile } from '@remixproject/plugin-api' import {windowProfile} from '@remixproject/plugin-api'
import { windowProfile } from '@remixproject/plugin-api' import {pluginManagerProfile} from '@remixproject/plugin-api'
import { pluginManagerProfile } from '@remixproject/plugin-api' import {Profile} from '@remixproject/plugin-utils'
import { Profile } from '@remixproject/plugin-utils'
import './app.css' import './app.css'
const client = new RemixPlugin() const client = new RemixPlugin()
function App () { function App() {
const [payload, setPayload] = useState<string>('') const [payload, setPayload] = useState<string>('')
const [log, setLog] = useState<any>() const [log, setLog] = useState<any>()
const [started, setStarted] = useState<boolean>(false) const [started, setStarted] = useState<boolean>(false)
const [events, setEvents] = useState<any>() const [events, setEvents] = useState<any>()
const [profiles, setProfiles] = useState<Profile[]>([pluginManagerProfile, filePanelProfile, filSystemProfile, dGitProfile, networkProfile, settingsProfile, editorProfile, compilerProfile, udappProfile, contentImportProfile, windowProfile]) const [profiles, setProfiles] = useState<Profile[]>([
pluginManagerProfile,
filePanelProfile,
filSystemProfile,
dGitProfile,
networkProfile,
settingsProfile,
editorProfile,
compilerProfile,
udappProfile,
contentImportProfile,
windowProfile
])
const handleChange = ({ target }: any) => { const handleChange = ({target}: any) => {
setPayload(target.value) setPayload(target.value)
} }
@ -44,7 +55,7 @@ function App () {
const p = await client.call('manager', 'getProfile', name) const p = await client.call('manager', 'getProfile', name)
addProfiles = [...addProfiles, p] addProfiles = [...addProfiles, p]
} }
setProfiles(profiles => [...profiles, ...addProfiles]) setProfiles((profiles) => [...profiles, ...addProfiles])
profiles.map((profile: Profile) => { profiles.map((profile: Profile) => {
if (profile.events) { if (profile.events) {
@ -77,8 +88,10 @@ function App () {
let ob: any = null let ob: any = null
try { try {
ob = JSON.parse(payload) ob = JSON.parse(payload)
if (ob && !Array.isArray(ob)) { ob = [ob] } if (ob && !Array.isArray(ob)) {
} catch (e) { } ob = [ob]
}
} catch (e) {}
const args = ob || [payload] const args = ob || [payload]
setStarted(true) setStarted(true)
setLog('') setLog('')
@ -97,30 +110,41 @@ function App () {
return ( return (
<div className="App container-fluid"> <div className="App container-fluid">
<h5>PLUGIN API TESTER</h5> <h5>PLUGIN API TESTER</h5>
<label id='callStatus'>{started ? <>start</> : <>stop</> }</label><br></br> <label id="callStatus">{started ? <>start</> : <>stop</>}</label>
<br></br>
<label>method results</label> <label>method results</label>
<Logger id='methods' log={log}></Logger> <Logger id="methods" log={log}></Logger>
<label>events</label> <label>events</label>
<Logger id='events' log={events}></Logger> <Logger id="events" log={events}></Logger>
<input <input className="form-control w-100" type="text" id="payload" placeholder="Enter payload here..." value={payload} onChange={handleChange} data-id="payload-input" />
className='form-control w-100'
type="text"
id="payload"
placeholder="Enter payload here..."
value={payload}
onChange={handleChange}
data-id="payload-input"
/>
{profiles.map((profile: Profile) => { {profiles.map((profile: Profile) => {
const methods = profile.methods.map((method: string) => { const methods = profile.methods.map((method: string) => {
return <button data-id={`${profile.name}:${method}`} key={method} className='btn btn-primary btn-sm ml-1 mb-1' onClick={async () => await clientMethod(profile, method)}>{method}</button> return (
<button data-id={`${profile.name}:${method}`} key={method} className="btn btn-primary btn-sm ml-1 mb-1" onClick={async () => await clientMethod(profile, method)}>
{method}
</button>
)
}) })
const events = profile.events ? profile.events.map((event: string) => { const events = profile.events
return <label key={event} className='m-1'>{event}</label> ? profile.events.map((event: string) => {
}) : null return (
return <div key={profile.name} className='small border-bottom'><label className='text-uppercase'>{profile.name}</label><br></br>{methods}<br></br>{events ? <label>EVENTS:</label> : null}{events}</div> <label key={event} className="m-1">
{event}
</label>
)
})
: null
return (
<div key={profile.name} className="small border-bottom">
<label className="text-uppercase">{profile.name}</label>
<br></br>
{methods}
<br></br>
{events ? <label>EVENTS:</label> : null}
{events}
</div>
)
})} })}
</div> </div>
) )
} }

@ -1,9 +1,13 @@
import React from 'react' import React from 'react'
interface loggerProps { interface loggerProps {
log: any, log: any
id: string id: string
} }
export const Logger: React.FC<loggerProps> = (props) => { export const Logger: React.FC<loggerProps> = (props) => {
return (<div id={props.id} className="jumbotron overflow-auto text-break mb-1 p-2">{props.log}</div>) return (
<div id={props.id} className="jumbotron overflow-auto text-break mb-1 p-2">
{props.log}
</div>
)
} }

@ -21,7 +21,7 @@ module.exports = {
browser.testContracts('test.sol',{ content: code } , ['A']) browser.testContracts('test.sol',{ content: code } , ['A'])
.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('.udapp_contractActionsContainerSingle > button') .click('.udapp_contractActionsContainerSingle > div')
.clickInstance(0) .clickInstance(0)
.clickFunction('foo - call', { types: 'uint256 p', values: '0' }) .clickFunction('foo - call', { types: 'uint256 p', values: '0' })
.perform((done) => { .perform((done) => {

@ -42,37 +42,43 @@ const sources = [
'TestContract.sol': { 'TestContract.sol': {
content: ` content: `
// SPDX-License-Identifier: MIT // SPDX-License-Identifier: MIT
pragma solidity ^0.8.9; pragma solidity ^0.8.20;
import "@openzeppelin/contracts-upgradeable/token/ERC721/ERC721Upgradeable.sol"; import "@openzeppelin/contracts/token/ERC20/ERC20.sol";
import "@openzeppelin/contracts-upgradeable/token/ERC721/extensions/ERC721BurnableUpgradeable.sol"; import "@openzeppelin/contracts/token/ERC20/extensions/ERC20Burnable.sol";
import "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol"; import "@openzeppelin/contracts/token/ERC20/extensions/ERC20Pausable.sol";
import "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol"; import "@openzeppelin/contracts/access/Ownable.sol";
import "@openzeppelin/contracts-upgradeable/proxy/utils/UUPSUpgradeable.sol"; import "@openzeppelin/contracts/token/ERC20/extensions/ERC20Permit.sol";
contract MyToken is Initializable, ERC721Upgradeable, ERC721BurnableUpgradeable, OwnableUpgradeable, UUPSUpgradeable { contract MyToken is ERC20, ERC20Burnable, ERC20Pausable, Ownable, ERC20Permit {
/// @custom:oz-upgrades-unsafe-allow constructor constructor(address initialOwner)
constructor() { ERC20("MyToken", "MTK")
_disableInitializers(); Ownable(initialOwner)
ERC20Permit("MyToken")
{}
function pause() public onlyOwner {
_pause();
} }
function initialize() initializer public { function unpause() public onlyOwner {
__ERC721_init("MyToken", "MTK"); _unpause();
__ERC721Burnable_init();
__Ownable_init();
__UUPSUpgradeable_init();
} }
function safeMint(address to, uint256 tokenId) public onlyOwner { function mint(address to, uint256 amount) public onlyOwner {
_safeMint(to, tokenId); _mint(to, amount);
} }
function _authorizeUpgrade(address newImplementation) // The following functions are overrides required by Solidity.
function _update(address from, address to, uint256 value)
internal internal
onlyOwner override(ERC20, ERC20Pausable)
override {
{} super._update(from, to, value);
}
} }
` `
}, },
} }

@ -1,16 +1,17 @@
'use strict' 'use strict'
import { NightwatchBrowser } from 'nightwatch' import {NightwatchBrowser} from 'nightwatch'
import init from '../helpers/init' import init from '../helpers/init'
module.exports = { module.exports = {
'@disabled': true, '@disabled': true,
before: function (browser: NightwatchBrowser, done: VoidFunction) { 'before': function (browser: NightwatchBrowser, done: VoidFunction) {
init(browser, done, 'http://127.0.0.1:8080', true) init(browser, done, 'http://127.0.0.1:8080', true)
}, },
'Should zoom in editor #group1': function (browser: NightwatchBrowser) { 'Should zoom in editor #group1': function (browser: NightwatchBrowser) {
browser.waitForElementVisible('div[data-id="mainPanelPluginsContainer"]') browser
.waitForElementVisible('div[data-id="mainPanelPluginsContainer"]')
.clickLaunchIcon('filePanel') .clickLaunchIcon('filePanel')
.waitForElementVisible('div[data-id="filePanelFileExplorerTree"]') .waitForElementVisible('div[data-id="filePanelFileExplorerTree"]')
.openFile('contracts') .openFile('contracts')
@ -23,7 +24,8 @@ module.exports = {
}, },
'Should zoom out editor #group1': function (browser: NightwatchBrowser) { 'Should zoom out editor #group1': function (browser: NightwatchBrowser) {
browser.waitForElementVisible('#editorView') browser
.waitForElementVisible('#editorView')
.checkElementStyle('.view-lines', 'font-size', '16px') .checkElementStyle('.view-lines', 'font-size', '16px')
.click('*[data-id="tabProxyZoomOut"]') .click('*[data-id="tabProxyZoomOut"]')
.click('*[data-id="tabProxyZoomOut"]') .click('*[data-id="tabProxyZoomOut"]')
@ -31,57 +33,72 @@ module.exports = {
}, },
'Should display compile error in editor #group1': function (browser: NightwatchBrowser) { 'Should display compile error in editor #group1': function (browser: NightwatchBrowser) {
browser.waitForElementVisible('#editorView') browser
.waitForElementVisible('#editorView')
.setEditorValue(storageContractWithError + 'error') .setEditorValue(storageContractWithError + 'error')
.pause(2000) .pause(2000)
.waitForElementVisible('.margin-view-overlays .fa-exclamation-square', 120000) .waitForElementVisible('.glyph-margin-widgets .fa-exclamation-square', 120000)
.checkAnnotations('fa-exclamation-square', 29) // error .checkAnnotations('fa-exclamation-square') // error
.clickLaunchIcon('udapp') .clickLaunchIcon('udapp')
.checkAnnotationsNotPresent('fa-exclamation-square') // error .checkAnnotationsNotPresent('fa-exclamation-square') // error
.clickLaunchIcon('solidity') .clickLaunchIcon('solidity')
.checkAnnotations('fa-exclamation-square', 29) // error .checkAnnotations('fa-exclamation-square') // error
}, },
'Should minimize and maximize codeblock in editor #group1': '' + function (browser: NightwatchBrowser) { 'Should minimize and maximize codeblock in editor #group1':
browser.waitForElementVisible('#editorView') '' +
.waitForElementVisible('.ace_open') function (browser: NightwatchBrowser) {
.click('.ace_start:nth-of-type(1)') browser
.waitForElementVisible('.ace_closed') .waitForElementVisible('#editorView')
.click('.ace_start:nth-of-type(1)') .waitForElementVisible('.ace_open')
.waitForElementVisible('.ace_open') .click('.ace_start:nth-of-type(1)')
}, .waitForElementVisible('.ace_closed')
.click('.ace_start:nth-of-type(1)')
.waitForElementVisible('.ace_open')
},
'Should add breakpoint to editor #group1': function (browser: NightwatchBrowser) { 'Should add breakpoint to editor #group1': function (browser: NightwatchBrowser) {
browser.waitForElementVisible('#editorView') browser
.waitForElementNotPresent('.margin-view-overlays .fa-circle') .waitForElementVisible('#editorView')
.execute(() => { .waitForElementNotPresent('.glyph-margin-widgets .fa-circle')
(window as any).addRemixBreakpoint(1) .execute(
}, [], () => {}) () => {
.waitForElementVisible('.margin-view-overlays .fa-circle') ;(window as any).addRemixBreakpoint(1)
}, },
[],
'Should load syntax highlighter for ace light theme #group1': '' + function (browser: NightwatchBrowser) { () => {}
browser.waitForElementVisible('#editorView') )
.checkElementStyle('.ace_keyword', 'color', aceThemes.light.keyword) .waitForElementVisible('.glyph-margin-widgets .fa-circle')
.checkElementStyle('.ace_comment.ace_doc', 'color', aceThemes.light.comment)
.checkElementStyle('.ace_function', 'color', aceThemes.light.function)
.checkElementStyle('.ace_variable', 'color', aceThemes.light.variable)
}, },
'Should load syntax highlighter for ace dark theme #group1': '' + function (browser: NightwatchBrowser) { 'Should load syntax highlighter for ace light theme #group1':
browser.waitForElementVisible('*[data-id="verticalIconsKindsettings"]') '' +
.click('*[data-id="verticalIconsKindsettings"]') function (browser: NightwatchBrowser) {
.waitForElementVisible('*[data-id="settingsTabThemeLabelDark"]') browser
.click('*[data-id="settingsTabThemeLabelDark"]') .waitForElementVisible('#editorView')
.pause(2000) .checkElementStyle('.ace_keyword', 'color', aceThemes.light.keyword)
.waitForElementVisible('#editorView') .checkElementStyle('.ace_comment.ace_doc', 'color', aceThemes.light.comment)
/* @todo(#2863) ch for class and not colors .checkElementStyle('.ace_function', 'color', aceThemes.light.function)
.checkElementStyle('.ace_variable', 'color', aceThemes.light.variable)
},
'Should load syntax highlighter for ace dark theme #group1':
'' +
function (browser: NightwatchBrowser) {
browser
.waitForElementVisible('*[data-id="verticalIconsKindsettings"]')
.click('*[data-id="verticalIconsKindsettings"]')
.waitForElementVisible('*[data-id="settingsTabThemeLabelDark"]')
.click('*[data-id="settingsTabThemeLabelDark"]')
.pause(2000)
.waitForElementVisible('#editorView')
/* @todo(#2863) ch for class and not colors
.checkElementStyle('.ace_keyword', 'color', aceThemes.dark.keyword) .checkElementStyle('.ace_keyword', 'color', aceThemes.dark.keyword)
.checkElementStyle('.ace_comment.ace_doc', 'color', aceThemes.dark.comment) .checkElementStyle('.ace_comment.ace_doc', 'color', aceThemes.dark.comment)
.checkElementStyle('.ace_function', 'color', aceThemes.dark.function) .checkElementStyle('.ace_function', 'color', aceThemes.dark.function)
.checkElementStyle('.ace_variable', 'color', aceThemes.dark.variable) .checkElementStyle('.ace_variable', 'color', aceThemes.dark.variable)
*/ */
}, },
'Should highlight source code #group1': function (browser: NightwatchBrowser) { 'Should highlight source code #group1': function (browser: NightwatchBrowser) {
// include all files here because switching between plugins in side-panel removes highlight // include all files here because switching between plugins in side-panel removes highlight
@ -101,22 +118,26 @@ module.exports = {
.checkElementStyle('.highlightLine51', 'background-color', 'rgb(52, 152, 219)') .checkElementStyle('.highlightLine51', 'background-color', 'rgb(52, 152, 219)')
}, },
'Should remove 1 highlight from source code #group1': '' + function (browser: NightwatchBrowser) { 'Should remove 1 highlight from source code #group1':
browser.waitForElementVisible('li[data-id="treeViewLitreeViewItemremoveSourcehighlightScript.js"]') '' +
.click('li[data-id="treeViewLitreeViewItemremoveSourcehighlightScript.js"]') function (browser: NightwatchBrowser) {
.pause(2000) browser
.executeScriptInTerminal('remix.exeCurrent()') .waitForElementVisible('li[data-id="treeViewLitreeViewItemremoveSourcehighlightScript.js"]')
.waitForElementVisible('li[data-id="treeViewLitreeViewItemcontracts"]') .click('li[data-id="treeViewLitreeViewItemremoveSourcehighlightScript.js"]')
.click('li[data-id="treeViewLitreeViewItemcontracts"]') .pause(2000)
.waitForElementVisible('li[data-id="treeViewLitreeViewItemcontracts/3_Ballot.sol"]') .executeScriptInTerminal('remix.exeCurrent()')
.click('li[data-id="treeViewLitreeViewItemcontracts/3_Ballot.sol"]') .waitForElementVisible('li[data-id="treeViewLitreeViewItemcontracts"]')
.waitForElementNotPresent('.highlightLine33', 60000) .click('li[data-id="treeViewLitreeViewItemcontracts"]')
.checkElementStyle('.highlightLine41', 'background-color', 'rgb(52, 152, 219)') .waitForElementVisible('li[data-id="treeViewLitreeViewItemcontracts/3_Ballot.sol"]')
.checkElementStyle('.highlightLine51', 'background-color', 'rgb(52, 152, 219)') .click('li[data-id="treeViewLitreeViewItemcontracts/3_Ballot.sol"]')
}, .waitForElementNotPresent('.highlightLine33', 60000)
.checkElementStyle('.highlightLine41', 'background-color', 'rgb(52, 152, 219)')
.checkElementStyle('.highlightLine51', 'background-color', 'rgb(52, 152, 219)')
},
'Should remove all highlights from source code #group1': function (browser: NightwatchBrowser) { 'Should remove all highlights from source code #group1': function (browser: NightwatchBrowser) {
browser.waitForElementVisible('li[data-id="treeViewLitreeViewItemremoveAllSourcehighlightScript.js"]') browser
.waitForElementVisible('li[data-id="treeViewLitreeViewItemremoveAllSourcehighlightScript.js"]')
.click('li[data-id="treeViewLitreeViewItemremoveAllSourcehighlightScript.js"]') .click('li[data-id="treeViewLitreeViewItemremoveAllSourcehighlightScript.js"]')
.pause(2000) .pause(2000)
.executeScriptInTerminal('remix.exeCurrent()') .executeScriptInTerminal('remix.exeCurrent()')
@ -126,8 +147,7 @@ module.exports = {
.waitForElementNotPresent('.highlightLine33', 60000) .waitForElementNotPresent('.highlightLine33', 60000)
.waitForElementNotPresent('.highlightLine41', 60000) .waitForElementNotPresent('.highlightLine41', 60000)
.waitForElementNotPresent('.highlightLine51', 60000) .waitForElementNotPresent('.highlightLine51', 60000)
}, }
} }
const aceThemes = { const aceThemes = {
@ -233,5 +253,3 @@ contract Storage {
return number; return number;
} }
}` }`

@ -17,7 +17,6 @@ module.exports = {
before: function (browser: NightwatchBrowser, done: VoidFunction) { before: function (browser: NightwatchBrowser, done: VoidFunction) {
init(browser, done, 'http://127.0.0.1:8080', false) init(browser, done, 'http://127.0.0.1:8080', false)
}, },
'Should load the test file': function (browser: NightwatchBrowser) { 'Should load the test file': function (browser: NightwatchBrowser) {
browser.openFile('contracts') browser.openFile('contracts')
.openFile('contracts/3_Ballot.sol') .openFile('contracts/3_Ballot.sol')
@ -86,7 +85,50 @@ module.exports = {
const path = "//*[@class='view-line' and contains(.,'Voter') and contains(.,'struct')]//span//span[contains(.,'Voter')]" const path = "//*[@class='view-line' and contains(.,'Voter') and contains(.,'struct')]//span//span[contains(.,'Voter')]"
const expectedContent = 'StructDefinition' const expectedContent = 'StructDefinition'
checkEditorHoverContent(browser, path, expectedContent) checkEditorHoverContent(browser, path, expectedContent)
} },
'Add token file': function (browser: NightwatchBrowser) {
browser
.clickLaunchIcon('solidity')
.setSolidityCompilerVersion('soljson-v0.8.20+commit.a1b79de6.js')
.addFile('contracts/mytoken.sol', {
content: myToken
}).useXpath().waitForElementVisible("//*[@class='view-line' and contains(.,'gas')]")
},
// here we change quickly between files to test the files being parsed correctly when switching between them
'Should show ERC20 hover over contract in editor #group1': function (browser: NightwatchBrowser) {
browser.scrollToLine(10)
const path = "//*[@class='view-line' and contains(.,'MyToken') and contains(.,'Pausable')]//span//span[contains(.,'ERC20Burnable')]"
const expectedContent = 'contract ERC20Burnable is ERC20Burnable, ERC20, IERC20Errors, IERC20Metadata, IERC20, Context'
checkEditorHoverContent(browser, path, expectedContent, 25)
},
'Go back to ballot file': function (browser: NightwatchBrowser) {
browser.openFile('contracts/3_Ballot.sol')
.useXpath().waitForElementVisible("//*[@class='view-line' and contains(.,'gas')]")
},
'Should show hover over function in editor again #group1': function (browser: NightwatchBrowser) {
browser
.scrollToLine(58)
const path: string = "//*[@class='view-line' and contains(.,'giveRightToVote(address') and contains(.,'function') and contains(.,'public')]//span//span[contains(.,'giveRightToVote')]"
let expectedContent = 'Estimated execution cost'
checkEditorHoverContent(browser, path, expectedContent)
expectedContent = 'function giveRightToVote (address internal voter) public nonpayable returns ()'
checkEditorHoverContent(browser, path, expectedContent)
expectedContent = "@dev Give 'voter' the right to vote on this ballot. May only be called by 'chairperson'"
checkEditorHoverContent(browser, path, expectedContent)
},
'Open token file': function (browser: NightwatchBrowser) {
browser.openFile('contracts/mytoken.sol')
.useXpath().waitForElementVisible("//*[@class='view-line' and contains(.,'gas')]")
},
'Should show ERC20 hover over contract in editor again #group1': function (browser: NightwatchBrowser) {
browser.scrollToLine(10)
const path = "//*[@class='view-line' and contains(.,'MyToken') and contains(.,'Pausable')]//span//span[contains(.,'ERC20Burnable')]"
const expectedContent = 'contract ERC20Burnable is ERC20Burnable, ERC20, IERC20Errors, IERC20Metadata, IERC20, Context'
checkEditorHoverContent(browser, path, expectedContent, 25)
},
} }
@ -234,3 +276,44 @@ contract BallotHoverTest {
} }
} }
` `
const myToken = `
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;
import "@openzeppelin/contracts/token/ERC20/ERC20.sol";
import "@openzeppelin/contracts/token/ERC20/extensions/ERC20Burnable.sol";
import "@openzeppelin/contracts/token/ERC20/extensions/ERC20Pausable.sol";
import "@openzeppelin/contracts/access/Ownable.sol";
import "@openzeppelin/contracts/token/ERC20/extensions/ERC20Permit.sol";
contract MyToken is ERC20, ERC20Burnable, ERC20Pausable, Ownable, ERC20Permit {
constructor(address initialOwner)
ERC20("MyToken", "MTK")
Ownable(initialOwner)
ERC20Permit("MyToken")
{}
function pause() public onlyOwner {
_pause();
}
function unpause() public onlyOwner {
_unpause();
}
function mint(address to, uint256 amount) public onlyOwner {
_mint(to, amount);
}
// The following functions are overrides required by Solidity.
function _update(address from, address to, uint256 value)
internal
override(ERC20, ERC20Pausable)
{
super._update(from, to, value);
}
}
`

@ -1,53 +1,55 @@
'use strict' 'use strict'
import { NightwatchBrowser } from 'nightwatch' import {NightwatchBrowser} from 'nightwatch'
import init from '../helpers/init' import init from '../helpers/init'
const openReferences = (browser: NightwatchBrowser, path: string) => { const openReferences = (browser: NightwatchBrowser, path: string) => {
(browser as any).useXpath() ;(browser as any)
.useXpath() .useXpath()
.waitForElementVisible(path) .useXpath()
.click(path) .waitForElementVisible(path)
.perform(function () { .click(path)
const actions = this.actions({ async: true }); .perform(function () {
return actions. const actions = this.actions({async: true})
keyDown(this.Keys.SHIFT). return actions.keyDown(this.Keys.SHIFT).sendKeys(this.Keys.F12)
sendKeys(this.Keys.F12) })
})
} }
module.exports = { module.exports = {
before: function (browser: NightwatchBrowser, done: VoidFunction) { 'before': function (browser: NightwatchBrowser, done: VoidFunction) {
init(browser, done, 'http://127.0.0.1:8080', false) init(browser, done, 'http://127.0.0.1:8080', false)
}, },
'Should load the test file': function (browser: NightwatchBrowser) { 'Should load the test file': function (browser: NightwatchBrowser) {
browser.openFile('contracts') browser
.openFile('contracts/3_Ballot.sol') .openFile('contracts')
.waitForElementVisible('#editorView') .openFile('contracts/3_Ballot.sol')
.setEditorValue(BallotWithARefToOwner) .waitForElementVisible('#editorView')
.pause(10000) // wait for the compiler to finish .setEditorValue(BallotWithARefToOwner)
.scrollToLine(37) .pause(10000) // wait for the compiler to finish
}, .scrollToLine(37)
'Should show local references': function (browser: NightwatchBrowser) { },
browser.scrollToLine(48) 'Should show local references': function (browser: NightwatchBrowser) {
const path = "//*[@class='view-line' and contains(.,'length') and contains(.,'proposalNames')]//span//span[contains(.,'proposalNames')]" browser.scrollToLine(48)
openReferences(browser, path) const path = "//*[@class='view-line' and contains(.,'length') and contains(.,'proposalNames')]//span//span[contains(.,'proposalNames')]"
browser.waitForElementVisible("//*[@class='monaco-highlighted-label referenceMatch']//span[contains(.,'length; i++')]") openReferences(browser, path)
.waitForElementVisible("//*[@class='monaco-highlighted-label referenceMatch']//span[contains(.,'name:')]") browser
.waitForElementVisible("//*[@class='monaco-highlighted-label referenceMatch']//span[contains(.,'constructor')]") .waitForElementVisible("//*[@class='monaco-highlighted-label referenceMatch'][contains(.,'length; i++')]")
.keys(browser.Keys.ESCAPE) .waitForElementVisible("//*[@class='monaco-highlighted-label referenceMatch'][contains(.,'name:')]")
}, .waitForElementVisible("//*[@class='monaco-highlighted-label referenceMatch'][contains(.,'constructor')]")
'Should show references of getOwner': function (browser: NightwatchBrowser) { .keys(browser.Keys.ESCAPE)
browser.scrollToLine(39) },
const path = "//*[@class='view-line' and contains(.,'getOwner') and contains(.,'cowner')]//span//span[contains(.,'getOwner')]" 'Should show references of getOwner': function (browser: NightwatchBrowser) {
openReferences(browser, path) browser.scrollToLine(39)
browser.useXpath() const path = "//*[@class='view-line' and contains(.,'getOwner') and contains(.,'cowner')]//span//span[contains(.,'getOwner')]"
.waitForElementVisible("//*[@class='monaco-highlighted-label']//span[contains(.,'2_Owner.sol')]") openReferences(browser, path)
.waitForElementVisible("//*[@class='monaco-highlighted-label']//span[contains(.,'3_Ballot.sol')]") browser
.waitForElementVisible("//*[@class='monaco-highlighted-label referenceMatch']//span[contains(.,'cowner.getOwner')]") .useXpath()
.waitForElementVisible("//*[contains(@class, 'results-loaded') and contains(., 'References (2)')]") .waitForElementVisible("//*[@class='monaco-highlighted-label'][contains(.,'2_Owner.sol')]")
.keys(browser.Keys.ESCAPE) .waitForElementVisible("//*[@class='monaco-highlighted-label'][contains(.,'3_Ballot.sol')]")
} .waitForElementVisible("//*[@class='monaco-highlighted-label referenceMatch'][contains(.,'cowner.getOwner')]")
.waitForElementVisible("//*[contains(@class, 'results-loaded') and contains(., 'References (2)')]")
.keys(browser.Keys.ESCAPE)
}
} }
// eslint-disable-next-line @typescript-eslint/no-unused-vars // eslint-disable-next-line @typescript-eslint/no-unused-vars

@ -42,6 +42,25 @@ module.exports = {
}) })
}, },
'Should execute `writeMultipleFiles` api from file manager external api #group1': function (browser: NightwatchBrowser) {
browser
.addFile('writeMultipleFiles.js', { content: executeWriteMultipleFiles })
.executeScriptInTerminal('remix.exeCurrent()')
.pause(2000)
.openFile('contracts/new_contract_1.sol')
.getEditorValue((content) => {
browser.assert.ok(content.indexOf('pragma solidity ^0.6.0') !== -1, 'content does not contain "pragma solidity ^0.6.0"')
})
.openFile('new_contract_2.sol')
.getEditorValue((content) => {
browser.assert.ok(content.indexOf('pragma solidity ^0.8.0') !== -1, 'content does not contain "pragma solidity ^0.8.0"')
})
.openFile('testing.txt')
.getEditorValue((content) => {
browser.assert.ok(content.indexOf('test') !== -1, 'content does not contain "test"')
})
},
'Should execute `readFile` api from file manager external api #group2': function (browser: NightwatchBrowser) { 'Should execute `readFile` api from file manager external api #group2': function (browser: NightwatchBrowser) {
browser browser
.addFile('writeFile.js', { content: executeWriteFile }) .addFile('writeFile.js', { content: executeWriteFile })
@ -143,6 +162,14 @@ const executeWriteFile = `
run() run()
` `
const executeWriteMultipleFiles = `
const run = async () => {
await remix.call('fileManager', 'writeMultipleFiles', ['contracts/new_contract_1.sol', 'new_contract_2.sol', 'testing.txt'], ['pragma solidity ^0.6.0', 'pragma solidity ^0.8.0', 'test'], '/')
}
run()
`
const executeReadFile = ` const executeReadFile = `
const run = async () => { const run = async () => {
const result = await remix.call('fileManager', 'readFile', 'new_contract.sol') const result = await remix.call('fileManager', 'readFile', 'new_contract.sol')

@ -0,0 +1,345 @@
'use strict'
import { NightwatchBrowser } from 'nightwatch'
import init from '../helpers/init'
let firstProxyAddress: string
let lastProxyAddress: string
let shortenedFirstAddress: string
let shortenedLastAddress: string
module.exports = {
'@disabled': true,
before: function (browser: NightwatchBrowser, done: VoidFunction) {
init(browser, done)
},
'@sources': function () {
return sources
},
'Should set the compiler version to 8.19': function(browser: NightwatchBrowser) {
browser.setSolidityCompilerVersion('soljson-v0.8.19+commit.7dd6d404.js')
},
'Should show deploy proxy option for UUPS upgradeable contract #group1': function (browser: NightwatchBrowser) {
browser
.addFile('myTokenV1.sol', sources[0]['myTokenV1.sol'])
.clickLaunchIcon('solidity')
.pause(2000)
// because the compilatiom imports are slow and sometimes stop loading (not sure why, it's bug) we need to recompile and check to see if the files are really in de FS
.click('[data-id="compilerContainerCompileBtn"]')
.clickLaunchIcon('filePanel')
.isVisible({
selector: '*[data-id="treeViewDivtreeViewItem.deps/npm/@openzeppelin/contracts-upgradeable@4.8.3/proxy/beacon/IBeaconUpgradeable.sol"]',
timeout: 120000,
suppressNotFoundErrors: true
})
.clickLaunchIcon('solidity')
.click('[data-id="compilerContainerCompileBtn"]')
.clickLaunchIcon('filePanel')
.isVisible({
selector: '*[data-id="treeViewDivtreeViewItem.deps/npm/@openzeppelin/contracts-upgradeable@4.8.3/proxy/beacon/IBeaconUpgradeable.sol"]',
timeout: 120000,
suppressNotFoundErrors: true
})
.clickLaunchIcon('solidity')
.click('[data-id="compilerContainerCompileBtn"]')
.clickLaunchIcon('filePanel')
.waitForElementVisible({
selector: '*[data-id="treeViewDivtreeViewItem.deps/npm/@openzeppelin/contracts-upgradeable@4.8.3/proxy/beacon/IBeaconUpgradeable.sol"]',
timeout: 120000,
})
.clickLaunchIcon('solidity')
.waitForElementPresent('select[id="compiledContracts"] option[value=MyToken]', 60000)
.clickLaunchIcon('udapp')
.click('select.udapp_contractNames')
.click('select.udapp_contractNames option[value=MyToken]')
.waitForElementPresent('[data-id="contractGUIDeployWithProxyLabel"]')
.waitForElementPresent('[data-id="contractGUIUpgradeImplementationLabel"]')
},
'Should show upgrade proxy option for child contract inheriting UUPS parent contract #group1': function (browser: NightwatchBrowser) {
browser
.addFile('myTokenV2.sol', sources[1]['myTokenV2.sol'])
.clickLaunchIcon('solidity')
.assert.visible('[data-id="compilerContainerCompileBtn"]')
.click('[data-id="compilerContainerCompileBtn"]')
.waitForElementPresent('select[id="compiledContracts"] option[value=MyTokenV2]', 60000)
.clickLaunchIcon('udapp')
.click('select.udapp_contractNames')
.click('select.udapp_contractNames option[value=MyTokenV2]')
.waitForElementPresent('[data-id="contractGUIDeployWithProxyLabel"]')
.waitForElementPresent('[data-id="contractGUIUpgradeImplementationLabel"]')
},
'Should deploy proxy without initialize parameters #group1': function (browser: NightwatchBrowser) {
browser
.openFile('myTokenV1.sol')
.clickLaunchIcon('solidity')
.assert.visible('[data-id="compilerContainerCompileBtn"]')
.click('[data-id="compilerContainerCompileBtn"]')
.waitForElementPresent('select[id="compiledContracts"] option[value=MyToken]', 60000)
.clickLaunchIcon('udapp')
.click('select.udapp_contractNames')
.click('select.udapp_contractNames option[value=MyToken]')
.verify.visible('[data-id="contractGUIDeployWithProxyLabel"]')
.waitForElementPresent('[data-id="contractGUIDeployWithProxyLabel"]')
.click('[data-id="contractGUIDeployWithProxyLabel"]')
.createContract('')
.waitForElementContainsText('[data-id="udappNotifyModalDialogModalTitle-react"]', 'Deploy Implementation & Proxy (ERC1967)')
.waitForElementVisible('[data-id="udappNotify-modal-footer-ok-react"]')
.click('[data-id="udappNotify-modal-footer-ok-react"]')
.waitForElementContainsText('[data-id="confirmProxyDeploymentModalDialogModalTitle-react"]', 'Confirm Deploy Proxy (ERC1967)')
.waitForElementVisible('[data-id="confirmProxyDeployment-modal-footer-ok-react"]')
.click('[data-id="confirmProxyDeployment-modal-footer-ok-react"]')
.waitForElementPresent('[data-id="universalDappUiTitleExpander0"]')
.waitForElementPresent('[data-id="universalDappUiTitleExpander1"]')
.waitForElementContainsText('*[data-id="terminalJournal"]', 'Deploying ERC1967 < 5.0.0 as proxy...', 60000)
},
'Should interact with deployed contract via ERC1967 (proxy) #group1': function (browser: NightwatchBrowser) {
browser
.getAddressAtPosition(1, (address) => {
firstProxyAddress = address
shortenedFirstAddress = address.slice(0, 5) + '...' + address.slice(address.length - 5, address.length)
})
.clickInstance(1)
.perform((done) => {
browser.testConstantFunction(firstProxyAddress, 'name - call', null, '0:\nstring: MyToken').perform(() => {
done()
})
})
.perform((done) => {
browser.testConstantFunction(firstProxyAddress, 'symbol - call', null, '0:\nstring: MTK').perform(() => {
done()
})
})
},
'Should deploy proxy with initialize parameters #group1': function (browser: NightwatchBrowser) {
browser
.waitForElementPresent('[data-id="deployAndRunClearInstances"]')
.click('[data-id="deployAndRunClearInstances"]')
.addFile('initializeProxy.sol', sources[2]['initializeProxy.sol'])
.clickLaunchIcon('solidity')
.assert.visible('[data-id="compilerContainerCompileBtn"]')
.click('[data-id="compilerContainerCompileBtn"]')
.waitForElementPresent('select[id="compiledContracts"] option[value=MyInitializedToken]', 60000)
.clickLaunchIcon('udapp')
.click('select.udapp_contractNames')
.click('select.udapp_contractNames option[value=MyInitializedToken]')
.waitForElementPresent('[data-id="contractGUIDeployWithProxyLabel"]')
.click('[data-id="contractGUIDeployWithProxyLabel"]')
.useXpath()
.waitForElementPresent('//*[@id="runTabView"]/div/div[2]/div[3]/div[1]/div/div[1]/div[4]/div/div[1]/input')
.waitForElementPresent('//*[@id="runTabView"]/div/div[2]/div[3]/div[1]/div/div[1]/div[4]/div/div[2]/input')
.setValue('//*[@id="runTabView"]/div/div[2]/div[3]/div[1]/div/div[1]/div[4]/div/div[1]/input', 'Remix')
.setValue('//*[@id="runTabView"]/div/div[2]/div[3]/div[1]/div/div[1]/div[4]/div/div[2]/input', "R")
.useCss()
.createContract('')
.waitForElementContainsText('[data-id="udappNotifyModalDialogModalTitle-react"]', 'Deploy Implementation & Proxy (ERC1967)')
.waitForElementVisible('[data-id="udappNotify-modal-footer-ok-react"]')
.click('[data-id="udappNotify-modal-footer-ok-react"]')
.waitForElementContainsText('[data-id="confirmProxyDeploymentModalDialogModalTitle-react"]', 'Confirm Deploy Proxy (ERC1967)')
.waitForElementVisible('[data-id="confirmProxyDeployment-modal-footer-ok-react"]')
.click('[data-id="confirmProxyDeployment-modal-footer-ok-react"]')
.waitForElementPresent('[data-id="universalDappUiTitleExpander0"]')
.waitForElementPresent('[data-id="universalDappUiTitleExpander1"]')
.waitForElementContainsText('*[data-id="terminalJournal"]', 'Deploying ERC1967 < 5.0.0 as proxy...', 60000)
},
'Should interact with initialized contract to verify parameters #group1': function (browser: NightwatchBrowser) {
browser
.getAddressAtPosition(1, (address) => {
lastProxyAddress = address
shortenedLastAddress = address.slice(0, 5) + '...' + address.slice(address.length - 5, address.length)
})
.clickInstance(1)
.perform((done) => {
browser.testConstantFunction(lastProxyAddress, 'name - call', null, '0:\nstring: Remix').perform(() => {
done()
})
})
.perform((done) => {
browser.testConstantFunction(lastProxyAddress, 'symbol - call', null, '0:\nstring: R').perform(() => {
done()
})
})
},
'Should upgrade contract by selecting a previously deployed proxy address from dropdown (MyTokenV1 to MyTokenV2) #group1': function (browser: NightwatchBrowser) {
browser
.click('*[data-id="terminalClearConsole"]')
.waitForElementPresent('[data-id="deployAndRunClearInstances"]')
.click('[data-id="deployAndRunClearInstances"]')
.openFile('myTokenV2.sol')
.clickLaunchIcon('solidity')
.assert.visible('[data-id="compilerContainerCompileBtn"]')
.click('[data-id="compilerContainerCompileBtn"]')
.waitForElementPresent('select[id="compiledContracts"] option[value=MyTokenV2]', 60000)
.clickLaunchIcon('udapp')
.click('select.udapp_contractNames')
.click('select.udapp_contractNames option[value=MyTokenV2]')
.waitForElementPresent('[data-id="contractGUIUpgradeImplementationLabel"]')
.click('[data-id="contractGUIUpgradeImplementationLabel"]')
.waitForElementPresent('[data-id="toggleProxyAddressDropdown"]')
.click('[data-id="toggleProxyAddressDropdown"]')
.waitForElementVisible('[data-id="proxy-dropdown-items"]')
.assert.textContains('[data-id="proxy-dropdown-items"]', shortenedFirstAddress)
.assert.textContains('[data-id="proxy-dropdown-items"]', shortenedLastAddress)
.click('[data-id="proxyAddress1"]')
.createContract('')
.waitForElementContainsText('[data-id="udappNotifyModalDialogModalTitle-react"]', 'Deploy Implementation & Update Proxy')
.waitForElementVisible('[data-id="udappNotify-modal-footer-ok-react"]')
.click('[data-id="udappNotify-modal-footer-ok-react"]')
.waitForElementContainsText('[data-id="confirmProxyDeploymentModalDialogModalTitle-react"]', 'Confirm Update Proxy (ERC1967)')
.waitForElementVisible('[data-id="confirmProxyDeployment-modal-footer-ok-react"]')
.click(
{
selector: '[data-id="confirmProxyDeployment-modal-footer-ok-react"]',
})
.waitForElementPresent('[data-id="universalDappUiTitleExpander0"]')
.waitForElementPresent('[data-id="universalDappUiTitleExpander1"]')
.waitForElementContainsText('*[data-id="terminalJournal"]', 'Using ERC1967 < 5.0.0 for the proxy upgrade..', 60000)
},
'Should interact with upgraded function in contract MyTokenV2 #group1': function (browser: NightwatchBrowser) {
browser
.clickInstance(1)
.perform((done) => {
browser.testConstantFunction(lastProxyAddress, 'version - call', null, '0:\nstring: MyTokenV2!').perform(() => {
done()
})
})
},
'Should upgrade contract by providing proxy address in input field (MyTokenV1 to MyTokenV2) #group1': function (browser: NightwatchBrowser) {
browser
.click('*[data-id="terminalClearConsole"]')
.waitForElementPresent('[data-id="deployAndRunClearInstances"]')
.click('[data-id="deployAndRunClearInstances"]')
.openFile('myTokenV2.sol')
.clickLaunchIcon('solidity')
.assert.visible('[data-id="compilerContainerCompileBtn"]')
.click('[data-id="compilerContainerCompileBtn"]')
.waitForElementPresent('select[id="compiledContracts"] option[value=MyTokenV2]', 60000)
.clickLaunchIcon('udapp')
.click('select.udapp_contractNames')
.click('select.udapp_contractNames option[value=MyTokenV2]')
.waitForElementPresent('[data-id="contractGUIUpgradeImplementationLabel"]')
.waitForElementPresent('[data-id="toggleProxyAddressDropdown"]')
.clearValue('[data-id="ERC1967AddressInput"]')
.setValue('[data-id="ERC1967AddressInput"]', firstProxyAddress)
.createContract('')
.waitForElementContainsText('[data-id="udappNotifyModalDialogModalTitle-react"]', 'Deploy Implementation & Update Proxy')
.waitForElementVisible('[data-id="udappNotify-modal-footer-ok-react"]')
.click('[data-id="udappNotify-modal-footer-ok-react"]')
.waitForElementContainsText('[data-id="confirmProxyDeploymentModalDialogModalTitle-react"]', 'Confirm Update Proxy (ERC1967)')
.waitForElementVisible('[data-id="confirmProxyDeployment-modal-footer-ok-react"]')
.click('[data-id="confirmProxyDeployment-modal-footer-ok-react"]')
.waitForElementPresent('[data-id="universalDappUiTitleExpander0"]')
.waitForElementPresent('[data-id="universalDappUiTitleExpander1"]')
.waitForElementContainsText('*[data-id="terminalJournal"]', 'Using ERC1967 < 5.0.0 for the proxy upgrade..', 60000)
},
'Should interact with upgraded contract through provided proxy address #group1': function (browser: NightwatchBrowser) {
browser
.clearConsole()
.clickInstance(1)
.perform((done) => {
browser.testConstantFunction(firstProxyAddress, 'version - call', null, '0:\nstring: MyTokenV2!').perform(() => {
done()
})
})
},
'Should debug the call': function(browser: NightwatchBrowser) {
browser
.debugTransaction(0)
.waitForElementVisible({
locateStrategy: 'xpath',
selector: '//*[@data-id="treeViewLivm trace step" and contains(.,"11")]',
timeout: 60000
})
.goToVMTraceStep(146)
.waitForElementContainsText('*[data-id="functionPanel"]', 'version()', 60000)
.end()
}
}
const sources = [
{
'myTokenV1.sol': {
content: `
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.4;
import "@openzeppelin/contracts-upgradeable@4.8.3/token/ERC721/ERC721Upgradeable.sol";
import "@openzeppelin/contracts-upgradeable@4.8.3/access/OwnableUpgradeable.sol";
import "@openzeppelin/contracts-upgradeable@4.8.3/proxy/utils/Initializable.sol";
import "@openzeppelin/contracts-upgradeable@4.8.3/proxy/utils/UUPSUpgradeable.sol";
contract MyToken is Initializable, ERC721Upgradeable, OwnableUpgradeable, UUPSUpgradeable {
/// @custom:oz-upgrades-unsafe-allow constructor
constructor() {
_disableInitializers();
}
function initialize() initializer public {
__ERC721_init("MyToken", "MTK");
__Ownable_init();
__UUPSUpgradeable_init();
}
function _authorizeUpgrade(address newImplementation)
internal
onlyOwner
override
{}
}
`
}
}, {
'myTokenV2.sol': {
content: `
import "./myTokenV1.sol";
contract MyTokenV2 is MyToken {
function version () public view returns (string memory) {
return "MyTokenV2!";
}
}
`
}
}, {
'initializeProxy.sol': {
content: `
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.4;
import "@openzeppelin/contracts-upgradeable@4.8.3/token/ERC721/ERC721Upgradeable.sol";
import "@openzeppelin/contracts-upgradeable@4.8.3/access/OwnableUpgradeable.sol";
import "@openzeppelin/contracts-upgradeable@4.8.3/proxy/utils/Initializable.sol";
import "@openzeppelin/contracts-upgradeable@4.8.3/proxy/utils/UUPSUpgradeable.sol";
contract MyInitializedToken is Initializable, ERC721Upgradeable, OwnableUpgradeable, UUPSUpgradeable {
/// @custom:oz-upgrades-unsafe-allow constructor
constructor() {
_disableInitializers();
}
function initialize(string memory tokenName, string memory tokenSymbol) initializer public {
__ERC721_init(tokenName, tokenSymbol);
__Ownable_init();
__UUPSUpgradeable_init();
}
function _authorizeUpgrade(address newImplementation)
internal
onlyOwner
override
{}
}
`
}
}
]

@ -26,7 +26,7 @@ module.exports = {
.click('[data-id="compilerContainerCompileBtn"]') .click('[data-id="compilerContainerCompileBtn"]')
.clickLaunchIcon('filePanel') .clickLaunchIcon('filePanel')
.isVisible({ .isVisible({
selector: '*[data-id="treeViewDivtreeViewItem.deps/npm/@openzeppelin/contracts-upgradeable/proxy/beacon/IBeaconUpgradeable.sol"]', selector: '*[data-id="treeViewDivtreeViewItem.deps/npm/@openzeppelin/contracts-upgradeable/utils/ContextUpgradeable.sol"]',
timeout: 120000, timeout: 120000,
suppressNotFoundErrors: true suppressNotFoundErrors: true
}) })
@ -34,7 +34,7 @@ module.exports = {
.click('[data-id="compilerContainerCompileBtn"]') .click('[data-id="compilerContainerCompileBtn"]')
.clickLaunchIcon('filePanel') .clickLaunchIcon('filePanel')
.isVisible({ .isVisible({
selector: '*[data-id="treeViewDivtreeViewItem.deps/npm/@openzeppelin/contracts-upgradeable/proxy/beacon/IBeaconUpgradeable.sol"]', selector: '*[data-id="treeViewDivtreeViewItem.deps/npm/@openzeppelin/contracts-upgradeable/utils/ContextUpgradeable.sol"]',
timeout: 120000, timeout: 120000,
suppressNotFoundErrors: true suppressNotFoundErrors: true
}) })
@ -42,7 +42,7 @@ module.exports = {
.click('[data-id="compilerContainerCompileBtn"]') .click('[data-id="compilerContainerCompileBtn"]')
.clickLaunchIcon('filePanel') .clickLaunchIcon('filePanel')
.waitForElementVisible({ .waitForElementVisible({
selector: '*[data-id="treeViewDivtreeViewItem.deps/npm/@openzeppelin/contracts-upgradeable/proxy/beacon/IBeaconUpgradeable.sol"]', selector: '*[data-id="treeViewDivtreeViewItem.deps/npm/@openzeppelin/contracts-upgradeable/utils/ContextUpgradeable.sol"]',
timeout: 120000, timeout: 120000,
}) })
.clickLaunchIcon('solidity') .clickLaunchIcon('solidity')
@ -81,6 +81,7 @@ module.exports = {
.verify.visible('[data-id="contractGUIDeployWithProxyLabel"]') .verify.visible('[data-id="contractGUIDeployWithProxyLabel"]')
.waitForElementPresent('[data-id="contractGUIDeployWithProxyLabel"]') .waitForElementPresent('[data-id="contractGUIDeployWithProxyLabel"]')
.click('[data-id="contractGUIDeployWithProxyLabel"]') .click('[data-id="contractGUIDeployWithProxyLabel"]')
.setValue('[data-id="initializeInputs-initialOwner"]', '0x5B38Da6a701c568545dCfcB03FcB875f56beddC4')
.createContract('') .createContract('')
.waitForElementContainsText('[data-id="udappNotifyModalDialogModalTitle-react"]', 'Deploy Implementation & Proxy (ERC1967)') .waitForElementContainsText('[data-id="udappNotifyModalDialogModalTitle-react"]', 'Deploy Implementation & Proxy (ERC1967)')
.waitForElementVisible('[data-id="udappNotify-modal-footer-ok-react"]') .waitForElementVisible('[data-id="udappNotify-modal-footer-ok-react"]')
@ -90,6 +91,7 @@ module.exports = {
.click('[data-id="confirmProxyDeployment-modal-footer-ok-react"]') .click('[data-id="confirmProxyDeployment-modal-footer-ok-react"]')
.waitForElementPresent('[data-id="universalDappUiTitleExpander0"]') .waitForElementPresent('[data-id="universalDappUiTitleExpander0"]')
.waitForElementPresent('[data-id="universalDappUiTitleExpander1"]') .waitForElementPresent('[data-id="universalDappUiTitleExpander1"]')
.waitForElementContainsText('*[data-id="terminalJournal"]', 'Deploying ERC1967 >= 5.0.0 as proxy...')
}, },
'Should interact with deployed contract via ERC1967 (proxy) #group1': function (browser: NightwatchBrowser) { 'Should interact with deployed contract via ERC1967 (proxy) #group1': function (browser: NightwatchBrowser) {
@ -131,6 +133,7 @@ module.exports = {
.setValue('//*[@id="runTabView"]/div/div[2]/div[3]/div[1]/div/div[1]/div[4]/div/div[1]/input', 'Remix') .setValue('//*[@id="runTabView"]/div/div[2]/div[3]/div[1]/div/div[1]/div[4]/div/div[1]/input', 'Remix')
.setValue('//*[@id="runTabView"]/div/div[2]/div[3]/div[1]/div/div[1]/div[4]/div/div[2]/input', "R") .setValue('//*[@id="runTabView"]/div/div[2]/div[3]/div[1]/div/div[1]/div[4]/div/div[2]/input', "R")
.useCss() .useCss()
.setValue('[data-id="initializeInputs-initialOwner"]', '0x5B38Da6a701c568545dCfcB03FcB875f56beddC4')
.createContract('') .createContract('')
.waitForElementContainsText('[data-id="udappNotifyModalDialogModalTitle-react"]', 'Deploy Implementation & Proxy (ERC1967)') .waitForElementContainsText('[data-id="udappNotifyModalDialogModalTitle-react"]', 'Deploy Implementation & Proxy (ERC1967)')
.waitForElementVisible('[data-id="udappNotify-modal-footer-ok-react"]') .waitForElementVisible('[data-id="udappNotify-modal-footer-ok-react"]')
@ -140,6 +143,7 @@ module.exports = {
.click('[data-id="confirmProxyDeployment-modal-footer-ok-react"]') .click('[data-id="confirmProxyDeployment-modal-footer-ok-react"]')
.waitForElementPresent('[data-id="universalDappUiTitleExpander0"]') .waitForElementPresent('[data-id="universalDappUiTitleExpander0"]')
.waitForElementPresent('[data-id="universalDappUiTitleExpander1"]') .waitForElementPresent('[data-id="universalDappUiTitleExpander1"]')
.waitForElementContainsText('*[data-id="terminalJournal"]', 'Deploying ERC1967 >= 5.0.0 as proxy...')
}, },
'Should interact with initialized contract to verify parameters #group1': function (browser: NightwatchBrowser) { 'Should interact with initialized contract to verify parameters #group1': function (browser: NightwatchBrowser) {
@ -163,6 +167,7 @@ module.exports = {
'Should upgrade contract by selecting a previously deployed proxy address from dropdown (MyTokenV1 to MyTokenV2) #group1': function (browser: NightwatchBrowser) { 'Should upgrade contract by selecting a previously deployed proxy address from dropdown (MyTokenV1 to MyTokenV2) #group1': function (browser: NightwatchBrowser) {
browser browser
.click('*[data-id="terminalClearConsole"]')
.waitForElementPresent('[data-id="deployAndRunClearInstances"]') .waitForElementPresent('[data-id="deployAndRunClearInstances"]')
.click('[data-id="deployAndRunClearInstances"]') .click('[data-id="deployAndRunClearInstances"]')
.openFile('myTokenV2.sol') .openFile('myTokenV2.sol')
@ -180,6 +185,7 @@ module.exports = {
.waitForElementVisible('[data-id="proxy-dropdown-items"]') .waitForElementVisible('[data-id="proxy-dropdown-items"]')
.assert.textContains('[data-id="proxy-dropdown-items"]', shortenedFirstAddress) .assert.textContains('[data-id="proxy-dropdown-items"]', shortenedFirstAddress)
.assert.textContains('[data-id="proxy-dropdown-items"]', shortenedLastAddress) .assert.textContains('[data-id="proxy-dropdown-items"]', shortenedLastAddress)
.click('[data-id="proxyAddress1"]') .click('[data-id="proxyAddress1"]')
.createContract('') .createContract('')
.waitForElementContainsText('[data-id="udappNotifyModalDialogModalTitle-react"]', 'Deploy Implementation & Update Proxy') .waitForElementContainsText('[data-id="udappNotifyModalDialogModalTitle-react"]', 'Deploy Implementation & Update Proxy')
@ -193,6 +199,7 @@ module.exports = {
}) })
.waitForElementPresent('[data-id="universalDappUiTitleExpander0"]') .waitForElementPresent('[data-id="universalDappUiTitleExpander0"]')
.waitForElementPresent('[data-id="universalDappUiTitleExpander1"]') .waitForElementPresent('[data-id="universalDappUiTitleExpander1"]')
.waitForElementContainsText('*[data-id="terminalJournal"]', 'Using ERC1967 >= 5.0.0 for the proxy upgrade...')
}, },
'Should interact with upgraded function in contract MyTokenV2 #group1': function (browser: NightwatchBrowser) { 'Should interact with upgraded function in contract MyTokenV2 #group1': function (browser: NightwatchBrowser) {
@ -207,6 +214,7 @@ module.exports = {
'Should upgrade contract by providing proxy address in input field (MyTokenV1 to MyTokenV2) #group1': function (browser: NightwatchBrowser) { 'Should upgrade contract by providing proxy address in input field (MyTokenV1 to MyTokenV2) #group1': function (browser: NightwatchBrowser) {
browser browser
.click('*[data-id="terminalClearConsole"]')
.waitForElementPresent('[data-id="deployAndRunClearInstances"]') .waitForElementPresent('[data-id="deployAndRunClearInstances"]')
.click('[data-id="deployAndRunClearInstances"]') .click('[data-id="deployAndRunClearInstances"]')
.openFile('myTokenV2.sol') .openFile('myTokenV2.sol')
@ -230,17 +238,30 @@ module.exports = {
.click('[data-id="confirmProxyDeployment-modal-footer-ok-react"]') .click('[data-id="confirmProxyDeployment-modal-footer-ok-react"]')
.waitForElementPresent('[data-id="universalDappUiTitleExpander0"]') .waitForElementPresent('[data-id="universalDappUiTitleExpander0"]')
.waitForElementPresent('[data-id="universalDappUiTitleExpander1"]') .waitForElementPresent('[data-id="universalDappUiTitleExpander1"]')
.waitForElementContainsText('*[data-id="terminalJournal"]', 'Using ERC1967 >= 5.0.0 for the proxy upgrade...')
}, },
'Should interact with upgraded contract through provided proxy address #group1': function (browser: NightwatchBrowser) { 'Should interact with upgraded contract through provided proxy address #group1': function (browser: NightwatchBrowser) {
browser browser
.clearConsole()
.clickInstance(1) .clickInstance(1)
.perform((done) => { .perform((done) => {
browser.testConstantFunction(firstProxyAddress, 'version - call', null, '0:\nstring: MyTokenV2!').perform(() => { browser.testConstantFunction(firstProxyAddress, 'version - call', null, '0:\nstring: MyTokenV2!').perform(() => {
done() done()
}) })
}) })
.end() },
'Should debug the call': function(browser: NightwatchBrowser) {
browser
.debugTransaction(0)
.waitForElementVisible({
locateStrategy: 'xpath',
selector: '//*[@data-id="treeViewLivm trace step" and contains(.,"7")]',
timeout: 60000
})
.goToVMTraceStep(129)
.waitForElementContainsText('*[data-id="functionPanel"]', 'version()', 60000)
.end()
} }
} }
@ -250,11 +271,11 @@ const sources = [
'myTokenV1.sol': { 'myTokenV1.sol': {
content: ` content: `
// SPDX-License-Identifier: MIT // SPDX-License-Identifier: MIT
pragma solidity ^0.8.4; pragma solidity ^0.8.20;
import "@openzeppelin/contracts-upgradeable/token/ERC721/ERC721Upgradeable.sol"; import "@openzeppelin/contracts-upgradeable/token/ERC721/ERC721Upgradeable.sol";
import "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol";
import "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol"; import "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol";
import "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol";
import "@openzeppelin/contracts-upgradeable/proxy/utils/UUPSUpgradeable.sol"; import "@openzeppelin/contracts-upgradeable/proxy/utils/UUPSUpgradeable.sol";
contract MyToken is Initializable, ERC721Upgradeable, OwnableUpgradeable, UUPSUpgradeable { contract MyToken is Initializable, ERC721Upgradeable, OwnableUpgradeable, UUPSUpgradeable {
@ -263,9 +284,9 @@ const sources = [
_disableInitializers(); _disableInitializers();
} }
function initialize() initializer public { function initialize(address initialOwner) initializer public {
__ERC721_init("MyToken", "MTK"); __ERC721_init("MyToken", "MTK");
__Ownable_init(); __Ownable_init(initialOwner);
__UUPSUpgradeable_init(); __UUPSUpgradeable_init();
} }
@ -280,6 +301,8 @@ const sources = [
}, { }, {
'myTokenV2.sol': { 'myTokenV2.sol': {
content: ` content: `
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;
import "./myTokenV1.sol"; import "./myTokenV1.sol";
contract MyTokenV2 is MyToken { contract MyTokenV2 is MyToken {
@ -293,11 +316,11 @@ const sources = [
'initializeProxy.sol': { 'initializeProxy.sol': {
content: ` content: `
// SPDX-License-Identifier: MIT // SPDX-License-Identifier: MIT
pragma solidity ^0.8.4; pragma solidity ^0.8.20;
import "@openzeppelin/contracts-upgradeable/token/ERC721/ERC721Upgradeable.sol"; import "@openzeppelin/contracts-upgradeable/token/ERC721/ERC721Upgradeable.sol";
import "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol";
import "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol"; import "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol";
import "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol";
import "@openzeppelin/contracts-upgradeable/proxy/utils/UUPSUpgradeable.sol"; import "@openzeppelin/contracts-upgradeable/proxy/utils/UUPSUpgradeable.sol";
contract MyInitializedToken is Initializable, ERC721Upgradeable, OwnableUpgradeable, UUPSUpgradeable { contract MyInitializedToken is Initializable, ERC721Upgradeable, OwnableUpgradeable, UUPSUpgradeable {
@ -306,9 +329,9 @@ const sources = [
_disableInitializers(); _disableInitializers();
} }
function initialize(string memory tokenName, string memory tokenSymbol) initializer public { function initialize(string memory tokenName, string memory tokenSymbol, address initialOwner) initializer public {
__ERC721_init(tokenName, tokenSymbol); __ERC721_init(tokenName, tokenSymbol);
__Ownable_init(); __Ownable_init(initialOwner);
__UUPSUpgradeable_init(); __UUPSUpgradeable_init();
} }

@ -104,7 +104,7 @@ module.exports = {
}) })
.addFile('test_import_node_modules_with_github_import.sol', sources[4]['test_import_node_modules_with_github_import.sol']) .addFile('test_import_node_modules_with_github_import.sol', sources[4]['test_import_node_modules_with_github_import.sol'])
.clickLaunchIcon('solidity') .clickLaunchIcon('solidity')
.setSolidityCompilerVersion('soljson-v0.8.20+commit.a1b79de6.js') // open-zeppelin moved to pragma ^0.8.0 (master branch) .setSolidityCompilerVersion('soljson-v0.8.20+commit.a1b79de6.js') // open-zeppelin moved to pragma ^0.8.20
.testContracts('test_import_node_modules_with_github_import.sol', sources[4]['test_import_node_modules_with_github_import.sol'], ['ERC20', 'test11']) .testContracts('test_import_node_modules_with_github_import.sol', sources[4]['test_import_node_modules_with_github_import.sol'], ['ERC20', 'test11'])
}, },
'Static Analysis run with remixd #group3': '' + function (browser) { 'Static Analysis run with remixd #group3': '' + function (browser) {

@ -22,15 +22,10 @@ module.exports = {
.rightClick('*[data-id="treeViewLitreeViewItemsecondContract.sol"]') .rightClick('*[data-id="treeViewLitreeViewItemsecondContract.sol"]')
.click('*[id="menuitemgeneratecustomaction"') .click('*[id="menuitemgeneratecustomaction"')
.waitForElementVisible('*[id="sol-uml-gen"]') .waitForElementVisible('*[id="sol-uml-gen"]')
.isVisible('*[data-id="treeViewLitreeViewItemsecondContract_flattened.sol"]') .waitForElementVisible('*[data-id="treeViewLitreeViewItemsecondContract_flattened.sol"]')
}, },
'Zoom into uml diagram #group1': function (browser: NightwatchBrowser) { 'Zoom into uml diagram #group1': function (browser: NightwatchBrowser) {
browser.addFile('secondContract.sol', sources[1]['secondContract.sol']) browser
.waitForElementVisible('*[data-id="treeViewLitreeViewItemsecondContract.sol"')
.pause(3000)
.rightClick('*[data-id="treeViewLitreeViewItemsecondContract.sol"]')
.click('*[id="menuitemgeneratecustomaction"')
.waitForElementVisible('*[id="sol-uml-gen"]')
.click('*[data-id="umlZoominbtn"]') .click('*[data-id="umlZoominbtn"]')
} }
} }
@ -182,37 +177,38 @@ contract Ballot {
{ {
'secondContract.sol': { 'secondContract.sol': {
content: ` content: `
// SPDX-License-Identifier: MIT // SPDX-License-Identifier: GPL-3.0
pragma solidity ^0.8.9;
import "@openzeppelin/contracts-upgradeable/token/ERC721/ERC721Upgradeable.sol";
import "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol";
import "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol";
import "@openzeppelin/contracts-upgradeable/proxy/utils/UUPSUpgradeable.sol";
contract MyToken is Initializable, ERC721Upgradeable, OwnableUpgradeable, UUPSUpgradeable {
/// @custom:oz-upgrades-unsafe-allow constructor
constructor() {
_disableInitializers();
}
function initialize() initializer public { pragma solidity ^0.5.9;
__ERC721_init("MyToken", "MTK");
__Ownable_init();
__UUPSUpgradeable_init();
}
function safeMint(address to, uint256 tokenId) public onlyOwner { import "@0x/contracts-erc20/contracts/src/ERC20Token.sol";
_safeMint(to, tokenId);
1 + 1;
}
function _authorizeUpgrade(address newImplementation) /**
internal * @title SampleERC20
onlyOwner * @dev Create a sample ERC20 standard token
override */
{} contract SampleERC20 is ERC20Token {
string public name;
string public symbol;
uint256 public decimals;
constructor (
string memory _name,
string memory _symbol,
uint256 _decimals,
uint256 _totalSupply
)
public
{
name = _name;
symbol = _symbol;
decimals = _decimals;
_totalSupply = _totalSupply;
balances[msg.sender] = _totalSupply;
}
} }
`} `}
} }
] ]

@ -38,7 +38,7 @@ module.exports = {
'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.20+commit.a1b79de6.js') // open-zeppelin moved to pragma ^0.8.0 (master branch) .setSolidityCompilerVersion('soljson-v0.8.20+commit.a1b79de6.js') // open-zeppelin moved to pragma ^0.8.20 (master branch)
.addFile('Untitled4.sol', sources[3]['Untitled4.sol']) .addFile('Untitled4.sol', sources[3]['Untitled4.sol'])
.clickLaunchIcon('filePanel') .clickLaunchIcon('filePanel')
.verifyContracts(['test7', 'ERC20'], { wait: 10000 }) .verifyContracts(['test7', 'ERC20'], { wait: 10000 })
@ -54,7 +54,7 @@ module.exports = {
'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.20+commit.a1b79de6.js') // open-zeppelin moved to pragma ^0.8.0 (master branch) .setSolidityCompilerVersion('soljson-v0.8.20+commit.a1b79de6.js') // open-zeppelin moved to pragma ^0.8.20 (master branch)
.clickLaunchIcon('filePanel') .clickLaunchIcon('filePanel')
.click('li[data-id="treeViewLitreeViewItemREADME.txt"') .click('li[data-id="treeViewLitreeViewItemREADME.txt"')
.addFile('Untitled6.sol', sources[5]['Untitled6.sol']) .addFile('Untitled6.sol', sources[5]['Untitled6.sol'])
@ -64,7 +64,7 @@ module.exports = {
'Test GitHub Import - raw URL #group4': function (browser: NightwatchBrowser) { 'Test GitHub Import - raw URL #group4': function (browser: NightwatchBrowser) {
browser browser
.setSolidityCompilerVersion('soljson-v0.8.20+commit.a1b79de6.js') // open-zeppelin moved to pragma ^0.8.0 (master branch) .setSolidityCompilerVersion('soljson-v0.8.20+commit.a1b79de6.js') // open-zeppelin moved to pragma ^0.8.20 (master branch)
.clickLaunchIcon('filePanel') .clickLaunchIcon('filePanel')
.click('li[data-id="treeViewLitreeViewItemREADME.txt"') .click('li[data-id="treeViewLitreeViewItemREADME.txt"')
.addFile('Untitled7.sol', sources[6]['Untitled7.sol']) .addFile('Untitled7.sol', sources[6]['Untitled7.sol'])

@ -238,8 +238,8 @@ module.exports = {
}) })
.addFile('tests/hhLogs_test.sol', sources[0]['tests/hhLogs_test.sol']) .addFile('tests/hhLogs_test.sol', sources[0]['tests/hhLogs_test.sol'])
.clickLaunchIcon('solidityUnitTesting') .clickLaunchIcon('solidityUnitTesting')
.waitForElementVisible('*[id="singleTesttests/Ballot_test.sol"]', 60000) .waitForElementVisible('*[id="idsingleTesttests/Ballot_test.sol"]', 60000)
.click('*[id="singleTesttests/Ballot_test.sol"]') .click('*[id="idsingleTesttests/Ballot_test.sol"]')
.click('#runTestsTabRunAction') .click('#runTestsTabRunAction')
.pause(2000) .pause(2000)
.waitForElementVisible('*[data-id="testTabSolidityUnitTestsOutputheader"]', 120000) .waitForElementVisible('*[data-id="testTabSolidityUnitTestsOutputheader"]', 120000)
@ -261,8 +261,8 @@ module.exports = {
.waitForElementPresent('*[data-id="verticalIconsKindfilePanel"]') .waitForElementPresent('*[data-id="verticalIconsKindfilePanel"]')
.addFile('tests/ballotFailedLog_test.sol', sources[0]['tests/ballotFailedLog_test.sol']) .addFile('tests/ballotFailedLog_test.sol', sources[0]['tests/ballotFailedLog_test.sol'])
.clickLaunchIcon('solidityUnitTesting') .clickLaunchIcon('solidityUnitTesting')
.waitForElementVisible('*[id="singleTesttests/Ballot_test.sol"]', 60000) .waitForElementVisible('*[id="idsingleTesttests/Ballot_test.sol"]', 60000)
.click('*[id="singleTesttests/Ballot_test.sol"]') .click('*[id="idsingleTesttests/Ballot_test.sol"]')
.click('#runTestsTabRunAction') .click('#runTestsTabRunAction')
.pause(2000) .pause(2000)
.waitForElementVisible('*[data-id="testTabSolidityUnitTestsOutputheader"]', 120000) .waitForElementVisible('*[data-id="testTabSolidityUnitTestsOutputheader"]', 120000)
@ -278,8 +278,8 @@ module.exports = {
.waitForElementPresent('*[data-id="verticalIconsKindfilePanel"]') .waitForElementPresent('*[data-id="verticalIconsKindfilePanel"]')
.addFile('tests/ballotFailedDebug_test.sol', sources[0]['tests/ballotFailedDebug_test.sol']) .addFile('tests/ballotFailedDebug_test.sol', sources[0]['tests/ballotFailedDebug_test.sol'])
.clickLaunchIcon('solidityUnitTesting') .clickLaunchIcon('solidityUnitTesting')
.waitForElementVisible('*[id="singleTesttests/Ballot_test.sol"]', 60000) .waitForElementVisible('*[id="idsingleTesttests/Ballot_test.sol"]', 60000)
.click('*[id="singleTesttests/Ballot_test.sol"]') .click('*[id="idsingleTesttests/Ballot_test.sol"]')
.click('#runTestsTabRunAction') .click('#runTestsTabRunAction')
.waitForElementVisible('*[data-id="testTabSolidityUnitTestsOutputheader"]', 120000) .waitForElementVisible('*[data-id="testTabSolidityUnitTestsOutputheader"]', 120000)
.waitForElementContainsText('#solidityUnittestsOutput', 'tests/ballotFailedDebug_test.sol', 60000) .waitForElementContainsText('#solidityUnittestsOutput', 'tests/ballotFailedDebug_test.sol', 60000)

@ -33,29 +33,32 @@ module.exports = {
}, },
'run analysis and filter results': function (browser: NightwatchBrowser) { 'run analysis and filter results': function (browser: NightwatchBrowser) {
browser browser
.clickLaunchIcon('filePanel') .clickLaunchIcon('filePanel')
.click('*[data-id="treeViewLitreeViewItemcontracts"]') .click('*[data-id="treeViewLitreeViewItemcontracts"]')
.click('*[data-id="treeViewLitreeViewItemcontracts/2_Owner.sol"]') .click('*[data-id="treeViewLitreeViewItemcontracts/2_Owner.sol"]')
.clickLaunchIcon('solidity') .clickLaunchIcon('solidity')
.click('*[id="compileBtn"]') .click('*[id="compileBtn"]')
.pause(10000) .pause(10000)
.clickLaunchIcon('solidityStaticAnalysis') .clickLaunchIcon('solidityStaticAnalysis')
.click('*[id="staticAnalysisRunBtn"]') .useXpath()
.waitForElementPresent('#staticanalysisresult .warning', 5000) .click('//*[@id="staticAnalysisRunBtn"]')
// Check warning count // .waitForElementPresent('div#staticanalysisresult .warning', 5000)
.click('*[data-rb-event-key="remix"]') .waitForElementPresent('//*[@id="staticanalysisresult"]', 5000)
.assert.containsText('span#ssaRemixtab > *[data-id="RemixStaticAnalysisErrorCount"]', '1') .useCss()
.verify.elementPresent('input[name="showLibWarnings"]') // Check warning count
.verify.not.elementPresent('input[name="showLibWarnings"]:checked') .click('*[data-rb-event-key="remix"]')
.verify.elementPresent('label[id="headingshowLibWarnings"]') .assert.containsText('span#ssaRemixtab > *[data-id="RemixStaticAnalysisErrorCount"]', '1')
.click('label[id="headingshowLibWarnings"]') .verify.elementPresent('input[name="showLibWarnings"]')
.pause(1000) .verify.not.elementPresent('input[name="showLibWarnings"]:checked')
.click('*[data-rb-event-key="remix"]') .verify.elementPresent('label[id="headingshowLibWarnings"]')
.assert.containsText('span#ssaRemixtab > *[data-id="RemixStaticAnalysisErrorCount', '382') .click('label[id="headingshowLibWarnings"]')
.click('label[id="headingshowLibWarnings"]') .pause(1000)
.pause(1000) .click('*[data-rb-event-key="remix"]')
.assert.containsText('span#ssaRemixtab > *[data-id="RemixStaticAnalysisErrorCount', '1') .assert.containsText('span#ssaRemixtab > *[data-id="RemixStaticAnalysisErrorCount', '386')
.end() .click('label[id="headingshowLibWarnings"]')
.pause(1000)
.assert.containsText('span#ssaRemixtab > *[data-id="RemixStaticAnalysisErrorCount', '1')
.end()
} }
} }
@ -72,8 +75,8 @@ function runTests (browser: NightwatchBrowser) {
'Fallback function of contract TooMuchGas requires too much gas', 'Fallback function of contract TooMuchGas requires too much gas',
'TooMuchGas.() : Variables have very similar names "test" and "test1".', 'TooMuchGas.() : Variables have very similar names "test" and "test1".',
'TooMuchGas.() : Variables have very similar names "test" and "test1".'], 'TooMuchGas.() : Variables have very similar names "test" and "test1".'],
'#staticanalysisresult .warning', '#staticanalysisresult .warning',
browser browser
) )
}) })
} }

@ -97,7 +97,7 @@ module.exports = {
.switchEnvironment('vm-london') .switchEnvironment('vm-london')
.click('*[data-id="terminalClearConsole"]') // clear the terminal .click('*[data-id="terminalClearConsole"]') // clear the terminal
.clickLaunchIcon('filePanel') .clickLaunchIcon('filePanel')
.click('*[data-id="treeViewDivtreeViewItem"]') // make sure we create the file at the root folder .click('*[data-id="treeViewDivMenu"]') // make sure we create the file at the root folder
.addFile('deployWithEthersJs.js', { content: deployWithEthersJs }) .addFile('deployWithEthersJs.js', { content: deployWithEthersJs })
// .openFile('deployWithEthersJs.js') // .openFile('deployWithEthersJs.js')
.pause(1000) .pause(1000)
@ -562,7 +562,7 @@ describe("Storage", function () {
contractName: 'StorageWithLib', contractName: 'StorageWithLib',
sourceName: 'contracts/StorageWithLib.sol', sourceName: 'contracts/StorageWithLib.sol',
abi: metadata.abi, abi: metadata.abi,
bytecode: '0x' + metadata.data.bytecode.object, bytecode: metadata.data.bytecode.object,
deployedBytecode: '0x' + metadata.data.deployedBytecode.object, deployedBytecode: '0x' + metadata.data.deployedBytecode.object,
linkReferences: metadata.data.bytecode.linkReferences, linkReferences: metadata.data.bytecode.linkReferences,
deployedLinkReferences: metadata.data.deployedBytecode.linkReferences, deployedLinkReferences: metadata.data.deployedBytecode.linkReferences,
@ -700,7 +700,7 @@ const scriptAutoExec = {
contractName: 'Lib', contractName: 'Lib',
sourceName: 'contracts/1_Storage.sol', sourceName: 'contracts/1_Storage.sol',
abi: metadataLib.abi, abi: metadataLib.abi,
bytecode: '0x' + metadataLib.data.bytecode.object, bytecode: metadataLib.data.bytecode.object,
deployedBytecode: '0x' + metadataLib.data.deployedBytecode.object, deployedBytecode: '0x' + metadataLib.data.deployedBytecode.object,
linkReferences: metadataLib.data.bytecode.linkReferences, linkReferences: metadataLib.data.bytecode.linkReferences,
deployedLinkReferences: metadataLib.data.deployedBytecode.linkReferences, deployedLinkReferences: metadataLib.data.deployedBytecode.linkReferences,
@ -720,7 +720,7 @@ const scriptAutoExec = {
contractName: 'Storage', contractName: 'Storage',
sourceName: 'contracts/1_Storage.sol', sourceName: 'contracts/1_Storage.sol',
abi: metadata.abi, abi: metadata.abi,
bytecode: '0x' + metadata.data.bytecode.object, bytecode: metadata.data.bytecode.object,
deployedBytecode: '0x' + metadata.data.deployedBytecode.object, deployedBytecode: '0x' + metadata.data.deployedBytecode.object,
linkReferences: metadata.data.bytecode.linkReferences, linkReferences: metadata.data.bytecode.linkReferences,
deployedLinkReferences: metadata.data.deployedBytecode.linkReferences, deployedLinkReferences: metadata.data.deployedBytecode.linkReferences,

@ -15,7 +15,7 @@ module.exports = {
browser.testContracts('Untitled.sol', sources[0]['Untitled.sol'], ['TestContract']) browser.testContracts('Untitled.sol', sources[0]['Untitled.sol'], ['TestContract'])
.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('.udapp_contractActionsContainerSingle > button') .click('.udapp_contractActionsContainerSingle > div')
.clickInstance(0) .clickInstance(0)
.clickFunction('f - transact (not payable)') .clickFunction('f - transact (not payable)')
.testFunction('last', .testFunction('last',
@ -40,7 +40,7 @@ module.exports = {
'Test Complex Return Values #group1': function (browser: NightwatchBrowser) { 'Test Complex Return Values #group1': function (browser: NightwatchBrowser) {
browser.testContracts('returnValues.sol', sources[1]['returnValues.sol'], ['testReturnValues']) browser.testContracts('returnValues.sol', sources[1]['returnValues.sol'], ['testReturnValues'])
.clickLaunchIcon('udapp') .clickLaunchIcon('udapp')
.click('.udapp_contractActionsContainerSingle > button') .click('.udapp_contractActionsContainerSingle > div')
.clickInstance(0) .clickInstance(0)
.clickFunction('retunValues1 - transact (not payable)') .clickFunction('retunValues1 - transact (not payable)')
.testFunction('last', .testFunction('last',
@ -84,7 +84,7 @@ module.exports = {
'Test Complex Input Values #group2': function (browser: NightwatchBrowser) { 'Test Complex Input Values #group2': function (browser: NightwatchBrowser) {
browser.testContracts('inputValues.sol', sources[2]['inputValues.sol'], ['test']) browser.testContracts('inputValues.sol', sources[2]['inputValues.sol'], ['test'])
.clickLaunchIcon('udapp') .clickLaunchIcon('udapp')
.click('.udapp_contractActionsContainerSingle > button') .click('.udapp_contractActionsContainerSingle > div')
.clickInstance(0) .clickInstance(0)
.clickFunction('inputValue1 - transact (not payable)', { types: 'uint256 _u, int256 _i, string _str', values: '"2343242", "-4324324", "string _ string _ string _ string _ string _ string _ string _ string _ string _ string _"' }) .clickFunction('inputValue1 - transact (not payable)', { types: 'uint256 _u, int256 _i, string _str', values: '"2343242", "-4324324", "string _ string _ string _ string _ string _ string _ string _ string _ string _ string _"' })
.testFunction('last', .testFunction('last',
@ -129,7 +129,7 @@ module.exports = {
browser.testContracts('eventFunctionInput.sol', sources[3]['eventFunctionInput.sol'], ['C']) browser.testContracts('eventFunctionInput.sol', sources[3]['eventFunctionInput.sol'], ['C'])
.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('.udapp_contractActionsContainerSingle > button') .click('.udapp_contractActionsContainerSingle > div')
.clickInstance(0) .clickInstance(0)
.click('*[data-id="deployAndRunClearInstances"]') .click('*[data-id="deployAndRunClearInstances"]')
}, },
@ -137,7 +137,7 @@ module.exports = {
'Should use scientific notation as parameters #group2': function (browser: NightwatchBrowser) { 'Should use scientific notation as parameters #group2': function (browser: NightwatchBrowser) {
browser.testContracts('scientific_notation.sol', sources[8]['scientific_notation.sol'], ['test']) browser.testContracts('scientific_notation.sol', sources[8]['scientific_notation.sol'], ['test'])
.clickLaunchIcon('udapp') .clickLaunchIcon('udapp')
.click('.udapp_contractActionsContainerSingle > button') .click('.udapp_contractActionsContainerSingle > div')
.clickInstance(0) .clickInstance(0)
.clickFunction('inputValue1 - transact (not payable)', { types: 'uint256 _u, int256 _i', values: '"101e3", "-1.13e4"' }) .clickFunction('inputValue1 - transact (not payable)', { types: 'uint256 _u, int256 _i', values: '"101e3", "-1.13e4"' })
.waitForElementContainsText('*[data-id="terminalJournal"]', '101000', 60000) .waitForElementContainsText('*[data-id="terminalJournal"]', '101000', 60000)
@ -154,7 +154,7 @@ module.exports = {
browser.testContracts('customError.sol', sources[4]['customError.sol'], ['C']) browser.testContracts('customError.sol', sources[4]['customError.sol'], ['C'])
.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('.udapp_contractActionsContainerSingle > button') .click('.udapp_contractActionsContainerSingle > div')
.clickInstance(0) .clickInstance(0)
.clickFunction('g - transact (not payable)') .clickFunction('g - transact (not payable)')
.journalLastChildIncludes('Error provided by the contract:') .journalLastChildIncludes('Error provided by the contract:')
@ -176,7 +176,7 @@ module.exports = {
.clearTransactions() .clearTransactions()
.switchEnvironment('vm-london') // switch to London fork .switchEnvironment('vm-london') // switch to London fork
.selectAccount('0xCA35b7d915458EF540aDe6068dFe2F44E8fa733c') // this account will be used for this test suite .selectAccount('0xCA35b7d915458EF540aDe6068dFe2F44E8fa733c') // this account will be used for this test suite
.click('.udapp_contractActionsContainerSingle > button') .click('.udapp_contractActionsContainerSingle > div')
.clickInstance(0) .clickInstance(0)
.clickFunction('g - transact (not payable)') .clickFunction('g - transact (not payable)')
.journalLastChildIncludes('Error provided by the contract:') .journalLastChildIncludes('Error provided by the contract:')
@ -194,7 +194,7 @@ module.exports = {
'Should Compile and Deploy a contract which define a custom error in a library, the error should be logged in the terminal #group3': function (browser: NightwatchBrowser) { 'Should Compile and Deploy a contract which define a custom error in a library, the error should be logged in the terminal #group3': function (browser: NightwatchBrowser) {
browser.testContracts('customErrorLib.sol', sources[5]['customErrorLib.sol'], ['D']) browser.testContracts('customErrorLib.sol', sources[5]['customErrorLib.sol'], ['D'])
.clickLaunchIcon('udapp') .clickLaunchIcon('udapp')
.click('.udapp_contractActionsContainerSingle > button') .click('.udapp_contractActionsContainerSingle > div')
.clickInstance(1) .clickInstance(1)
.clickFunction('h - transact (not payable)') .clickFunction('h - transact (not payable)')
.journalLastChildIncludes('Error provided by the contract:') .journalLastChildIncludes('Error provided by the contract:')

@ -10,11 +10,11 @@ const sources = [
'myTokenV1.sol': { 'myTokenV1.sol': {
content: ` content: `
// SPDX-License-Identifier: MIT // SPDX-License-Identifier: MIT
pragma solidity ^0.8.4; pragma solidity ^0.8.20;
import "@openzeppelin/contracts-upgradeable/token/ERC721/ERC721Upgradeable.sol"; import "@openzeppelin/contracts-upgradeable/token/ERC721/ERC721Upgradeable.sol";
import "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol";
import "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol"; import "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol";
import "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol";
import "@openzeppelin/contracts-upgradeable/proxy/utils/UUPSUpgradeable.sol"; import "@openzeppelin/contracts-upgradeable/proxy/utils/UUPSUpgradeable.sol";
contract MyToken is Initializable, ERC721Upgradeable, OwnableUpgradeable, UUPSUpgradeable { contract MyToken is Initializable, ERC721Upgradeable, OwnableUpgradeable, UUPSUpgradeable {
@ -23,9 +23,9 @@ const sources = [
_disableInitializers(); _disableInitializers();
} }
function initialize() initializer public { function initialize(address initialOwner) initializer public {
__ERC721_init("MyToken", "MTK"); __ERC721_init("MyToken", "MTK");
__Ownable_init(); __Ownable_init(initialOwner);
__UUPSUpgradeable_init(); __UUPSUpgradeable_init();
} }
@ -35,6 +35,7 @@ const sources = [
override override
{} {}
} }
` `
} }
} }
@ -166,7 +167,7 @@ module.exports = {
.click('[data-id="compilerContainerCompileBtn"]') .click('[data-id="compilerContainerCompileBtn"]')
.clickLaunchIcon('filePanel') .clickLaunchIcon('filePanel')
.isVisible({ .isVisible({
selector: '*[data-id="treeViewDivtreeViewItem.deps/npm/@openzeppelin/contracts-upgradeable/proxy/beacon/IBeaconUpgradeable.sol"]', selector: '*[data-id="treeViewDivtreeViewItem.deps/npm/@openzeppelin/contracts-upgradeable/utils/ContextUpgradeable.sol"]',
timeout: 120000, timeout: 120000,
suppressNotFoundErrors: true suppressNotFoundErrors: true
}) })
@ -174,7 +175,7 @@ module.exports = {
.click('[data-id="compilerContainerCompileBtn"]') .click('[data-id="compilerContainerCompileBtn"]')
.clickLaunchIcon('filePanel') .clickLaunchIcon('filePanel')
.isVisible({ .isVisible({
selector: '*[data-id="treeViewDivtreeViewItem.deps/npm/@openzeppelin/contracts-upgradeable/proxy/beacon/IBeaconUpgradeable.sol"]', selector: '*[data-id="treeViewDivtreeViewItem.deps/npm/@openzeppelin/contracts-upgradeable/utils/ContextUpgradeable.sol"]',
timeout: 120000, timeout: 120000,
suppressNotFoundErrors: true suppressNotFoundErrors: true
}) })
@ -182,7 +183,7 @@ module.exports = {
.click('[data-id="compilerContainerCompileBtn"]') .click('[data-id="compilerContainerCompileBtn"]')
.clickLaunchIcon('filePanel') .clickLaunchIcon('filePanel')
.waitForElementVisible({ .waitForElementVisible({
selector: '*[data-id="treeViewDivtreeViewItem.deps/npm/@openzeppelin/contracts-upgradeable/proxy/beacon/IBeaconUpgradeable.sol"]', selector: '*[data-id="treeViewDivtreeViewItem.deps/npm/@openzeppelin/contracts-upgradeable/utils/ContextUpgradeable.sol"]',
timeout: 120000, timeout: 120000,
}) })
.clickLaunchIcon('solidity') .clickLaunchIcon('solidity')

@ -333,7 +333,7 @@ module.exports = {
.click('*[data-id="treeViewLitreeViewItemcontracts/MyToken.sol"]') .click('*[data-id="treeViewLitreeViewItemcontracts/MyToken.sol"]')
.pause(1000) .pause(1000)
.getEditorValue((content) => { .getEditorValue((content) => {
browser.assert.ok(content.indexOf(`contract MyToken is Initializable, ERC1155Upgradeable, OwnableUpgradeable, PausableUpgradeable, ERC1155BurnableUpgradeable, UUPSUpgradeable {`) !== -1, browser.assert.ok(content.indexOf(`contract MyToken is Initializable, ERC1155Upgradeable, OwnableUpgradeable, ERC1155PausableUpgradeable, ERC1155BurnableUpgradeable, UUPSUpgradeable {`) !== -1,
'Incorrect content') 'Incorrect content')
}) })
.waitForElementVisible('*[data-id="treeViewLitreeViewItemscripts"]') .waitForElementVisible('*[data-id="treeViewLitreeViewItemscripts"]')

@ -1,105 +1,105 @@
// Merge custom command types with nightwatch types // Merge custom command types with nightwatch types
/* eslint-disable no-use-before-define */ /* eslint-disable no-use-before-define */
import { NightwatchBrowser } from 'nightwatch' // eslint-disable-line @typescript-eslint/no-unused-vars import {NightwatchBrowser} from 'nightwatch' // eslint-disable-line @typescript-eslint/no-unused-vars
export type callbackCheckVerifyCallReturnValue = (values: string[]) => { message: string, pass: boolean } export type callbackCheckVerifyCallReturnValue = (values: string[]) => {message: string; pass: boolean}
declare module 'nightwatch' { declare module 'nightwatch' {
export interface NightwatchCustomCommands { export interface NightwatchCustomCommands {
clickLaunchIcon(icon: string): NightwatchBrowser, clickLaunchIcon(icon: string): NightwatchBrowser
switchBrowserTab(index: number): NightwatchBrowser, switchBrowserTab(index: number): NightwatchBrowser
scrollAndClick(target: string): NightwatchBrowser, scrollAndClick(target: string): NightwatchBrowser
scrollInto(target: string): NightwatchBrowser, scrollInto(target: string): NightwatchBrowser
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, runs?: 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
goToVMTraceStep(step: number, incr?: number): NightwatchBrowser, goToVMTraceStep(step: number, incr?: number): NightwatchBrowser
checkVariableDebug(id: string, debugValue: NightwatchCheckVariableDebugValue): NightwatchBrowser, checkVariableDebug(id: string, debugValue: NightwatchCheckVariableDebugValue): NightwatchBrowser
addAtAddressInstance(address: string, isValidFormat: boolean, isValidChecksum: boolean, isAbi?: boolean): NightwatchBrowser, addAtAddressInstance(address: string, isValidFormat: boolean, isValidChecksum: boolean, isAbi?: boolean): NightwatchBrowser
modalFooterOKClick(id?: string): NightwatchBrowser, modalFooterOKClick(id?: string): NightwatchBrowser
clickInstance(index: number): NightwatchBrowser, clickInstance(index: number): NightwatchBrowser
journalLastChildIncludes(val: string): NightwatchBrowser, journalLastChildIncludes(val: string): NightwatchBrowser
executeScriptInTerminal(script: string): NightwatchBrowser, executeScriptInTerminal(script: string): NightwatchBrowser
clearEditableContent(cssSelector: string): NightwatchBrowser, clearEditableContent(cssSelector: string): NightwatchBrowser
journalChildIncludes(val: string, opts = { shouldHaveOnlyOneOccurence: boolean }): NightwatchBrowser, journalChildIncludes(val: string, opts = {shouldHaveOnlyOneOccurence: boolean}): NightwatchBrowser
debugTransaction(index: number): NightwatchBrowser, debugTransaction(index: number): NightwatchBrowser
checkElementStyle(cssSelector: string, styleProperty: string, expectedResult: string): NightwatchBrowser, checkElementStyle(cssSelector: string, styleProperty: string, expectedResult: string): NightwatchBrowser
openFile(name: string): NightwatchBrowser, openFile(name: string): NightwatchBrowser
refreshPage(): NightwatchBrowser, refreshPage(): NightwatchBrowser
verifyLoad(): NightwatchBrowser, verifyLoad(): NightwatchBrowser
renamePath(path: string, newFileName: string, renamedPath: string): NightwatchBrowser, renamePath(path: string, newFileName: string, renamedPath: string): NightwatchBrowser
rightClickCustom(cssSelector: string): NightwatchBrowser, rightClickCustom(cssSelector: string): NightwatchBrowser
scrollToLine(line: number): NightwatchBrowser, scrollToLine(line: number): NightwatchBrowser
waitForElementContainsText(id: string, value: string, timeout?: number): NightwatchBrowser, waitForElementContainsText(id: string, value: string, timeout?: number): NightwatchBrowser
getModalBody(callback: (value: string, cb: VoidFunction) => void): NightwatchBrowser, getModalBody(callback: (value: string, cb: VoidFunction) => void): NightwatchBrowser
modalFooterCancelClick(id?: string): NightwatchBrowser, modalFooterCancelClick(id?: string): NightwatchBrowser
selectContract(contractName: string): NightwatchBrowser, selectContract(contractName: string): NightwatchBrowser
createContract(inputParams: string): NightwatchBrowser, createContract(inputParams: string): NightwatchBrowser
getAddressAtPosition(index: number, cb: (pos: string) => void): NightwatchBrowser, getAddressAtPosition(index: number, cb: (pos: string) => void): NightwatchBrowser
testConstantFunction(address: string, fnFullName: string, expectedInput: NightwatchTestConstantFunctionExpectedInput | null, expectedOutput: string): NightwatchBrowser, testConstantFunction(address: string, fnFullName: string, expectedInput: NightwatchTestConstantFunctionExpectedInput | null, expectedOutput: string): NightwatchBrowser
getEditorValue(callback: (content: string) => void): NightwatchBrowser, getEditorValue(callback: (content: string) => void): NightwatchBrowser
getInstalledPlugins(cb: (plugins: string[]) => void): NightwatchBrowser, getInstalledPlugins(cb: (plugins: string[]) => void): NightwatchBrowser
verifyCallReturnValue(address: string, checks: string[] | callbackCheckVerifyCallReturnValue): NightwatchBrowser, verifyCallReturnValue(address: string, checks: string[] | callbackCheckVerifyCallReturnValue): NightwatchBrowser
testEditorValue(testvalue: string): NightwatchBrowser, testEditorValue(testvalue: string): NightwatchBrowser
removeFile(path: string, workspace: string): NightwatchBrowser, removeFile(path: string, workspace: string): NightwatchBrowser
switchBrowserWindow(url: string, windowName: string, cb: (browser: NightwatchBrowser, window?: NightwatchCallbackResult<Window>) => void): NightwatchBrowser, switchBrowserWindow(url: string, windowName: string, cb: (browser: NightwatchBrowser, window?: NightwatchCallbackResult<Window>) => void): NightwatchBrowser
setupMetamask(passphrase: string, password: string): NightwatchBrowser, setupMetamask(passphrase: string, password: string): NightwatchBrowser
signMessage(msg: string, callback: (hash: { value: string }, signature: { value: string }) => void): NightwatchBrowser, signMessage(msg: string, callback: (hash: {value: string}, signature: {value: string}) => void): NightwatchBrowser
setSolidityCompilerVersion(version: string): NightwatchBrowser, setSolidityCompilerVersion(version: string): NightwatchBrowser
clickElementAtPosition(cssSelector: string, index: number, opt?: { forceSelectIfUnselected: boolean }): NightwatchBrowser, clickElementAtPosition(cssSelector: string, index: number, opt?: {forceSelectIfUnselected: boolean}): NightwatchBrowser
notContainsText(cssSelector: string, text: string): NightwatchBrowser, notContainsText(cssSelector: string, text: string): NightwatchBrowser
sendLowLevelTx(address: string, value: string, callData: string): NightwatchBrowser, sendLowLevelTx(address: string, value: string, callData: string): NightwatchBrowser
journalLastChild(val: string): NightwatchBrowser, journalLastChild(val: string): NightwatchBrowser
checkTerminalFilter(filter: string, test: string): NightwatchBrowser, checkTerminalFilter(filter: string, test: string): NightwatchBrowser
noWorkerErrorFor(version: string): NightwatchBrowser, noWorkerErrorFor(version: string): NightwatchBrowser
validateValueInput(selector: string, valueTosSet: string, expectedValue: string): NightwatchBrowser validateValueInput(selector: string, valueTosSet: string, expectedValue: string): NightwatchBrowser
checkAnnotations(type: string, line: number): NightwatchBrowser checkAnnotations(type: string): NightwatchBrowser
checkAnnotationsNotPresent(type: string): NightwatchBrowser checkAnnotationsNotPresent(type: string): NightwatchBrowser
getLastTransactionHash(callback: (hash: string) => void) getLastTransactionHash(callback: (hash: string) => void)
currentWorkspaceIs(name: string): NightwatchBrowser currentWorkspaceIs(name: string): NightwatchBrowser
addLocalPlugin(this: NightwatchBrowser, profile: Profile & LocationProfile & ExternalProfile): NightwatchBrowser addLocalPlugin(this: NightwatchBrowser, profile: Profile & LocationProfile & ExternalProfile): NightwatchBrowser
acceptAndRemember (this: NightwatchBrowser, remember: boolean, accept: boolean): NightwatchBrowser acceptAndRemember(this: NightwatchBrowser, remember: boolean, accept: boolean): NightwatchBrowser
clearConsole (this: NightwatchBrowser): NightwatchBrowser clearConsole(this: NightwatchBrowser): NightwatchBrowser
clearTransactions (this: NightwatchBrowser): NightwatchBrowser clearTransactions(this: NightwatchBrowser): NightwatchBrowser
getBrowserLogs (this: NightwatchBrowser): NightwatchBrowser getBrowserLogs(this: NightwatchBrowser): NightwatchBrowser
currentSelectedFileIs (name: string): NightwatchBrowser, currentSelectedFileIs(name: string): NightwatchBrowser
switchWorkspace: (workspaceName: string) => NightwatchBrowser switchWorkspace: (workspaceName: string) => NightwatchBrowser
switchEnvironment: (provider: string) => NightwatchBrowser switchEnvironment: (provider: string) => NightwatchBrowser
connectToExternalHttpProvider: (url: string, identifier: string) => NightwatchBrowser connectToExternalHttpProvider: (url: string, identifier: string) => NightwatchBrowser
} }
export interface NightwatchBrowser { export interface NightwatchBrowser {
api: this, api: this
emit: (status: string) => void, emit: (status: string) => void
fullscreenWindow: (result?: any) => this, fullscreenWindow: (result?: any) => this
keys(keysToSend: string, callback?: (this: NightwatchAPI, result: NightwatchCallbackResult<void>) => void): NightwatchBrowser, keys(keysToSend: string, callback?: (this: NightwatchAPI, result: NightwatchCallbackResult<void>) => void): NightwatchBrowser
sendKeys: (selector: string, inputValue: string | string[], callback?: (this: NightwatchAPI, result: NightwatchCallbackResult<void>) => void) => NightwatchBrowser sendKeys: (selector: string, inputValue: string | string[], callback?: (this: NightwatchAPI, result: NightwatchCallbackResult<void>) => void) => NightwatchBrowser
} }
export interface NightwatchAPI { export interface NightwatchAPI {
keys(keysToSend: string, callback?: (this: NightwatchAPI, result: NightwatchCallbackResult<void>) => void): NightwatchAPI keys(keysToSend: string, callback?: (this: NightwatchAPI, result: NightwatchCallbackResult<void>) => void): NightwatchAPI
} }
export interface NightwatchContractContent { export interface NightwatchContractContent {
content: string; content: string
} }
export interface NightwatchClickFunctionExpectedInput { export interface NightwatchClickFunctionExpectedInput {
types: string, types: string
values: string values: string
} }
export interface NightwatchTestFunctionExpectedInput { export interface NightwatchTestFunctionExpectedInput {
[key: string]: any [key: string]: any
} }
export interface NightwatchTestConstantFunctionExpectedInput { export interface NightwatchTestConstantFunctionExpectedInput {
types: string, types: string
values: string values: string
} }
export type NightwatchCheckVariableDebugValue = NightwatchTestFunctionExpectedInput export type NightwatchCheckVariableDebugValue = NightwatchTestFunctionExpectedInput
} }

File diff suppressed because it is too large Load Diff

@ -2,9 +2,9 @@
'use strict' 'use strict'
chrome.browserAction.onClicked.addListener(function (tab) { chrome.browserAction.onClicked.addListener(function (tab) {
chrome.storage.sync.set({ 'chrome-app-sync': true }) chrome.storage.sync.set({'chrome-app-sync': true})
chrome.tabs.create({ 'url': chrome.extension.getURL('index.html') }, function (tab) { chrome.tabs.create({url: chrome.extension.getURL('index.html')}, function (tab) {
// tab opened // tab opened
}) })
}) })

@ -5,8 +5,8 @@ const { exit } = require('process');
const child = child_process.spawnSync('grep -r --include="*.json" --include="*.ts" --include="*.tsx" "+commit" apps/**/* libs/**/*', [], { encoding: 'utf8', cwd: process.cwd(), shell: true }); const child = child_process.spawnSync('grep -r --include="*.json" --include="*.ts" --include="*.tsx" "+commit" apps/**/* libs/**/*', [], { encoding: 'utf8', cwd: process.cwd(), shell: true });
if (child.error) { if (child.error) {
console.log("ERROR: ", child); console.log("ERROR: ", child);
exit(1); exit(1);
} }
@ -15,63 +15,63 @@ let soljson =[];
const quotedVersionsRegex = /['"v]\d*\.\d*\.\d*\+commit\.[\d\w]*/g; const quotedVersionsRegex = /['"v]\d*\.\d*\.\d*\+commit\.[\d\w]*/g;
let quotedVersionsRegexMatch = child.stdout.match(quotedVersionsRegex) let quotedVersionsRegexMatch = child.stdout.match(quotedVersionsRegex)
if(quotedVersionsRegexMatch){ if(quotedVersionsRegexMatch){
let soljson2 = quotedVersionsRegexMatch.map((item) => item.replace('\'', 'v').replace('"', 'v')) let soljson2 = quotedVersionsRegexMatch.map((item) => item.replace('\'', 'v').replace('"', 'v'))
console.log('non nightly soljson versions found: ', soljson2); console.log('non nightly soljson versions found: ', soljson2);
if(soljson2) soljson = soljson.concat(soljson2); if(soljson2) soljson = soljson.concat(soljson2);
} }
const nightlyVersionsRegex = /\d*\.\d*\.\d-nightly.*\+commit\.[\d\w]*/g const nightlyVersionsRegex = /\d*\.\d*\.\d-nightly.*\+commit\.[\d\w]*/g
const nightlyVersionsRegexMatch = child.stdout.match(nightlyVersionsRegex) const nightlyVersionsRegexMatch = child.stdout.match(nightlyVersionsRegex)
if(nightlyVersionsRegexMatch){ if(nightlyVersionsRegexMatch){
let soljson3 = nightlyVersionsRegexMatch.map((item) => 'v' + item); let soljson3 = nightlyVersionsRegexMatch.map((item) => 'v' + item);
console.log('nightly soljson versions found: ', soljson3); console.log('nightly soljson versions found: ', soljson3);
if(soljson3) soljson = soljson.concat(soljson3); if(soljson3) soljson = soljson.concat(soljson3);
} }
if (soljson) { if (soljson) {
// filter out duplicates // filter out duplicates
soljson = soljson.filter((item, index) => soljson.indexOf(item) === index); soljson = soljson.filter((item, index) => soljson.indexOf(item) === index);
// manually add some versions // manually add some versions
soljson.push('v0.7.6+commit.7338295f'); soljson.push('v0.7.6+commit.7338295f');
console.log('soljson versions found: ', soljson, soljson.length); console.log('soljson versions found: ', soljson, soljson.length);
for (let i = 0; i < soljson.length; i++) { for (let i = 0; i < soljson.length; i++) {
const version = soljson[i]; const version = soljson[i];
if (version) { if (version) {
let url = '' let url = ''
// if nightly // if nightly
if (version.includes('nightly')) { if (version.includes('nightly')) {
url = `https://binaries.soliditylang.org/bin/soljson-${version}.js`; url = `https://binaries.soliditylang.org/bin/soljson-${version}.js`;
}else{ }else{
url = `https://binaries.soliditylang.org/wasm/soljson-${version}.js`; url = `https://binaries.soliditylang.org/wasm/soljson-${version}.js`;
} }
const dir = './dist/apps/remix-ide/assets/js/soljson'; const dir = './dist/apps/remix-ide/assets/js/soljson';
if (!fs.existsSync(dir)) { if (!fs.existsSync(dir)) {
fs.mkdirSync(dir); fs.mkdirSync(dir);
} }
const path = `./dist/apps/remix-ide/assets/js/soljson/soljson-${version}.js`; const path = `./dist/apps/remix-ide/assets/js/soljson/soljson-${version}.js`;
// check if the file exists // check if the file exists
const exists = fs.existsSync(path); const exists = fs.existsSync(path);
if (!exists) { if (!exists) {
console.log('URL:', url) console.log('URL:', url)
try { try {
// use curl to download the file // use curl to download the file
child_process.exec(`curl -o ${path} ${url}`, { encoding: 'utf8', cwd: process.cwd(), shell: true }) child_process.exec(`curl -o ${path} ${url}`, { encoding: 'utf8', cwd: process.cwd(), shell: true })
} catch (e) { } catch (e) {
console.log('Failed to download soljson' + version + ' from ' + url) console.log('Failed to download soljson' + version + ' from ' + url)
}
}
} }
}
} }
}
} }

@ -5,15 +5,15 @@ const file = fs.readFileSync('projects.json')
const projects = JSON.parse(file) const projects = JSON.parse(file)
console.log(Object.keys(projects.graph.nodes)) console.log(Object.keys(projects.graph.nodes))
for(let node of Object.keys(projects.graph.nodes)){ for(let node of Object.keys(projects.graph.nodes)){
if(projects.graph.nodes[node].data.targets.lint){ if(projects.graph.nodes[node].data.targets.lint){
console.log(projects.graph.nodes[node].data.name) console.log(projects.graph.nodes[node].data.name)
const result = spawnSync('yarn', ['lint', projects.graph.nodes[node].data.name]) const result = spawnSync('yarn', ['lint', projects.graph.nodes[node].data.name])
if(result.status == 0){ if(result.status == 0){
console.log('success') console.log('success')
}else{ }else{
console.log(result.stdout.toString()) console.log(result.stdout.toString())
console.log(result.stderr.toString()) console.log(result.stderr.toString())
exit(1) exit(1)
} }
} }
} }

@ -7,21 +7,21 @@ let args = process.argv.slice(2)
const jobsize = args[0] || 10; const jobsize = args[0] || 10;
const job = args[1] || 0; const job = args[1] || 0;
exec(cmd, (error, stdout, stderr) => { exec(cmd, (error, stdout, stderr) => {
if (error) { if (error) {
console.error(`error: ${error.message}`); console.error(`error: ${error.message}`);
return; return;
} }
if (stderr) { if (stderr) {
console.error(`stderr: ${stderr}`); console.error(`stderr: ${stderr}`);
return; return;
} }
let files = stdout.split('\n').filter(f => f.includes('.test')).map(f => f.replace('dist/apps/remix-ide-e2e/src/tests/', '')).map(f => f.replace('.js', '')) let files = stdout.split('\n').filter(f => f.includes('.test')).map(f => f.replace('dist/apps/remix-ide-e2e/src/tests/', '')).map(f => f.replace('.js', ''))
let splitIndex = Math.ceil(files.length / jobsize); let splitIndex = Math.ceil(files.length / jobsize);
const parts = [] const parts = []
for (let i = 0; i < jobsize; i++) { for (let i = 0; i < jobsize; i++) {
parts.push(files.slice(i * splitIndex, (i + 1) * splitIndex)) parts.push(files.slice(i * splitIndex, (i + 1) * splitIndex))
} }
console.log(parts[job].join('\n')) console.log(parts[job].join('\n'))
}); });

@ -3,7 +3,7 @@
"$schema": "../../node_modules/nx/schemas/project-schema.json", "$schema": "../../node_modules/nx/schemas/project-schema.json",
"sourceRoot": "apps/remix-ide/src", "sourceRoot": "apps/remix-ide/src",
"projectType": "application", "projectType": "application",
"implicitDependencies": ["doc-gen", "doc-viewer", "etherscan", "vyper", "solhint", "walletconnect"], "implicitDependencies": ["doc-gen", "doc-viewer", "etherscan", "vyper", "solhint", "walletconnect", "circuit-compiler"],
"targets": { "targets": {
"build": { "build": {
"executor": "@nrwl/webpack:webpack", "executor": "@nrwl/webpack:webpack",

@ -45,9 +45,9 @@ This is not strictly speaking a release. Updating the remix site is done through
- git reset --hard -master-commit-hash- - git reset --hard -master-commit-hash-
- git push -f origin remix_live - git push -f origin remix_live
CircleCI will build automaticaly and remix.ethereum.org will be updated CircleCI will build automatically and remix.ethereum.org will be updated
# remix-alpha.ethereum.org update # remix-alpha.ethereum.org update
remix-alpha.ethereum.org is automaticaly updated every time commits are pushed to master remix-alpha.ethereum.org is automatically updated every time commits are pushed to master

@ -1,26 +1,26 @@
'use strict' 'use strict'
import { RunTab, makeUdapp } from './app/udapp' import {RunTab, makeUdapp} from './app/udapp'
import { RemixEngine } from './remixEngine' import {RemixEngine} from './remixEngine'
import { RemixAppManager } from './remixAppManager' import {RemixAppManager} from './remixAppManager'
import { ThemeModule } from './app/tabs/theme-module' import {ThemeModule} from './app/tabs/theme-module'
import { LocaleModule } from './app/tabs/locale-module' import {LocaleModule} from './app/tabs/locale-module'
import { NetworkModule } from './app/tabs/network-module' import {NetworkModule} from './app/tabs/network-module'
import { Web3ProviderModule } from './app/tabs/web3-provider' import {Web3ProviderModule} from './app/tabs/web3-provider'
import { CompileAndRun } from './app/tabs/compile-and-run' import {CompileAndRun} from './app/tabs/compile-and-run'
import { SidePanel } from './app/components/side-panel' import {SidePanel} from './app/components/side-panel'
import { HiddenPanel } from './app/components/hidden-panel' import {HiddenPanel} from './app/components/hidden-panel'
import { VerticalIcons } from './app/components/vertical-icons' import {VerticalIcons} from './app/components/vertical-icons'
import { LandingPage } from './app/ui/landing-page/landing-page' import {LandingPage} from './app/ui/landing-page/landing-page'
import { MainPanel } from './app/components/main-panel' import {MainPanel} from './app/components/main-panel'
import { PermissionHandlerPlugin } from './app/plugins/permission-handler-plugin' import {PermissionHandlerPlugin} from './app/plugins/permission-handler-plugin'
import { AstWalker } from '@remix-project/remix-astwalker' import {AstWalker} from '@remix-project/remix-astwalker'
import { LinkLibraries, DeployLibraries, OpenZeppelinProxy } from '@remix-project/core-plugin' import {LinkLibraries, DeployLibraries, OpenZeppelinProxy} from '@remix-project/core-plugin'
import { CodeParser } from './app/plugins/parser/code-parser' import {CodeParser} from './app/plugins/parser/code-parser'
import { SolidityScript } from './app/plugins/solidity-script' import {SolidityScript} from './app/plugins/solidity-script'
import { WalkthroughService } from './walkthroughService' import {WalkthroughService} from './walkthroughService'
import { OffsetToLineColumnConverter, CompilerMetadata, CompilerArtefacts, FetchAndCompile, CompilerImports, GistHandler } from '@remix-project/core-plugin' import {OffsetToLineColumnConverter, CompilerMetadata, CompilerArtefacts, FetchAndCompile, CompilerImports, GistHandler} from '@remix-project/core-plugin'
import Registry from './app/state/registry' import Registry from './app/state/registry'
import { ConfigPlugin } from './app/plugins/config' import { ConfigPlugin } from './app/plugins/config'
@ -53,6 +53,31 @@ import { electronTemplates } from './app/plugins/electron/templatesPlugin'
import { xtermPlugin } from './app/plugins/electron/xtermPlugin' import { xtermPlugin } from './app/plugins/electron/xtermPlugin'
import {ConfigPlugin} from './app/plugins/config'
import {StoragePlugin} from './app/plugins/storage'
import {Layout} from './app/panels/layout'
import {NotificationPlugin} from './app/plugins/notification'
import {Blockchain} from './blockchain/blockchain'
import {MergeVMProvider, LondonVMProvider, BerlinVMProvider, ShanghaiVMProvider} from './app/providers/vm-provider'
import {MainnetForkVMProvider} from './app/providers/mainnet-vm-fork-provider'
import {SepoliaForkVMProvider} from './app/providers/sepolia-vm-fork-provider'
import {GoerliForkVMProvider} from './app/providers/goerli-vm-fork-provider'
import {CustomForkVMProvider} from './app/providers/custom-vm-fork-provider'
import {HardhatProvider} from './app/providers/hardhat-provider'
import {GanacheProvider} from './app/providers/ganache-provider'
import {FoundryProvider} from './app/providers/foundry-provider'
import {ExternalHttpProvider} from './app/providers/external-http-provider'
import {InjectedProviderDefault} from './app/providers/injected-provider-default'
import {InjectedProviderTrustWallet} from './app/providers/injected-provider-trustwallet'
import {Injected0ptimismProvider} from './app/providers/injected-optimism-provider'
import {InjectedArbitrumOneProvider} from './app/providers/injected-arbitrum-one-provider'
import {InjectedEphemeryTestnetProvider} from './app/providers/injected-ephemery-testnet-provider'
import {InjectedSKALEChaosTestnetProvider} from './app/providers/injected-skale-chaos-testnet-provider'
import {FileDecorator} from './app/plugins/file-decorator'
import {CodeFormat} from './app/plugins/code-format'
import {SolidityUmlGen} from './app/plugins/solidity-umlgen'
import {ContractFlattener} from './app/plugins/contractFlattener'
import {OpenAIGpt} from './app/plugins/openaigpt'
const isElectron = require('is-electron') const isElectron = require('is-electron')
@ -61,6 +86,8 @@ const remixLib = require('@remix-project/remix-lib')
import { QueryParams } from '@remix-project/remix-lib' import { QueryParams } from '@remix-project/remix-lib'
import { SearchPlugin } from './app/tabs/search' import { SearchPlugin } from './app/tabs/search'
import { ElectronProvider } from './app/files/electronProvider' import { ElectronProvider } from './app/files/electronProvider'
import {QueryParams} from '@remix-project/remix-lib'
import {SearchPlugin} from './app/tabs/search'
const Storage = remixLib.Storage const Storage = remixLib.Storage
const RemixDProvider = require('./app/files/remixDProvider') const RemixDProvider = require('./app/files/remixDProvider')
@ -76,12 +103,12 @@ const PluginManagerComponent = require('./app/components/plugin-manager-componen
const CompileTab = require('./app/tabs/compile-tab') const CompileTab = require('./app/tabs/compile-tab')
const SettingsTab = require('./app/tabs/settings-tab') const SettingsTab = require('./app/tabs/settings-tab')
const AnalysisTab = require('./app/tabs/analysis-tab') const AnalysisTab = require('./app/tabs/analysis-tab')
const { DebuggerTab } = require('./app/tabs/debugger-tab') const {DebuggerTab} = require('./app/tabs/debugger-tab')
const TestTab = require('./app/tabs/test-tab') const TestTab = require('./app/tabs/test-tab')
const FilePanel = require('./app/panels/file-panel') const FilePanel = require('./app/panels/file-panel')
const Editor = require('./app/editor/editor') const Editor = require('./app/editor/editor')
const Terminal = require('./app/panels/terminal') const Terminal = require('./app/panels/terminal')
const { TabProxy } = require('./app/panels/tab-proxy.js') const {TabProxy} = require('./app/panels/tab-proxy.js')
@ -96,7 +123,7 @@ class AppComponent {
// load app config // load app config
const config = new Config(configStorage) const config = new Config(configStorage)
Registry.getInstance().put({ api: config, name: 'config' }) Registry.getInstance().put({api: config, name: 'config'})
// load file system // load file system
this._components.filesProviders = {} this._components.filesProviders = {}
@ -105,9 +132,7 @@ class AppComponent {
api: this._components.filesProviders.browser, api: this._components.filesProviders.browser,
name: 'fileproviders/browser' name: 'fileproviders/browser'
}) })
this._components.filesProviders.localhost = new RemixDProvider( this._components.filesProviders.localhost = new RemixDProvider(this.appManager)
this.appManager
)
Registry.getInstance().put({ Registry.getInstance().put({
api: this._components.filesProviders.localhost, api: this._components.filesProviders.localhost,
name: 'fileproviders/localhost' name: 'fileproviders/localhost'
@ -137,7 +162,7 @@ class AppComponent {
this.panels = {} this.panels = {}
this.workspace = pluginLoader.get() this.workspace = pluginLoader.get()
this.engine = new RemixEngine() this.engine = new RemixEngine()
this.engine.register(appManager); this.engine.register(appManager)
const matomoDomains = { const matomoDomains = {
'remix-alpha.ethereum.org': 27, 'remix-alpha.ethereum.org': 27,
@ -145,15 +170,8 @@ class AppComponent {
'remix.ethereum.org': 23, 'remix.ethereum.org': 23,
'6fd22d6fe5549ad4c4d8fd3ca0b7816b.mod': 35 // remix desktop '6fd22d6fe5549ad4c4d8fd3ca0b7816b.mod': 35 // remix desktop
} }
this.showMatamo = this.showMatamo = matomoDomains[window.location.hostname] && !Registry.getInstance().get('config').api.exists('settings/matomo-analytics')
matomoDomains[window.location.hostname] && this.walkthroughService = new WalkthroughService(appManager, this.showMatamo)
!Registry.getInstance()
.get('config')
.api.exists('settings/matomo-analytics')
this.walkthroughService = new WalkthroughService(
appManager,
this.showMatamo
)
const hosts = ['127.0.0.1:8080', '192.168.0.101:8080', 'localhost:8080'] const hosts = ['127.0.0.1:8080', '192.168.0.101:8080', 'localhost:8080']
// workaround for Electron support // workaround for Electron support
@ -171,19 +189,20 @@ class AppComponent {
this.themeModule = new ThemeModule() this.themeModule = new ThemeModule()
// ----------------- locale service --------------------------------- // ----------------- locale service ---------------------------------
this.localeModule = new LocaleModule() this.localeModule = new LocaleModule()
Registry.getInstance().put({ api: this.themeModule, name: 'themeModule' }) Registry.getInstance().put({api: this.themeModule, name: 'themeModule'})
Registry.getInstance().put({ api: this.localeModule, name: 'localeModule' }) Registry.getInstance().put({api: this.localeModule, name: 'localeModule'})
// ----------------- editor service ---------------------------- // ----------------- editor service ----------------------------
const editor = new Editor() // wrapper around ace editor const editor = new Editor() // wrapper around ace editor
Registry.getInstance().put({ api: editor, name: 'editor' }) Registry.getInstance().put({api: editor, name: 'editor'})
editor.event.register('requiringToSaveCurrentfile', () => editor.event.register('requiringToSaveCurrentfile', (currentFile) => {
fileManager.saveCurrentFile() fileManager.saveCurrentFile()
) if (currentFile.endsWith('.circom')) this.appManager.activatePlugin(['circuit-compiler'])
})
// ----------------- fileManager service ---------------------------- // ----------------- fileManager service ----------------------------
const fileManager = new FileManager(editor, appManager) const fileManager = new FileManager(editor, appManager)
Registry.getInstance().put({ api: fileManager, name: 'filemanager' }) Registry.getInstance().put({api: fileManager, name: 'filemanager'})
// ----------------- dGit provider --------------------------------- // ----------------- dGit provider ---------------------------------
const dGitProvider = new DGitProvider() const dGitProvider = new DGitProvider()
@ -208,6 +227,9 @@ class AppComponent {
// ----------------- ContractFlattener ---------------------------- // ----------------- ContractFlattener ----------------------------
const contractFlattener = new ContractFlattener() const contractFlattener = new ContractFlattener()
// ----------------- Open AI --------------------------------------
const openaigpt = new OpenAIGpt()
// ----------------- import content service ------------------------ // ----------------- import content service ------------------------
const contentImport = new CompilerImports() const contentImport = new CompilerImports()
@ -241,9 +263,11 @@ class AppComponent {
const foundryProvider = new FoundryProvider(blockchain) const foundryProvider = new FoundryProvider(blockchain)
const externalHttpProvider = new ExternalHttpProvider(blockchain) const externalHttpProvider = new ExternalHttpProvider(blockchain)
const trustWalletInjectedProvider = new InjectedProviderTrustWallet() const trustWalletInjectedProvider = new InjectedProviderTrustWallet()
const defaultInjectedProvider = new InjectedProviderDefault const defaultInjectedProvider = new InjectedProviderDefault()
const injected0ptimismProvider = new Injected0ptimismProvider() const injected0ptimismProvider = new Injected0ptimismProvider()
const injectedArbitrumOneProvider = new InjectedArbitrumOneProvider() const injectedArbitrumOneProvider = new InjectedArbitrumOneProvider()
const injectedEphemeryTestnetProvider = new InjectedEphemeryTestnetProvider()
const injectedSKALEChaosTestnetProvider = new InjectedSKALEChaosTestnetProvider()
// ----------------- 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({
@ -253,11 +277,11 @@ class AppComponent {
// ----------------- run script after each compilation results ----------- // ----------------- run script after each compilation results -----------
const compileAndRun = new CompileAndRun() const compileAndRun = new CompileAndRun()
// -------------------Terminal---------------------------------------- // -------------------Terminal----------------------------------------
makeUdapp(blockchain, compilersArtefacts, domEl => terminal.logHtml(domEl)) makeUdapp(blockchain, compilersArtefacts, (domEl) => terminal.logHtml(domEl))
const terminal = new Terminal( const terminal = new Terminal(
{ appManager, blockchain }, {appManager, blockchain},
{ {
getPosition: event => { getPosition: (event) => {
const limitUp = 36 const limitUp = 36
const limitDown = 20 const limitDown = 20
const height = window.innerHeight const height = window.innerHeight
@ -320,12 +344,15 @@ class AppComponent {
trustWalletInjectedProvider, trustWalletInjectedProvider,
injected0ptimismProvider, injected0ptimismProvider,
injectedArbitrumOneProvider, injectedArbitrumOneProvider,
injectedEphemeryTestnetProvider,
injectedSKALEChaosTestnetProvider,
this.walkthroughService, this.walkthroughService,
search, search,
solidityumlgen, solidityumlgen,
contractFlattener, contractFlattener,
solidityScript, solidityScript,
templates templates,
openaigpt
]) ])
//---- fs plugin //---- fs plugin
@ -344,7 +371,7 @@ class AppComponent {
// LAYOUT & SYSTEM VIEWS // LAYOUT & SYSTEM VIEWS
const appPanel = new MainPanel() const appPanel = new MainPanel()
Registry.getInstance().put({ api: this.mainview, name: 'mainview' }) Registry.getInstance().put({api: this.mainview, name: 'mainview'})
const tabProxy = new TabProxy(fileManager, editor) const tabProxy = new TabProxy(fileManager, editor)
this.engine.register([appPanel, tabProxy]) this.engine.register([appPanel, tabProxy])
@ -353,42 +380,18 @@ class AppComponent {
this.sidePanel = new SidePanel() this.sidePanel = new SidePanel()
this.hiddenPanel = new HiddenPanel() this.hiddenPanel = new HiddenPanel()
const pluginManagerComponent = new PluginManagerComponent( const pluginManagerComponent = new PluginManagerComponent(appManager, this.engine)
appManager,
this.engine
)
const filePanel = new FilePanel(appManager) const filePanel = new FilePanel(appManager)
const landingPage = new LandingPage( const landingPage = new LandingPage(appManager, this.menuicons, fileManager, filePanel, contentImport)
appManager, this.settings = new SettingsTab(Registry.getInstance().get('config').api, editor, appManager)
this.menuicons,
fileManager,
filePanel,
contentImport
)
this.settings = new SettingsTab(
Registry.getInstance().get('config').api,
editor,
appManager
)
this.engine.register([ this.engine.register([this.menuicons, landingPage, this.hiddenPanel, this.sidePanel, filePanel, pluginManagerComponent, this.settings])
this.menuicons,
landingPage,
this.hiddenPanel,
this.sidePanel,
filePanel,
pluginManagerComponent,
this.settings
])
// CONTENT VIEWS & DEFAULT PLUGINS // CONTENT VIEWS & DEFAULT PLUGINS
const openZeppelinProxy = new OpenZeppelinProxy(blockchain) const openZeppelinProxy = new OpenZeppelinProxy(blockchain)
const linkLibraries = new LinkLibraries(blockchain) const linkLibraries = new LinkLibraries(blockchain)
const deployLibraries = new DeployLibraries(blockchain) const deployLibraries = new DeployLibraries(blockchain)
const compileTab = new CompileTab( const compileTab = new CompileTab(Registry.getInstance().get('config').api, Registry.getInstance().get('filemanager').api)
Registry.getInstance().get('config').api,
Registry.getInstance().get('filemanager').api
)
const run = new RunTab( const run = new RunTab(
blockchain, blockchain,
Registry.getInstance().get('config').api, Registry.getInstance().get('config').api,
@ -428,10 +431,10 @@ class AppComponent {
]) ])
this.layout.panels = { this.layout.panels = {
tabs: { plugin: tabProxy, active: true }, tabs: {plugin: tabProxy, active: true},
editor: { plugin: editor, active: true }, editor: {plugin: editor, active: true},
main: { plugin: appPanel, active: false }, main: {plugin: appPanel, active: false},
terminal: { plugin: terminal, active: true, minimized: false } terminal: {plugin: terminal, active: true, minimized: false}
} }
} }
@ -447,12 +450,33 @@ class AppComponent {
await this.appManager.activatePlugin(['layout']) await this.appManager.activatePlugin(['layout'])
await this.appManager.activatePlugin(['notification']) await this.appManager.activatePlugin(['notification'])
await this.appManager.activatePlugin(['editor']) await this.appManager.activatePlugin(['editor'])
await this.appManager.activatePlugin(['permissionhandler', 'theme', 'locale', 'fileManager', 'compilerMetadata', 'compilerArtefacts', 'network', 'web3Provider', 'offsetToLineColumnConverter']) await this.appManager.activatePlugin([
'permissionhandler',
'theme',
'locale',
'fileManager',
'compilerMetadata',
'compilerArtefacts',
'network',
'web3Provider',
'offsetToLineColumnConverter'
])
await this.appManager.activatePlugin(['mainPanel', 'menuicons', 'tabs']) await this.appManager.activatePlugin(['mainPanel', 'menuicons', 'tabs'])
await this.appManager.activatePlugin(['sidePanel']) // activating host plugin separately await this.appManager.activatePlugin(['sidePanel']) // activating host plugin separately
await this.appManager.activatePlugin(['home']) await this.appManager.activatePlugin(['home'])
await this.appManager.activatePlugin(['settings', 'config']) await this.appManager.activatePlugin(['settings', 'config'])
await this.appManager.activatePlugin(['hiddenPanel', 'pluginManager', 'codeParser', 'codeFormatter', 'fileDecorator', 'terminal', 'blockchain', 'fetchAndCompile', 'contentImport', 'gistHandler']) await this.appManager.activatePlugin([
'hiddenPanel',
'pluginManager',
'codeParser',
'codeFormatter',
'fileDecorator',
'terminal',
'blockchain',
'fetchAndCompile',
'contentImport',
'gistHandler'
])
await this.appManager.activatePlugin(['settings']) await this.appManager.activatePlugin(['settings'])
await this.appManager.activatePlugin(['walkthrough', 'storage', 'search', 'compileAndRun', 'recorder']) await this.appManager.activatePlugin(['walkthrough', 'storage', 'search', 'compileAndRun', 'recorder'])
await this.appManager.activatePlugin(['solidity-script', 'remix-templates']) await this.appManager.activatePlugin(['solidity-script', 'remix-templates'])
@ -472,6 +496,7 @@ class AppComponent {
await this.appManager.registerContextMenuItems() await this.appManager.registerContextMenuItems()
} }
) )
await this.appManager.activatePlugin(['solidity-script', 'openaigpt'])
await this.appManager.activatePlugin(['filePanel']) await this.appManager.activatePlugin(['filePanel'])
@ -483,9 +508,7 @@ class AppComponent {
.then(async () => { .then(async () => {
try { try {
if (params.deactivate) { if (params.deactivate) {
await this.appManager.deactivatePlugin( await this.appManager.deactivatePlugin(params.deactivate.split(','))
params.deactivate.split(',')
)
} }
} catch (e) { } catch (e) {
console.log(e) console.log(e)
@ -495,10 +518,7 @@ class AppComponent {
this.menuicons.select('solidity') this.menuicons.select('solidity')
} else { } else {
// If plugins are loaded from the URL params, we focus on the last one. // If plugins are loaded from the URL params, we focus on the last one.
if ( if (this.appManager.pluginLoader.current === 'queryParams' && this.workspace.length > 0) {
this.appManager.pluginLoader.current === 'queryParams' &&
this.workspace.length > 0
) {
this.menuicons.select(this.workspace[this.workspace.length - 1]) this.menuicons.select(this.workspace[this.workspace.length - 1])
} else { } else {
this.appManager.call('tabs', 'focus', 'home') this.appManager.call('tabs', 'focus', 'home')
@ -515,17 +535,13 @@ class AppComponent {
} }
if (params.calls) { if (params.calls) {
const calls = params.calls.split("///"); const calls = params.calls.split('///')
// call all functions in the list, one after the other // call all functions in the list, one after the other
for (const call of calls) { for (const call of calls) {
const callDetails = call.split("//"); const callDetails = call.split('//')
if (callDetails.length > 1) { if (callDetails.length > 1) {
this.appManager.call( this.appManager.call('notification', 'toast', `initiating ${callDetails[0]} and calling "${callDetails[1]}" ...`)
"notification",
"toast",
`initiating ${callDetails[0]} and calling "${callDetails[1]}" ...`
);
// @todo(remove the timeout when activatePlugin is on 0.3.0) // @todo(remove the timeout when activatePlugin is on 0.3.0)
try { try {

@ -1,9 +1,9 @@
// eslint-disable-next-line no-use-before-define // eslint-disable-next-line no-use-before-define
import React from 'react' import React from 'react'
import { AbstractPanel } from './panel' import {AbstractPanel} from './panel'
import * as packageJson from '../../../../../package.json' import * as packageJson from '../../../../../package.json'
import { RemixPluginPanel } from '@remix-ui/panel' import {RemixPluginPanel} from '@remix-ui/panel'
import { PluginViewWrapper } from '@remix-ui/helper' import {PluginViewWrapper} from '@remix-ui/helper'
const profile = { const profile = {
name: 'hiddenPanel', name: 'hiddenPanel',
@ -16,35 +16,37 @@ const profile = {
export class HiddenPanel extends AbstractPanel { export class HiddenPanel extends AbstractPanel {
el: HTMLElement el: HTMLElement
dispatch: React.Dispatch<any> = () => {} dispatch: React.Dispatch<any> = () => {}
constructor () { constructor() {
super(profile) super(profile)
this.el = document.createElement('div') this.el = document.createElement('div')
this.el.setAttribute('class', 'pluginsContainer') this.el.setAttribute('class', 'pluginsContainer')
} }
addView (profile: any, view: any): void { addView(profile: any, view: any): void {
super.removeView(profile) super.removeView(profile)
super.addView(profile, view) super.addView(profile, view)
this.renderComponent() this.renderComponent()
} }
updateComponent (state: any) { updateComponent(state: any) {
return <RemixPluginPanel header={<></>} plugins={state.plugins}/> return <RemixPluginPanel header={<></>} plugins={state.plugins} />
} }
setDispatch (dispatch: React.Dispatch<any>) { setDispatch(dispatch: React.Dispatch<any>) {
this.dispatch = dispatch this.dispatch = dispatch
} }
render() { render() {
return ( return (
<div className='pluginsContainer'><PluginViewWrapper plugin={this} /></div> <div className="pluginsContainer">
); <PluginViewWrapper plugin={this} />
</div>
)
} }
renderComponent () { renderComponent() {
this.dispatch({ this.dispatch({
plugins: this.plugins, plugins: this.plugins
}) })
} }
} }

@ -1,8 +1,8 @@
import React from 'react' // eslint-disable-line import React from 'react' // eslint-disable-line
import { AbstractPanel } from './panel' import {AbstractPanel} from './panel'
import { RemixPluginPanel } from '@remix-ui/panel' import {RemixPluginPanel} from '@remix-ui/panel'
import packageJson from '../../../../../package.json' import packageJson from '../../../../../package.json'
import { PluginViewWrapper } from '@remix-ui/helper' import {PluginViewWrapper} from '@remix-ui/helper'
const profile = { const profile = {
name: 'mainPanel', name: 'mainPanel',
@ -13,56 +13,60 @@ const profile = {
} }
export class MainPanel extends AbstractPanel { export class MainPanel extends AbstractPanel {
element: HTMLDivElement element: HTMLDivElement
dispatch: React.Dispatch<any> = () => {} dispatch: React.Dispatch<any> = () => {}
constructor (config) { constructor(config) {
super(profile) super(profile)
this.element = document.createElement('div') this.element = document.createElement('div')
this.element.setAttribute('data-id', 'mainPanelPluginsContainer') this.element.setAttribute('data-id', 'mainPanelPluginsContainer')
this.element.setAttribute('style', 'height: 100%; width: 100%;') this.element.setAttribute('style', 'height: 100%; width: 100%;')
// this.config = config // this.config = config
} }
setDispatch (dispatch: React.Dispatch<any>) { setDispatch(dispatch: React.Dispatch<any>) {
this.dispatch = dispatch this.dispatch = dispatch
} }
onActivation () { onActivation() {
this.renderComponent() this.renderComponent()
} }
focus (name) { focus(name) {
this.emit('focusChanged', name) this.emit('focusChanged', name)
super.focus(name) super.focus(name)
this.renderComponent() this.renderComponent()
} }
addView (profile, view) { addView(profile, view) {
super.addView(profile, view) super.addView(profile, view)
this.renderComponent() this.renderComponent()
} }
removeView (profile) { removeView(profile) {
super.removeView(profile) super.removeView(profile)
this.renderComponent() this.renderComponent()
} }
async showContent (name) { async showContent(name) {
super.showContent(name) super.showContent(name)
this.renderComponent() this.renderComponent()
} }
renderComponent () { renderComponent() {
this.dispatch({ this.dispatch({
plugins: this.plugins plugins: this.plugins
}) })
} }
render() { render() {
return <div style={{height: '100%', width: '100%'}} data-id='mainPanelPluginsContainer'><PluginViewWrapper plugin={this} /></div> return (
} <div style={{height: '100%', width: '100%'}} data-id="mainPanelPluginsContainer">
<PluginViewWrapper plugin={this} />
</div>
)
}
updateComponent (state: any) { updateComponent(state: any) {
return <RemixPluginPanel header={<></>} plugins={state.plugins}/> return <RemixPluginPanel header={<></>} plugins={state.plugins} />
} }
} }

@ -1,144 +1,174 @@
import { RemixApp } from '@remix-ui/app' import {RemixApp} from '@remix-ui/app'
import React, { useEffect, useRef, useState } from 'react' import React, {useEffect, useRef, useState} from 'react'
import { render } from 'react-dom' import {render} from 'react-dom'
import * as packageJson from '../../../../../package.json' import * as packageJson from '../../../../../package.json'
import { fileSystem, fileSystems } from '../files/fileSystem' import {fileSystem, fileSystems} from '../files/fileSystem'
import { indexedDBFileSystem } from '../files/filesystems/indexedDB' import {indexedDBFileSystem} from '../files/filesystems/indexedDB'
import { localStorageFS } from '../files/filesystems/localStorage' import {localStorageFS} from '../files/filesystems/localStorage'
import { fileSystemUtility, migrationTestData } from '../files/filesystems/fileSystemUtility' import {fileSystemUtility, migrationTestData} from '../files/filesystems/fileSystemUtility'
import './styles/preload.css' import './styles/preload.css'
const _paq = window._paq = window._paq || [] const _paq = (window._paq = window._paq || [])
export const Preload = () => { export const Preload = () => {
const [supported, setSupported] = useState<boolean>(true)
const [error, setError] = useState<boolean>(false)
const [showDownloader, setShowDownloader] = useState<boolean>(false)
const remixFileSystems = useRef<fileSystems>(new fileSystems())
const remixIndexedDB = useRef<fileSystem>(new indexedDBFileSystem())
const localStorageFileSystem = useRef<fileSystem>(new localStorageFS())
// url parameters to e2e test the fallbacks and error warnings
const testmigrationFallback = useRef<boolean>(
window.location.hash.includes('e2e_testmigration_fallback=true') && window.location.host === '127.0.0.1:8080' && window.location.protocol === 'http:'
)
const testmigrationResult = useRef<boolean>(
window.location.hash.includes('e2e_testmigration=true') && window.location.host === '127.0.0.1:8080' && window.location.protocol === 'http:'
)
const testBlockStorage = useRef<boolean>(
window.location.hash.includes('e2e_testblock_storage=true') && window.location.host === '127.0.0.1:8080' && window.location.protocol === 'http:'
)
const [supported, setSupported] = useState<boolean>(true) function loadAppComponent() {
const [error, setError] = useState<boolean>(false) import('../../app')
const [showDownloader, setShowDownloader] = useState<boolean>(false) .then((AppComponent) => {
const remixFileSystems = useRef<fileSystems>(new fileSystems()) const appComponent = new AppComponent.default()
const remixIndexedDB = useRef<fileSystem>(new indexedDBFileSystem()) appComponent.run().then(() => {
const localStorageFileSystem = useRef<fileSystem>(new localStorageFS()) render(
// url parameters to e2e test the fallbacks and error warnings <>
const testmigrationFallback = useRef<boolean>(window.location.hash.includes('e2e_testmigration_fallback=true') && window.location.host === '127.0.0.1:8080' && window.location.protocol === 'http:') <RemixApp app={appComponent} />
const testmigrationResult = useRef<boolean>(window.location.hash.includes('e2e_testmigration=true') && window.location.host === '127.0.0.1:8080' && window.location.protocol === 'http:') </>,
const testBlockStorage = useRef<boolean>(window.location.hash.includes('e2e_testblock_storage=true') && window.location.host === '127.0.0.1:8080' && window.location.protocol === 'http:') document.getElementById('root')
)
function loadAppComponent() {
import('../../app').then((AppComponent) => {
const appComponent = new AppComponent.default()
appComponent.run().then(() => {
render(
<>
<RemixApp app={appComponent} />
</>,
document.getElementById('root')
)
})
}).catch(err => {
_paq.push(['trackEvent', 'Preload', 'error', err && err.message])
console.error('Error loading Remix:', err)
setError(true)
}) })
} })
.catch((err) => {
_paq.push(['trackEvent', 'Preload', 'error', err && err.message])
console.error('Error loading Remix:', err)
setError(true)
})
}
const downloadBackup = async () => { const downloadBackup = async () => {
setShowDownloader(false) setShowDownloader(false)
const fsUtility = new fileSystemUtility() const fsUtility = new fileSystemUtility()
await fsUtility.downloadBackup(remixFileSystems.current.fileSystems['localstorage']) await fsUtility.downloadBackup(remixFileSystems.current.fileSystems['localstorage'])
await migrateAndLoad() await migrateAndLoad()
} }
const migrateAndLoad = async () => { const migrateAndLoad = async () => {
setShowDownloader(false) setShowDownloader(false)
const fsUtility = new fileSystemUtility() const fsUtility = new fileSystemUtility()
const migrationResult = await fsUtility.migrate(localStorageFileSystem.current, remixIndexedDB.current) const migrationResult = await fsUtility.migrate(localStorageFileSystem.current, remixIndexedDB.current)
_paq.push(['trackEvent', 'Migrate', 'result', migrationResult?'success' : 'fail']) _paq.push(['trackEvent', 'Migrate', 'result', migrationResult ? 'success' : 'fail'])
await setFileSystems() await setFileSystems()
} }
const setFileSystems = async() => { const setFileSystems = async () => {
const fsLoaded = await remixFileSystems.current.setFileSystem([(testmigrationFallback.current || testBlockStorage.current)? null: remixIndexedDB.current, testBlockStorage.current? null:localStorageFileSystem.current]) const fsLoaded = await remixFileSystems.current.setFileSystem([
if (fsLoaded) { testmigrationFallback.current || testBlockStorage.current ? null : remixIndexedDB.current,
console.log(fsLoaded.name + ' activated') testBlockStorage.current ? null : localStorageFileSystem.current
_paq.push(['trackEvent', 'Storage', 'activate', fsLoaded.name]) ])
loadAppComponent() if (fsLoaded) {
} else { console.log(fsLoaded.name + ' activated')
_paq.push(['trackEvent', 'Storage', 'error', 'no supported storage']) _paq.push(['trackEvent', 'Storage', 'activate', fsLoaded.name])
setSupported(false) loadAppComponent()
} } else {
_paq.push(['trackEvent', 'Storage', 'error', 'no supported storage'])
setSupported(false)
} }
}
const testmigration = async() => { const testmigration = async () => {
if (testmigrationResult.current) { if (testmigrationResult.current) {
const fsUtility = new fileSystemUtility() const fsUtility = new fileSystemUtility()
fsUtility.populateWorkspace(migrationTestData, remixFileSystems.current.fileSystems['localstorage'].fs) fsUtility.populateWorkspace(migrationTestData, remixFileSystems.current.fileSystems['localstorage'].fs)
}
} }
}
useEffect(() => { useEffect(() => {
async function loadStorage() { async function loadStorage() {
await remixFileSystems.current.addFileSystem(remixIndexedDB.current) || _paq.push(['trackEvent', 'Storage', 'error', 'indexedDB not supported']) ;(await remixFileSystems.current.addFileSystem(remixIndexedDB.current)) || _paq.push(['trackEvent', 'Storage', 'error', 'indexedDB not supported'])
await remixFileSystems.current.addFileSystem(localStorageFileSystem.current) || _paq.push(['trackEvent', 'Storage', 'error', 'localstorage not supported']) ;(await remixFileSystems.current.addFileSystem(localStorageFileSystem.current)) || _paq.push(['trackEvent', 'Storage', 'error', 'localstorage not supported'])
await testmigration() await testmigration()
remixIndexedDB.current.loaded && await remixIndexedDB.current.checkWorkspaces() remixIndexedDB.current.loaded && (await remixIndexedDB.current.checkWorkspaces())
localStorageFileSystem.current.loaded && await localStorageFileSystem.current.checkWorkspaces() localStorageFileSystem.current.loaded && (await localStorageFileSystem.current.checkWorkspaces())
remixIndexedDB.current.loaded && ( (remixIndexedDB.current.hasWorkSpaces || !localStorageFileSystem.current.hasWorkSpaces)? await setFileSystems():setShowDownloader(true)) remixIndexedDB.current.loaded && (remixIndexedDB.current.hasWorkSpaces || !localStorageFileSystem.current.hasWorkSpaces ? await setFileSystems() : setShowDownloader(true))
!remixIndexedDB.current.loaded && await setFileSystems() !remixIndexedDB.current.loaded && (await setFileSystems())
} }
loadStorage() loadStorage()
}, []) }, [])
return <> return (
<div className='preload-container'> <>
<div className='preload-logo pb-4'> <div className="preload-container">
{logo} <div className="preload-logo pb-4">
<div className="info-secondary splash"> {logo}
REMIX IDE <div className="info-secondary splash">
<br /> REMIX IDE
<span className='version'> v{packageJson.version}</span> <br />
</div> <span className="version"> v{packageJson.version}</span>
</div> </div>
{!supported ?
<div className='preload-info-container alert alert-warning'>
Your browser does not support any of the filesystems required by Remix.
Either change the settings in your browser or use a supported browser.
</div> : null}
{error ?
<div className='preload-info-container alert alert-danger text-left'>
An unknown error has occurred while loading the application.<br></br>
Doing a hard refresh might fix this issue:<br></br>
<div className='pt-2'>
Windows:<br></br>
- Chrome: CTRL + F5 or CTRL + Reload Button<br></br>
- Firefox: CTRL + SHIFT + R or CTRL + F5<br></br>
</div>
<div className='pt-2'>
MacOS:<br></br>
- Chrome & FireFox: CMD + SHIFT + R or SHIFT + Reload Button<br></br>
</div>
<div className='pt-2'>
Linux:<br></br>
- Chrome & FireFox: CTRL + SHIFT + R<br></br>
</div>
</div> : null}
{showDownloader ?
<div className='preload-info-container alert alert-info'>
This app will be updated now. Please download a backup of your files now to make sure you don't lose your work.
<br></br>
You don't need to do anything else, your files will be available when the app loads.
<div onClick={async () => { await downloadBackup() }} data-id='downloadbackup-btn' className='btn btn-primary mt-1'>download backup</div>
<div onClick={async () => { await migrateAndLoad() }} data-id='skipbackup-btn' className='btn btn-primary mt-1'>skip backup</div>
</div> : null}
{(supported && !error && !showDownloader) ?
<div>
<i className="fas fa-spinner fa-spin fa-2x"></i>
</div> : null}
</div> </div>
{!supported ? (
<div className="preload-info-container alert alert-warning">
Your browser does not support any of the filesystems required by Remix. Either change the settings in your browser or use a supported browser.
</div>
) : null}
{error ? (
<div className="preload-info-container alert alert-danger text-left">
An unknown error has occurred while loading the application.
<br></br>
Doing a hard refresh might fix this issue:<br></br>
<div className="pt-2">
Windows:<br></br>- Chrome: CTRL + F5 or CTRL + Reload Button
<br></br>- Firefox: CTRL + SHIFT + R or CTRL + F5<br></br>
</div>
<div className="pt-2">
MacOS:<br></br>- Chrome & FireFox: CMD + SHIFT + R or SHIFT + Reload Button<br></br>
</div>
<div className="pt-2">
Linux:<br></br>- Chrome & FireFox: CTRL + SHIFT + R<br></br>
</div>
</div>
) : null}
{showDownloader ? (
<div className="preload-info-container alert alert-info">
This app will be updated now. Please download a backup of your files now to make sure you don't lose your work.
<br></br>
You don't need to do anything else, your files will be available when the app loads.
<div
onClick={async () => {
await downloadBackup()
}}
data-id="downloadbackup-btn"
className="btn btn-primary mt-1"
>
download backup
</div>
<div
onClick={async () => {
await migrateAndLoad()
}}
data-id="skipbackup-btn"
className="btn btn-primary mt-1"
>
skip backup
</div>
</div>
) : null}
{supported && !error && !showDownloader ? (
<div>
<i className="fas fa-spinner fa-spin fa-2x"></i>
</div>
) : null}
</div>
</> </>
)
} }
const logo = (
const logo = <svg id="Ebene_2" data-name="Ebene 2" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 105 100"> <svg id="Ebene_2" data-name="Ebene 2" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 105 100">
<path d="M91.84,35a.09.09,0,0,1-.1-.07,41,41,0,0,0-79.48,0,.09.09,0,0,1-.1.07C9.45,35,1,35.35,1,42.53c0,8.56,1,16,6,20.32,2.16,1.85,5.81,2.3,9.27,2.22a44.4,44.4,0,0,0,6.45-.68.09.09,0,0,0,.06-.15A34.81,34.81,0,0,1,17,45c0-.1,0-.21,0-.31a35,35,0,0,1,70,0c0,.1,0,.21,0,.31a34.81,34.81,0,0,1-5.78,19.24.09.09,0,0,0,.06.15,44.4,44.4,0,0,0,6.45.68c3.46.08,7.11-.37,9.27-2.22,5-4.27,6-11.76,6-20.32C103,35.35,94.55,35,91.84,35Z" /> <path d="M91.84,35a.09.09,0,0,1-.1-.07,41,41,0,0,0-79.48,0,.09.09,0,0,1-.1.07C9.45,35,1,35.35,1,42.53c0,8.56,1,16,6,20.32,2.16,1.85,5.81,2.3,9.27,2.22a44.4,44.4,0,0,0,6.45-.68.09.09,0,0,0,.06-.15A34.81,34.81,0,0,1,17,45c0-.1,0-.21,0-.31a35,35,0,0,1,70,0c0,.1,0,.21,0,.31a34.81,34.81,0,0,1-5.78,19.24.09.09,0,0,0,.06.15,44.4,44.4,0,0,0,6.45.68c3.46.08,7.11-.37,9.27-2.22,5-4.27,6-11.76,6-20.32C103,35.35,94.55,35,91.84,35Z" />
<path d="M52,74,25.4,65.13a.1.1,0,0,0-.1.17L51.93,91.93a.1.1,0,0,0,.14,0L78.7,65.3a.1.1,0,0,0-.1-.17L52,74A.06.06,0,0,1,52,74Z" /> <path d="M52,74,25.4,65.13a.1.1,0,0,0-.1.17L51.93,91.93a.1.1,0,0,0,.14,0L78.7,65.3a.1.1,0,0,0-.1-.17L52,74A.06.06,0,0,1,52,74Z" />
<path d="M75.68,46.9,82,45a.09.09,0,0,0,.08-.09,29.91,29.91,0,0,0-.87-6.94.11.11,0,0,0-.09-.08l-6.43-.58a.1.1,0,0,1-.06-.18l4.78-4.18a.13.13,0,0,0,0-.12,30.19,30.19,0,0,0-3.65-6.07.09.09,0,0,0-.11,0l-5.91,2a.1.1,0,0,1-.12-.14L72.19,23a.11.11,0,0,0,0-.12,29.86,29.86,0,0,0-5.84-4.13.09.09,0,0,0-.11,0l-4.47,4.13a.1.1,0,0,1-.17-.07l.09-6a.1.1,0,0,0-.07-.1,30.54,30.54,0,0,0-7-1.47.1.1,0,0,0-.1.07l-2.38,5.54a.1.1,0,0,1-.18,0l-2.37-5.54a.11.11,0,0,0-.11-.06,30,30,0,0,0-7,1.48.12.12,0,0,0-.07.1l.08,6.05a.09.09,0,0,1-.16.07L37.8,18.76a.11.11,0,0,0-.12,0,29.75,29.75,0,0,0-5.83,4.13.11.11,0,0,0,0,.12l2.59,5.6a.11.11,0,0,1-.13.14l-5.9-2a.11.11,0,0,0-.12,0,30.23,30.23,0,0,0-3.62,6.08.11.11,0,0,0,0,.12l4.79,4.19a.1.1,0,0,1-.06.17L23,37.91a.1.1,0,0,0-.09.07A29.9,29.9,0,0,0,22,44.92a.1.1,0,0,0,.07.1L28.4,47a.1.1,0,0,1,0,.18l-5.84,3.26a.16.16,0,0,0,0,.11,30.17,30.17,0,0,0,2.1,6.76c.32.71.67,1.4,1,2.08a.1.1,0,0,0,.06,0L52,68.16H52l26.34-8.78a.1.1,0,0,0,.06-.05,30.48,30.48,0,0,0,3.11-8.88.1.1,0,0,0-.05-.11l-5.83-3.26A.1.1,0,0,1,75.68,46.9Z" /> <path d="M75.68,46.9,82,45a.09.09,0,0,0,.08-.09,29.91,29.91,0,0,0-.87-6.94.11.11,0,0,0-.09-.08l-6.43-.58a.1.1,0,0,1-.06-.18l4.78-4.18a.13.13,0,0,0,0-.12,30.19,30.19,0,0,0-3.65-6.07.09.09,0,0,0-.11,0l-5.91,2a.1.1,0,0,1-.12-.14L72.19,23a.11.11,0,0,0,0-.12,29.86,29.86,0,0,0-5.84-4.13.09.09,0,0,0-.11,0l-4.47,4.13a.1.1,0,0,1-.17-.07l.09-6a.1.1,0,0,0-.07-.1,30.54,30.54,0,0,0-7-1.47.1.1,0,0,0-.1.07l-2.38,5.54a.1.1,0,0,1-.18,0l-2.37-5.54a.11.11,0,0,0-.11-.06,30,30,0,0,0-7,1.48.12.12,0,0,0-.07.1l.08,6.05a.09.09,0,0,1-.16.07L37.8,18.76a.11.11,0,0,0-.12,0,29.75,29.75,0,0,0-5.83,4.13.11.11,0,0,0,0,.12l2.59,5.6a.11.11,0,0,1-.13.14l-5.9-2a.11.11,0,0,0-.12,0,30.23,30.23,0,0,0-3.62,6.08.11.11,0,0,0,0,.12l4.79,4.19a.1.1,0,0,1-.06.17L23,37.91a.1.1,0,0,0-.09.07A29.9,29.9,0,0,0,22,44.92a.1.1,0,0,0,.07.1L28.4,47a.1.1,0,0,1,0,.18l-5.84,3.26a.16.16,0,0,0,0,.11,30.17,30.17,0,0,0,2.1,6.76c.32.71.67,1.4,1,2.08a.1.1,0,0,0,.06,0L52,68.16H52l26.34-8.78a.1.1,0,0,0,.06-.05,30.48,30.48,0,0,0,3.11-8.88.1.1,0,0,0-.05-.11l-5.83-3.26A.1.1,0,0,1,75.68,46.9Z" />
</svg> </svg>
)

@ -1,10 +1,10 @@
// eslint-disable-next-line no-use-before-define // eslint-disable-next-line no-use-before-define
import React from 'react' import React from 'react'
import { AbstractPanel } from './panel' import {AbstractPanel} from './panel'
import { RemixPluginPanel } from '@remix-ui/panel' import {RemixPluginPanel} from '@remix-ui/panel'
import packageJson from '../../../../../package.json' import packageJson from '../../../../../package.json'
import { RemixUIPanelHeader } from '@remix-ui/panel' import {RemixUIPanelHeader} from '@remix-ui/panel'
import { PluginViewWrapper } from '@remix-ui/helper' import {PluginViewWrapper} from '@remix-ui/helper'
// const csjs = require('csjs-inject') // const csjs = require('csjs-inject')
const sidePanel = { const sidePanel = {
@ -79,14 +79,17 @@ export class SidePanel extends AbstractPanel {
this.renderComponent() this.renderComponent()
} }
setDispatch (dispatch: React.Dispatch<any>) { setDispatch(dispatch: React.Dispatch<any>) {
this.dispatch = dispatch this.dispatch = dispatch
} }
render() { render() {
return ( return (
<section className='panel plugin-manager'> <PluginViewWrapper plugin={this} /></section> <section className="panel plugin-manager">
); {' '}
<PluginViewWrapper plugin={this} />
</section>
)
} }
updateComponent(state: any) { updateComponent(state: any) {

@ -1,11 +1,11 @@
// eslint-disable-next-line no-use-before-define // eslint-disable-next-line no-use-before-define
import React from 'react' import React from 'react'
import packageJson from '../../../../../package.json' import packageJson from '../../../../../package.json'
import { Plugin } from '@remixproject/engine' import {Plugin} from '@remixproject/engine'
import { EventEmitter } from 'events' import {EventEmitter} from 'events'
import { IconRecord, RemixUiVerticalIconsPanel } from '@remix-ui/vertical-icons-panel' import {IconRecord, RemixUiVerticalIconsPanel} from '@remix-ui/vertical-icons-panel'
import { Profile } from '@remixproject/plugin-utils' import {Profile} from '@remixproject/plugin-utils'
import { PluginViewWrapper } from '@remix-ui/helper' import {PluginViewWrapper} from '@remix-ui/helper'
const profile = { const profile = {
name: 'menuicons', name: 'menuicons',
@ -21,44 +21,51 @@ export class VerticalIcons extends Plugin {
htmlElement: HTMLDivElement htmlElement: HTMLDivElement
icons: Record<string, IconRecord> = {} icons: Record<string, IconRecord> = {}
dispatch: React.Dispatch<any> = () => {} dispatch: React.Dispatch<any> = () => {}
constructor () { constructor() {
super(profile) super(profile)
this.events = new EventEmitter() this.events = new EventEmitter()
this.htmlElement = document.createElement('div') this.htmlElement = document.createElement('div')
this.htmlElement.setAttribute('id', 'icon-panel') this.htmlElement.setAttribute('id', 'icon-panel')
} }
renderComponent () { renderComponent() {
const fixedOrder = ['filePanel', 'search', 'solidity','udapp', 'debugger', 'solidityStaticAnalysis', 'solidityUnitTesting', 'pluginManager'] const fixedOrder = ['filePanel', 'search', 'solidity', 'udapp', 'debugger', 'solidityStaticAnalysis', 'solidityUnitTesting', 'pluginManager']
const divived = Object.values(this.icons).map((value) => { return { const divived = Object.values(this.icons)
...value, .map((value) => {
isRequired: fixedOrder.indexOf(value.profile.name) > -1 return {
}}).sort((a,b) => { ...value,
return a.timestamp - b.timestamp isRequired: fixedOrder.indexOf(value.profile.name) > -1
}) }
})
.sort((a, b) => {
return a.timestamp - b.timestamp
})
const required = divived.filter((value) => value.isRequired).sort((a,b) => { const required = divived
return fixedOrder.indexOf(a.profile.name) - fixedOrder.indexOf(b.profile.name) .filter((value) => value.isRequired)
}) .sort((a, b) => {
return fixedOrder.indexOf(a.profile.name) - fixedOrder.indexOf(b.profile.name)
})
const sorted: IconRecord[] = [ const sorted: IconRecord[] = [
...required, ...required,
...divived.filter((value) => { return !value.isRequired }) ...divived.filter((value) => {
return !value.isRequired
})
] ]
this.dispatch({ this.dispatch({
verticalIconsPlugin: this, verticalIconsPlugin: this,
icons: sorted icons: sorted
}) })
} }
setDispatch (dispatch: React.Dispatch<any>) { setDispatch(dispatch: React.Dispatch<any>) {
this.dispatch = dispatch this.dispatch = dispatch
} }
onActivation () { onActivation() {
this.renderComponent() this.renderComponent()
this.on('sidePanel', 'focusChanged', (name: string) => { this.on('sidePanel', 'focusChanged', (name: string) => {
Object.keys(this.icons).map((o) => { Object.keys(this.icons).map((o) => {
@ -69,7 +76,7 @@ export class VerticalIcons extends Plugin {
}) })
} }
async linkContent (profile: Profile) { async linkContent(profile: Profile) {
if (!profile.icon) return if (!profile.icon) return
if (!profile.kind) profile.kind = 'none' if (!profile.kind) profile.kind = 'none'
this.icons[profile.name] = { this.icons[profile.name] = {
@ -81,7 +88,7 @@ export class VerticalIcons extends Plugin {
this.renderComponent() this.renderComponent()
} }
unlinkContent (profile: Profile) { unlinkContent(profile: Profile) {
delete this.icons[profile.name] delete this.icons[profile.name]
this.renderComponent() this.renderComponent()
} }
@ -95,7 +102,7 @@ export class VerticalIcons extends Plugin {
* Set an icon as active * Set an icon as active
* @param {string} name Name of profile of the module to activate * @param {string} name Name of profile of the module to activate
*/ */
select (name: string) { select(name: string) {
// TODO: Only keep `this.emit` (issue#2210) // TODO: Only keep `this.emit` (issue#2210)
this.emit('showContent', name) this.emit('showContent', name)
this.events.emit('showContent', name) this.events.emit('showContent', name)
@ -105,22 +112,21 @@ export class VerticalIcons extends Plugin {
* Toggles the side panel for plugin * Toggles the side panel for plugin
* @param {string} name Name of profile of the module to activate * @param {string} name Name of profile of the module to activate
*/ */
toggle (name: string) { toggle(name: string) {
// TODO: Only keep `this.emit` (issue#2210) // TODO: Only keep `this.emit` (issue#2210)
this.emit('toggleContent', name) this.emit('toggleContent', name)
this.events.emit('toggleContent', name) this.events.emit('toggleContent', name)
} }
updateComponent(state: any){ updateComponent(state: any) {
return <RemixUiVerticalIconsPanel return <RemixUiVerticalIconsPanel verticalIconsPlugin={state.verticalIconsPlugin} icons={state.icons} />
verticalIconsPlugin={state.verticalIconsPlugin}
icons={state.icons}
/>
} }
render() { render() {
return ( return (
<div id='icon-panel'><PluginViewWrapper plugin={this} /></div> <div id="icon-panel">
); <PluginViewWrapper plugin={this} />
</div>
)
} }
} }

@ -13,7 +13,7 @@ const profile = {
name: 'editor', name: 'editor',
description: 'service - editor', description: 'service - editor',
version: packageJson.version, version: packageJson.version,
methods: ['highlight', 'discardHighlight', 'clearAnnotations', 'addLineText', 'discardLineTexts', 'addAnnotation', 'gotoLine', 'revealRange', 'getCursorPosition', 'open', 'addModel','addErrorMarker', 'clearErrorMarkers', 'getText'], methods: ['highlight', 'discardHighlight', 'clearAnnotations', 'addLineText', 'discardLineTexts', 'addAnnotation', 'gotoLine', 'revealRange', 'getCursorPosition', 'open', 'addModel','addErrorMarker', 'clearErrorMarkers', 'getText', 'getPositionAt'],
} }
class Editor extends Plugin { class Editor extends Plugin {
@ -51,7 +51,8 @@ class Editor extends Plugin {
rs: 'rust', rs: 'rust',
cairo: 'cairo', cairo: 'cairo',
ts: 'typescript', ts: 'typescript',
move: 'move' move: 'move',
circom: 'circom'
} }
this.activated = false this.activated = false
@ -99,8 +100,8 @@ class Editor extends Plugin {
this.ref.clearDecorationsByPlugin = (filePath, plugin, typeOfDecoration) => this.clearDecorationsByPlugin(filePath, plugin, typeOfDecoration) this.ref.clearDecorationsByPlugin = (filePath, plugin, typeOfDecoration) => this.clearDecorationsByPlugin(filePath, plugin, typeOfDecoration)
this.ref.keepDecorationsFor = (name, typeOfDecoration) => this.keepDecorationsFor(name, typeOfDecoration) this.ref.keepDecorationsFor = (name, typeOfDecoration) => this.keepDecorationsFor(name, typeOfDecoration)
}} id='editorView'> }} id='editorView'>
<PluginViewWrapper plugin={this} /> <PluginViewWrapper plugin={this} />
</div> </div>
} }
renderComponent () { renderComponent () {
@ -174,8 +175,8 @@ class Editor extends Plugin {
} }
this.saveTimeout = window.setTimeout(() => { this.saveTimeout = window.setTimeout(() => {
this.triggerEvent('contentChanged', []) this.triggerEvent('contentChanged', [currentFile, input])
this.triggerEvent('requiringToSaveCurrentfile', []) this.triggerEvent('requiringToSaveCurrentfile', [currentFile])
}, 500) }, 500)
} }
@ -293,7 +294,7 @@ class Editor extends Plugin {
* Get the text in the current session, if any. * Get the text in the current session, if any.
* @param {string} url Address of the content to retrieve. * @param {string} url Address of the content to retrieve.
*/ */
getText (url) { getText (url) {
if (this.sessions[url]) { if (this.sessions[url]) {
return this.sessions[url].getValue() return this.sessions[url].getValue()
} }
@ -578,6 +579,10 @@ class Editor extends Plugin {
this.clearDecorationsByPlugin(session, from, 'lineTextPerFile', this.registeredDecorations, this.currentDecorations) this.clearDecorationsByPlugin(session, from, 'lineTextPerFile', this.registeredDecorations, this.currentDecorations)
} }
} }
getPositionAt(offset) {
return this.api.getPositionAt(offset)
}
} }
module.exports = Editor module.exports = Editor

@ -1,3 +1,4 @@
'use strict' 'use strict'
import { saveAs } from 'file-saver' import { saveAs } from 'file-saver'
import JSZip from 'jszip' import JSZip from 'jszip'
@ -23,8 +24,8 @@ const profile = {
icon: 'assets/img/fileManager.webp', icon: 'assets/img/fileManager.webp',
permission: true, permission: true,
version: packageJson.version, version: packageJson.version,
methods: ['closeAllFiles', 'closeFile', 'file', 'exists', 'open', 'writeFile', 'readFile', 'copyFile', 'copyDir', 'rename', 'mkdir', methods: ['closeAllFiles', 'closeFile', 'file', 'exists', 'open', 'writeFile', 'writeMultipleFiles', 'readFile', 'copyFile', 'copyDir', 'rename', 'mkdir',
'readdir', 'dirList', 'fileList', 'remove', 'getCurrentFile', 'getFile', 'selectFolder', 'setFile', 'switchFile', 'refresh', 'readdir', 'dirList', 'fileList', 'remove', 'getCurrentFile', 'getFile', 'getFolder', 'setFile', 'switchFile', 'refresh',
'getProviderOf', 'getProviderByName', 'getPathFromUrl', 'getUrlFromPath', 'saveCurrentFile', 'setBatchFiles', 'isGitRepo'], 'getProviderOf', 'getProviderByName', 'getPathFromUrl', 'getUrlFromPath', 'saveCurrentFile', 'setBatchFiles', 'isGitRepo'],
kind: 'file-system' kind: 'file-system'
} }
@ -223,6 +224,38 @@ class FileManager extends Plugin {
} }
} }
/**
* Set the content of multiple files
* @param {string[]} filePaths paths of the files
* @param {string[]} data content to write to each file
* @param {string} folderPath base folder path
* @returns {void}
*/
async writeMultipleFiles(filePaths: string[], fileData: string[], folderPath: string) {
if (this.currentRequest) {
const canCall = await this.askUserPermission(`writeFile`, `will write multiple files to ${folderPath}...`)
const required = this.appManager.isRequired(this.currentRequest.from)
if (canCall && !required) {
this.call('notification', 'toast', fileChangedToastMsg(this.currentRequest.from, folderPath))
}
}
try {
for (let i = 0; i < filePaths.length; i++) {
const installPath = folderPath + "/" + filePaths[i]
let path = this.normalize(installPath)
path = this.limitPluginScope(path)
if (!await this.exists(path)) {
await this._setFileInternal(path, fileData[i])
this.emit('fileAdded', path)
}
}
} catch (e) {
throw new Error(e)
}
}
/** /**
* Return the content of a specific file * Return the content of a specific file
* @param {string} path path of the file * @param {string} path path of the file
@ -353,7 +386,7 @@ class FileManager extends Plugin {
async zipDir(dirPath, zip) { async zipDir(dirPath, zip) {
const filesAndFolders = await this.readdir(dirPath) const filesAndFolders = await this.readdir(dirPath)
for(let path in filesAndFolders) { for (let path in filesAndFolders) {
if (filesAndFolders[path].isDirectory) await this.zipDir(path, zip) if (filesAndFolders[path].isDirectory) await this.zipDir(path, zip)
else { else {
path = this.normalize(path) path = this.normalize(path)
@ -367,15 +400,15 @@ class FileManager extends Plugin {
try { try {
const downloadFileName = helper.extractNameFromKey(path) const downloadFileName = helper.extractNameFromKey(path)
if (await this.isDirectory(path)) { if (await this.isDirectory(path)) {
const zip = new JSZip() const zip = new JSZip()
await this.zipDir(path, zip) await this.zipDir(path, zip)
const content = await zip.generateAsync({type: 'blob'}) const content = await zip.generateAsync({ type: 'blob' })
saveAs(content, `${downloadFileName}.zip`) saveAs(content, `${downloadFileName}.zip`)
} else { } else {
path = this.normalize(path) path = this.normalize(path)
const content: any = await this.readFile(path) const content: any = await this.readFile(path)
saveAs(new Blob([content]), downloadFileName) saveAs(new Blob([content]), downloadFileName)
} }
} catch (e) { } catch (e) {
throw new Error(e) throw new Error(e)
} }
@ -589,7 +622,7 @@ class FileManager extends Plugin {
// TODO : Add permission // TODO : Add permission
// TODO : Change Provider to Promise // TODO : Change Provider to Promise
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
provider.set(path, content, (error) => { provider.set(path, content, async (error) => {
if (error) reject(error) if (error) reject(error)
this.syncEditor(path) this.syncEditor(path)
this.emit('fileSaved', path) this.emit('fileSaved', path)
@ -759,14 +792,14 @@ class FileManager extends Plugin {
return collectList(path) return collectList(path)
} }
async fileList (dirPath) { async fileList(dirPath) {
const paths: any = await this.readdir(dirPath) const paths: any = await this.readdir(dirPath)
for( const path in paths) for (const path in paths)
if(paths[path].isDirectory) delete paths[path] if (paths[path].isDirectory) delete paths[path]
return Object.keys(paths) return Object.keys(paths)
} }
isRemixDActive () { isRemixDActive() {
return this.appManager.isActive('remixd') return this.appManager.isActive('remixd')
} }
@ -781,7 +814,7 @@ class FileManager extends Plugin {
provider.get(currentFile, (error, oldContent) => { provider.get(currentFile, (error, oldContent) => {
provider.set(currentFile, input, (error) => { provider.set(currentFile, input, (error) => {
if (error) { if (error) {
if (error.message ) this.call('notification', 'toast', if (error.message) this.call('notification', 'toast',
error.message.indexOf( error.message.indexOf(
'LocalStorage is full') !== -1 ? storageFullMessage() 'LocalStorage is full') !== -1 ? storageFullMessage()
: error.message : error.message
@ -789,7 +822,7 @@ class FileManager extends Plugin {
provider.set(currentFile, oldContent) provider.set(currentFile, oldContent)
return console.error(error) return console.error(error)
} else { } else {
this.emit('fileSaved', currentFile) this.emit('fileSaved', currentFile)
} }
}) })
}) })
@ -805,10 +838,10 @@ class FileManager extends Plugin {
if (path !== currentFile) return if (path !== currentFile) return
const provider = this.fileProviderOf(currentFile) const provider = this.fileProviderOf(currentFile)
if (provider) { if (provider) {
try{ try {
const content = await provider.get(currentFile) const content = await provider.get(currentFile)
if(content) this.editor.setText(currentFile, content) if (content) this.editor.setText(currentFile, content)
}catch(error){ } catch (error) {
console.log(error) console.log(error)
} }
} else { } else {
@ -829,7 +862,7 @@ class FileManager extends Plugin {
} }
await self.syncEditor(fileProvider + file) await self.syncEditor(fileProvider + file)
} else { } else {
try{ try {
const name = await helper.createNonClashingNameAsync(file, self._deps.filesProviders[fileProvider]) const name = await helper.createNonClashingNameAsync(file, self._deps.filesProviders[fileProvider])
if (helper.checkSpecialChars(name)) { if (helper.checkSpecialChars(name)) {
this.call('notification', 'alert', { this.call('notification', 'alert', {
@ -844,7 +877,7 @@ class FileManager extends Plugin {
} }
self.syncEditor(fileProvider + name) self.syncEditor(fileProvider + name)
} }
}catch(error){ } catch (error) {
if (error) { if (error) {
this.call('notification', 'alert', { this.call('notification', 'alert', {
id: 'fileManagerAlert', id: 'fileManagerAlert',
@ -870,13 +903,64 @@ class FileManager extends Plugin {
} }
} }
async isGitRepo (): Promise<boolean> { async isGitRepo(): Promise<boolean> {
const path = '.git' const path = '.git'
const exists = await this.exists(path) const exists = await this.exists(path)
return exists return exists
} }
async moveFileIsAllowed (src: string, dest: string) {
try {
src = this.normalize(src)
dest = this.normalize(dest)
src = this.limitPluginScope(src)
dest = this.limitPluginScope(dest)
await this._handleExists(src, `Cannot move ${src}. Path does not exist.`)
await this._handleExists(dest, `Cannot move content into ${dest}. Path does not exist.`)
await this._handleIsFile(src, `Cannot move ${src}. Path is not a file.`)
await this._handleIsDir(dest, `Cannot move content into ${dest}. Path is not directory.`)
const fileName = helper.extractNameFromKey(src)
if (await this.exists(dest + '/' + fileName)) {
return false
}
return true
} catch (e) {
console.log(e)
return false
}
}
async moveDirIsAllowed (src: string, dest: string) {
try {
src = this.normalize(src)
dest = this.normalize(dest)
src = this.limitPluginScope(src)
dest = this.limitPluginScope(dest)
await this._handleExists(src, `Cannot move ${src}. Path does not exist.`)
await this._handleExists(dest, `Cannot move content into ${dest}. Path does not exist.`)
await this._handleIsDir(src, `Cannot move ${src}. Path is not directory.`)
await this._handleIsDir(dest, `Cannot move content into ${dest}. Path is not directory.`)
const dirName = helper.extractNameFromKey(src)
const provider = this.fileProviderOf(src)
if (await this.exists(dest + '/' + dirName) || src === dest) {
return false
}
if (provider.isSubDirectory(src, dest)) {
this.call('notification', 'toast', recursivePasteToastMsg())
return false
}
return true
} catch (e) {
console.log(e)
return false
}
}
/** /**
* Moves a file to a new folder * Moves a file to a new folder
* @param {string} src path of the source file * @param {string} src path of the source file
@ -884,7 +968,7 @@ class FileManager extends Plugin {
* @returns {void} * @returns {void}
*/ */
async moveFile(src: string, dest: string) { async moveFile(src: string, dest: string) {
try { try {
src = this.normalize(src) src = this.normalize(src)
dest = this.normalize(dest) dest = this.normalize(dest)
@ -897,7 +981,7 @@ class FileManager extends Plugin {
const fileName = helper.extractNameFromKey(src) const fileName = helper.extractNameFromKey(src)
if (await this.exists(dest + '/' + fileName)) { if (await this.exists(dest + '/' + fileName)) {
throw createError({ code: 'EEXIST', message: `Cannot move ${src}. File already exists at destination ${dest}`}) throw createError({ code: 'EEXIST', message: `Cannot move ${src}. File already exists at destination ${dest}` })
} }
await this.copyFile(src, dest, fileName) await this.copyFile(src, dest, fileName)
await this.remove(src) await this.remove(src)
@ -925,9 +1009,15 @@ class FileManager extends Plugin {
await this._handleIsDir(dest, `Cannot move content into ${dest}. Path is not directory.`) await this._handleIsDir(dest, `Cannot move content into ${dest}. Path is not directory.`)
const dirName = helper.extractNameFromKey(src) const dirName = helper.extractNameFromKey(src)
if (await this.exists(dest + '/' + dirName) || src === dest) { if (await this.exists(dest + '/' + dirName) || src === dest) {
throw createError({ code: 'EEXIST', message: `Cannot move ${src}. Folder already exists at destination ${dest}`}) throw createError({ code: 'EEXIST', message: `Cannot move ${src}. Folder already exists at destination ${dest}` })
}
const provider = this.fileProviderOf(src)
if (provider.isSubDirectory(src, dest)) {
this.call('notification', 'toast', recursivePasteToastMsg())
return false
} }
await this.copyDir(src, dest, dirName) await this.inDepthCopy(src, dest, dirName)
await this.remove(src) await this.remove(src)
} catch (e) { } catch (e) {

@ -1,71 +1,71 @@
export class fileSystem { export class fileSystem {
name: string name: string
enabled: boolean enabled: boolean
available: boolean available: boolean
fs: any fs: any
fsCallBack: any; fsCallBack: any;
hasWorkSpaces: boolean hasWorkSpaces: boolean
loaded: boolean loaded: boolean
load: () => Promise<unknown> load: () => Promise<unknown>
test: () => Promise<unknown> test: () => Promise<unknown>
constructor() { constructor() {
this.available = false this.available = false
this.enabled = false this.enabled = false
this.hasWorkSpaces = false this.hasWorkSpaces = false
this.loaded = false this.loaded = false
} }
checkWorkspaces = async () => { checkWorkspaces = async () => {
try { try {
await this.fs.stat('.workspaces') await this.fs.stat('.workspaces')
this.hasWorkSpaces = true this.hasWorkSpaces = true
} catch (e) { } catch (e) {
}
} }
}
set = async () => { set = async () => {
const w = (window as any) const w = (window as any)
if (!this.loaded) return false if (!this.loaded) return false
w.remixFileSystem = this.fs w.remixFileSystem = this.fs
w.remixFileSystem.name = this.name w.remixFileSystem.name = this.name
w.remixFileSystemCallback = this.fsCallBack w.remixFileSystemCallback = this.fsCallBack
return true return true
} }
} }
export class fileSystems { export class fileSystems {
fileSystems: Record<string, fileSystem> fileSystems: Record<string, fileSystem>
constructor() { constructor() {
this.fileSystems = {} this.fileSystems = {}
} }
addFileSystem = async (fs: fileSystem): Promise<boolean> => { addFileSystem = async (fs: fileSystem): Promise<boolean> => {
try { try {
this.fileSystems[fs.name] = fs this.fileSystems[fs.name] = fs
await fs.test() && await fs.load() await fs.test() && await fs.load()
console.log(fs.name + ' is loaded...') console.log(fs.name + ' is loaded...')
return true return true
} catch (e) { } catch (e) {
console.log(fs.name + ' not available...') console.log(fs.name + ' not available...')
return false return false
}
} }
/** }
/**
* sets filesystem using list as fallback * sets filesystem using list as fallback
* @param {string[]} names * @param {string[]} names
* @returns {Promise} * @returns {Promise}
*/ */
setFileSystem = async (filesystems?: fileSystem[]): Promise<fileSystem> => { setFileSystem = async (filesystems?: fileSystem[]): Promise<fileSystem> => {
for (const fs of filesystems) { for (const fs of filesystems) {
if (fs && this.fileSystems[fs.name]) { if (fs && this.fileSystems[fs.name]) {
const result = await this.fileSystems[fs.name].set() const result = await this.fileSystems[fs.name].set()
if (result) return this.fileSystems[fs.name] if (result) return this.fileSystems[fs.name]
} }
}
return null
} }
return null
}
} }

@ -3,189 +3,189 @@ import JSZip from "jszip"
import { fileSystem } from "../fileSystem" import { fileSystem } from "../fileSystem"
const _paq = window._paq = window._paq || [] const _paq = window._paq = window._paq || []
export class fileSystemUtility { export class fileSystemUtility {
migrate = async (fsFrom: fileSystem, fsTo: fileSystem) => { migrate = async (fsFrom: fileSystem, fsTo: fileSystem) => {
try { try {
await fsFrom.checkWorkspaces() await fsFrom.checkWorkspaces()
await fsTo.checkWorkspaces() await fsTo.checkWorkspaces()
if (fsTo.hasWorkSpaces) { if (fsTo.hasWorkSpaces) {
console.log(`${fsTo.name} already has files`) console.log(`${fsTo.name} already has files`)
return true return true
} }
if (!fsFrom.hasWorkSpaces) { if (!fsFrom.hasWorkSpaces) {
console.log('no files to migrate') console.log('no files to migrate')
return true return true
} }
const fromFiles = await this.copyFolderToJson('/', null, null, fsFrom.fs) const fromFiles = await this.copyFolderToJson('/', null, null, fsFrom.fs)
await this.populateWorkspace(fromFiles, fsTo.fs) await this.populateWorkspace(fromFiles, fsTo.fs)
const toFiles = await this.copyFolderToJson('/', null, null, fsTo.fs) const toFiles = await this.copyFolderToJson('/', null, null, fsTo.fs)
if (hashMessage(JSON.stringify(toFiles)) === hashMessage(JSON.stringify(fromFiles))) { if (hashMessage(JSON.stringify(toFiles)) === hashMessage(JSON.stringify(fromFiles))) {
console.log('file migration successful') console.log('file migration successful')
return true return true
} else { } else {
_paq.push(['trackEvent', 'Migrate', 'error', 'hash mismatch']) _paq.push(['trackEvent', 'Migrate', 'error', 'hash mismatch'])
console.log('file migration failed falling back to ' + fsFrom.name) console.log('file migration failed falling back to ' + fsFrom.name)
fsTo.loaded = false fsTo.loaded = false
return false return false
} }
} catch (err) { } catch (err) {
console.log(err) console.log(err)
_paq.push(['trackEvent', 'Migrate', 'error', err && err.message]) _paq.push(['trackEvent', 'Migrate', 'error', err && err.message])
console.log('file migration failed falling back to ' + fsFrom.name) console.log('file migration failed falling back to ' + fsFrom.name)
fsTo.loaded = false fsTo.loaded = false
return false return false
}
} }
}
downloadBackup = async (fs: fileSystem) => {
try { downloadBackup = async (fs: fileSystem) => {
const zip = new JSZip() try {
zip.file("readme.txt", "This is a Remix backup file.\nThis zip should be used by the restore backup tool in Remix.\nThe .workspaces directory contains your workspaces.") const zip = new JSZip()
await fs.checkWorkspaces() zip.file("readme.txt", "This is a Remix backup file.\nThis zip should be used by the restore backup tool in Remix.\nThe .workspaces directory contains your workspaces.")
await this.copyFolderToJson('/', null, null, fs.fs, ({ path, content }) => { await fs.checkWorkspaces()
zip.file(path, content) await this.copyFolderToJson('/', null, null, fs.fs, ({ path, content }) => {
}) zip.file(path, content)
const blob = await zip.generateAsync({ type: 'blob' }) })
const today = new Date() const blob = await zip.generateAsync({ type: 'blob' })
const date = today.getFullYear() + '-' + (today.getMonth() + 1) + '-' + today.getDate() const today = new Date()
const time = today.getHours() + 'h' + today.getMinutes() + 'min' const date = today.getFullYear() + '-' + (today.getMonth() + 1) + '-' + today.getDate()
this.saveAs(blob, `remix-backup-at-${time}-${date}.zip`) const time = today.getHours() + 'h' + today.getMinutes() + 'min'
_paq.push(['trackEvent','Backup','download','preload']) this.saveAs(blob, `remix-backup-at-${time}-${date}.zip`)
} catch (err) { _paq.push(['trackEvent','Backup','download','preload'])
_paq.push(['trackEvent','Backup','error',err && err.message]) } catch (err) {
console.log(err) _paq.push(['trackEvent','Backup','error',err && err.message])
} console.log(err)
} }
}
populateWorkspace = async (json, fs) => {
for (const item in json) { populateWorkspace = async (json, fs) => {
const isFolder = json[item].content === undefined for (const item in json) {
if (isFolder) { const isFolder = json[item].content === undefined
await this.createDir(item, fs) if (isFolder) {
await this.populateWorkspace(json[item].children, fs) await this.createDir(item, fs)
} else { await this.populateWorkspace(json[item].children, fs)
await fs.writeFile(item, json[item].content, 'utf8') } else {
} await fs.writeFile(item, json[item].content, 'utf8')
} }
} }
}
/** /**
* copy the folder recursively * copy the folder recursively
* @param {string} path is the folder to be copied over * @param {string} path is the folder to be copied over
* @param {Function} visitFile is a function called for each visited files * @param {Function} visitFile is a function called for each visited files
* @param {Function} visitFolder is a function called for each visited folders * @param {Function} visitFolder is a function called for each visited folders
*/ */
copyFolderToJson = async (path, visitFile, visitFolder, fs, cb = null) => { copyFolderToJson = async (path, visitFile, visitFolder, fs, cb = null) => {
visitFile = visitFile || (() => { }) visitFile = visitFile || (() => { })
visitFolder = visitFolder || (() => { }) visitFolder = visitFolder || (() => { })
return await this._copyFolderToJsonInternal(path, visitFile, visitFolder, fs, cb) return await this._copyFolderToJsonInternal(path, visitFile, visitFolder, fs, cb)
} }
/** /**
* copy the folder recursively (internal use) * copy the folder recursively (internal use)
* @param {string} path is the folder to be copied over * @param {string} path is the folder to be copied over
* @param {Function} visitFile is a function called for each visited files * @param {Function} visitFile is a function called for each visited files
* @param {Function} visitFolder is a function called for each visited folders * @param {Function} visitFolder is a function called for each visited folders
*/ */
async _copyFolderToJsonInternal(path, visitFile, visitFolder, fs, cb) { async _copyFolderToJsonInternal(path, visitFile, visitFolder, fs, cb) {
visitFile = visitFile || function () { /* do nothing. */ } visitFile = visitFile || function () { /* do nothing. */ }
visitFolder = visitFolder || function () { /* do nothing. */ } visitFolder = visitFolder || function () { /* do nothing. */ }
const json = {} const json = {}
// path = this.removePrefix(path) // path = this.removePrefix(path)
if (await fs.exists(path)) { if (await fs.exists(path)) {
const items = await fs.readdir(path) const items = await fs.readdir(path)
visitFolder({ path }) visitFolder({ path })
if (items.length !== 0) { if (items.length !== 0) {
for (const item of items) { for (const item of items) {
const file: any = {} const file: any = {}
const curPath = `${path}${path.endsWith('/') ? '' : '/'}${item}` const curPath = `${path}${path.endsWith('/') ? '' : '/'}${item}`
if ((await fs.stat(curPath)).isDirectory()) { if ((await fs.stat(curPath)).isDirectory()) {
file.children = await this._copyFolderToJsonInternal(curPath, visitFile, visitFolder, fs, cb) file.children = await this._copyFolderToJsonInternal(curPath, visitFile, visitFolder, fs, cb)
} else { } else {
file.content = await fs.readFile(curPath, 'utf8') file.content = await fs.readFile(curPath, 'utf8')
if (cb) cb({ path: curPath, content: file.content }) if (cb) cb({ path: curPath, content: file.content })
visitFile({ path: curPath, content: file.content }) visitFile({ path: curPath, content: file.content })
} }
json[curPath] = file json[curPath] = file
}
}
}
return json
}
createDir = async (path, fs) => {
const paths = path.split('/')
if (paths.length && paths[0] === '') paths.shift()
let currentCheck = ''
for (const value of paths) {
currentCheck = currentCheck + (currentCheck ? '/' : '') + value
if (!await fs.exists(currentCheck)) {
await fs.mkdir(currentCheck)
}
} }
}
} }
return json
saveAs = (blob, name) => { }
const node = document.createElement('a')
node.download = name createDir = async (path, fs) => {
node.rel = 'noopener' const paths = path.split('/')
node.href = URL.createObjectURL(blob) if (paths.length && paths[0] === '') paths.shift()
setTimeout(function () { URL.revokeObjectURL(node.href) }, 4E4) // 40s let currentCheck = ''
setTimeout(function () { for (const value of paths) {
try { currentCheck = currentCheck + (currentCheck ? '/' : '') + value
node.dispatchEvent(new MouseEvent('click')) if (!await fs.exists(currentCheck)) {
} catch (e) { await fs.mkdir(currentCheck)
const evt = document.createEvent('MouseEvents') }
evt.initMouseEvent('click', true, true, window, 0, 0, 0, 80,
20, false, false, false, false, 0, null)
node.dispatchEvent(evt)
}
}, 0) // 40s
} }
}
saveAs = (blob, name) => {
const node = document.createElement('a')
node.download = name
node.rel = 'noopener'
node.href = URL.createObjectURL(blob)
setTimeout(function () { URL.revokeObjectURL(node.href) }, 4E4) // 40s
setTimeout(function () {
try {
node.dispatchEvent(new MouseEvent('click'))
} catch (e) {
const evt = document.createEvent('MouseEvents')
evt.initMouseEvent('click', true, true, window, 0, 0, 0, 80,
20, false, false, false, false, 0, null)
node.dispatchEvent(evt)
}
}, 0) // 40s
}
} }
/* eslint-disable no-template-curly-in-string */ /* eslint-disable no-template-curly-in-string */
export const migrationTestData = { export const migrationTestData = {
'.workspaces': { '.workspaces': {
children: {
'.workspaces/default_workspace': {
children: { children: {
'.workspaces/default_workspace': { '.workspaces/default_workspace/README.txt': {
children: { content: 'TEST README'
'.workspaces/default_workspace/README.txt': { }
content: 'TEST README' }
} },
} '.workspaces/emptyspace': {
},
'.workspaces/emptyspace': {
}, },
'.workspaces/workspace_test': { '.workspaces/workspace_test': {
children: {
'.workspaces/workspace_test/TEST_README.txt': {
content: 'TEST README'
},
'.workspaces/workspace_test/test_contracts': {
children: {
'.workspaces/workspace_test/test_contracts/1_Storage.sol': {
content: 'testing'
},
'.workspaces/workspace_test/test_contracts/artifacts': {
children: { children: {
'.workspaces/workspace_test/TEST_README.txt': { '.workspaces/workspace_test/test_contracts/artifacts/Storage_metadata.json': {
content: 'TEST README' content: '{ "test": "data" }'
}, }
'.workspaces/workspace_test/test_contracts': {
children: {
'.workspaces/workspace_test/test_contracts/1_Storage.sol': {
content: 'testing'
},
'.workspaces/workspace_test/test_contracts/artifacts': {
children: {
'.workspaces/workspace_test/test_contracts/artifacts/Storage_metadata.json': {
content: '{ "test": "data" }'
}
}
}
}
}
} }
}
} }
}
} }
}
} }
}
} }

@ -2,90 +2,90 @@ import LightningFS from "@isomorphic-git/lightning-fs"
import { fileSystem } from "../fileSystem" import { fileSystem } from "../fileSystem"
export class IndexedDBStorage extends LightningFS { export class IndexedDBStorage extends LightningFS {
base: LightningFS.PromisifedFS base: LightningFS.PromisifedFS
addSlash: (file: string) => string addSlash: (file: string) => string
extended: { exists: (path: string) => Promise<unknown>; rmdir: (path: any) => Promise<void>; readdir: (path: any) => Promise<string[]>; unlink: (path: any) => Promise<void>; mkdir: (path: any) => Promise<void>; readFile: (path: any, options: any) => Promise<Uint8Array>; rename: (from: any, to: any) => Promise<void>; writeFile: (path: any, content: any, options: any) => Promise<void>; stat: (path: any) => Promise<import("fs").Stats>; init(name: string, opt?: LightningFS.FSConstructorOptions): void; activate(): Promise<void>; deactivate(): Promise<void>; lstat(filePath: string): Promise<import("fs").Stats>; readlink(filePath: string): Promise<string>; symlink(target: string, filePath: string): Promise<void> } extended: { exists: (path: string) => Promise<unknown>; rmdir: (path: any) => Promise<void>; readdir: (path: any) => Promise<string[]>; unlink: (path: any) => Promise<void>; mkdir: (path: any) => Promise<void>; readFile: (path: any, options: any) => Promise<Uint8Array>; rename: (from: any, to: any) => Promise<void>; writeFile: (path: any, content: any, options: any) => Promise<void>; stat: (path: any) => Promise<import("fs").Stats>; init(name: string, opt?: LightningFS.FSConstructorOptions): void; activate(): Promise<void>; deactivate(): Promise<void>; lstat(filePath: string): Promise<import("fs").Stats>; readlink(filePath: string): Promise<string>; symlink(target: string, filePath: string): Promise<void> }
constructor(name: string) { constructor(name: string) {
super(name) super(name)
this.addSlash = (file) => { this.addSlash = (file) => {
if (!file.startsWith('/')) file = '/' + file if (!file.startsWith('/')) file = '/' + file
return file return file
}
this.base = this.promises
this.extended = {
...this.promises,
exists: async (path: string) => {
return new Promise((resolve) => {
this.base.stat(this.addSlash(path)).then(() => resolve(true)).catch(() => resolve(false))
})
},
rmdir: async (path) => {
return this.base.rmdir(this.addSlash(path))
},
readdir: async (path) => {
return this.base.readdir(this.addSlash(path))
},
unlink: async (path) => {
return this.base.unlink(this.addSlash(path))
},
mkdir: async (path) => {
return this.base.mkdir(this.addSlash(path))
},
readFile: async (path, options) => {
return this.base.readFile(this.addSlash(path), options)
},
rename: async (from, to) => {
return this.base.rename(this.addSlash(from), this.addSlash(to))
},
writeFile: async (path, content, options) => {
return this.base.writeFile(this.addSlash(path), content, options)
},
stat: async (path) => {
return this.base.stat(this.addSlash(path))
}
}
} }
this.base = this.promises
this.extended = {
...this.promises,
exists: async (path: string) => {
return new Promise((resolve) => {
this.base.stat(this.addSlash(path)).then(() => resolve(true)).catch(() => resolve(false))
})
},
rmdir: async (path) => {
return this.base.rmdir(this.addSlash(path))
},
readdir: async (path) => {
return this.base.readdir(this.addSlash(path))
},
unlink: async (path) => {
return this.base.unlink(this.addSlash(path))
},
mkdir: async (path) => {
return this.base.mkdir(this.addSlash(path))
},
readFile: async (path, options) => {
return this.base.readFile(this.addSlash(path), options)
},
rename: async (from, to) => {
return this.base.rename(this.addSlash(from), this.addSlash(to))
},
writeFile: async (path, content, options) => {
return this.base.writeFile(this.addSlash(path), content, options)
},
stat: async (path) => {
return this.base.stat(this.addSlash(path))
}
}
}
} }
export class indexedDBFileSystem extends fileSystem { export class indexedDBFileSystem extends fileSystem {
constructor() { constructor() {
super() super()
this.name = 'indexedDB' this.name = 'indexedDB'
} }
load = async () => { load = async () => {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
try { try {
const fs = new IndexedDBStorage('RemixFileSystem') const fs = new IndexedDBStorage('RemixFileSystem')
fs.init('RemixFileSystem') fs.init('RemixFileSystem')
this.fs = fs.extended this.fs = fs.extended
this.fsCallBack = fs this.fsCallBack = fs
this.loaded = true this.loaded = true
resolve(true) resolve(true)
} catch (e) { } catch (e) {
reject(e) reject(e)
} }
}) })
} }
test = async () => { test = async () => {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
if (!window.indexedDB) { if (!window.indexedDB) {
this.available = false this.available = false
reject('No indexedDB on window') reject('No indexedDB on window')
} }
const request = window.indexedDB.open("RemixTestDataBase"); const request = window.indexedDB.open("RemixTestDataBase");
request.onerror = () => { request.onerror = () => {
this.available = false this.available = false
reject('Error creating test database') reject('Error creating test database')
}; };
request.onsuccess = () => { request.onsuccess = () => {
window.indexedDB.deleteDatabase("RemixTestDataBase"); window.indexedDB.deleteDatabase("RemixTestDataBase");
this.available = true this.available = true
resolve(true) resolve(true)
}; };
}) })
} }
} }

@ -2,56 +2,56 @@ import { fileSystem } from "../fileSystem";
export class localStorageFS extends fileSystem { export class localStorageFS extends fileSystem {
constructor() { constructor() {
super() super()
this.name = 'localstorage' this.name = 'localstorage'
} }
load = async () => { load = async () => {
const me = this const me = this
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
try { try {
const w = window as any const w = window as any
w.BrowserFS.install(window) w.BrowserFS.install(window)
w.BrowserFS.configure({ w.BrowserFS.configure({
fs: 'LocalStorage' fs: 'LocalStorage'
}, async function (e) { }, async function (e) {
if (e) { if (e) {
console.log('BrowserFS Error: ' + e) console.log('BrowserFS Error: ' + e)
reject(e) reject(e)
} else { } else {
me.fs = { ...window.require('fs') } me.fs = { ...window.require('fs') }
me.fsCallBack = window.require('fs') me.fsCallBack = window.require('fs')
me.fs.readdir = me.fs.readdirSync me.fs.readdir = me.fs.readdirSync
me.fs.readFile = me.fs.readFileSync me.fs.readFile = me.fs.readFileSync
me.fs.writeFile = me.fs.writeFileSync me.fs.writeFile = me.fs.writeFileSync
me.fs.stat = me.fs.statSync me.fs.stat = me.fs.statSync
me.fs.unlink = me.fs.unlinkSync me.fs.unlink = me.fs.unlinkSync
me.fs.rmdir = me.fs.rmdirSync me.fs.rmdir = me.fs.rmdirSync
me.fs.mkdir = me.fs.mkdirSync me.fs.mkdir = me.fs.mkdirSync
me.fs.rename = me.fs.renameSync me.fs.rename = me.fs.renameSync
me.fs.exists = me.fs.existsSync me.fs.exists = me.fs.existsSync
me.loaded = true me.loaded = true
resolve(true) resolve(true)
} }
})
} catch (e) {
console.log('BrowserFS is not ready!')
reject(e)
}
}) })
} } catch (e) {
console.log('BrowserFS is not ready!')
reject(e)
}
})
}
test = async () => { test = async () => {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
const test = 'test'; const test = 'test';
try { try {
localStorage.setItem(test, test); localStorage.setItem(test, test);
localStorage.removeItem(test); localStorage.removeItem(test);
resolve(true) resolve(true)
} catch(e) { } catch(e) {
reject(e) reject(e)
} }
}) })
} }
} }

@ -6,7 +6,7 @@ import { QueryParams } from '@remix-project/remix-lib'
const profile: Profile = { const profile: Profile = {
name: 'layout', name: 'layout',
description: 'layout', description: 'layout',
methods: ['minimize', 'maximiseSidePanel', 'resetSidePanel'] methods: ['minimize', 'maximiseSidePanel', 'resetSidePanel', 'maximizeTerminal']
} }
interface panelState { interface panelState {
@ -61,9 +61,9 @@ export class Layout extends Plugin {
}) })
this.on('manager', 'activate', (profile: Profile) => { this.on('manager', 'activate', (profile: Profile) => {
switch (profile.name) { switch (profile.name) {
case 'filePanel': case 'filePanel':
this.call('menuicons', 'select', 'filePanel') this.call('menuicons', 'select', 'filePanel')
break break
} }
}) })
this.on('sidePanel', 'focusChanged', async (name) => { this.on('sidePanel', 'focusChanged', async (name) => {
@ -109,6 +109,12 @@ export class Layout extends Plugin {
this.maximised[current] = true this.maximised[current] = true
} }
async maximizeTerminal() {
this.panels.terminal.minimized = false
this.event.emit('change', this.panels)
this.emit('change', this.panels)
}
async resetSidePanel () { async resetSidePanel () {
this.event.emit('resetsidepanel') this.event.emit('resetsidepanel')
const current = await this.call('sidePanel', 'currentFocus') const current = await this.call('sidePanel', 'currentFocus')

@ -8,279 +8,279 @@ import toml from 'toml'
import { filePathFilter, AnyFilter } from '@jsdevtools/file-path-filter' import { filePathFilter, AnyFilter } from '@jsdevtools/file-path-filter'
const profile = { const profile = {
name: 'codeFormatter', name: 'codeFormatter',
desciption: 'prettier plugin for Remix', desciption: 'prettier plugin for Remix',
methods: ['format'], methods: ['format'],
events: [''], events: [''],
version: '0.0.1' version: '0.0.1'
} }
const defaultOptions = { const defaultOptions = {
"overrides": [ "overrides": [
{ {
"files": "*.sol", "files": "*.sol",
"options": { "options": {
"printWidth": 80, "printWidth": 80,
"tabWidth": 4, "tabWidth": 4,
"useTabs": false, "useTabs": false,
"singleQuote": false, "singleQuote": false,
"bracketSpacing": false, "bracketSpacing": false,
} }
}, },
{ {
"files": "*.yml", "files": "*.yml",
"options": { "options": {
} }
}, },
{ {
"files": "*.yaml", "files": "*.yaml",
"options": { "options": {
} }
}, },
{ {
"files": "*.toml", "files": "*.toml",
"options": { "options": {
} }
}, },
{ {
"files": "*.json", "files": "*.json",
"options": { "options": {
} }
}, },
{ {
"files": "*.js", "files": "*.js",
"options": { "options": {
} }
}, },
{ {
"files": "*.ts", "files": "*.ts",
"options": { "options": {
} }
} }
] ]
} }
export class CodeFormat extends Plugin { export class CodeFormat extends Plugin {
prettier: any prettier: any
ts: any ts: any
babel: any babel: any
espree: any espree: any
yml: any yml: any
sol: any sol: any
constructor() { constructor() {
super(profile) super(profile)
} }
async format(file: string) { async format(file: string) {
// lazy load // lazy load
if (!this.prettier) { if (!this.prettier) {
this.prettier = await import('prettier/standalone') this.prettier = await import('prettier/standalone')
this.ts = await import('prettier/parser-typescript') this.ts = await import('prettier/parser-typescript')
this.babel = await import('prettier/parser-babel') this.babel = await import('prettier/parser-babel')
this.espree = await import('prettier/parser-espree') this.espree = await import('prettier/parser-espree')
this.yml = await import('prettier/parser-yaml') this.yml = await import('prettier/parser-yaml')
} }
try { try {
const content = await this.call('fileManager', 'readFile', file) const content = await this.call('fileManager', 'readFile', file)
if (!content) return if (!content) return
let parserName = '' let parserName = ''
let options: Options = { let options: Options = {
} }
switch (path.extname(file)) { switch (path.extname(file)) {
case '.sol': case '.sol':
parserName = 'solidity-parse' parserName = 'solidity-parse'
break break
case '.ts': case '.ts':
parserName = 'typescript' parserName = 'typescript'
options = { options = {
...options, ...options,
trailingComma: 'all', trailingComma: 'all',
semi: false, semi: false,
singleQuote: true, singleQuote: true,
quoteProps: 'as-needed', quoteProps: 'as-needed',
bracketSpacing: true, bracketSpacing: true,
arrowParens: 'always', arrowParens: 'always',
} }
break break
case '.js': case '.js':
parserName = "espree" parserName = "espree"
options = { options = {
...options, ...options,
semi: false, semi: false,
singleQuote: true, singleQuote: true,
} }
break break
case '.json': case '.json':
parserName = 'json' parserName = 'json'
break break
case '.yml': case '.yml':
parserName = 'yaml' parserName = 'yaml'
break break
case '.yaml': case '.yaml':
parserName = 'yaml' parserName = 'yaml'
break break
} }
if (file === '.prettierrc') { if (file === '.prettierrc') {
parserName = 'json' parserName = 'json'
} }
const possibleFileNames = [ const possibleFileNames = [
'.prettierrc', '.prettierrc',
'.prettierrc.json', '.prettierrc.json',
'.prettierrc.yaml', '.prettierrc.yaml',
'.prettierrc.yml', '.prettierrc.yml',
'.prettierrc.toml', '.prettierrc.toml',
'.prettierrc.js', '.prettierrc.js',
'.prettierrc.cjs', '.prettierrc.cjs',
'prettier.config.js', 'prettier.config.js',
'prettier.config.cjs', 'prettier.config.cjs',
'.prettierrc.json5', '.prettierrc.json5',
] ]
const prettierConfigFile = await findAsync(possibleFileNames, async (fileName) => { const prettierConfigFile = await findAsync(possibleFileNames, async (fileName) => {
const exists = await this.call('fileManager', 'exists', fileName) const exists = await this.call('fileManager', 'exists', fileName)
return exists return exists
}) })
let parsed = null let parsed = null
if (prettierConfigFile) { if (prettierConfigFile) {
let prettierConfig = await this.call('fileManager', 'readFile', prettierConfigFile) let prettierConfig = await this.call('fileManager', 'readFile', prettierConfigFile)
if (prettierConfig) { if (prettierConfig) {
if (prettierConfigFile.endsWith('.yaml') || prettierConfigFile.endsWith('.yml')) { if (prettierConfigFile.endsWith('.yaml') || prettierConfigFile.endsWith('.yml')) {
try { try {
parsed = yaml.load(prettierConfig) parsed = yaml.load(prettierConfig)
} catch (e) { } catch (e) {
// do nothing // do nothing
}
} else if (prettierConfigFile.endsWith('.toml')) {
try {
parsed = toml.parse(prettierConfig)
} catch (e) {
// do nothing
}
} else if (prettierConfigFile.endsWith('.json') || prettierConfigFile.endsWith('.json5')) {
try {
parsed = JSON.parse(prettierConfig)
} catch (e) {
// do nothing
}
} else if (prettierConfigFile === '.prettierrc') {
try {
parsed = JSON.parse(prettierConfig)
} catch (e) {
// do nothing
}
if (!parsed) {
try {
parsed = yaml.load(prettierConfig)
} catch (e) {
// do nothing
}
}
} else if (prettierConfigFile.endsWith('.js') || prettierConfigFile.endsWith('.cjs')) {
// remove any comments
prettierConfig = prettierConfig.replace(/\/\*[\s\S]*?\*\/|([^\\:]|^)\/\/.*$/gm, '')
// add quotes to keys
prettierConfig = prettierConfig.replace(/([a-zA-Z0-9_]+)(\s*):/g, '"$1"$2:')
// remove comma from last key
prettierConfig = prettierConfig.replace(/,(\s*})/g, '$1')
// remove any semi-colons
prettierConfig = prettierConfig.replace(/;/g, '')
// convert single quotes to double quotes
prettierConfig = prettierConfig.replace(/'/g, '"')
try {
parsed = JSON.parse(prettierConfig.replace('module.exports = ', '').replace('module.exports=', ''))
} catch (e) {
// do nothing
}
}
}
} else {
parsed = defaultOptions
await this.call('fileManager', 'writeFile', '.prettierrc.json', JSON.stringify(parsed, null, 2))
await this.call('notification', 'toast', 'A prettier config file has been created in the workspace.')
} }
} else if (prettierConfigFile.endsWith('.toml')) {
if (!parsed && prettierConfigFile) { try {
this.call('notification', 'toast', `Error parsing prettier config file: ${prettierConfigFile}`) parsed = toml.parse(prettierConfig)
} catch (e) {
// do nothing
}
} else if (prettierConfigFile.endsWith('.json') || prettierConfigFile.endsWith('.json5')) {
try {
parsed = JSON.parse(prettierConfig)
} catch (e) {
// do nothing
}
} else if (prettierConfigFile === '.prettierrc') {
try {
parsed = JSON.parse(prettierConfig)
} catch (e) {
// do nothing
}
if (!parsed) {
try {
parsed = yaml.load(prettierConfig)
} catch (e) {
// do nothing
}
}
} else if (prettierConfigFile.endsWith('.js') || prettierConfigFile.endsWith('.cjs')) {
// remove any comments
prettierConfig = prettierConfig.replace(/\/\*[\s\S]*?\*\/|([^\\:]|^)\/\/.*$/gm, '')
// add quotes to keys
prettierConfig = prettierConfig.replace(/([a-zA-Z0-9_]+)(\s*):/g, '"$1"$2:')
// remove comma from last key
prettierConfig = prettierConfig.replace(/,(\s*})/g, '$1')
// remove any semi-colons
prettierConfig = prettierConfig.replace(/;/g, '')
// convert single quotes to double quotes
prettierConfig = prettierConfig.replace(/'/g, '"')
try {
parsed = JSON.parse(prettierConfig.replace('module.exports = ', '').replace('module.exports=', ''))
} catch (e) {
// do nothing
} }
}
}
} else {
parsed = defaultOptions
await this.call('fileManager', 'writeFile', '.prettierrc.json', JSON.stringify(parsed, null, 2))
await this.call('notification', 'toast', 'A prettier config file has been created in the workspace.')
}
if (!parsed && prettierConfigFile) {
this.call('notification', 'toast', `Error parsing prettier config file: ${prettierConfigFile}`)
}
// merge options
if (parsed) {
options = {
...options,
...parsed,
}
}
// search for overrides // merge options
if (parsed && parsed.overrides) { if (parsed) {
const override = parsed.overrides.find((override) => { options = {
if (override.files) { ...options,
const pathFilter: AnyFilter = {} ...parsed,
pathFilter.include = setGlobalExpression(override.files) }
const filteredFiles = [file] }
.filter(filePathFilter(pathFilter))
if (filteredFiles.length) {
return true
}
}
})
const validParsers = ['typescript', 'babel', 'espree', 'solidity-parse', 'json', 'yaml', 'solidity-parse']
if (override && override.options && override.options.parser) {
if (validParsers.includes(override.options.parser)) {
parserName = override.options.parser
} else {
this.call('notification', 'toast', `Invalid parser: ${override.options.parser}! Valid options are ${validParsers.join(', ')}`)
}
delete override.options.parser
}
if (override) { // search for overrides
options = { if (parsed && parsed.overrides) {
...options, const override = parsed.overrides.find((override) => {
...override.options, if (override.files) {
} const pathFilter: AnyFilter = {}
} pathFilter.include = setGlobalExpression(override.files)
const filteredFiles = [file]
.filter(filePathFilter(pathFilter))
if (filteredFiles.length) {
return true
} }
}
})
const validParsers = ['typescript', 'babel', 'espree', 'solidity-parse', 'json', 'yaml', 'solidity-parse']
if (override && override.options && override.options.parser) {
if (validParsers.includes(override.options.parser)) {
parserName = override.options.parser
} else {
this.call('notification', 'toast', `Invalid parser: ${override.options.parser}! Valid options are ${validParsers.join(', ')}`)
}
delete override.options.parser
}
if (override) {
const result = this.prettier.format(content, { options = {
plugins: [sol as any, this.ts, this.babel, this.espree, this.yml], ...options,
parser: parserName, ...override.options,
...options }
})
await this.call('fileManager', 'writeFile', file, result)
} catch (e) {
// do nothing
} }
}
const result = this.prettier.format(content, {
plugins: [sol as any, this.ts, this.babel, this.espree, this.yml],
parser: parserName,
...options
})
await this.call('fileManager', 'writeFile', file, result)
} catch (e) {
// do nothing
} }
}
} }
//*.sol, **/*.txt, contracts/* //*.sol, **/*.txt, contracts/*
const setGlobalExpression = (paths: string) => { const setGlobalExpression = (paths: string) => {
const results = [] const results = []
paths.split(',').forEach(path => { paths.split(',').forEach(path => {
path = path.trim() path = path.trim()
if (path.startsWith('*.')) path = path.replace(/(\*\.)/g, '**/*.') if (path.startsWith('*.')) path = path.replace(/(\*\.)/g, '**/*.')
if (path.endsWith('/*') && !path.endsWith('/**/*')) if (path.endsWith('/*') && !path.endsWith('/**/*'))
path = path.replace(/(\*)/g, '**/*.*') path = path.replace(/(\*)/g, '**/*.*')
results.push(path) results.push(path)
}) })
return results return results
} }
async function findAsync(arr, asyncCallback) { async function findAsync(arr, asyncCallback) {
const promises = arr.map(asyncCallback); const promises = arr.map(asyncCallback);
const results = await Promise.all(promises); const results = await Promise.all(promises);
const index = results.findIndex(result => result); const index = results.findIndex(result => result);
return arr[index]; return arr[index];
} }

@ -8,54 +8,54 @@ import { parse } from './parser'
// https://prettier.io/docs/en/plugins.html#languages // https://prettier.io/docs/en/plugins.html#languages
// https://github.com/ikatyang/linguist-languages/blob/master/data/Solidity.json // https://github.com/ikatyang/linguist-languages/blob/master/data/Solidity.json
const languages = [ const languages = [
{ {
linguistLanguageId: 237469032, linguistLanguageId: 237469032,
name: 'Solidity', name: 'Solidity',
type: 'programming', type: 'programming',
color: '#AA6746', color: '#AA6746',
aceMode: 'text', aceMode: 'text',
tmScope: 'source.solidity', tmScope: 'source.solidity',
extensions: ['.sol'], extensions: ['.sol'],
parsers: ['solidity-parse'], parsers: ['solidity-parse'],
vscodeLanguageIds: ['solidity'] vscodeLanguageIds: ['solidity']
} }
]; ];
// https://prettier.io/docs/en/plugins.html#parsers // https://prettier.io/docs/en/plugins.html#parsers
const parser = { astFormat: 'solidity-ast', parse, ...loc }; const parser = { astFormat: 'solidity-ast', parse, ...loc };
const parsers = { const parsers = {
'solidity-parse': parser 'solidity-parse': parser
}; };
const canAttachComment = (node) => const canAttachComment = (node) =>
node.type && node.type !== 'BlockComment' && node.type !== 'LineComment'; node.type && node.type !== 'BlockComment' && node.type !== 'LineComment';
// https://prettier.io/docs/en/plugins.html#printers // https://prettier.io/docs/en/plugins.html#printers
const printers = { const printers = {
'solidity-ast': { 'solidity-ast': {
canAttachComment, canAttachComment,
handleComments: { handleComments: {
ownLine: handleComments.handleOwnLineComment, ownLine: handleComments.handleOwnLineComment,
endOfLine: handleComments.handleEndOfLineComment, endOfLine: handleComments.handleEndOfLineComment,
remaining: handleComments.handleRemainingComment remaining: handleComments.handleRemainingComment
}, },
isBlockComment: handleComments.isBlockComment, isBlockComment: handleComments.isBlockComment,
massageAstNode, massageAstNode,
print, print,
printComment printComment
} }
}; };
// https://prettier.io/docs/en/plugins.html#defaultoptions // https://prettier.io/docs/en/plugins.html#defaultoptions
const defaultOptions = { const defaultOptions = {
bracketSpacing: false, bracketSpacing: false,
tabWidth: 4 tabWidth: 4
}; };
export default { export default {
languages, languages,
parsers, parsers,
printers, printers,
options, options,
defaultOptions defaultOptions
}; };

@ -80,113 +80,113 @@ export function parse(text, _parsers, options) {
}, },
BinaryOperation(ctx) { BinaryOperation(ctx) {
switch (ctx.operator) { switch (ctx.operator) {
case '+': case '+':
case '-': case '-':
ctx.left = tryHug(ctx.left, ['%']); ctx.left = tryHug(ctx.left, ['%']);
ctx.right = tryHug(ctx.right, ['%']); ctx.right = tryHug(ctx.right, ['%']);
break; break;
case '*': case '*':
ctx.left = tryHug(ctx.left, ['/', '%']); ctx.left = tryHug(ctx.left, ['/', '%']);
break; break;
case '/': case '/':
ctx.left = tryHug(ctx.left, ['*', '%']); ctx.left = tryHug(ctx.left, ['*', '%']);
break; break;
case '%': case '%':
ctx.left = tryHug(ctx.left, ['*', '/', '%']); ctx.left = tryHug(ctx.left, ['*', '/', '%']);
break; break;
case '**': case '**':
// If the compiler has not been given as an option using we leave a**b**c. // If the compiler has not been given as an option using we leave a**b**c.
if (!compiler) break; if (!compiler) break;
if (semver.satisfies(compiler, '<0.8.0')) { if (semver.satisfies(compiler, '<0.8.0')) {
// If the compiler is less than 0.8.0 then a**b**c is formatted as // If the compiler is less than 0.8.0 then a**b**c is formatted as
// (a**b)**c. // (a**b)**c.
ctx.left = tryHug(ctx.left, ['**']); ctx.left = tryHug(ctx.left, ['**']);
break;
}
if (
ctx.left.type === 'BinaryOperation' &&
ctx.left.operator === '**'
) {
// the parser still organizes the a**b**c as (a**b)**c so we need
// to restructure it.
ctx.right = {
type: 'TupleExpression',
components: [
{
type: 'BinaryOperation',
operator: '**',
left: ctx.left.right,
right: ctx.right
}
],
isArray: false
};
ctx.left = ctx.left.left;
}
break;
case '<<':
case '>>':
ctx.left = tryHug(ctx.left, ['+', '-', '*', '/', '**', '<<', '>>']);
ctx.right = tryHug(ctx.right, ['+', '-', '*', '/', '**']);
break;
case '&':
ctx.left = tryHug(ctx.left, ['+', '-', '*', '/', '**', '<<', '>>']);
ctx.right = tryHug(ctx.right, ['+', '-', '*', '/', '**', '<<', '>>']);
break;
case '|':
ctx.left = tryHug(ctx.left, [
'+',
'-',
'*',
'/',
'**',
'<<',
'>>',
'&',
'^'
]);
ctx.right = tryHug(ctx.right, [
'+',
'-',
'*',
'/',
'**',
'<<',
'>>',
'&',
'^'
]);
break;
case '^':
ctx.left = tryHug(ctx.left, [
'+',
'-',
'*',
'/',
'**',
'<<',
'>>',
'&'
]);
ctx.right = tryHug(ctx.right, [
'+',
'-',
'*',
'/',
'**',
'<<',
'>>',
'&'
]);
break;
case '||':
ctx.left = tryHug(ctx.left, ['&&']);
ctx.right = tryHug(ctx.right, ['&&']);
break;
case '&&':
default:
break; break;
}
if (
ctx.left.type === 'BinaryOperation' &&
ctx.left.operator === '**'
) {
// the parser still organizes the a**b**c as (a**b)**c so we need
// to restructure it.
ctx.right = {
type: 'TupleExpression',
components: [
{
type: 'BinaryOperation',
operator: '**',
left: ctx.left.right,
right: ctx.right
}
],
isArray: false
};
ctx.left = ctx.left.left;
}
break;
case '<<':
case '>>':
ctx.left = tryHug(ctx.left, ['+', '-', '*', '/', '**', '<<', '>>']);
ctx.right = tryHug(ctx.right, ['+', '-', '*', '/', '**']);
break;
case '&':
ctx.left = tryHug(ctx.left, ['+', '-', '*', '/', '**', '<<', '>>']);
ctx.right = tryHug(ctx.right, ['+', '-', '*', '/', '**', '<<', '>>']);
break;
case '|':
ctx.left = tryHug(ctx.left, [
'+',
'-',
'*',
'/',
'**',
'<<',
'>>',
'&',
'^'
]);
ctx.right = tryHug(ctx.right, [
'+',
'-',
'*',
'/',
'**',
'<<',
'>>',
'&',
'^'
]);
break;
case '^':
ctx.left = tryHug(ctx.left, [
'+',
'-',
'*',
'/',
'**',
'<<',
'>>',
'&'
]);
ctx.right = tryHug(ctx.right, [
'+',
'-',
'*',
'/',
'**',
'<<',
'>>',
'&'
]);
break;
case '||':
ctx.left = tryHug(ctx.left, ['&&']);
ctx.right = tryHug(ctx.right, ['&&']);
break;
case '&&':
default:
break;
} }
} }
}); });

@ -1,9 +1,9 @@
import React from 'react' import React from 'react'
import { Plugin } from '@remixproject/engine' import {Plugin} from '@remixproject/engine'
import { customAction } from '@remixproject/plugin-api' import {customAction} from '@remixproject/plugin-api'
import { concatSourceFiles, getDependencyGraph, normalizeContractPath } from '@remix-ui/solidity-compiler' import {concatSourceFiles, getDependencyGraph, normalizeContractPath} from '@remix-ui/solidity-compiler'
const _paq = window._paq = window._paq || [] const _paq = (window._paq = window._paq || [])
const profile = { const profile = {
name: 'contractflattener', name: 'contractflattener',
@ -11,11 +11,10 @@ const profile = {
description: 'Flatten solidity contracts', description: 'Flatten solidity contracts',
methods: ['flattenAContract', 'flattenContract'], methods: ['flattenAContract', 'flattenContract'],
events: [], events: [],
maintainedBy: 'Remix', maintainedBy: 'Remix'
} }
export class ContractFlattener extends Plugin { export class ContractFlattener extends Plugin {
triggerFlattenContract: boolean = false triggerFlattenContract: boolean = false
constructor() { constructor() {
super(profile) super(profile)
@ -23,8 +22,8 @@ export class ContractFlattener extends Plugin {
onActivation(): void { onActivation(): void {
this.on('solidity', 'compilationFinished', async (file, source, languageVersion, data, input, version) => { this.on('solidity', 'compilationFinished', async (file, source, languageVersion, data, input, version) => {
if(data.sources && Object.keys(data.sources).length > 1) { if (data.sources && Object.keys(data.sources).length > 1) {
if(this.triggerFlattenContract) { if (this.triggerFlattenContract) {
this.triggerFlattenContract = false this.triggerFlattenContract = false
await this.flattenContract(source, file, data) await this.flattenContract(source, file, data)
} }
@ -48,8 +47,7 @@ export class ContractFlattener extends Plugin {
* Takes the flattened result, writes it to a file and returns the result. * Takes the flattened result, writes it to a file and returns the result.
* @returns {Promise<string>} * @returns {Promise<string>}
*/ */
async flattenContract (source: { sources: any, target: string }, async flattenContract(source: {sources: any; target: string}, filePath: string, data: {contracts: any; sources: any}): Promise<string> {
filePath: string, data: { contracts: any, sources: any }): Promise<string> {
const appendage = '_flattened.sol' const appendage = '_flattened.sol'
const normalized = normalizeContractPath(filePath) const normalized = normalizeContractPath(filePath)
const path = `${normalized[normalized.length - 2]}${appendage}` const path = `${normalized[normalized.length - 2]}${appendage}`
@ -58,17 +56,15 @@ export class ContractFlattener extends Plugin {
let sorted let sorted
let result let result
let sources let sources
try{ try {
dependencyGraph = getDependencyGraph(ast, filePath) dependencyGraph = getDependencyGraph(ast, filePath)
sorted = dependencyGraph.isEmpty() sorted = dependencyGraph.isEmpty() ? [filePath] : dependencyGraph.sort().reverse()
? [filePath]
: dependencyGraph.sort().reverse()
sources = source.sources sources = source.sources
result = concatSourceFiles(sorted, sources) result = concatSourceFiles(sorted, sources)
}catch(err){ } catch (err) {
console.warn(err) console.warn(err)
} }
await this.call('fileManager', 'writeFile', path , result) await this.call('fileManager', 'writeFile', path, result)
_paq.push(['trackEvent', 'plugin', 'contractFlattener', 'flattenAContract']) _paq.push(['trackEvent', 'plugin', 'contractFlattener', 'flattenAContract'])
// clean up memory references & return result // clean up memory references & return result
sorted = null sorted = null

@ -6,79 +6,79 @@ import { Plugin } from '@remixproject/engine'
import { fileDecoration } from '@remix-ui/file-decorators' import { fileDecoration } from '@remix-ui/file-decorators'
const profile = { const profile = {
name: 'fileDecorator', name: 'fileDecorator',
desciption: 'Keeps decorators of the files', desciption: 'Keeps decorators of the files',
methods: ['setFileDecorators', 'clearFileDecorators', 'clearAllFileDecorators'], methods: ['setFileDecorators', 'clearFileDecorators', 'clearAllFileDecorators'],
events: ['fileDecoratorsChanged'], events: ['fileDecoratorsChanged'],
version: '0.0.1' version: '0.0.1'
} }
export class FileDecorator extends Plugin { export class FileDecorator extends Plugin {
private _fileStates: fileDecoration[] = [] private _fileStates: fileDecoration[] = []
constructor() { constructor() {
super(profile) super(profile)
} }
onActivation(): void { onActivation(): void {
this.on('filePanel', 'setWorkspace', async () => { this.on('filePanel', 'setWorkspace', async () => {
await this.clearAllFileDecorators() await this.clearAllFileDecorators()
}) })
} }
/** /**
* @param fileStates Array of file states * @param fileStates Array of file states
*/ */
async setFileDecorators(fileStates: fileDecoration[] | fileDecoration) { async setFileDecorators(fileStates: fileDecoration[] | fileDecoration) {
const { from } = this.currentRequest const { from } = this.currentRequest
const workspace = await this.call('filePanel', 'getCurrentWorkspace') const workspace = await this.call('filePanel', 'getCurrentWorkspace')
const fileStatesPayload = Array.isArray(fileStates) ? fileStates : [fileStates] const fileStatesPayload = Array.isArray(fileStates) ? fileStates : [fileStates]
// clear all file states in the previous state of this owner on the files called // clear all file states in the previous state of this owner on the files called
fileStatesPayload.forEach((state) => { fileStatesPayload.forEach((state) => {
state.workspace = workspace state.workspace = workspace
state.owner = from state.owner = from
}) })
const filteredState = this._fileStates.filter((state) => { const filteredState = this._fileStates.filter((state) => {
const index = fileStatesPayload.findIndex((payloadFileState: fileDecoration) => { const index = fileStatesPayload.findIndex((payloadFileState: fileDecoration) => {
return from == state.owner && payloadFileState.path == state.path return from == state.owner && payloadFileState.path == state.path
}) })
return index == -1 return index == -1
}) })
const newState = [...filteredState, ...fileStatesPayload].sort(sortByPath) const newState = [...filteredState, ...fileStatesPayload].sort(sortByPath)
if (!deepequal(newState, this._fileStates)) { if (!deepequal(newState, this._fileStates)) {
this._fileStates = newState this._fileStates = newState
this.emit('fileDecoratorsChanged', this._fileStates) this.emit('fileDecoratorsChanged', this._fileStates)
}
} }
}
async clearFileDecorators(path?: string) { async clearFileDecorators(path?: string) {
const { from } = this.currentRequest const { from } = this.currentRequest
if (!from) return if (!from) return
const filteredState = this._fileStates.filter((state) => { const filteredState = this._fileStates.filter((state) => {
if(state.owner != from) return true if(state.owner != from) return true
if(path && state.path != path) return true if(path && state.path != path) return true
}) })
const newState = [...filteredState].sort(sortByPath) const newState = [...filteredState].sort(sortByPath)
if (!deepequal(newState, this._fileStates)) {
this._fileStates = newState
this.emit('fileDecoratorsChanged', this._fileStates)
}
if (!deepequal(newState, this._fileStates)) {
this._fileStates = newState
this.emit('fileDecoratorsChanged', this._fileStates)
} }
async clearAllFileDecorators() { }
this._fileStates = []
this.emit('fileDecoratorsChanged', []) async clearAllFileDecorators() {
} this._fileStates = []
this.emit('fileDecoratorsChanged', [])
}
} }
const sortByPath = (a: fileDecoration, b: fileDecoration) => { const sortByPath = (a: fileDecoration, b: fileDecoration) => {
if (a.path < b.path) { if (a.path < b.path) {
return -1; return -1;
} }
if (a.path > b.path) { if (a.path > b.path) {
return 1; return 1;
} }
return 0; return 0;
} }

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

Loading…
Cancel
Save