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. 8
      apps/debugger/src/app/app.tsx
  21. 1
      apps/debugger/src/app/debugger.ts
  22. 11
      apps/debugger/src/main.tsx
  23. 65
      apps/debugger/webpack.config.js
  24. 26
      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. 5
      apps/doc-viewer/src/app/App.tsx
  30. 4
      apps/doc-viewer/src/main.tsx
  31. 28
      apps/doc-viewer/webpack.config.js
  32. 20
      apps/etherscan/src/app/AppContext.tsx
  33. 37
      apps/etherscan/src/app/RemixPlugin.tsx
  34. 76
      apps/etherscan/src/app/app.tsx
  35. 46
      apps/etherscan/src/app/components/HeaderWithSettings.tsx
  36. 26
      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. 64
      apps/etherscan/src/app/utils/networks.ts
  41. 2
      apps/etherscan/src/app/utils/verify.ts
  42. 48
      apps/etherscan/src/app/views/CaptureKeyView.tsx
  43. 16
      apps/etherscan/src/app/views/ErrorView.tsx
  44. 20
      apps/etherscan/src/app/views/HomeView.tsx
  45. 120
      apps/etherscan/src/app/views/ReceiptsView.tsx
  46. 177
      apps/etherscan/src/app/views/VerifyView.tsx
  47. 14
      apps/etherscan/src/main.tsx
  48. 62
      apps/etherscan/webpack.config.js
  49. 56
      apps/remix-ide-e2e/nightwatch.ts
  50. 10
      apps/remix-ide-e2e/package.json
  51. 4
      apps/remix-ide-e2e/src/commands/checkAnnotations.ts
  52. 2
      apps/remix-ide-e2e/src/commands/checkAnnotationsNotPresent.ts
  53. 35
      apps/remix-ide-e2e/src/commands/clickFunction.ts
  54. 4
      apps/remix-ide-e2e/src/commands/createContract.ts
  55. 76
      apps/remix-ide-e2e/src/commands/testConstantFunction.ts
  56. 68
      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. 50
      apps/remix-ide-e2e/src/tests/contract_flattener.test.ts
  60. 70
      apps/remix-ide-e2e/src/tests/editor.test.ts
  61. 87
      apps/remix-ide-e2e/src/tests/editorHoverContext.test.ts
  62. 30
      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. 45
      apps/remix-ide-e2e/src/tests/proxy_oz_v5.test.ts
  66. 2
      apps/remix-ide-e2e/src/tests/remixd.test.ts
  67. 60
      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. 9
      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. 120
      apps/remix-ide-e2e/src/types/index.d.ts
  76. 760
      apps/remix-ide-e2e/yarn.lock
  77. 2
      apps/remix-ide/background.js
  78. 2
      apps/remix-ide/project.json
  79. 4
      apps/remix-ide/release-process.md
  80. 144
      apps/remix-ide/src/app.js
  81. 8
      apps/remix-ide/src/app/components/hidden-panel.tsx
  82. 6
      apps/remix-ide/src/app/components/main-panel.tsx
  83. 122
      apps/remix-ide/src/app/components/preload.tsx
  84. 7
      apps/remix-ide/src/app/components/side-panel.tsx
  85. 28
      apps/remix-ide/src/app/components/vertical-icons.tsx
  86. 13
      apps/remix-ide/src/app/editor/editor.js
  87. 98
      apps/remix-ide/src/app/files/fileManager.ts
  88. 8
      apps/remix-ide/src/app/panels/layout.ts
  89. 12
      apps/remix-ide/src/app/plugins/contractFlattener.tsx
  90. 2
      apps/remix-ide/src/app/plugins/notification.tsx
  91. 47
      apps/remix-ide/src/app/plugins/openaigpt.tsx
  92. 202
      apps/remix-ide/src/app/plugins/parser/code-parser.tsx
  93. 53
      apps/remix-ide/src/app/plugins/parser/services/code-parser-antlr-service.ts
  94. 7
      apps/remix-ide/src/app/plugins/parser/services/code-parser-compiler.ts
  95. 5
      apps/remix-ide/src/app/plugins/parser/services/code-parser-gas-service.ts
  96. 15
      apps/remix-ide/src/app/plugins/parser/services/code-parser-imports.ts
  97. 14
      apps/remix-ide/src/app/plugins/permission-handler-plugin.tsx
  98. 80
      apps/remix-ide/src/app/plugins/remixd-handle.tsx
  99. 17
      apps/remix-ide/src/app/plugins/solidity-script.tsx
  100. 158
      apps/remix-ide/src/app/plugins/solidity-umlgen.tsx
  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,4 +1,4 @@
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
@ -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
}); })

@ -8,9 +8,9 @@ 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
Explorer and select "Generate Docs" from the context menu.).
</h5>
{fileName && (
<div className="border-bottom border-top px-2 py-3 justify-center align-items-center d-flex">
<h6>File: {fileName}</h6> <h6>File: {fileName}</h6>
</div>} </div>
{hasBuild && <button className="btn btn-primary btn-block mt-4" onClick={() => client.generateDocs()}>Generate Docs</button>} )}
{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,7 +11,6 @@ export default function App() {
client.eventEmitter.on('contentsReady', (fileContents: string) => { client.eventEmitter.on('contentsReady', (fileContents: string) => {
setContents(fileContents) setContents(fileContents)
}) })
}, []) }, [])
return ( return (
<> <>

@ -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,24 +1,43 @@
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() { loaded() {
return this.onload() return this.onload()
} }
async verify (apiKey: string, contractAddress: string, contractArguments: string, contractName: string, compilationResultParam: any, chainRef?: number | string, isProxyContract?: boolean, expectedImplAddress?: string) { async verify(
const result = await verify(apiKey, contractAddress, contractArguments, contractName, compilationResultParam, chainRef, isProxyContract, expectedImplAddress, this, apiKey: string,
(value: EtherScanReturn) => {}, (value: string) => {}) contractAddress: string,
contractArguments: string,
contractName: string,
compilationResultParam: any,
chainRef?: number | string,
isProxyContract?: boolean,
expectedImplAddress?: string
) {
const result = await verify(
apiKey,
contractAddress,
contractArguments,
contractName,
compilationResultParam,
chainRef,
isProxyContract,
expectedImplAddress,
this,
(value: EtherScanReturn) => {},
(value: string) => {}
)
return result 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

@ -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",
(
fileName: string,
source: CompilationFileSources,
languageVersion: string,
data: CompilationResult
) => {
const newContractsNames = getNewContractNames(data) const newContractsNames = getNewContractNames(data)
const newContractsToSave: string[] = [ const newContractsToSave: string[] = [...contractsRef.current, ...newContractsNames]
...contractsRef.current,
...newContractsNames,
]
const uniqueContracts: string[] = [...new Set(newContractsToSave)] const uniqueContracts: string[] = [...new Set(newContractsToSave)]
setContracts(uniqueContracts) 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) {
@ -96,36 +82,26 @@ const App = () => {
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
@ -18,16 +18,13 @@ const HomeIcon: React.FC<IconProps> = ({ from }: IconProps) => {
<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')}
style={({isActive}) => (!isActive ? {width: '1.8rem', filter: 'contrast(0.5)'} : {width: '1.8rem'})}
state={from} 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>
@ -39,16 +36,13 @@ const ReceiptsIcon: React.FC<IconProps> = ({ from }: IconProps) => {
<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')}
style={({isActive}) => (!isActive ? {width: '1.8rem', filter: 'contrast(0.5)'} : {width: '1.8rem'})}
state={from} 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>
@ -60,32 +54,26 @@ const SettingsIcon: React.FC<IconProps> = ({ from }: IconProps) => {
<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')}
style={({isActive}) => (!isActive ? {width: '1.8rem', filter: 'contrast(0.5)'} : {width: '1.8rem'})}
state={from} 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,4 +1,4 @@
import React from "react" import React from 'react'
import {CustomTooltip} from '@remix-ui/helper' import {CustomTooltip} from '@remix-ui/helper'
interface Props { interface Props {
@ -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,28 +1,29 @@
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
}
return <Formik
initialValues={{apiKey}} initialValues={{apiKey}}
validate={(values) => { validate={(values) => {
const errors = {} as any const errors = {} as any
if (!values.apiKey) { if (!values.apiKey) {
errors.apiKey = "Required" errors.apiKey = 'Required'
} else if (values.apiKey.length !== 34) {
errors.apiKey = 'API key should be 34 characters long'
} }
return errors return errors
}} }}
@ -30,9 +31,8 @@ export const CaptureKeyView: React.FC = () => {
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}) => (
@ -40,28 +40,24 @@ export const CaptureKeyView: React.FC = () => {
<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={ className={errors.apiKey && touched.apiKey ? 'form-control form-control-sm is-invalid' : 'form-control form-control-sm'}
errors.apiKey && touched.apiKey
? "form-control form-control-sm is-invalid"
: "form-control form-control-sm"
}
type="password" type="password"
name="apiKey" name="apiKey"
placeholder="e.g. GM1T20XY6JGSAPWKDCYZ7B2FJXKTJRFVGZ" placeholder="e.g. GM1T20XY6JGSAPWKDCYZ7B2FJXKTJRFVGZ"
/> />
<ErrorMessage <ErrorMessage className="invalid-feedback" name="apiKey" component="div" />
className="invalid-feedback"
name="apiKey"
component="div"
/>
</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,12 +1,12 @@
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 {
@ -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)
@ -64,50 +51,35 @@ 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">
@ -122,7 +94,9 @@ export const ReceiptsView: React.FC = () => {
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>
@ -130,22 +104,27 @@ export const ReceiptsView: React.FC = () => {
</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={() => {
setReceipts([])
}}
> >
<Button className="btn-sm" onClick={() => { setReceipts([]) }} >Clear</Button> Clear
</Button>
</CustomTooltip> </CustomTooltip>
</div> </div>
) )
} }}
}
</AppContext.Consumer> </AppContext.Consumer>
) )
} }
@ -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} }
> >
{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> <i style={{fontSize: 'small'}} className={'ml-1 fal fa-info-circle align-self-center'} aria-hidden="true"></i>
</CustomTooltip> </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,
} from "@remixproject/plugin"
import {CustomTooltip} from '@remix-ui/helper' import {CustomTooltip} from '@remix-ui/helper'
import { Formik, ErrorMessage, Field } from "formik" 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,7 +49,7 @@ 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)
@ -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,53 +97,42 @@ 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 (
<form onSubmit={handleSubmit}>
<div className="form-group"> <div className="form-group">
<label htmlFor="network">Selected Network</label> <label htmlFor="network">Selected Network</label>
<CustomTooltip <CustomTooltip
tooltipText="Network is fetched from 'Deploy and Run Transactions' plugin's ENVIRONMENT field" tooltipText="Network is fetched from 'Deploy and Run Transactions' plugin's ENVIRONMENT field"
tooltipId='etherscan-impl-address2' tooltipId="etherscan-impl-address2"
placement='bottom' placement="bottom"
> >
<Field <Field className="form-control" type="text" name="network" value={networkName} disabled={true} />
className="form-control"
type="text"
name="network"
value={networkName}
disabled={true}
/>
</CustomTooltip> </CustomTooltip>
</div> </div>
<div className="form-group"> <div className="form-group">
<label htmlFor="contractName">Contract Name</label> <label htmlFor="contractName">Contract Name</label>
<Field <Field
as="select" as="select"
className={ className={errors.contractName && touched.contractName && contracts.length ? 'form-control is-invalid' : 'form-control'}
errors.contractName && touched.contractName && contracts.length
? "form-control is-invalid"
: "form-control"
}
name="contractName" name="contractName"
onChange={async (e) => { onChange={async (e) => {
handleChange(e) handleChange(e)
@ -170,62 +149,30 @@ export const VerifyView: React.FC<Props> = ({
</option> </option>
))} ))}
</Field> </Field>
<ErrorMessage <ErrorMessage className="invalid-feedback" name="contractName" component="div" />
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"> <div className="form-group">
<label htmlFor="contractAddress">Contract Address</label> <label htmlFor="contractAddress">Contract Address</label>
<Field <Field
className={ className={errors.contractAddress && touched.contractAddress ? 'form-control is-invalid' : 'form-control'}
errors.contractAddress && touched.contractAddress
? "form-control is-invalid"
: "form-control"
}
type="text" type="text"
name="contractAddress" name="contractAddress"
placeholder="e.g. 0x11b79afc03baf25c631dd70169bb6a3160b2706e" placeholder="e.g. 0x11b79afc03baf25c631dd70169bb6a3160b2706e"
/> />
<ErrorMessage <ErrorMessage className="invalid-feedback" name="contractAddress" component="div" />
className="invalid-feedback"
name="contractAddress"
component="div"
/>
<div className="d-flex mb-2 custom-control custom-checkbox"> <div className="d-flex mb-2 custom-control custom-checkbox">
<Field <Field
className="custom-control-input" className="custom-control-input"
@ -238,45 +185,40 @@ export const VerifyView: React.FC<Props> = ({
else setIsProxyContract(false) else setIsProxyContract(false)
}} }}
/> />
<label className="form-check-label custom-control-label" htmlFor="isProxy">It's a proxy contract address</label> <label className="form-check-label custom-control-label" htmlFor="isProxy">
It's a proxy contract address
</label>
</div> </div>
</div> </div>
<div className={isProxyContract ? 'form-group d-block' : 'form-group d-none'}> <div className={isProxyContract ? 'form-group d-block' : 'form-group d-none'}>
<label htmlFor="expectedImplAddress">Expected Implementation Address</label> <label htmlFor="expectedImplAddress">Expected Implementation Address</label>
<CustomTooltip <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' 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' tooltipId="etherscan-impl-address"
placement='bottom' placement="bottom"
> >
<Field <Field className="form-control" type="text" name="expectedImplAddress" placeholder="verified implementation contract address" />
className="form-control"
type="text"
name="expectedImplAddress"
placeholder="verified implementation contract address"
/>
</CustomTooltip> </CustomTooltip>
<i style={{fontSize: 'x-small'}} className={'ml-1 fal fa-info-circle align-self-center'} aria-hidden="true"></i> <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> <label> &nbsp;Make sure contract is already verified on Etherscan</label>
</div> </div>
<SubmitButton
<SubmitButton dataId="verify-contract" text="Verify" dataId="verify-contract"
text="Verify"
isSubmitting={isSubmitting} isSubmitting={isSubmitting}
disable={ !contracts.length || disable={
!contracts.length ||
!touched.contractName || !touched.contractName ||
!touched.contractAddress || !touched.contractAddress ||
(touched.contractName && errors.contractName) || (touched.contractName && errors.contractName) ||
(touched.contractAddress && errors.contractAddress) || (touched.contractAddress && errors.contractAddress) ||
(networkName === 'VM (Not supported)') networkName === 'VM (Not supported)'
? true ? true
: false} : false
}
/> />
<br /> <br />
<CustomTooltip <CustomTooltip tooltipText="Generate the required TS scripts to verify a contract on Etherscan" tooltipId="etherscan-generate-scripts" placement="bottom">
tooltipText='Generate the required TS scripts to verify a contract on Etherscan'
tooltipId='etherscan-generate-scripts'
placement='bottom'
>
<button <button
type="button" type="button"
className="mr-2 mb-2 py-1 px-2 btn btn-secondary btn-block" className="mr-2 mb-2 py-1 px-2 btn btn-secondary btn-block"
@ -289,17 +231,14 @@ export const VerifyView: React.FC<Props> = ({
</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: [
'window-size=2560,1440',
'start-fullscreen', 'start-fullscreen',
'--no-sandbox', '--no-sandbox',
'--headless', '--headless',
'--verbose', '--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", '--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": {

@ -2,8 +2,8 @@ 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
} }
} }

@ -3,7 +3,7 @@ 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 =
document.querySelector('#runTabView').scrollHeight
},
[],
function () {
if (expectedInput) { if (expectedInput) {
client.setValue('#runTabView input[data-title="' + expectedInput.types + '"]', expectedInput.values, _ => _) client.setValue(
'#runTabView input[data-title="' + expectedInput.types + '"]',
expectedInput.values,
(_) => _
)
} }
done() done()
}
)
}) })
}) .scrollAndClick('.instance *[data-title="' + fnFullName + '"]')
.scrollAndClick('.instance button[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(
this.api,
address,
fnFullName,
expectedInput,
expectedOutput,
() => {
done() done()
this.emit('complete') 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,
expectedOutput: string,
cb: VoidFunction
) {
browser
.waitForElementPresent('.instance *[data-title="' + fnFullName + '"]')
.perform(function (client, done) {
client.execute(
function () {
document.querySelector('#runTabView').scrollTop =
document.querySelector('#runTabView').scrollHeight
},
[],
function () {
if (expectedInput) { if (expectedInput) {
client.waitForElementPresent('#runTabView input[data-title="' + expectedInput.types + '"]') client
.setValue('#runTabView input[data-title="' + expectedInput.types + '"]', expectedInput.values) .waitForElementPresent(
'#runTabView input[data-title="' + expectedInput.types + '"]'
)
.setValue(
'#runTabView input[data-title="' + expectedInput.types + '"]',
expectedInput.values
)
} }
done() 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,4 +1,3 @@
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'
@ -24,7 +23,19 @@ function App () {
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,7 +88,9 @@ 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)) {
ob = [ob]
}
} catch (e) {} } catch (e) {}
const args = ob || [payload] const args = ob || [payload]
setStarted(true) setStarted(true)
@ -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 ERC20, ERC20Burnable, ERC20Pausable, Ownable, ERC20Permit {
constructor(address initialOwner)
ERC20("MyToken", "MTK")
Ownable(initialOwner)
ERC20Permit("MyToken")
{}
contract MyToken is Initializable, ERC721Upgradeable, ERC721BurnableUpgradeable, OwnableUpgradeable, UUPSUpgradeable { function pause() public onlyOwner {
/// @custom:oz-upgrades-unsafe-allow constructor _pause();
constructor() {
_disableInitializers();
} }
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);
}
} }
` `
}, },
} }

@ -5,12 +5,13 @@ 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,19 +33,23 @@ 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') '' +
function (browser: NightwatchBrowser) {
browser
.waitForElementVisible('#editorView')
.waitForElementVisible('.ace_open') .waitForElementVisible('.ace_open')
.click('.ace_start:nth-of-type(1)') .click('.ace_start:nth-of-type(1)')
.waitForElementVisible('.ace_closed') .waitForElementVisible('.ace_closed')
@ -52,24 +58,35 @@ module.exports = {
}, },
'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)
},
[],
() => {}
)
.waitForElementVisible('.glyph-margin-widgets .fa-circle')
}, },
'Should load syntax highlighter for ace light theme #group1': '' + function (browser: NightwatchBrowser) { 'Should load syntax highlighter for ace light theme #group1':
browser.waitForElementVisible('#editorView') '' +
function (browser: NightwatchBrowser) {
browser
.waitForElementVisible('#editorView')
.checkElementStyle('.ace_keyword', 'color', aceThemes.light.keyword) .checkElementStyle('.ace_keyword', 'color', aceThemes.light.keyword)
.checkElementStyle('.ace_comment.ace_doc', 'color', aceThemes.light.comment) .checkElementStyle('.ace_comment.ace_doc', 'color', aceThemes.light.comment)
.checkElementStyle('.ace_function', 'color', aceThemes.light.function) .checkElementStyle('.ace_function', 'color', aceThemes.light.function)
.checkElementStyle('.ace_variable', 'color', aceThemes.light.variable) .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 dark theme #group1':
browser.waitForElementVisible('*[data-id="verticalIconsKindsettings"]') '' +
function (browser: NightwatchBrowser) {
browser
.waitForElementVisible('*[data-id="verticalIconsKindsettings"]')
.click('*[data-id="verticalIconsKindsettings"]') .click('*[data-id="verticalIconsKindsettings"]')
.waitForElementVisible('*[data-id="settingsTabThemeLabelDark"]') .waitForElementVisible('*[data-id="settingsTabThemeLabelDark"]')
.click('*[data-id="settingsTabThemeLabelDark"]') .click('*[data-id="settingsTabThemeLabelDark"]')
@ -101,8 +118,11 @@ 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"]') '' +
function (browser: NightwatchBrowser) {
browser
.waitForElementVisible('li[data-id="treeViewLitreeViewItemremoveSourcehighlightScript.js"]')
.click('li[data-id="treeViewLitreeViewItemremoveSourcehighlightScript.js"]') .click('li[data-id="treeViewLitreeViewItemremoveSourcehighlightScript.js"]')
.pause(2000) .pause(2000)
.executeScriptInTerminal('remix.exeCurrent()') .executeScriptInTerminal('remix.exeCurrent()')
@ -116,7 +136,8 @@ module.exports = {
}, },
'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);
}
}
`

@ -3,25 +3,25 @@ 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() .useXpath()
.waitForElementVisible(path) .waitForElementVisible(path)
.click(path) .click(path)
.perform(function () { .perform(function () {
const actions = this.actions({ async: true }); const actions = this.actions({async: true})
return actions. return actions.keyDown(this.Keys.SHIFT).sendKeys(this.Keys.F12)
keyDown(this.Keys.SHIFT).
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')
.openFile('contracts/3_Ballot.sol') .openFile('contracts/3_Ballot.sol')
.waitForElementVisible('#editorView') .waitForElementVisible('#editorView')
.setEditorValue(BallotWithARefToOwner) .setEditorValue(BallotWithARefToOwner)
@ -32,19 +32,21 @@ module.exports = {
browser.scrollToLine(48) browser.scrollToLine(48)
const path = "//*[@class='view-line' and contains(.,'length') and contains(.,'proposalNames')]//span//span[contains(.,'proposalNames')]" const path = "//*[@class='view-line' and contains(.,'length') and contains(.,'proposalNames')]//span//span[contains(.,'proposalNames')]"
openReferences(browser, path) openReferences(browser, path)
browser.waitForElementVisible("//*[@class='monaco-highlighted-label referenceMatch']//span[contains(.,'length; i++')]") browser
.waitForElementVisible("//*[@class='monaco-highlighted-label referenceMatch']//span[contains(.,'name:')]") .waitForElementVisible("//*[@class='monaco-highlighted-label referenceMatch'][contains(.,'length; i++')]")
.waitForElementVisible("//*[@class='monaco-highlighted-label referenceMatch']//span[contains(.,'constructor')]") .waitForElementVisible("//*[@class='monaco-highlighted-label referenceMatch'][contains(.,'name:')]")
.waitForElementVisible("//*[@class='monaco-highlighted-label referenceMatch'][contains(.,'constructor')]")
.keys(browser.Keys.ESCAPE) .keys(browser.Keys.ESCAPE)
}, },
'Should show references of getOwner': function (browser: NightwatchBrowser) { 'Should show references of getOwner': function (browser: NightwatchBrowser) {
browser.scrollToLine(39) browser.scrollToLine(39)
const path = "//*[@class='view-line' and contains(.,'getOwner') and contains(.,'cowner')]//span//span[contains(.,'getOwner')]" const path = "//*[@class='view-line' and contains(.,'getOwner') and contains(.,'cowner')]//span//span[contains(.,'getOwner')]"
openReferences(browser, path) openReferences(browser, path)
browser.useXpath() browser
.waitForElementVisible("//*[@class='monaco-highlighted-label']//span[contains(.,'2_Owner.sol')]") .useXpath()
.waitForElementVisible("//*[@class='monaco-highlighted-label']//span[contains(.,'3_Ballot.sol')]") .waitForElementVisible("//*[@class='monaco-highlighted-label'][contains(.,'2_Owner.sol')]")
.waitForElementVisible("//*[@class='monaco-highlighted-label referenceMatch']//span[contains(.,'cowner.getOwner')]") .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)')]") .waitForElementVisible("//*[contains(@class, 'results-loaded') and contains(., 'References (2)')]")
.keys(browser.Keys.ESCAPE) .keys(browser.Keys.ESCAPE)
} }

@ -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,16 +238,29 @@ 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()
}) })
}) })
},
'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() .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"; pragma solidity ^0.5.9;
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 { import "@0x/contracts-erc20/contracts/src/ERC20Token.sol";
/// @custom:oz-upgrades-unsafe-allow constructor
constructor() {
_disableInitializers();
}
function initialize() initializer public { /**
__ERC721_init("MyToken", "MTK"); * @title SampleERC20
__Ownable_init(); * @dev Create a sample ERC20 standard token
__UUPSUpgradeable_init(); */
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;
} }
function safeMint(address to, uint256 tokenId) public onlyOwner {
_safeMint(to, tokenId);
1 + 1;
} }
function _authorizeUpgrade(address newImplementation)
internal
onlyOwner
override
{}
}
`} `}
} }
] ]

@ -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)

@ -40,8 +40,11 @@ module.exports = {
.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"]')
// .waitForElementPresent('div#staticanalysisresult .warning', 5000)
.waitForElementPresent('//*[@id="staticanalysisresult"]', 5000)
.useCss()
// Check warning count // Check warning count
.click('*[data-rb-event-key="remix"]') .click('*[data-rb-event-key="remix"]')
.assert.containsText('span#ssaRemixtab > *[data-id="RemixStaticAnalysisErrorCount"]', '1') .assert.containsText('span#ssaRemixtab > *[data-id="RemixStaticAnalysisErrorCount"]', '1')
@ -51,7 +54,7 @@ module.exports = {
.click('label[id="headingshowLibWarnings"]') .click('label[id="headingshowLibWarnings"]')
.pause(1000) .pause(1000)
.click('*[data-rb-event-key="remix"]') .click('*[data-rb-event-key="remix"]')
.assert.containsText('span#ssaRemixtab > *[data-id="RemixStaticAnalysisErrorCount', '382') .assert.containsText('span#ssaRemixtab > *[data-id="RemixStaticAnalysisErrorCount', '386')
.click('label[id="headingshowLibWarnings"]') .click('label[id="headingshowLibWarnings"]')
.pause(1000) .pause(1000)
.assert.containsText('span#ssaRemixtab > *[data-id="RemixStaticAnalysisErrorCount', '1') .assert.containsText('span#ssaRemixtab > *[data-id="RemixStaticAnalysisErrorCount', '1')

@ -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,62 +1,62 @@
// 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
@ -65,17 +65,17 @@ declare module 'nightwatch' {
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
} }
@ -84,11 +84,11 @@ declare module 'nightwatch' {
} }
export interface NightwatchContractContent { export interface NightwatchContractContent {
content: string; content: string
} }
export interface NightwatchClickFunctionExpectedInput { export interface NightwatchClickFunctionExpectedInput {
types: string, types: string
values: string values: string
} }
@ -97,7 +97,7 @@ declare module 'nightwatch' {
} }
export interface NightwatchTestConstantFunctionExpectedInput { export interface NightwatchTestConstantFunctionExpectedInput {
types: string, types: string
values: string values: string
} }

File diff suppressed because it is too large Load Diff

@ -4,7 +4,7 @@
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
}) })
}) })

@ -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

@ -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')
@ -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
@ -177,9 +195,10 @@ class AppComponent {
// ----------------- 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)
@ -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
@ -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,
@ -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 {

@ -38,13 +38,15 @@ export class HiddenPanel extends AbstractPanel {
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
}) })
} }
} }

@ -59,7 +59,11 @@ export class MainPanel extends AbstractPanel {
} }
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) {

@ -7,10 +7,9 @@ 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 [supported, setSupported] = useState<boolean>(true)
const [error, setError] = useState<boolean>(false) const [error, setError] = useState<boolean>(false)
const [showDownloader, setShowDownloader] = useState<boolean>(false) const [showDownloader, setShowDownloader] = useState<boolean>(false)
@ -18,12 +17,19 @@ export const Preload = () => {
const remixIndexedDB = useRef<fileSystem>(new indexedDBFileSystem()) const remixIndexedDB = useRef<fileSystem>(new indexedDBFileSystem())
const localStorageFileSystem = useRef<fileSystem>(new localStorageFS()) const localStorageFileSystem = useRef<fileSystem>(new localStorageFS())
// url parameters to e2e test the fallbacks and error warnings // 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 testmigrationFallback = useRef<boolean>(
const testmigrationResult = useRef<boolean>(window.location.hash.includes('e2e_testmigration=true') && window.location.host === '127.0.0.1:8080' && window.location.protocol === 'http:') window.location.hash.includes('e2e_testmigration_fallback=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 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:'
)
function loadAppComponent() { function loadAppComponent() {
import('../../app').then((AppComponent) => { import('../../app')
.then((AppComponent) => {
const appComponent = new AppComponent.default() const appComponent = new AppComponent.default()
appComponent.run().then(() => { appComponent.run().then(() => {
render( render(
@ -33,7 +39,8 @@ export const Preload = () => {
document.getElementById('root') document.getElementById('root')
) )
}) })
}).catch(err => { })
.catch((err) => {
_paq.push(['trackEvent', 'Preload', 'error', err && err.message]) _paq.push(['trackEvent', 'Preload', 'error', err && err.message])
console.error('Error loading Remix:', err) console.error('Error loading Remix:', err)
setError(true) setError(true)
@ -56,7 +63,10 @@ export const Preload = () => {
} }
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([
testmigrationFallback.current || testBlockStorage.current ? null : remixIndexedDB.current,
testBlockStorage.current ? null : localStorageFileSystem.current
])
if (fsLoaded) { if (fsLoaded) {
console.log(fsLoaded.name + ' activated') console.log(fsLoaded.name + ' activated')
_paq.push(['trackEvent', 'Storage', 'activate', fsLoaded.name]) _paq.push(['trackEvent', 'Storage', 'activate', fsLoaded.name])
@ -76,69 +86,89 @@ export const Preload = () => {
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">
<div className="preload-logo pb-4">
{logo} {logo}
<div className="info-secondary splash"> <div className="info-secondary splash">
REMIX IDE REMIX IDE
<br /> <br />
<span className='version'> v{packageJson.version}</span> <span className="version"> v{packageJson.version}</span>
</div>
</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> </div>
{!supported ? ) : null}
<div className='preload-info-container alert alert-warning'> {error ? (
Your browser does not support any of the filesystems required by Remix. <div className="preload-info-container alert alert-danger text-left">
Either change the settings in your browser or use a supported browser. An unknown error has occurred while loading the application.
</div> : null} <br></br>
{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> Doing a hard refresh might fix this issue:<br></br>
<div className='pt-2'> <div className="pt-2">
Windows:<br></br> Windows:<br></br>- Chrome: CTRL + F5 or CTRL + Reload Button
- Chrome: CTRL + F5 or CTRL + Reload Button<br></br> <br></br>- Firefox: CTRL + SHIFT + R or CTRL + F5<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>
<div className='pt-2'> <div className="pt-2">
MacOS:<br></br> Linux:<br></br>- Chrome & FireFox: CTRL + SHIFT + R<br></br>
- Chrome & FireFox: CMD + SHIFT + R or SHIFT + Reload Button<br></br>
</div> </div>
<div className='pt-2'>
Linux:<br></br>
- Chrome & FireFox: CTRL + SHIFT + R<br></br>
</div> </div>
</div> : null} ) : null}
{showDownloader ? {showDownloader ? (
<div className='preload-info-container alert alert-info'> <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. 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> <br></br>
You don't need to do anything else, your files will be available when the app loads. 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
<div onClick={async () => { await migrateAndLoad() }} data-id='skipbackup-btn' className='btn btn-primary mt-1'>skip backup</div> onClick={async () => {
</div> : null} await downloadBackup()
{(supported && !error && !showDownloader) ? }}
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> <div>
<i className="fas fa-spinner fa-spin fa-2x"></i> <i className="fas fa-spinner fa-spin fa-2x"></i>
</div> : null} </div>
) : null}
</div> </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>
)

@ -85,8 +85,11 @@ export class SidePanel extends AbstractPanel {
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) {

@ -31,27 +31,34 @@ export class VerticalIcons extends Plugin {
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)
.map((value) => {
return {
...value, ...value,
isRequired: fixedOrder.indexOf(value.profile.name) > -1 isRequired: fixedOrder.indexOf(value.profile.name) > -1
}}).sort((a,b) => { }
})
.sort((a, b) => {
return a.timestamp - b.timestamp return a.timestamp - b.timestamp
}) })
const required = divived.filter((value) => value.isRequired).sort((a,b) => { const required = divived
.filter((value) => value.isRequired)
.sort((a, b) => {
return fixedOrder.indexOf(a.profile.name) - fixedOrder.indexOf(b.profile.name) 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>) {
@ -112,15 +119,14 @@ export class VerticalIcons extends Plugin {
} }
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
@ -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)
} }
@ -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
@ -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)
@ -877,6 +910,57 @@ class FileManager extends Plugin {
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
@ -927,7 +1011,13 @@ class FileManager extends Plugin {
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}` })
} }
await this.copyDir(src, dest, dirName) const provider = this.fileProviderOf(src)
if (provider.isSubDirectory(src, dest)) {
this.call('notification', 'toast', recursivePasteToastMsg())
return false
}
await this.inDepthCopy(src, dest, dirName)
await this.remove(src) await this.remove(src)
} catch (e) { } catch (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 {
@ -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')

@ -3,7 +3,7 @@ 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)
@ -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}`
@ -60,9 +58,7 @@ export class ContractFlattener extends Plugin {
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) {

@ -5,7 +5,7 @@ import { AlertModal } from '@remix-ui/app'
import {dispatchModalInterface} from '@remix-ui/app' import {dispatchModalInterface} from '@remix-ui/app'
interface INotificationApi { interface INotificationApi {
events: StatusEvents, events: StatusEvents
methods: { methods: {
modal: (args: AppModal) => void modal: (args: AppModal) => void
alert: (args: AlertModal) => void alert: (args: AlertModal) => void

@ -0,0 +1,47 @@
import { Plugin } from '@remixproject/engine'
import { CreateChatCompletionResponse } from 'openai'
const _paq = (window._paq = window._paq || [])
const profile = {
name: 'openaigpt',
displayName: 'openaigpt',
description: 'openaigpt',
methods: ['message'],
events: [],
maintainedBy: 'Remix',
}
export class OpenAIGpt extends Plugin {
constructor() {
super(profile)
}
async message(prompt): Promise<CreateChatCompletionResponse> {
this.call('layout', 'maximizeTerminal')
this.call('terminal', 'log', 'Waiting for GPT answer...')
let result
try {
result = await (
await fetch('https://openai-gpt.remixproject.org', {
method: 'POST',
headers: {
Accept: 'application/json',
'Content-Type': 'application/json',
},
body: JSON.stringify({ prompt }),
})
).json()
} catch (e) {
this.call('terminal', 'log', { type: 'typewritererror', value: `Unable to get a response ${e.message}` })
return
}
if (result && result.choices && result.choices.length) {
this.call('terminal', 'log', { type: 'typewritersuccess', value: result.choices[0].message.content })
} else {
this.call('terminal', 'log', { type: 'typewritersuccess', value: 'No response...' })
}
return result.data
}
}

@ -9,29 +9,75 @@ import CodeParserAntlrService from './services/code-parser-antlr-service'
import CodeParserImports, {CodeParserImportsData} from './services/code-parser-imports' import CodeParserImports, {CodeParserImportsData} from './services/code-parser-imports'
import React from 'react' import React from 'react'
import {Profile} from '@remixproject/plugin-utils' import {Profile} from '@remixproject/plugin-utils'
import { ContractDefinitionAstNode, EventDefinitionAstNode, FunctionCallAstNode, FunctionDefinitionAstNode, IdentifierAstNode, ImportDirectiveAstNode, ModifierDefinitionAstNode, SourceUnitAstNode, StructDefinitionAstNode, VariableDeclarationAstNode } from '@remix-project/remix-analyzer' import {
ContractDefinitionAstNode,
EventDefinitionAstNode,
FunctionCallAstNode,
FunctionDefinitionAstNode,
IdentifierAstNode,
ImportDirectiveAstNode,
ModifierDefinitionAstNode,
SourceUnitAstNode,
StructDefinitionAstNode,
VariableDeclarationAstNode
} from '@remix-project/remix-analyzer'
import {lastCompilationResult, RemixApi} from '@remixproject/plugin-api' import {lastCompilationResult, RemixApi} from '@remixproject/plugin-api'
import {antlr} from './types' import {antlr} from './types'
import {ParseResult} from './types/antlr-types' import {ParseResult} from './types/antlr-types'
const profile: Profile = { const profile: Profile = {
name: 'codeParser', name: 'codeParser',
methods: ['nodesAtPosition', 'getContractNodes', 'getCurrentFileNodes', 'getLineColumnOfNode', 'getLineColumnOfPosition', 'getFunctionParamaters', 'getDeclaration', 'getFunctionReturnParameters', 'getVariableDeclaration', 'getNodeDocumentation', 'getNodeLink', 'listAstNodes', 'getANTLRBlockAtPosition', 'getLastNodeInLine', 'resolveImports', 'parseSolidity', 'getNodesWithScope', 'getNodesWithName', 'getNodes', 'compile', 'getNodeById', 'getLastCompilationResult', 'positionOfDefinition', 'definitionAtPosition', 'jumpToDefinition', 'referrencesAtPosition', 'referencesOf', 'getActiveHighlights', 'gasEstimation', 'declarationOf', 'getGasEstimates', 'getImports'], methods: [
'nodesAtPosition',
'getContractNodes',
'getCurrentFileNodes',
'getLineColumnOfNode',
'getLineColumnOfPosition',
'getFunctionParamaters',
'getDeclaration',
'getFunctionReturnParameters',
'getVariableDeclaration',
'getNodeDocumentation',
'getNodeLink',
'listAstNodes',
'getANTLRBlockAtPosition',
'getLastNodeInLine',
'resolveImports',
'parseSolidity',
'getNodesWithScope',
'getNodesWithName',
'getNodes',
'compile',
'getNodeById',
'getLastCompilationResult',
'positionOfDefinition',
'definitionAtPosition',
'jumpToDefinition',
'referrencesAtPosition',
'referencesOf',
'getActiveHighlights',
'gasEstimation',
'declarationOf',
'getGasEstimates',
'getImports'
],
events: [], events: [],
version: '0.0.1' version: '0.0.1'
} }
export function isNodeDefinition(node: genericASTNode) { export function isNodeDefinition(node: genericASTNode) {
return node.nodeType === 'ContractDefinition' || return (
node.nodeType === 'ContractDefinition' ||
node.nodeType === 'FunctionDefinition' || node.nodeType === 'FunctionDefinition' ||
node.nodeType === 'ModifierDefinition' || node.nodeType === 'ModifierDefinition' ||
node.nodeType === 'VariableDeclaration' || node.nodeType === 'VariableDeclaration' ||
node.nodeType === 'StructDefinition' || node.nodeType === 'StructDefinition' ||
node.nodeType === 'EventDefinition' node.nodeType === 'EventDefinition'
)
} }
export type genericASTNode = export type genericASTNode =
ContractDefinitionAstNode | ContractDefinitionAstNode
| FunctionDefinitionAstNode | FunctionDefinitionAstNode
| ModifierDefinitionAstNode | ModifierDefinitionAstNode
| VariableDeclarationAstNode | VariableDeclarationAstNode
@ -51,13 +97,12 @@ interface declarationIndexNode {
} }
interface codeParserIndex { interface codeParserIndex {
declarations: declarationIndexNode, declarations: declarationIndexNode
flatReferences: flatReferenceIndexNode, flatReferences: flatReferenceIndexNode
nodesPerFile: any nodesPerFile: any
} }
export class CodeParser extends Plugin { export class CodeParser extends Plugin {
compilerAbstract: CompilerAbstract compilerAbstract: CompilerAbstract
currentFile: string currentFile: string
nodeIndex: codeParserIndex nodeIndex: codeParserIndex
@ -80,7 +125,6 @@ export class CodeParser extends Plugin {
debuggerIsOn: boolean = false debuggerIsOn: boolean = false
constructor(astWalker: any) { constructor(astWalker: any) {
super(profile) super(profile)
this.astWalker = astWalker this.astWalker = astWalker
@ -106,7 +150,6 @@ export class CodeParser extends Plugin {
} }
async onActivation() { async onActivation() {
this.gasService = new CodeParserGasService(this) this.gasService = new CodeParserGasService(this)
this.compilerService = new CodeParserCompiler(this) this.compilerService = new CodeParserCompiler(this)
this.antlrService = new CodeParserAntlrService(this) this.antlrService = new CodeParserAntlrService(this)
@ -189,7 +232,7 @@ export class CodeParser extends Plugin {
} }
getSubNodes<T extends genericASTNode>(node: T): number[] { getSubNodes<T extends genericASTNode>(node: T): number[] {
return node.nodeType == "ContractDefinition" && node.contractDependencies; return node.nodeType == 'ContractDefinition' && node.contractDependencies
} }
/** /**
@ -200,7 +243,7 @@ export class CodeParser extends Plugin {
_buildIndex(compilationResult: CompilationResult, source) { _buildIndex(compilationResult: CompilationResult, source) {
if (compilationResult && compilationResult.sources) { if (compilationResult && compilationResult.sources) {
const callback = (node: genericASTNode) => { const callback = (node: genericASTNode) => {
if (node && ("referencedDeclaration" in node) && node.referencedDeclaration) { if (node && 'referencedDeclaration' in node && node.referencedDeclaration) {
if (!this.nodeIndex.declarations[node.referencedDeclaration]) { if (!this.nodeIndex.declarations[node.referencedDeclaration]) {
this.nodeIndex.declarations[node.referencedDeclaration] = [] this.nodeIndex.declarations[node.referencedDeclaration] = []
} }
@ -211,9 +254,7 @@ export class CodeParser extends Plugin {
for (const s in compilationResult.sources) { for (const s in compilationResult.sources) {
this.astWalker.walkFull(compilationResult.sources[s].ast, callback) this.astWalker.walkFull(compilationResult.sources[s].ast, callback)
} }
} }
} }
// NODE HELPERS // NODE HELPERS
@ -232,15 +273,13 @@ export class CodeParser extends Plugin {
return '(' + params.toString() + ')' return '(' + params.toString() + ')'
} }
_flatNodeList(contractNode: ContractDefinitionAstNode, fileName: string, inScope: boolean, compilatioResult: any) { _flatNodeList(contractNode: ContractDefinitionAstNode, fileName: string, inScope: boolean, compilatioResult: any) {
const index = {} const index = {}
const contractName: string = contractNode.name const contractName: string = contractNode.name
const callback = (node) => { const callback = (node) => {
if (inScope && node.scope !== contractNode.id if (inScope && node.scope !== contractNode.id && !(node.nodeType === 'EnumDefinition' || node.nodeType === 'EventDefinition' || node.nodeType === 'ModifierDefinition'))
&& !(node.nodeType === 'EnumDefinition' || node.nodeType === 'EventDefinition' || node.nodeType === 'ModifierDefinition'))
return return
if (inScope) node.isClassNode = true; if (inScope) node.isClassNode = true
node.gasEstimate = this._getContractGasEstimate(node, contractName, fileName, compilatioResult) node.gasEstimate = this._getContractGasEstimate(node, contractName, fileName, compilatioResult)
node.functionName = node.name + this._getInputParams(node) node.functionName = node.name + this._getInputParams(node)
node.contractName = contractName node.contractName = contractName
@ -261,7 +300,10 @@ export class CodeParser extends Plugin {
if (node.nodeType === 'ContractDefinition') { if (node.nodeType === 'ContractDefinition') {
const flatNodes = this._flatNodeList(node, fileName, false, compilationResult) const flatNodes = this._flatNodeList(node, fileName, false, compilationResult)
node.gasEstimate = this._getContractGasEstimate(node, node.name, fileName, compilationResult) node.gasEstimate = this._getContractGasEstimate(node, node.name, fileName, compilationResult)
nodesByContract.contracts[node.name] = { contractDefinition: node, contractNodes: flatNodes } nodesByContract.contracts[node.name] = {
contractDefinition: node,
contractNodes: flatNodes
}
const baseNodes = {} const baseNodes = {}
const baseNodesWithBaseContractScope = {} const baseNodesWithBaseContractScope = {}
if (node.linearizedBaseContracts) { if (node.linearizedBaseContracts) {
@ -271,19 +313,16 @@ export class CodeParser extends Plugin {
const callback = (node) => { const callback = (node) => {
node.contractName = (baseContract as any).name node.contractName = (baseContract as any).name
node.contractId = (baseContract as any).id node.contractId = (baseContract as any).id
node.isBaseNode = true; node.isBaseNode = true
baseNodes[node.id] = node baseNodes[node.id] = node
if ((node.scope && node.scope === baseContract.id) if ((node.scope && node.scope === baseContract.id) || node.nodeType === 'EnumDefinition' || node.nodeType === 'EventDefinition') {
|| node.nodeType === 'EnumDefinition'
|| node.nodeType === 'EventDefinition'
) {
baseNodesWithBaseContractScope[node.id] = node baseNodesWithBaseContractScope[node.id] = node
} }
if (node.members) { if (node.members) {
for (const member of node.members) { for (const member of node.members) {
member.contractName = (baseContract as any).name member.contractName = (baseContract as any).name
member.contractId = (baseContract as any).id member.contractId = (baseContract as any).id
member.isBaseNode = true; member.isBaseNode = true
} }
} }
} }
@ -296,10 +335,9 @@ export class CodeParser extends Plugin {
nodesByContract.contracts[node.name].contractScopeNodes = this._flatNodeList(node, fileName, true, compilationResult) nodesByContract.contracts[node.name].contractScopeNodes = this._flatNodeList(node, fileName, true, compilationResult)
} }
if (node.nodeType === 'ImportDirective') { if (node.nodeType === 'ImportDirective') {
const imported = await this.resolveImports(node, {}) const imported = await this.resolveImports(node, {})
for (const importedNode of (Object.values(imported) as any)) { for (const importedNode of Object.values(imported) as any) {
if (importedNode.nodes) if (importedNode.nodes)
for (const subNode of importedNode.nodes) { for (const subNode of importedNode.nodes) {
nodesByContract.imports[subNode.id] = subNode nodesByContract.imports[subNode.id] = subNode
@ -312,7 +350,6 @@ export class CodeParser extends Plugin {
} }
_getContractGasEstimate(node: ContractDefinitionAstNode | FunctionDefinitionAstNode, contractName: string, fileName: string, compilationResult: lastCompilationResult) { _getContractGasEstimate(node: ContractDefinitionAstNode | FunctionDefinitionAstNode, contractName: string, fileName: string, compilationResult: lastCompilationResult) {
const contracts = compilationResult.data.contracts && compilationResult.data.contracts[this.currentFile] const contracts = compilationResult.data.contracts && compilationResult.data.contracts[this.currentFile]
for (const name in contracts) { for (const name in contracts) {
if (name === contractName) { if (name === contractName) {
@ -335,7 +372,7 @@ export class CodeParser extends Plugin {
} else { } else {
return { return {
creationCost: estimationObj === null ? '-' : estimationObj.creation.totalCost, creationCost: estimationObj === null ? '-' : estimationObj.creation.totalCost,
codeDepositCost: estimationObj === null ? '-' : estimationObj.creation.codeDepositCost, codeDepositCost: estimationObj === null ? '-' : estimationObj.creation.codeDepositCost
} }
} }
} }
@ -357,8 +394,18 @@ export class CodeParser extends Plugin {
} }
if (!lastCompilationResult) return [] if (!lastCompilationResult) return []
const urlFromPath = await this.call('fileManager', 'getUrlFromPath', this.currentFile) const urlFromPath = await this.call('fileManager', 'getUrlFromPath', this.currentFile)
if (lastCompilationResult && lastCompilationResult.languageversion.indexOf('soljson') === 0 && lastCompilationResult.data && lastCompilationResult.data.sources && lastCompilationResult.data.sources[this.currentFile]) { if (
const nodes: genericASTNode[] = sourceMappingDecoder.nodesAtPosition(type, position, lastCompilationResult.data.sources[this.currentFile] || lastCompilationResult.data.sources[urlFromPath.file]) lastCompilationResult &&
lastCompilationResult.languageversion.indexOf('soljson') === 0 &&
lastCompilationResult.data &&
lastCompilationResult.data.sources &&
lastCompilationResult.data.sources[this.currentFile]
) {
const nodes: genericASTNode[] = sourceMappingDecoder.nodesAtPosition(
type,
position,
lastCompilationResult.data.sources[this.currentFile] || lastCompilationResult.data.sources[urlFromPath.file]
)
return nodes return nodes
} }
return [] return []
@ -417,7 +464,7 @@ export class CodeParser extends Plugin {
* @returns * @returns
*/ */
declarationOf<T extends genericASTNode>(node: T) { declarationOf<T extends genericASTNode>(node: T) {
if (node && ('referencedDeclaration' in node) && node.referencedDeclaration) { if (node && 'referencedDeclaration' in node && node.referencedDeclaration) {
return this.nodeIndex.flatReferences[node.referencedDeclaration] return this.nodeIndex.flatReferences[node.referencedDeclaration]
} }
return null return null
@ -430,56 +477,84 @@ export class CodeParser extends Plugin {
*/ */
async definitionAtPosition(position: number) { async definitionAtPosition(position: number) {
const nodes = await this.nodesAtPosition(position) const nodes = await this.nodesAtPosition(position)
let nodeDefinition: any const nodeDefinition = {
'ast': null,
'parser': null
}
let node: genericASTNode let node: genericASTNode
if (nodes && nodes.length && !this.errorState) { if (nodes && nodes.length) {
node = nodes[nodes.length - 1] node = nodes[nodes.length - 1]
nodeDefinition = node let astNodeDefinition = node
if (!isNodeDefinition(node)) { if (!isNodeDefinition(node)) {
nodeDefinition = await this.declarationOf(node) || node astNodeDefinition = (await this.declarationOf(node)) || node
} }
if (node.nodeType === 'ImportDirective') { if (node.nodeType === 'ImportDirective') {
for (const key in this.nodeIndex.flatReferences) { for (const key in this.nodeIndex.flatReferences) {
if (this.nodeIndex.flatReferences[key].id === node.sourceUnit) { if (this.nodeIndex.flatReferences[key].id === node.sourceUnit) {
nodeDefinition = this.nodeIndex.flatReferences[key] astNodeDefinition = this.nodeIndex.flatReferences[key]
} }
} }
} }
return nodeDefinition
} else { nodeDefinition.ast = astNodeDefinition
}
const astNodes = await this.antlrService.listAstNodes() const astNodes = await this.antlrService.listAstNodes()
let parserNodeDefinition = null
if (astNodes && astNodes.length) { if (astNodes && astNodes.length) {
for (const node of astNodes) { for (const node of astNodes) {
if (node.range[0] <= position && node.range[1] >= position) { if (node.range[0] <= position && node.range[1] >= position) {
if (nodeDefinition && nodeDefinition.range[0] < node.range[0]) { if (parserNodeDefinition && parserNodeDefinition.range[0] < node.range[0]) {
nodeDefinition = node parserNodeDefinition = node
} }
if (!nodeDefinition) nodeDefinition = node if (!parserNodeDefinition) parserNodeDefinition = node
} }
} }
if (nodeDefinition && nodeDefinition.type && nodeDefinition.type === 'Identifier') { if (parserNodeDefinition && parserNodeDefinition.type && parserNodeDefinition.type === 'Identifier') {
const nodeForIdentifier = await this.findIdentifier(nodeDefinition) const nodeForIdentifier = await this.findIdentifier(parserNodeDefinition)
if (nodeForIdentifier) nodeDefinition = nodeForIdentifier if (nodeForIdentifier) parserNodeDefinition = nodeForIdentifier
}
nodeDefinition.parser = parserNodeDefinition
}
/* if the AST node name & type is the same as the parser node name & type,
/ then we can assume that the AST node is the definition,
/ because the parser will always return most nodes it can find even with an error in the code
*/
if (nodeDefinition.ast && nodeDefinition.parser) {
if (nodeDefinition.ast.name === nodeDefinition.parser.name && nodeDefinition.ast.nodeType === nodeDefinition.parser.type) {
return nodeDefinition.ast
}else{
// if there is a difference and the compiler has compiled correctly assume the ast node is the definition
if(this.compilerService.errorState === false){
return nodeDefinition.ast
} }
return nodeDefinition
} }
} }
if (nodeDefinition.ast && !nodeDefinition.parser) {
return nodeDefinition.ast
}
return nodeDefinition.parser
} }
async getContractNodes(contractName: string) { async getContractNodes(contractName: string) {
if (this.nodeIndex.nodesPerFile if (
&& this.nodeIndex.nodesPerFile[this.currentFile] this.nodeIndex.nodesPerFile &&
&& this.nodeIndex.nodesPerFile[this.currentFile].contracts[contractName] this.nodeIndex.nodesPerFile[this.currentFile] &&
&& this.nodeIndex.nodesPerFile[this.currentFile].contracts[contractName].contractNodes) { this.nodeIndex.nodesPerFile[this.currentFile].contracts[contractName] &&
this.nodeIndex.nodesPerFile[this.currentFile].contracts[contractName].contractNodes
) {
return this.nodeIndex.nodesPerFile[this.currentFile].contracts[contractName] return this.nodeIndex.nodesPerFile[this.currentFile].contracts[contractName]
} }
return false return false
} }
async getCurrentFileNodes() { async getCurrentFileNodes() {
if (this.nodeIndex.nodesPerFile if (this.nodeIndex.nodesPerFile && this.nodeIndex.nodesPerFile[this.currentFile]) {
&& this.nodeIndex.nodesPerFile[this.currentFile]) {
return this.nodeIndex.nodesPerFile[this.currentFile] return this.nodeIndex.nodesPerFile[this.currentFile]
} }
return false return false
@ -535,8 +610,6 @@ export class CodeParser extends Plugin {
return imported return imported
} }
/** /**
* *
* @param node * @param node
@ -553,7 +626,7 @@ export class CodeParser extends Plugin {
} }
} }
} }
if (node && ("referencedDeclaration" in node) && node.referencedDeclaration) { if (node && 'referencedDeclaration' in node && node.referencedDeclaration) {
highlights(node.referencedDeclaration) highlights(node.referencedDeclaration)
const current = this.nodeIndex.flatReferences[node.referencedDeclaration] const current = this.nodeIndex.flatReferences[node.referencedDeclaration]
results.push(current) results.push(current)
@ -586,8 +659,6 @@ export class CodeParser extends Plugin {
return this.nodeIndex.flatReferences return this.nodeIndex.flatReferences
} }
/** /**
* *
* @param node * @param node
@ -629,9 +700,9 @@ export class CodeParser extends Plugin {
* @returns * @returns
*/ */
async getNodeDocumentation(node: genericASTNode) { async getNodeDocumentation(node: genericASTNode) {
if (("documentation" in node) && node.documentation && (node.documentation as any).text) { if ('documentation' in node && node.documentation && (node.documentation as any).text) {
let text = ''; let text = ''
(node.documentation as any).text.split('\n').forEach(line => { ;(node.documentation as any).text.split('\n').forEach((line) => {
text += `${line.trim()}\n` text += `${line.trim()}\n`
}) })
return text return text
@ -651,11 +722,9 @@ export class CodeParser extends Plugin {
} else { } else {
if (node.typeName && node.typeName.name) { if (node.typeName && node.typeName.name) {
return `${node.typeName.name} ${nodeVisibility}${nodeName}` return `${node.typeName.name} ${nodeVisibility}${nodeName}`
} } else if (node.typeName && node.typeName.namePath) {
else if (node.typeName && node.typeName.namePath) {
return `${node.typeName.namePath} ${nodeVisibility}${nodeName}` return `${node.typeName.namePath} ${nodeVisibility}${nodeName}`
} } else {
else {
return `${nodeName}${nodeName}` return `${nodeName}${nodeName}`
} }
} }
@ -667,7 +736,7 @@ export class CodeParser extends Plugin {
* @returns * @returns
*/ */
async getFunctionParamaters(node: any) { async getFunctionParamaters(node: any) {
const localParam = (node.parameters && node.parameters.parameters) || (node.parameters) const localParam = (node.parameters && node.parameters.parameters) || node.parameters
if (localParam) { if (localParam) {
const params = [] const params = []
for (const param of localParam) { for (const param of localParam) {
@ -683,7 +752,7 @@ export class CodeParser extends Plugin {
* @returns * @returns
*/ */
async getFunctionReturnParameters(node: any) { async getFunctionReturnParameters(node: any) {
const localParam = (node.returnParameters && node.returnParameters.parameters) const localParam = node.returnParameters && node.returnParameters.parameters
if (localParam) { if (localParam) {
const params = [] const params = []
for (const param of localParam) { for (const param of localParam) {
@ -692,7 +761,4 @@ export class CodeParser extends Plugin {
return `(${params.join(', ')})` return `(${params.join(', ')})`
} }
} }
} }

@ -29,14 +29,14 @@ export default class CodeParserAntlrService {
parserThresholdSampleAmount = 3 parserThresholdSampleAmount = 3
cache: { cache: {
[name: string]: { [name: string]: {
text: string, text: string
ast: antlr.ParseResult | null, ast: antlr.ParseResult | null
duration?: number, duration?: number
parsingEnabled?: boolean, parsingEnabled?: boolean
blocks?: BlockDefinition[], blocks?: BlockDefinition[]
blockDurations?: number[] blockDurations?: number[]
} }
} = {}; } = {}
constructor(plugin: CodeParser) { constructor(plugin: CodeParser) {
this.plugin = plugin this.plugin = plugin
this.createWorker() this.createWorker()
@ -47,7 +47,7 @@ export default class CodeParserAntlrService {
this.worker.postMessage({ this.worker.postMessage({
cmd: 'load', cmd: 'load',
url: isElectron() ? 'assets/js/parser/antlr.js' : document.location.protocol + '//' + document.location.host + '/assets/js/parser/antlr.js', url: isElectron() ? 'assets/js/parser/antlr.js' : document.location.protocol + '//' + document.location.host + '/assets/js/parser/antlr.js',
}); })
const self = this const self = this
this.worker.addEventListener('message', function (ev) { this.worker.addEventListener('message', function (ev) {
@ -60,14 +60,15 @@ export default class CodeParserAntlrService {
ast: ev.data.ast, ast: ev.data.ast,
duration: ev.data.duration, duration: ev.data.duration,
blocks: ev.data.blocks, blocks: ev.data.blocks,
blockDurations: self.cache[ev.data.file].blockDurations? [...self.cache[ev.data.file].blockDurations.slice(-self.parserThresholdSampleAmount), ev.data.blockDuration]: [ev.data.blockDuration] blockDurations: self.cache[ev.data.file].blockDurations
? [...self.cache[ev.data.file].blockDurations.slice(-self.parserThresholdSampleAmount), ev.data.blockDuration]
: [ev.data.blockDuration],
} }
self.setFileParsingState(ev.data.file) self.setFileParsingState(ev.data.file)
} }
break; break
} }
})
});
} }
setFileParsingState(file: string) { setFileParsingState(file: string) {
@ -97,7 +98,6 @@ export default class CodeParserAntlrService {
clearInterval(this.workerTimer) clearInterval(this.workerTimer)
} }
async parseWithWorker(text: string, file: string) { async parseWithWorker(text: string, file: string) {
this.parserStartTime = Date.now() this.parserStartTime = Date.now()
this.worker.postMessage({ this.worker.postMessage({
@ -105,9 +105,8 @@ export default class CodeParserAntlrService {
text, text,
timestamp: this.parserStartTime, timestamp: this.parserStartTime,
file, file,
parsingEnabled: (this.cache[file] && this.cache[file].parsingEnabled) || true parsingEnabled: (this.cache[file] && this.cache[file].parsingEnabled) || true,
}); })
} }
async parseSolidity(text: string) { async parseSolidity(text: string) {
@ -125,13 +124,13 @@ export default class CodeParserAntlrService {
try { try {
this.plugin.currentFile = await this.plugin.call('fileManager', 'file') this.plugin.currentFile = await this.plugin.call('fileManager', 'file')
if (this.plugin.currentFile && this.plugin.currentFile.endsWith('.sol')) { if (this.plugin.currentFile && this.plugin.currentFile.endsWith('.sol')) {
const fileContent = text || await this.plugin.call('fileManager', 'readFile', this.plugin.currentFile) const fileContent = text || (await this.plugin.call('fileManager', 'readFile', this.plugin.currentFile))
if (!this.cache[this.plugin.currentFile]) { if (!this.cache[this.plugin.currentFile]) {
this.cache[this.plugin.currentFile] = { this.cache[this.plugin.currentFile] = {
text: '', text: '',
ast: null, ast: null,
parsingEnabled: true, parsingEnabled: true,
blockDurations: [] blockDurations: [],
} }
} }
if (this.cache[this.plugin.currentFile] && this.cache[this.plugin.currentFile].text !== fileContent) { if (this.cache[this.plugin.currentFile] && this.cache[this.plugin.currentFile].text !== fileContent) {
@ -155,8 +154,8 @@ export default class CodeParserAntlrService {
async listAstNodes() { async listAstNodes() {
this.plugin.currentFile = await this.plugin.call('fileManager', 'file') this.plugin.currentFile = await this.plugin.call('fileManager', 'file')
if (!this.cache[this.plugin.currentFile]) return if (!this.cache[this.plugin.currentFile]) return
const nodes: AstNode[] = []; const nodes: AstNode[] = []
(SolidityParser as any).visit(this.cache[this.plugin.currentFile].ast, { ;(SolidityParser as any).visit(this.cache[this.plugin.currentFile].ast, {
StateVariableDeclaration: (node: antlr.StateVariableDeclaration) => { StateVariableDeclaration: (node: antlr.StateVariableDeclaration) => {
if (node.variables) { if (node.variables) {
for (const variable of node.variables) { for (const variable of node.variables) {
@ -196,13 +195,11 @@ export default class CodeParserAntlrService {
}, },
StructDefinition: function (node: antlr.StructDefinition) { StructDefinition: function (node: antlr.StructDefinition) {
nodes.push({ ...node, nodeType: node.type, id: null, src: null }) nodes.push({ ...node, nodeType: node.type, id: null, src: null })
} },
}) })
return nodes return nodes
} }
/** /**
* *
* @param ast * @param ast
@ -220,13 +217,13 @@ export default class CodeParserAntlrService {
} }
} }
(SolidityParser as any).visit(ast, { ;(SolidityParser as any).visit(ast, {
MemberAccess: function (node: antlr.MemberAccess) { MemberAccess: function (node: antlr.MemberAccess) {
checkLastNode(node) checkLastNode(node)
}, },
Identifier: function (node: antlr.Identifier) { Identifier: function (node: antlr.Identifier) {
checkLastNode(node) checkLastNode(node)
} },
}) })
if (lastNode && lastNode.expression) { if (lastNode && lastNode.expression) {
return lastNode.expression return lastNode.expression
@ -244,12 +241,15 @@ export default class CodeParserAntlrService {
} }
} }
if (this.plugin.currentFile && this.plugin.currentFile.endsWith('.sol')) { if (this.plugin.currentFile && this.plugin.currentFile.endsWith('.sol')) {
const fileContent = text || await this.plugin.call('fileManager', 'readFile', this.plugin.currentFile) const fileContent = text || (await this.plugin.call('fileManager', 'readFile', this.plugin.currentFile))
try { try {
const startTime = Date.now() const startTime = Date.now()
const blocks = (SolidityParser as any).parseBlock(fileContent, { loc: true, range: true, tolerant: true }) const blocks = (SolidityParser as any).parseBlock(fileContent, { loc: true, range: true, tolerant: true })
if (this.cache[this.plugin.currentFile] && this.cache[this.plugin.currentFile].blockDurations) { if (this.cache[this.plugin.currentFile] && this.cache[this.plugin.currentFile].blockDurations) {
this.cache[this.plugin.currentFile].blockDurations = [...this.cache[this.plugin.currentFile].blockDurations.slice(-this.parserThresholdSampleAmount), Date.now() - startTime] this.cache[this.plugin.currentFile].blockDurations = [
...this.cache[this.plugin.currentFile].blockDurations.slice(-this.parserThresholdSampleAmount),
Date.now() - startTime,
]
this.setFileParsingState(this.plugin.currentFile) this.setFileParsingState(this.plugin.currentFile)
} }
if (blocks) this.cache[this.plugin.currentFile].blocks = blocks if (blocks) this.cache[this.plugin.currentFile].blocks = blocks
@ -285,5 +285,4 @@ export default class CodeParserAntlrService {
const block = walkAst(blocks) const block = walkAst(blocks)
return block return block
} }
} }

@ -99,11 +99,11 @@ export default class CodeParserCompiler {
await this.clearDecorators(result.getSourceCode().sources) await this.clearDecorators(result.getSourceCode().sources)
} }
if (!data.sources) return if (!data.sources) return
if (data.sources && Object.keys(data.sources).length === 0) return if (data.sources && Object.keys(data.sources).length === 0) return
this.plugin.compilerAbstract = new CompilerAbstract('soljson', data, source, input) this.plugin.compilerAbstract = new CompilerAbstract('soljson', data, source, input)
this.errorState = false this.errorState = false
this.plugin.nodeIndex = { this.plugin.nodeIndex = {
declarations: {}, declarations: {},
flatReferences: {}, flatReferences: {},
@ -113,7 +113,10 @@ export default class CodeParserCompiler {
this.plugin._buildIndex(data, source) this.plugin._buildIndex(data, source)
// cast from the remix-plugin interface to the solidity one. Should be fixed when remix-plugin move to the remix-project repository // cast from the remix-plugin interface to the solidity one. Should be fixed when remix-plugin move to the remix-project repository
this.plugin.nodeIndex.nodesPerFile[this.plugin.currentFile] = this.plugin._extractFileNodes(this.plugin.currentFile, this.plugin.compilerAbstract as unknown as lastCompilationResult) const extractedFiledNodes = this.plugin._extractFileNodes(this.plugin.currentFile, this.plugin.compilerAbstract as unknown as lastCompilationResult)
if(extractedFiledNodes) {
this.plugin.nodeIndex.nodesPerFile[this.plugin.currentFile] = extractedFiledNodes
}
await this.plugin.gasService.showGasEstimates() await this.plugin.gasService.showGasEstimates()
this.plugin.emit('astFinished') this.plugin.emit('astFinished')
} }

@ -42,7 +42,10 @@ export default class CodeParserGasService {
} }
this.plugin.currentFile = await this.plugin.call('fileManager', 'file') this.plugin.currentFile = await this.plugin.call('fileManager', 'file')
// cast from the remix-plugin interface to the solidity one. Should be fixed when remix-plugin move to the remix-project repository // cast from the remix-plugin interface to the solidity one. Should be fixed when remix-plugin move to the remix-project repository
this.plugin.nodeIndex.nodesPerFile[this.plugin.currentFile] = await this.plugin._extractFileNodes(this.plugin.currentFile, this.plugin.compilerAbstract as unknown as lastCompilationResult) const extractedFiledNodes = await this.plugin._extractFileNodes(this.plugin.currentFile, this.plugin.compilerAbstract as unknown as lastCompilationResult)
if(extractedFiledNodes) {
this.plugin.nodeIndex.nodesPerFile[this.plugin.currentFile] = extractedFiledNodes
}
const gasEstimates = await this.getGasEstimates(this.plugin.currentFile) const gasEstimates = await this.getGasEstimates(this.plugin.currentFile)

@ -34,31 +34,24 @@ export default class CodeParserImports {
return true return true
} }
}) })
// get unique first words of the values in the array // get unique first words of the values in the array
this.data.packages = [...new Set(this.data.modules.map(x => x.split('/')[0]))] this.data.packages = [...new Set(this.data.modules.map(x => x.split('/')[0]))]
} }
setFileTree = async () => { setFileTree = async () => {
if (isElectron()) { if (isElectron()) {
const files = await this.plugin.call('fs', 'glob', '/', '**/*.sol') const files = await this.plugin.call('fs', 'glob', '/', '**/*.sol')
// only get path property of files // only get path property of files
this.data.files = files.map(x => x.path) this.data.files = files.map((x) => x.path)
} else { } else {
this.data.files = await this.getDirectory('/') this.data.files = await this.getDirectory('/')
this.data.files = this.data.files.filter(x => x.endsWith('.sol') && !x.startsWith('.deps') && !x.startsWith('.git')) this.data.files = this.data.files.filter((x) => x.endsWith('.sol') && !x.startsWith('.deps') && !x.startsWith('.git'))
} }
} }
getDirectory = async (dir: string) => { getDirectory = async (dir: string) => {
console.log('getDirectorySEARCH', dir)
let result = [] let result = []
let files = {} let files = {}
try { try {
if (await this.plugin.call('fileManager', 'exists', dir)) { if (await this.plugin.call('fileManager', 'exists', dir)) {
@ -77,12 +70,10 @@ export default class CodeParserImports {
} }
} }
} }
return result return result
} }
normalize = filesList => { normalize = filesList => {
console.log('normalize', filesList)
const folders = [] const folders = []
const files = [] const files = []
Object.keys(filesList || {}).forEach(key => { Object.keys(filesList || {}).forEach(key => {

@ -48,13 +48,9 @@ export class PermissionHandlerPlugin extends Plugin {
switchMode(from: Profile, to: Profile, method: string, set: boolean, sensitiveCall: boolean) { switchMode(from: Profile, to: Profile, method: string, set: boolean, sensitiveCall: boolean) {
if (sensitiveCall) { if (sensitiveCall) {
set set ? (this.sessionPermissions[to.name][method][from.name] = {}) : delete this.sessionPermissions[to.name][method][from.name]
? this.sessionPermissions[to.name][method][from.name] = {}
: delete this.sessionPermissions[to.name][method][from.name]
} else { } else {
set set ? (this.permissions[to.name][method][from.name] = {}) : delete this.permissions[to.name][method][from.name]
? this.permissions[to.name][method][from.name] = {}
: delete this.permissions[to.name][method][from.name]
} }
} }
@ -124,10 +120,10 @@ export class PermissionHandlerPlugin extends Plugin {
} }
const modal: AppModal = { const modal: AppModal = {
id: 'PermissionHandler', id: 'PermissionHandler',
title: <FormattedMessage id='permissionHandler.permissionNeededFor' values={{ to: to.displayName || to.name }} />, title: <FormattedMessage id="permissionHandler.permissionNeededFor" values={{to: to.displayName || to.name}} />,
message: <PermissionHandlerDialog plugin={this} theme={await this.getTheme()} value={value}></PermissionHandlerDialog>, message: <PermissionHandlerDialog plugin={this} theme={await this.getTheme()} value={value}></PermissionHandlerDialog>,
okLabel: <FormattedMessage id='permissionHandler.accept' />, okLabel: <FormattedMessage id="permissionHandler.accept" />,
cancelLabel: <FormattedMessage id='permissionHandler.decline' /> cancelLabel: <FormattedMessage id="permissionHandler.decline" />
} }
const result = await this.call('notification', 'modal', modal) const result = await this.call('notification', 'modal', modal)

@ -19,10 +19,10 @@ const profile = {
description: 'Using Remixd daemon, allow to access file system', description: 'Using Remixd daemon, allow to access file system',
kind: 'other', kind: 'other',
version: packageJson.version, version: packageJson.version,
repo: "https://github.com/ethereum/remix-project/tree/master/libs/remixd", repo: 'https://github.com/ethereum/remix-project/tree/master/libs/remixd',
maintainedBy: "Remix", maintainedBy: 'Remix',
documentation: "https://remix-ide.readthedocs.io/en/latest/remixd.html", documentation: 'https://remix-ide.readthedocs.io/en/latest/remixd.html',
authorContact: "" authorContact: ''
} }
export class RemixdHandle extends WebsocketPlugin { export class RemixdHandle extends WebsocketPlugin {
@ -63,7 +63,6 @@ export class RemixdHandle extends WebsocketPlugin {
} }
await this.appManager.deactivatePlugin('remixd') await this.appManager.deactivatePlugin('remixd')
} }
async callPluginMethod(key: string, payload?: any[]) { async callPluginMethod(key: string, payload?: any[]) {
@ -92,7 +91,8 @@ export class RemixdHandle extends WebsocketPlugin {
this.canceled() this.canceled()
} else { } else {
const intervalId = setInterval(() => { const intervalId = setInterval(() => {
if (!this.socket || (this.socket && this.socket.readyState === 3)) { // 3 means connection closed if (!this.socket || (this.socket && this.socket.readyState === 3)) {
// 3 means connection closed
clearInterval(intervalId) clearInterval(intervalId)
const alert: AlertModal = { const alert: AlertModal = {
id: 'connectionAlert', id: 'connectionAlert',
@ -104,7 +104,7 @@ export class RemixdHandle extends WebsocketPlugin {
}, 3000) }, 3000)
this.localhostProvider.init(() => { this.localhostProvider.init(() => {
this.call('filePanel', 'setWorkspace', {name: LOCALHOST, isLocalhost: true}, true) this.call('filePanel', 'setWorkspace', {name: LOCALHOST, isLocalhost: true}, true)
}); })
for (const plugin of this.dependentPlugins) { for (const plugin of this.dependentPlugins) {
await this.appManager.activatePlugin(plugin) await this.appManager.activatePlugin(plugin)
} }
@ -119,7 +119,7 @@ export class RemixdHandle extends WebsocketPlugin {
title: 'Access file system using remixd', title: 'Access file system using remixd',
message: remixdDialog(), message: remixdDialog(),
okLabel: 'Connect', okLabel: 'Connect',
cancelLabel: 'Cancel', cancelLabel: 'Cancel'
} }
const result = await this.call('notification', 'modal', mod) const result = await this.call('notification', 'modal', mod)
if (result) { if (result) {
@ -127,7 +127,8 @@ export class RemixdHandle extends WebsocketPlugin {
this.localhostProvider.preInit() this.localhostProvider.preInit()
super.activate() super.activate()
setTimeout(() => { setTimeout(() => {
if (!this.socket || (this.socket && this.socket.readyState === 3)) { // 3 means connection closed if (!this.socket || (this.socket && this.socket.readyState === 3)) {
// 3 means connection closed
connection(new Error('Connection with daemon failed.')) connection(new Error('Connection with daemon failed.'))
} else { } else {
connection() connection()
@ -136,14 +137,15 @@ export class RemixdHandle extends WebsocketPlugin {
} catch (error) { } catch (error) {
connection(error) connection(error)
} }
} } else {
else {
await this.canceled() await this.canceled()
} }
} else { } else {
try { try {
super.activate() super.activate()
setTimeout(() => { connection() }, 2000) setTimeout(() => {
connection()
}, 2000)
} catch (error) { } catch (error) {
connection(error) connection(error)
} }
@ -154,37 +156,53 @@ export class RemixdHandle extends WebsocketPlugin {
function remixdDialog() { function remixdDialog() {
const commandText = 'remixd' const commandText = 'remixd'
const fullCommandText = 'remixd -s <path-to-the-shared-folder> -u <remix-ide-instance-URL>' const fullCommandText = 'remixd -s <path-to-the-shared-folder> -u <remix-ide-instance-URL>'
return (<> return (
<div className=''> <>
<div className='mb-2 text-break'> <div className="">
Access your local file system from Remix IDE using <a target="_blank" href="https://www.npmjs.com/package/@remix-project/remixd">Remixd NPM package</a>. <div className="mb-2 text-break">
Access your local file system from Remix IDE using{' '}
<a target="_blank" href="https://www.npmjs.com/package/@remix-project/remixd">
Remixd NPM package
</a>
.
</div> </div>
<div className='mb-2 text-break'> <div className="mb-2 text-break">
Remixd <a target="_blank" href="https://remix-ide.readthedocs.io/en/latest/remixd.html">documentation</a>. Remixd{' '}
<a target="_blank" href="https://remix-ide.readthedocs.io/en/latest/remixd.html">
documentation
</a>
.
</div> </div>
<div className='mb-2 text-break'> <div className="mb-2 text-break">
The remixd command is: The remixd command is:
<br /><b>{commandText}</b> <br />
<b>{commandText}</b>
</div> </div>
<div className='mb-2 text-break'> <div className="mb-2 text-break">
The remixd command without options uses the terminal's current directory as the shared directory and the shared Remix domain can only be https://remix.ethereum.org, https://remix-alpha.ethereum.org, or https://remix-beta.ethereum.org The remixd command without options uses the terminal's current directory as the shared directory and the shared Remix domain can only be https://remix.ethereum.org,
https://remix-alpha.ethereum.org, or https://remix-beta.ethereum.org
</div> </div>
<div className='mb-2 text-break'> <div className="mb-2 text-break">
Example command with flags: <br /> Example command with flags: <br />
<b>{fullCommandText}</b> <b>{fullCommandText}</b>
</div> </div>
<div className='mb-2 text-break'> <div className="mb-2 text-break">
For info about ports, see <a target="_blank" href="https://remix-ide.readthedocs.io/en/latest/remixd.html#ports-usage">Remixd ports usage</a> For info about ports, see{' '}
</div> <a target="_blank" href="https://remix-ide.readthedocs.io/en/latest/remixd.html#ports-usage">
<div className='mb-2 text-break'> Remixd ports usage
This feature is still in Alpha. We recommend to keep a backup of the shared folder. </a>
</div> </div>
<div className='mb-2 text-break'> <div className="mb-2 text-break">This feature is still in Alpha. We recommend to keep a backup of the shared folder.</div>
<div className="mb-2 text-break">
<h6 className="text-danger"> <h6 className="text-danger">
Before using, make sure remixd version is latest i.e. <b>v{remixdVersion}</b> Before using, make sure remixd version is latest i.e. <b>v{remixdVersion}</b>
<br></br><a target="_blank" href="https://remix-ide.readthedocs.io/en/latest/remixd.html#update-to-the-latest-remixd">Read here how to update it</a> <br></br>
<a target="_blank" href="https://remix-ide.readthedocs.io/en/latest/remixd.html#update-to-the-latest-remixd">
Read here how to update it
</a>
</h6> </h6>
</div> </div>
</div> </div>
</>) </>
)
} }

@ -3,7 +3,7 @@ import { format } from 'util'
import {Plugin} from '@remixproject/engine' import {Plugin} from '@remixproject/engine'
import {compile} from '@remix-project/remix-solidity' import {compile} from '@remix-project/remix-solidity'
import {TransactionConfig} from 'web3-core' import {TransactionConfig} from 'web3-core'
const _paq = window._paq = window._paq || [] //eslint-disable-line const _paq = (window._paq = window._paq || []) //eslint-disable-line
const profile = { const profile = {
name: 'solidity-script', name: 'solidity-script',
@ -23,7 +23,6 @@ export class SolidityScript extends Plugin {
let content = await this.call('fileManager', 'readFile', path) let content = await this.call('fileManager', 'readFile', path)
const params = await this.call('solidity', 'getCompilerParameters') const params = await this.call('solidity', 'getCompilerParameters')
content = ` content = `
// SPDX-License-Identifier: GPL-3.0 // SPDX-License-Identifier: GPL-3.0
@ -42,7 +41,9 @@ export class SolidityScript extends Plugin {
// compile // compile
const compilation = await compile(targets, params, async (url, cb) => { const compilation = await compile(targets, params, async (url, cb) => {
await this.call('contentImport', 'resolveAndSave', url).then((result) => cb(null, result)).catch((error) => cb(error.message)) await this.call('contentImport', 'resolveAndSave', url)
.then((result) => cb(null, result))
.catch((error) => cb(error.message))
}) })
if (compilation.data.error) { if (compilation.data.error) {
@ -83,9 +84,12 @@ export class SolidityScript extends Plugin {
const hhlogs = await web3.eth.getHHLogsForTx(receiptCall.transactionHash) const hhlogs = await web3.eth.getHHLogsForTx(receiptCall.transactionHash)
if (hhlogs && hhlogs.length) { if (hhlogs && hhlogs.length) {
const finalLogs = <div><div><b>console.log:</b></div> const finalLogs = (
{ <div>
hhlogs.map((log) => { <div>
<b>console.log:</b>
</div>
{hhlogs.map((log) => {
let formattedLog let formattedLog
// Hardhat implements the same formatting options that can be found in Node.js' console.log, // Hardhat implements the same formatting options that can be found in Node.js' console.log,
// which in turn uses util.format: https://nodejs.org/dist/latest-v12.x/docs/api/util.html#util_util_format_format_args // which in turn uses util.format: https://nodejs.org/dist/latest-v12.x/docs/api/util.html#util_util_format_format_args
@ -99,6 +103,7 @@ export class SolidityScript extends Plugin {
return <div>{formattedLog}</div> return <div>{formattedLog}</div>
})} })}
</div> </div>
)
_paq.push(['trackEvent', 'udapp', 'hardhat', 'console.log']) _paq.push(['trackEvent', 'udapp', 'hardhat', 'console.log'])
this.call('terminal', 'logHtml', finalLogs) this.call('terminal', 'logHtml', finalLogs)
} }

@ -13,7 +13,7 @@ import { customAction } from '@remixproject/plugin-api'
import {ClassOptions} from 'sol2uml/lib/converterClass2Dot' import {ClassOptions} from 'sol2uml/lib/converterClass2Dot'
const parser = (window as any).SolidityParser const parser = (window as any).SolidityParser
const _paq = window._paq = window._paq || [] const _paq = (window._paq = window._paq || [])
const profile = { const profile = {
name: 'solidityumlgen', name: 'solidityumlgen',
@ -21,35 +21,8 @@ const profile = {
description: 'Generates UML diagram in svg format from last compiled contract', description: 'Generates UML diagram in svg format from last compiled contract',
location: 'mainPanel', location: 'mainPanel',
methods: ['showUmlDiagram', 'generateUml', 'generateCustomAction'], methods: ['showUmlDiagram', 'generateUml', 'generateCustomAction'],
events: [], events: []
} }
const themeCollection = [
{ themeName: 'HackerOwl', backgroundColor: '#011628', textColor: '#babbcc',
shapeColor: '#8694a1',fillColor: '#011C32'},
{ themeName: 'Cerulean', backgroundColor: '#ffffff', textColor: '#343a40',
shapeColor: '#343a40',fillColor: '#f8f9fa'},
{ themeName: 'Cyborg', backgroundColor: '#060606', textColor: '#adafae',
shapeColor: '#adafae', fillColor: '#222222'},
{ themeName: 'Dark', backgroundColor: '#222336', textColor: '#babbcc',
shapeColor: '#babbcc',fillColor: '#2a2c3f'},
{ themeName: 'Flatly', backgroundColor: '#ffffff', textColor: '#343a40',
shapeColor: '#7b8a8b',fillColor: '#ffffff'},
{ themeName: 'Black', backgroundColor: '#1a1a1a', textColor: '#babbcc',
shapeColor: '#b5b4bc',fillColor: '#1f2020'},
{ themeName: 'Light', backgroundColor: '#eef1f6', textColor: '#3b445e',
shapeColor: '#343a40',fillColor: '#ffffff'},
{ themeName: 'Midcentury', backgroundColor: '#DBE2E0', textColor: '#11556c',
shapeColor: '#343a40',fillColor: '#eeede9'},
{ themeName: 'Spacelab', backgroundColor: '#ffffff', textColor: '#343a40',
shapeColor: '#333333', fillColor: '#eeeeee'},
{ themeName: 'Candy', backgroundColor: '#d5efff', textColor: '#11556c',
shapeColor: '#343a40',fillColor: '#fbe7f8' },
{ themeName: 'Violet', backgroundColor: '#f1eef6', textColor: '#3b445e',
shapeColor: '#343a40',fillColor: '#f8fafe' },
{ themeName: 'Unicorn', backgroundColor: '#f1eef6', textColor: '#343a40',
shapeColor: '#343a40',fillColor: '#f8fafe' },
]
/** /**
* add context menu which will offer download as pdf and download png. * add context menu which will offer download as pdf and download png.
@ -81,8 +54,7 @@ export class SolidityUmlGen extends ViewPlugin implements ISolidityUmlGen {
this.currentlySelectedTheme = '' this.currentlySelectedTheme = ''
this.themeName = '' this.themeName = ''
this.themeCollection = themeCollection this.activeTheme = {} as ThemeSummary
this.activeTheme = themeCollection.find(t => t.themeName === 'Dark')
this.appManager = appManager this.appManager = appManager
this.element = document.createElement('div') this.element = document.createElement('div')
this.element.setAttribute('id', 'sol-uml-gen') this.element.setAttribute('id', 'sol-uml-gen')
@ -100,14 +72,20 @@ export class SolidityUmlGen extends ViewPlugin implements ISolidityUmlGen {
const normalized = normalizeContractPath(file) const normalized = normalizeContractPath(file)
this.currentFile = normalized[normalized.length - 1] this.currentFile = normalized[normalized.length - 1]
try { try {
if (data.sources && Object.keys(data.sources).length > 1) { // we should flatten first as there are multiple asts if (data.sources && Object.keys(data.sources).length > 1) {
// we should flatten first as there are multiple asts
result = await this.flattenContract(source, file, data) result = await this.flattenContract(source, file, data)
} }
const ast = result.length > 1 ? parser.parse(result) : parser.parse(source.sources[file].content) const ast = result.length > 1 ? parser.parse(result) : parser.parse(source.sources[file].content)
this.umlClasses = convertAST2UmlClasses(ast, this.currentFile) this.umlClasses = convertAST2UmlClasses(ast, this.currentFile)
let umlDot = '' let umlDot = ''
this.activeTheme = themeCollection.find(theme => theme.themeName === currentTheme.name) this.activeTheme = await this.call('theme', 'currentTheme')
umlDot = convertUmlClasses2Dot(this.umlClasses, false, { backColor: this.activeTheme.backgroundColor, textColor: this.activeTheme.textColor, shapeColor: this.activeTheme.shapeColor, fillColor: this.activeTheme.fillColor }) umlDot = convertUmlClasses2Dot(this.umlClasses, false, {
backColor: this.activeTheme.backgroundColor,
textColor: this.activeTheme.textColor,
shapeColor: this.activeTheme.shapeColor,
fillColor: this.activeTheme.fillColor
})
const payload = vizRenderStringSync(umlDot) const payload = vizRenderStringSync(umlDot)
this.updatedSvg = payload this.updatedSvg = payload
_paq.push(['trackEvent', 'solidityumlgen', 'umlgenerated']) _paq.push(['trackEvent', 'solidityumlgen', 'umlgenerated'])
@ -120,42 +98,26 @@ export class SolidityUmlGen extends ViewPlugin implements ISolidityUmlGen {
} }
getThemeCssVariables(cssVars: string) { getThemeCssVariables(cssVars: string) {
return window.getComputedStyle(document.documentElement) return window.getComputedStyle(document.documentElement).getPropertyValue(cssVars)
.getPropertyValue(cssVars)
} }
private handleThemeChange() { private handleThemeChange() {
this.on('theme', 'themeChanged', async (theme) => { this.on('theme', 'themeChanged', async (theme) => {
this.currentlySelectedTheme = theme.quality this.currentlySelectedTheme = theme.quality
const themeQuality: ThemeQualityType = await this.call('theme', 'currentTheme')
themeCollection.forEach((theme) => {
if (theme.themeName === themeQuality.name) {
this.themeDark = theme.backgroundColor
this.activeTheme = theme this.activeTheme = theme
const umlDot = convertUmlClasses2Dot(this.umlClasses, false, { backColor: this.activeTheme.backgroundColor, textColor: this.activeTheme.textColor, shapeColor: this.activeTheme.shapeColor, fillColor: this.activeTheme.fillColor }) this.themeDark = theme.backgroundColor
const umlDot = convertUmlClasses2Dot(this.umlClasses, false, {
backColor: this.activeTheme.backgroundColor,
textColor: this.activeTheme.textColor,
shapeColor: this.activeTheme.shapeColor,
fillColor: this.activeTheme.fillColor
})
this.updatedSvg = vizRenderStringSync(umlDot) this.updatedSvg = vizRenderStringSync(umlDot)
this.renderComponent() this.renderComponent()
}
})
await this.call('tabs', 'focus', 'solidityumlgen') await this.call('tabs', 'focus', 'solidityumlgen')
}) })
} }
async mangleSvgPayload(svgPayload: string) : Promise<string> {
const parser = new DOMParser()
const themeQuality: ThemeQualityType = await this.call('theme', 'currentTheme')
const parsedDocument = parser.parseFromString(svgPayload, 'image/svg+xml')
const element = parsedDocument.getElementsByTagName('svg')
themeCollection.forEach((theme) => {
if (theme.themeName === themeQuality.name) {
parsedDocument.documentElement.setAttribute('style', `background-color: var(${this.getThemeCssVariables('--body-bg')})`)
element[0].setAttribute('fill', theme.backgroundColor)
}
})
const stringifiedSvg = new XMLSerializer().serializeToString(parsedDocument)
return stringifiedSvg
}
onDeactivation(): void { onDeactivation(): void {
this.off('solidity', 'compilationFinished') this.off('solidity', 'compilationFinished')
} }
@ -185,8 +147,6 @@ export class SolidityUmlGen extends ViewPlugin implements ISolidityUmlGen {
return result return result
} }
async showUmlDiagram(svgPayload: string) { async showUmlDiagram(svgPayload: string) {
this.updatedSvg = svgPayload this.updatedSvg = svgPayload
this.renderComponent() this.renderComponent()
@ -203,9 +163,11 @@ export class SolidityUmlGen extends ViewPlugin implements ISolidityUmlGen {
} }
render() { render() {
return <div id='sol-uml-gen'> return (
<div id="sol-uml-gen">
<PluginViewWrapper plugin={this} /> <PluginViewWrapper plugin={this} />
</div> </div>
)
} }
renderComponent() { renderComponent() {
@ -218,12 +180,13 @@ export class SolidityUmlGen extends ViewPlugin implements ISolidityUmlGen {
themeDark: this.themeDark, themeDark: this.themeDark,
fileName: this.currentFile, fileName: this.currentFile,
themeCollection: this.themeCollection, themeCollection: this.themeCollection,
activeTheme: this.activeTheme, activeTheme: this.activeTheme
}) })
} }
updateComponent(state: any) { updateComponent(state: any) {
return <RemixUiSolidityUmlGen return (
<RemixUiSolidityUmlGen
updatedSvg={state.updatedSvg} updatedSvg={state.updatedSvg}
loading={state.loading} loading={state.loading}
themeSelected={state.currentlySelectedTheme} themeSelected={state.currentlySelectedTheme}
@ -232,10 +195,10 @@ export class SolidityUmlGen extends ViewPlugin implements ISolidityUmlGen {
themeCollection={state.themeCollection} themeCollection={state.themeCollection}
themeDark={state.themeDark} themeDark={state.themeDark}
/> />
)
} }
} }
interface Sol2umlClassOptions extends ClassOptions { interface Sol2umlClassOptions extends ClassOptions {
backColor?: string backColor?: string
shapeColor?: string shapeColor?: string
@ -245,12 +208,7 @@ interface Sol2umlClassOptions extends ClassOptions {
import {dirname} from 'path' import {dirname} from 'path'
import {convertClass2Dot} from 'sol2uml/lib/converterClass2Dot' import {convertClass2Dot} from 'sol2uml/lib/converterClass2Dot'
import { import {Association, ClassStereotype, ReferenceType, UmlClass} from 'sol2uml/lib/umlClass'
Association,
ClassStereotype,
ReferenceType,
UmlClass,
} from 'sol2uml/lib/umlClass'
import {findAssociatedClass} from 'sol2uml/lib/associations' import {findAssociatedClass} from 'sol2uml/lib/associations'
// const debug = require('debug')('sol2uml') // const debug = require('debug')('sol2uml')
@ -263,11 +221,7 @@ import { findAssociatedClass } from 'sol2uml/lib/associations'
* @param classOptions command line options for the `class` command * @param classOptions command line options for the `class` command
* @return dotString Graphviz's DOT format for defining nodes, edges and clusters. * @return dotString Graphviz's DOT format for defining nodes, edges and clusters.
*/ */
export function convertUmlClasses2Dot( export function convertUmlClasses2Dot(umlClasses: UmlClass[], clusterFolders: boolean = false, classOptions: Sol2umlClassOptions = {}): string {
umlClasses: UmlClass[],
clusterFolders: boolean = false,
classOptions: Sol2umlClassOptions = {}
): string {
let dotString: string = ` let dotString: string = `
digraph UmlClassDiagram { digraph UmlClassDiagram {
rankdir=BT rankdir=BT
@ -332,10 +286,7 @@ function sortUmlClassesByCodePath(umlClasses: UmlClass[]): UmlClass[] {
}) })
} }
export function addAssociationsToDot( export function addAssociationsToDot(umlClasses: UmlClass[], classOptions: ClassOptions = {}): string {
umlClasses: UmlClass[],
classOptions: ClassOptions = {}
): string {
let dotString: string = '' let dotString: string = ''
// for each class // for each class
@ -365,18 +316,9 @@ export function addAssociationsToDot(
// for each association in that class // for each association in that class
for (const association of Object.values(sourceUmlClass.associations)) { for (const association of Object.values(sourceUmlClass.associations)) {
const targetUmlClass = findAssociatedClass( const targetUmlClass = findAssociatedClass(association, sourceUmlClass, umlClasses)
association,
sourceUmlClass,
umlClasses
)
if (targetUmlClass) { if (targetUmlClass) {
dotString += addAssociationToDot( dotString += addAssociationToDot(sourceUmlClass, targetUmlClass, association, classOptions)
sourceUmlClass,
targetUmlClass,
association,
classOptions
)
} }
} }
} }
@ -384,41 +326,23 @@ export function addAssociationsToDot(
return dotString return dotString
} }
function addAssociationToDot( function addAssociationToDot(sourceUmlClass: UmlClass, targetUmlClass: UmlClass, association: Association, classOptions: ClassOptions = {}): string {
sourceUmlClass: UmlClass,
targetUmlClass: UmlClass,
association: Association,
classOptions: ClassOptions = {}
): string {
// do not include library or interface associations if hidden // do not include library or interface associations if hidden
// Or associations to Structs, Enums or Constants if they are hidden // Or associations to Structs, Enums or Constants if they are hidden
if ( if (
(classOptions.hideLibraries && (classOptions.hideLibraries && (sourceUmlClass.stereotype === ClassStereotype.Library || targetUmlClass.stereotype === ClassStereotype.Library)) ||
(sourceUmlClass.stereotype === ClassStereotype.Library || (classOptions.hideInterfaces && (targetUmlClass.stereotype === ClassStereotype.Interface || sourceUmlClass.stereotype === ClassStereotype.Interface)) ||
targetUmlClass.stereotype === ClassStereotype.Library)) || (classOptions.hideAbstracts && (targetUmlClass.stereotype === ClassStereotype.Abstract || sourceUmlClass.stereotype === ClassStereotype.Abstract)) ||
(classOptions.hideInterfaces && (classOptions.hideStructs && targetUmlClass.stereotype === ClassStereotype.Struct) ||
(targetUmlClass.stereotype === ClassStereotype.Interface || (classOptions.hideEnums && targetUmlClass.stereotype === ClassStereotype.Enum) ||
sourceUmlClass.stereotype === ClassStereotype.Interface)) || (classOptions.hideConstants && targetUmlClass.stereotype === ClassStereotype.Constant)
(classOptions.hideAbstracts &&
(targetUmlClass.stereotype === ClassStereotype.Abstract ||
sourceUmlClass.stereotype === ClassStereotype.Abstract)) ||
(classOptions.hideStructs &&
targetUmlClass.stereotype === ClassStereotype.Struct) ||
(classOptions.hideEnums &&
targetUmlClass.stereotype === ClassStereotype.Enum) ||
(classOptions.hideConstants &&
targetUmlClass.stereotype === ClassStereotype.Constant)
) { ) {
return '' return ''
} }
let dotString = `\n${sourceUmlClass.id} -> ${targetUmlClass.id} [` let dotString = `\n${sourceUmlClass.id} -> ${targetUmlClass.id} [`
if ( if (association.referenceType == ReferenceType.Memory || (association.realization && targetUmlClass.stereotype === ClassStereotype.Interface)) {
association.referenceType == ReferenceType.Memory ||
(association.realization &&
targetUmlClass.stereotype === ClassStereotype.Interface)
) {
dotString += 'style=dashed, ' dotString += 'style=dashed, '
} }

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

Loading…
Cancel
Save