desktopmerge
filip mertens 1 year ago
parent e42a392286
commit e9c806d750
  1. 46
      .circleci/config.yml
  2. 12
      .eslintrc.json
  3. 4
      .husky/pre-commit
  4. 13
      .prettierignore
  5. 7
      .prettierrc.json
  6. 18
      CONTRIBUTING.md
  7. 23
      README.md
  8. 59
      apps/circuit-compiler/project.json
  9. 17
      apps/circuit-compiler/src/app/app.tsx
  10. 224
      apps/circuit-compiler/src/app/services/circomPluginClient.ts
  11. 0
      apps/circuit-compiler/src/css/app.css
  12. 11
      apps/circuit-compiler/src/example/simple.circom
  13. 15
      apps/circuit-compiler/src/index.html
  14. 8
      apps/circuit-compiler/src/main.tsx
  15. 7
      apps/circuit-compiler/src/polyfills.ts
  16. 17
      apps/circuit-compiler/src/profile.json
  17. 24
      apps/circuit-compiler/tsconfig.app.json
  18. 17
      apps/circuit-compiler/tsconfig.json
  19. 92
      apps/circuit-compiler/webpack.config.js
  20. 12
      apps/debugger/src/app/app.tsx
  21. 1
      apps/debugger/src/app/debugger.ts
  22. 11
      apps/debugger/src/main.tsx
  23. 67
      apps/debugger/webpack.config.js
  24. 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. 7
      apps/doc-viewer/src/app/App.tsx
  30. 4
      apps/doc-viewer/src/main.tsx
  31. 32
      apps/doc-viewer/webpack.config.js
  32. 20
      apps/etherscan/src/app/AppContext.tsx
  33. 43
      apps/etherscan/src/app/RemixPlugin.tsx
  34. 78
      apps/etherscan/src/app/app.tsx
  35. 60
      apps/etherscan/src/app/components/HeaderWithSettings.tsx
  36. 30
      apps/etherscan/src/app/components/SubmitButton.tsx
  37. 5
      apps/etherscan/src/app/hooks/useLocalStorage.tsx
  38. 10
      apps/etherscan/src/app/layouts/Default.tsx
  39. 16
      apps/etherscan/src/app/routes.tsx
  40. 64
      apps/etherscan/src/app/utils/networks.ts
  41. 2
      apps/etherscan/src/app/utils/verify.ts
  42. 54
      apps/etherscan/src/app/views/CaptureKeyView.tsx
  43. 16
      apps/etherscan/src/app/views/ErrorView.tsx
  44. 22
      apps/etherscan/src/app/views/HomeView.tsx
  45. 134
      apps/etherscan/src/app/views/ReceiptsView.tsx
  46. 195
      apps/etherscan/src/app/views/VerifyView.tsx
  47. 14
      apps/etherscan/src/main.tsx
  48. 64
      apps/etherscan/webpack.config.js
  49. 56
      apps/remix-ide-e2e/nightwatch.ts
  50. 10
      apps/remix-ide-e2e/package.json
  51. 6
      apps/remix-ide-e2e/src/commands/checkAnnotations.ts
  52. 6
      apps/remix-ide-e2e/src/commands/checkAnnotationsNotPresent.ts
  53. 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. 104
      apps/remix-ide-e2e/src/local-plugin/src/app/app.tsx
  57. 8
      apps/remix-ide-e2e/src/local-plugin/src/app/logger.tsx
  58. 2
      apps/remix-ide-e2e/src/tests/blockchain.test.ts
  59. 50
      apps/remix-ide-e2e/src/tests/contract_flattener.test.ts
  60. 72
      apps/remix-ide-e2e/src/tests/editor.test.ts
  61. 87
      apps/remix-ide-e2e/src/tests/editorHoverContext.test.ts
  62. 32
      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. 64
      apps/remix-ide-e2e/src/tests/sol2uml.test.ts
  68. 6
      apps/remix-ide-e2e/src/tests/solidityImport.test.ts
  69. 12
      apps/remix-ide-e2e/src/tests/solidityUnittests.test.ts
  70. 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. 130
      apps/remix-ide-e2e/src/types/index.d.ts
  76. 760
      apps/remix-ide-e2e/yarn.lock
  77. 4
      apps/remix-ide/background.js
  78. 2
      apps/remix-ide/ci/splice_tests.js
  79. 2
      apps/remix-ide/project.json
  80. 4
      apps/remix-ide/release-process.md
  81. 214
      apps/remix-ide/src/app.js
  82. 26
      apps/remix-ide/src/app/components/hidden-panel.tsx
  83. 32
      apps/remix-ide/src/app/components/main-panel.tsx
  84. 144
      apps/remix-ide/src/app/components/preload.tsx
  85. 17
      apps/remix-ide/src/app/components/side-panel.tsx
  86. 58
      apps/remix-ide/src/app/components/vertical-icons.tsx
  87. 13
      apps/remix-ide/src/app/editor/editor.js
  88. 128
      apps/remix-ide/src/app/files/fileManager.ts
  89. 8
      apps/remix-ide/src/app/panels/layout.ts
  90. 28
      apps/remix-ide/src/app/plugins/contractFlattener.tsx
  91. 24
      apps/remix-ide/src/app/plugins/notification.tsx
  92. 47
      apps/remix-ide/src/app/plugins/openaigpt.tsx
  93. 226
      apps/remix-ide/src/app/plugins/parser/code-parser.tsx
  94. 57
      apps/remix-ide/src/app/plugins/parser/services/code-parser-antlr-service.ts
  95. 7
      apps/remix-ide/src/app/plugins/parser/services/code-parser-compiler.ts
  96. 5
      apps/remix-ide/src/app/plugins/parser/services/code-parser-gas-service.ts
  97. 23
      apps/remix-ide/src/app/plugins/parser/services/code-parser-imports.ts
  98. 28
      apps/remix-ide/src/app/plugins/permission-handler-plugin.tsx
  99. 92
      apps/remix-ide/src/app/plugins/remixd-handle.tsx
  100. 31
      apps/remix-ide/src/app/plugins/solidity-script.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
default: false
orbs:
browser-tools: circleci/browser-tools@1.4.1
browser-tools: circleci/browser-tools@1.4.4
win: circleci/windows@5.0
jobs:
build:
@ -286,8 +286,9 @@ jobs:
- browser-tools/install-browser-tools:
install-firefox: false
install-chrome: true
install-chromedriver: false
install-geckodriver: false
install-chromedriver: true
- install-chromedriver-custom-linux
- run: google-chrome --version
- run: chromedriver --version
- run: rm LICENSE.chromedriver 2> /dev/null || true
@ -310,6 +311,11 @@ jobs:
- run: ls -la ./dist/apps/remix-ide/assets/js
- 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:
name: Start Selenium
command: yarn run selenium
@ -344,7 +350,8 @@ jobs:
install-firefox: false
install-chrome: true
install-geckodriver: false
install-chromedriver: true
install-chromedriver: false
- install-chromedriver-custom-linux
- run: google-chrome --version
- run: chromedriver --version
- run: rm LICENSE.chromedriver 2> /dev/null || true
@ -355,6 +362,7 @@ jobs:
- run: unzip ./persist/plugin-<< parameters.plugin >>.zip
- run: yarn install --cwd ./apps/remix-ide-e2e --modules-folder ../../node_modules
- 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:
name: Start Selenium
command: yarn run selenium
@ -522,3 +530,35 @@ workflows:
only: remix_beta
# 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",
"no-empty": "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": [
"plugin:@nrwl/nx/javascript"
],
"rules": {}
"rules": {
"indent": ["error", 2]
}
}
],
"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>
```
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)
[![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)
[![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)
</div>
@ -63,14 +63,13 @@ yarn global add nx
```bash
git clone https://github.com/ethereum/remix-project.git
```
* Build `remix-project`:
```bash
cd remix-project
yarn install
yarn run build:libs // Build remix libs
nx build
nx serve
```
* Build and Run `remix-project`:
1. Move to project directory: `cd remix-project`
2. Install dependencies: `yarn install` or simply run `yarn`
3. Build Remix libraries: `yarn run build:libs`
4. Build Remix project: `yarn build`
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.
@ -151,7 +150,7 @@ To run the Selenium tests via Nightwatch:
- Install Selenium for the first time: `yarn run selenium-install`
- Run a selenium server: `yarn run selenium`
- Build & Serve Remix: `nx serve`
- Build & Serve Remix: `yarn serve`
- Run all the end-to-end tests:
for Firefox: `yarn run nightwatch_local_firefox`, or
@ -283,7 +282,9 @@ parameters:
## Important Links
- Official website: https://remix-project.org
- 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
- Twitter: https://twitter.com/ethereumremix
- Join Discord: https://discord.gg/q4vS2GVn

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

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

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

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

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

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

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

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

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

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

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

@ -1,8 +1,8 @@
import React, { useState, useEffect } from 'react';
import React, {useState, useEffect} from 'react'
import { DebuggerUI } from '@remix-ui/debugger-ui' // eslint-disable-line
import {DebuggerUI} from '@remix-ui/debugger-ui' // eslint-disable-line
import { DebuggerClientApi } from './debugger'
import {DebuggerClientApi} from './debugger'
const remix = new DebuggerClientApi()
@ -11,7 +11,7 @@ export const App = () => {
<div className="debugger">
<DebuggerUI debuggerAPI={remix} />
</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
onStopDebugging: () => void // called when debug stops
}

@ -1,9 +1,6 @@
import React from 'react';
import ReactDOM from 'react-dom';
import React from 'react'
import ReactDOM from 'react-dom'
import App from './app/app';
import App from './app/app'
ReactDOM.render(
<App />,
document.getElementById('root')
);
ReactDOM.render(<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 TerserPlugin = require("terser-webpack-plugin")
const CssMinimizerPlugin = require("css-minimizer-webpack-plugin")
const TerserPlugin = require('terser-webpack-plugin')
const CssMinimizerPlugin = require('css-minimizer-webpack-plugin')
// Nx plugins for webpack.
module.exports = composePlugins(withNx(), (config) => {
@ -12,56 +11,52 @@ module.exports = composePlugins(withNx(), (config) => {
// 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'),
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',
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',
process: 'process/browser'
})
)
// souce-map loader
config.module.rules.push({
test: /\.js$/,
use: ["source-map-loader"],
enforce: "pre"
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({
@ -71,17 +66,17 @@ module.exports = composePlugins(withNx(), (config) => {
compress: false,
mangle: false,
format: {
comments: false,
},
comments: false
}
},
extractComments: false,
extractComments: false
}),
new CssMinimizerPlugin(),
];
new CssMinimizerPlugin()
]
config.watchOptions = {
ignored: /node_modules/
}
return config;
});
return config
})

@ -8,9 +8,9 @@ export const client = new DocGenClient()
const App = () => {
// eslint-disable-next-line @typescript-eslint/no-unused-vars
const [themeType, setThemeType] = useState<string>('dark');
const [hasBuild, setHasBuild] = useState<boolean>(false);
const [fileName, setFileName] = useState<string>('');
const [themeType, setThemeType] = useState<string>('dark')
const [hasBuild, setHasBuild] = useState<boolean>(false)
const [fileName, setFileName] = useState<string>('')
useEffect(() => {
const watchThemeSwitch = async () => {
@ -21,19 +21,27 @@ const App = () => {
setHasBuild(true)
setFileName(fileName)
})
client.eventEmitter.on('docsGenerated', (docs: string[]) => {
})
client.eventEmitter.on('docsGenerated', (docs: string[]) => {})
}
watchThemeSwitch()
}, [])
return (
<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>
{fileName && <div className="border-bottom border-top px-2 py-3 justify-center align-items-center d-flex">
<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>
{fileName && (
<div className="border-bottom border-top px-2 py-3 justify-center align-items-center d-flex">
<h6>File: {fileName}</h6>
</div>}
{hasBuild && <button className="btn btn-primary btn-block mt-4" onClick={() => client.generateDocs()}>Generate Docs</button>}
</div>
)}
{hasBuild && (
<button className="btn btn-primary btn-block mt-4" onClick={() => client.generateDocs()}>
Generate Docs
</button>
)}
</div>
)
}

@ -1,4 +1,4 @@
import { useState } from "react";
import { useState } from 'react'
export function useLocalStorage(key: string, initialValue: any) {
// State to store our value
@ -6,32 +6,31 @@ export function useLocalStorage(key: string, initialValue: any) {
const [storedValue, setStoredValue] = useState(() => {
try {
// 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
return item ? JSON.parse(item) : initialValue;
return item ? JSON.parse(item) : initialValue
} catch (error) {
// If error also return initialValue
console.log(error);
return initialValue;
console.log(error)
return initialValue
}
});
})
// Return a wrapped version of useState's setter function that ...
// ... persists the new value to localStorage.
const setValue = (value: any) => {
try {
// Allow value to be a function so we have same API as useState
const valueToStore =
value instanceof Function ? value(storedValue) : value;
const valueToStore = value instanceof Function ? value(storedValue) : value
// Save state
setStoredValue(valueToStore);
setStoredValue(valueToStore)
// Save to local storage
window.localStorage.setItem(key, JSON.stringify(valueToStore));
window.localStorage.setItem(key, JSON.stringify(valueToStore))
} catch (error) {
// 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 = () => {
return (
<div
style={{
width: "100%",
display: "flex",
flexDirection: "column",
alignItems: "center",
width: '100%',
display: 'flex',
flexDirection: 'column',
alignItems: 'center',
}}
>
<img
style={{ paddingBottom: "2em" }}
style={{ paddingBottom: '2em' }}
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>
Please raise an issue:{" "}
<a
style={{ color: "red" }}
href="https://github.com/Machinalabs/remix-ethdoc-plugin/issues"
>
Please raise an issue:{' '}
<a style={{ color: 'red' }} href="https://github.com/Machinalabs/remix-ethdoc-plugin/issues">
Here
</a>
</h5>
</div>
);
};
)
}

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

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

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

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

@ -1,8 +1,8 @@
const { composePlugins, withNx } = require('@nrwl/webpack')
const { withReact } = require('@nrwl/react')
const {composePlugins, withNx} = require('@nrwl/webpack')
const {withReact} = require('@nrwl/react')
const webpack = require('webpack')
const TerserPlugin = require("terser-webpack-plugin")
const CssMinimizerPlugin = require("css-minimizer-webpack-plugin")
const TerserPlugin = require('terser-webpack-plugin')
const CssMinimizerPlugin = require('css-minimizer-webpack-plugin')
// Nx plugins for webpack.
module.exports = composePlugins(withNx(), withReact(), (config) => {
@ -12,7 +12,7 @@ module.exports = composePlugins(withNx(), withReact(), (config) => {
// add externals
config.externals = {
...config.externals,
solc: 'solc',
solc: 'solc'
}
// add public path
@ -23,18 +23,16 @@ module.exports = composePlugins(withNx(), withReact(), (config) => {
new webpack.ProvidePlugin({
Buffer: ['buffer', 'Buffer'],
url: ['url', 'URL'],
process: 'process/browser',
}),
new webpack.DefinePlugin({
process: 'process/browser'
}),
new webpack.DefinePlugin({})
)
// souce-map loader
config.module.rules.push({
test: /\.js$/,
use: ["source-map-loader"],
enforce: "pre"
use: ['source-map-loader'],
enforce: 'pre'
})
config.ignoreWarnings = [/Failed to parse source map/] // ignore source-map-loader warnings
@ -48,13 +46,13 @@ module.exports = composePlugins(withNx(), withReact(), (config) => {
compress: false,
mangle: false,
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 { PluginClient } from "@remixproject/plugin"
import React from 'react'
import {PluginClient} from '@remixproject/plugin'
import { Receipt, ThemeType } from "./types"
import {Receipt, ThemeType} from './types'
export const AppContext = React.createContext({
apiKey: "",
apiKey: '',
setAPIKey: (value: string) => {
console.log("Set API Key from Context")
console.log('Set API Key from Context')
},
clientInstance: {} as PluginClient,
receipts: [] as Receipt[],
setReceipts: (receipts: Receipt[]) => {
console.log("Calling Set Receipts")
console.log('Calling Set Receipts')
},
contracts: [] as 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) => {
console.log("Calling Set Theme Type")
},
console.log('Calling Set Theme Type')
}
})

@ -1,24 +1,43 @@
import { PluginClient } from '@remixproject/plugin';
import { verify, EtherScanReturn } from './utils/verify';
import { getReceiptStatus, getEtherScanApi, getNetworkName, getProxyContractReceiptStatus } from './utils';
import {PluginClient} from '@remixproject/plugin'
import {verify, EtherScanReturn} from './utils/verify'
import {getReceiptStatus, getEtherScanApi, getNetworkName, getProxyContractReceiptStatus} from './utils'
export class RemixClient extends PluginClient {
loaded() {
return this.onload()
}
async verify (apiKey: 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) => {})
async verify(
apiKey: 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
}
async receiptStatus (receiptGuid: string, apiKey: string, isProxyContract: boolean) {
async receiptStatus(receiptGuid: string, apiKey: string, isProxyContract: boolean) {
try {
const { network, networkId } = await getNetworkName(this)
if (network === "vm") {
throw new Error("Cannot check the receipt status in the selected network")
const {network, networkId} = await getNetworkName(this)
if (network === 'vm') {
throw new Error('Cannot check the receipt status in the selected network')
}
const etherscanApi = getEtherScanApi(networkId)
let receiptStatus
@ -29,7 +48,7 @@ export class RemixClient extends PluginClient {
message: receiptStatus.result,
succeed: receiptStatus.status === '0' ? false : true
}
} catch (e: any){
} catch (e: any) {
return {
status: 'error',
message: e.message,

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

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

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

@ -1,4 +1,4 @@
import { useState } from "react"
import {useState} from 'react'
export function useLocalStorage(key: string, initialValue: any) {
// State to store our value
@ -21,8 +21,7 @@ export function useLocalStorage(key: string, initialValue: any) {
const setValue = (value: any) => {
try {
// Allow value to be a function so we have same API as useState
const valueToStore =
value instanceof Function ? value(storedValue) : value
const valueToStore = value instanceof Function ? value(storedValue) : value
// Save state
setStoredValue(valueToStore)
// 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 {
from: string
title?: string
}
export const DefaultLayout: React.FC<PropsWithChildren<Props>> = ({
children,
from,
title
}) => {
export const DefaultLayout: React.FC<PropsWithChildren<Props>> = ({children, from, title}) => {
return (
<div>
<HeaderWithSettings from={from} title={title} />

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

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

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

@ -1,21 +1,13 @@
import React from "react"
import React from 'react'
export const ErrorView: React.FC = () => {
return (
<div className="d-flex w-100 flex-column align-items-center">
<img
className="pb-4"
width="250"
src="https://res.cloudinary.com/key-solutions/image/upload/v1580400635/solid/error-png.png"
alt="Error page"
/>
<img 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>
Please raise an issue:{" "}
<a
className="text-danger"
href="https://github.com/ethereum/remix-project/issues"
>
Please raise an issue:{' '}
<a className="text-danger" href="https://github.com/ethereum/remix-project/issues">
Here
</a>
</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 { Receipt } from "../types"
import {AppContext} from '../AppContext'
import {Receipt} from '../types'
import { VerifyView } from "./VerifyView"
import {VerifyView} from './VerifyView'
export const HomeView: React.FC = () => {
return (
<AppContext.Consumer>
{({ 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')
})
}
{({apiKey, clientInstance, setReceipts, receipts, contracts}) => {
return !apiKey ? (
<Navigate
to={{
pathname: "/settings"
pathname: '/settings'
}}
/>
) : (
@ -33,8 +28,7 @@ export const HomeView: React.FC = () => {
}}
/>
)
}
}
}}
</AppContext.Consumer>
)
}

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

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

@ -1,7 +1,11 @@
import { StrictMode } from 'react';
import * as ReactDOM from 'react-dom';
import {StrictMode} from 'react'
import * as ReactDOM from 'react-dom'
import App from './app/app'
import App from './app/app';
ReactDOM.render(<StrictMode><App /></StrictMode>, document.getElementById('root'));
ReactDOM.render(
<StrictMode>
<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 TerserPlugin = require("terser-webpack-plugin")
const CssMinimizerPlugin = require("css-minimizer-webpack-plugin")
const TerserPlugin = require('terser-webpack-plugin')
const CssMinimizerPlugin = require('css-minimizer-webpack-plugin')
const versionData = {
timestamp: Date.now(),
@ -15,29 +15,29 @@ module.exports = composePlugins(withNx(), (config) => {
// 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'),
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',
solc: 'solc'
}
// add public path
@ -47,26 +47,24 @@ module.exports = composePlugins(withNx(), (config) => {
config.output.filename = `[name].plugin-etherscan.${versionData.timestamp}.js`
config.output.chunkFilename = `[name].plugin-etherscan.${versionData.timestamp}.js`
// add copy & provide plugin
config.plugins.push(
new webpack.ProvidePlugin({
Buffer: ['buffer', 'Buffer'],
url: ['url', 'URL'],
process: 'process/browser',
process: 'process/browser'
})
)
// souce-map loader
config.module.rules.push({
test: /\.js$/,
use: ["source-map-loader"],
enforce: "pre"
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({
@ -76,17 +74,17 @@ module.exports = composePlugins(withNx(), (config) => {
compress: false,
mangle: false,
format: {
comments: false,
},
comments: false
}
},
extractComments: false,
extractComments: false
}),
new CssMinimizerPlugin(),
];
new CssMinimizerPlugin()
]
config.watchOptions = {
ignored: /node_modules/
}
return config;
});
return config
})

@ -7,7 +7,7 @@ module.exports = {
globals_path: '',
test_settings: {
default: {
'default': {
selenium_port: 4444,
selenium_host: 'localhost',
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']
},
chrome: {
'chrome': {
desiredCapabilities: {
browserName: 'chrome',
javascriptEnabled: true,
acceptSslCerts: true,
'browserName': 'chrome',
'javascriptEnabled': true,
'acceptSslCerts': true,
'goog:chromeOptions': {
args: ['window-size=2560,1440',
args: [
'window-size=2560,1440',
'start-fullscreen',
'--no-sandbox',
'--headless',
'--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: {
browserName: 'chrome',
javascriptEnabled: true,
acceptSslCerts: true,
'browserName': 'chrome',
'javascriptEnabled': true,
'acceptSslCerts': true,
'goog:chromeOptions': {
args: ['window-size=2560,1440', 'start-fullscreen', '--no-sandbox']
}
@ -53,40 +54,33 @@ module.exports = {
'chrome-runAndDeploy': {
desiredCapabilities: {
browserName: 'chrome',
javascriptEnabled: true,
acceptSslCerts: true,
'browserName': 'chrome',
'javascriptEnabled': true,
'acceptSslCerts': true,
'goog:chromeOptions': {
args: ['window-size=2560,1440', 'start-fullscreen', '--no-sandbox', '--headless', '--verbose']
}
}
},
firefoxDesktop: {
'firefoxDesktop': {
desiredCapabilities: {
browserName: 'firefox',
javascriptEnabled: true,
acceptSslCerts: true,
'browserName': 'firefox',
'javascriptEnabled': true,
'acceptSslCerts': true,
'moz:firefoxOptions': {
args: [
'-width=2560',
'-height=1440'
]
args: ['-width=2560', '-height=1440']
}
}
},
firefox: {
'firefox': {
desiredCapabilities: {
browserName: 'firefox',
javascriptEnabled: true,
acceptSslCerts: true,
'browserName': 'firefox',
'javascriptEnabled': true,
'acceptSslCerts': true,
'moz:firefoxOptions': {
args: [
'-headless',
'-width=2560',
'-height=1440'
]
args: ['-headless', '-width=2560', '-height=1440']
}
}
}

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

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

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

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

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

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

@ -1,32 +1,43 @@
import React, { useEffect, useState } from 'react'
import { RemixPlugin } from './Client'
import { Logger } from './logger'
import { filePanelProfile } from '@remixproject/plugin-api'
import { filSystemProfile } from '@remixproject/plugin-api'
import { dGitProfile } from '@remixproject/plugin-api'
import { editorProfile } from '@remixproject/plugin-api'
import { settingsProfile } from '@remixproject/plugin-api'
import { networkProfile } from '@remixproject/plugin-api'
import { udappProfile } from '@remixproject/plugin-api'
import { compilerProfile } from '@remixproject/plugin-api'
import { contentImportProfile } from '@remixproject/plugin-api'
import { windowProfile } from '@remixproject/plugin-api'
import { pluginManagerProfile } from '@remixproject/plugin-api'
import { Profile } from '@remixproject/plugin-utils'
import React, {useEffect, useState} from 'react'
import {RemixPlugin} from './Client'
import {Logger} from './logger'
import {filePanelProfile} from '@remixproject/plugin-api'
import {filSystemProfile} from '@remixproject/plugin-api'
import {dGitProfile} from '@remixproject/plugin-api'
import {editorProfile} from '@remixproject/plugin-api'
import {settingsProfile} from '@remixproject/plugin-api'
import {networkProfile} from '@remixproject/plugin-api'
import {udappProfile} from '@remixproject/plugin-api'
import {compilerProfile} from '@remixproject/plugin-api'
import {contentImportProfile} from '@remixproject/plugin-api'
import {windowProfile} from '@remixproject/plugin-api'
import {pluginManagerProfile} from '@remixproject/plugin-api'
import {Profile} from '@remixproject/plugin-utils'
import './app.css'
const client = new RemixPlugin()
function App () {
function App() {
const [payload, setPayload] = useState<string>('')
const [log, setLog] = useState<any>()
const [started, setStarted] = useState<boolean>(false)
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)
}
@ -44,7 +55,7 @@ function App () {
const p = await client.call('manager', 'getProfile', name)
addProfiles = [...addProfiles, p]
}
setProfiles(profiles => [...profiles, ...addProfiles])
setProfiles((profiles) => [...profiles, ...addProfiles])
profiles.map((profile: Profile) => {
if (profile.events) {
@ -77,8 +88,10 @@ function App () {
let ob: any = null
try {
ob = JSON.parse(payload)
if (ob && !Array.isArray(ob)) { ob = [ob] }
} catch (e) { }
if (ob && !Array.isArray(ob)) {
ob = [ob]
}
} catch (e) {}
const args = ob || [payload]
setStarted(true)
setLog('')
@ -97,30 +110,41 @@ function App () {
return (
<div className="App container-fluid">
<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>
<Logger id='methods' log={log}></Logger>
<Logger id="methods" log={log}></Logger>
<label>events</label>
<Logger id='events' log={events}></Logger>
<input
className='form-control w-100'
type="text"
id="payload"
placeholder="Enter payload here..."
value={payload}
onChange={handleChange}
data-id="payload-input"
/>
<Logger id="events" log={events}></Logger>
<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) => {
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) => {
return <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>
const events = profile.events
? profile.events.map((event: string) => {
return (
<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>
)
}

@ -1,9 +1,13 @@
import React from 'react'
interface loggerProps {
log: any,
log: any
id: string
}
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'])
.clickLaunchIcon('udapp')
.selectAccount('0xCA35b7d915458EF540aDe6068dFe2F44E8fa733c') // this account will be used for this test suite
.click('.udapp_contractActionsContainerSingle > button')
.click('.udapp_contractActionsContainerSingle > div')
.clickInstance(0)
.clickFunction('foo - call', { types: 'uint256 p', values: '0' })
.perform((done) => {

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

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

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

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

@ -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) {
browser
.addFile('writeFile.js', { content: executeWriteFile })
@ -143,6 +162,14 @@ const executeWriteFile = `
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 run = async () => {
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"]')
.clickLaunchIcon('filePanel')
.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,
suppressNotFoundErrors: true
})
@ -34,7 +34,7 @@ module.exports = {
.click('[data-id="compilerContainerCompileBtn"]')
.clickLaunchIcon('filePanel')
.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,
suppressNotFoundErrors: true
})
@ -42,7 +42,7 @@ module.exports = {
.click('[data-id="compilerContainerCompileBtn"]')
.clickLaunchIcon('filePanel')
.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,
})
.clickLaunchIcon('solidity')
@ -81,6 +81,7 @@ module.exports = {
.verify.visible('[data-id="contractGUIDeployWithProxyLabel"]')
.waitForElementPresent('[data-id="contractGUIDeployWithProxyLabel"]')
.click('[data-id="contractGUIDeployWithProxyLabel"]')
.setValue('[data-id="initializeInputs-initialOwner"]', '0x5B38Da6a701c568545dCfcB03FcB875f56beddC4')
.createContract('')
.waitForElementContainsText('[data-id="udappNotifyModalDialogModalTitle-react"]', 'Deploy Implementation & Proxy (ERC1967)')
.waitForElementVisible('[data-id="udappNotify-modal-footer-ok-react"]')
@ -90,6 +91,7 @@ module.exports = {
.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...')
},
'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[2]/input', "R")
.useCss()
.setValue('[data-id="initializeInputs-initialOwner"]', '0x5B38Da6a701c568545dCfcB03FcB875f56beddC4')
.createContract('')
.waitForElementContainsText('[data-id="udappNotifyModalDialogModalTitle-react"]', 'Deploy Implementation & Proxy (ERC1967)')
.waitForElementVisible('[data-id="udappNotify-modal-footer-ok-react"]')
@ -140,6 +143,7 @@ module.exports = {
.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...')
},
'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) {
browser
.click('*[data-id="terminalClearConsole"]')
.waitForElementPresent('[data-id="deployAndRunClearInstances"]')
.click('[data-id="deployAndRunClearInstances"]')
.openFile('myTokenV2.sol')
@ -180,6 +185,7 @@ module.exports = {
.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')
@ -193,6 +199,7 @@ module.exports = {
})
.waitForElementPresent('[data-id="universalDappUiTitleExpander0"]')
.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) {
@ -207,6 +214,7 @@ module.exports = {
'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')
@ -230,16 +238,29 @@ module.exports = {
.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...')
},
'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(.,"7")]',
timeout: 60000
})
.goToVMTraceStep(129)
.waitForElementContainsText('*[data-id="functionPanel"]', 'version()', 60000)
.end()
}
}
@ -250,11 +271,11 @@ const sources = [
'myTokenV1.sol': {
content: `
// 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/access/OwnableUpgradeable.sol";
import "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol";
import "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol";
import "@openzeppelin/contracts-upgradeable/proxy/utils/UUPSUpgradeable.sol";
contract MyToken is Initializable, ERC721Upgradeable, OwnableUpgradeable, UUPSUpgradeable {
@ -263,9 +284,9 @@ const sources = [
_disableInitializers();
}
function initialize() initializer public {
function initialize(address initialOwner) initializer public {
__ERC721_init("MyToken", "MTK");
__Ownable_init();
__Ownable_init(initialOwner);
__UUPSUpgradeable_init();
}
@ -280,6 +301,8 @@ const sources = [
}, {
'myTokenV2.sol': {
content: `
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;
import "./myTokenV1.sol";
contract MyTokenV2 is MyToken {
@ -293,11 +316,11 @@ const sources = [
'initializeProxy.sol': {
content: `
// 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/access/OwnableUpgradeable.sol";
import "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol";
import "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol";
import "@openzeppelin/contracts-upgradeable/proxy/utils/UUPSUpgradeable.sol";
contract MyInitializedToken is Initializable, ERC721Upgradeable, OwnableUpgradeable, UUPSUpgradeable {
@ -306,9 +329,9 @@ const sources = [
_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);
__Ownable_init();
__Ownable_init(initialOwner);
__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'])
.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'])
},
'Static Analysis run with remixd #group3': '' + function (browser) {

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

@ -38,7 +38,7 @@ module.exports = {
'Test GitHub Import - from master branch #group1': function (browser: NightwatchBrowser) {
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'])
.clickLaunchIcon('filePanel')
.verifyContracts(['test7', 'ERC20'], { wait: 10000 })
@ -54,7 +54,7 @@ module.exports = {
'Test GitHub Import - no branch specified #group2': function (browser: NightwatchBrowser) {
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')
.click('li[data-id="treeViewLitreeViewItemREADME.txt"')
.addFile('Untitled6.sol', sources[5]['Untitled6.sol'])
@ -64,7 +64,7 @@ module.exports = {
'Test GitHub Import - raw URL #group4': function (browser: NightwatchBrowser) {
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')
.click('li[data-id="treeViewLitreeViewItemREADME.txt"')
.addFile('Untitled7.sol', sources[6]['Untitled7.sol'])

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

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

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

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

@ -10,11 +10,11 @@ const sources = [
'myTokenV1.sol': {
content: `
// 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/access/OwnableUpgradeable.sol";
import "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol";
import "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol";
import "@openzeppelin/contracts-upgradeable/proxy/utils/UUPSUpgradeable.sol";
contract MyToken is Initializable, ERC721Upgradeable, OwnableUpgradeable, UUPSUpgradeable {
@ -23,9 +23,9 @@ const sources = [
_disableInitializers();
}
function initialize() initializer public {
function initialize(address initialOwner) initializer public {
__ERC721_init("MyToken", "MTK");
__Ownable_init();
__Ownable_init(initialOwner);
__UUPSUpgradeable_init();
}
@ -35,6 +35,7 @@ const sources = [
override
{}
}
`
}
}
@ -166,7 +167,7 @@ module.exports = {
.click('[data-id="compilerContainerCompileBtn"]')
.clickLaunchIcon('filePanel')
.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,
suppressNotFoundErrors: true
})
@ -174,7 +175,7 @@ module.exports = {
.click('[data-id="compilerContainerCompileBtn"]')
.clickLaunchIcon('filePanel')
.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,
suppressNotFoundErrors: true
})
@ -182,7 +183,7 @@ module.exports = {
.click('[data-id="compilerContainerCompileBtn"]')
.clickLaunchIcon('filePanel')
.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,
})
.clickLaunchIcon('solidity')

@ -333,7 +333,7 @@ module.exports = {
.click('*[data-id="treeViewLitreeViewItemcontracts/MyToken.sol"]')
.pause(1000)
.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')
})
.waitForElementVisible('*[data-id="treeViewLitreeViewItemscripts"]')

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

File diff suppressed because it is too large Load Diff

@ -2,9 +2,9 @@
'use strict'
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
})
})

@ -24,4 +24,4 @@ exec(cmd, (error, stdout, stderr) => {
parts.push(files.slice(i * splitIndex, (i + 1) * splitIndex))
}
console.log(parts[job].join('\n'))
});
});

@ -3,7 +3,7 @@
"$schema": "../../node_modules/nx/schemas/project-schema.json",
"sourceRoot": "apps/remix-ide/src",
"projectType": "application",
"implicitDependencies": ["doc-gen", "doc-viewer", "etherscan", "vyper", "solhint", "walletconnect"],
"implicitDependencies": ["doc-gen", "doc-viewer", "etherscan", "vyper", "solhint", "walletconnect", "circuit-compiler"],
"targets": {
"build": {
"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 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 is automaticaly updated every time commits are pushed to master
remix-alpha.ethereum.org is automatically updated every time commits are pushed to master

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

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

@ -1,8 +1,8 @@
import React from 'react' // eslint-disable-line
import { AbstractPanel } from './panel'
import { RemixPluginPanel } from '@remix-ui/panel'
import {AbstractPanel} from './panel'
import {RemixPluginPanel} from '@remix-ui/panel'
import packageJson from '../../../../../package.json'
import { PluginViewWrapper } from '@remix-ui/helper'
import {PluginViewWrapper} from '@remix-ui/helper'
const profile = {
name: 'mainPanel',
@ -15,7 +15,7 @@ const profile = {
export class MainPanel extends AbstractPanel {
element: HTMLDivElement
dispatch: React.Dispatch<any> = () => {}
constructor (config) {
constructor(config) {
super(profile)
this.element = document.createElement('div')
this.element.setAttribute('data-id', 'mainPanelPluginsContainer')
@ -23,46 +23,50 @@ export class MainPanel extends AbstractPanel {
// this.config = config
}
setDispatch (dispatch: React.Dispatch<any>) {
setDispatch(dispatch: React.Dispatch<any>) {
this.dispatch = dispatch
}
onActivation () {
onActivation() {
this.renderComponent()
}
focus (name) {
focus(name) {
this.emit('focusChanged', name)
super.focus(name)
this.renderComponent()
}
addView (profile, view) {
addView(profile, view) {
super.addView(profile, view)
this.renderComponent()
}
removeView (profile) {
removeView(profile) {
super.removeView(profile)
this.renderComponent()
}
async showContent (name) {
async showContent(name) {
super.showContent(name)
this.renderComponent()
}
renderComponent () {
renderComponent() {
this.dispatch({
plugins: this.plugins
})
}
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) {
return <RemixPluginPanel header={<></>} plugins={state.plugins}/>
updateComponent(state: any) {
return <RemixPluginPanel header={<></>} plugins={state.plugins} />
}
}

@ -1,16 +1,15 @@
import { RemixApp } from '@remix-ui/app'
import React, { useEffect, useRef, useState } from 'react'
import { render } from 'react-dom'
import {RemixApp} from '@remix-ui/app'
import React, {useEffect, useRef, useState} from 'react'
import {render} from 'react-dom'
import * as packageJson from '../../../../../package.json'
import { fileSystem, fileSystems } from '../files/fileSystem'
import { indexedDBFileSystem } from '../files/filesystems/indexedDB'
import { localStorageFS } from '../files/filesystems/localStorage'
import { fileSystemUtility, migrationTestData } from '../files/filesystems/fileSystemUtility'
import {fileSystem, fileSystems} from '../files/fileSystem'
import {indexedDBFileSystem} from '../files/filesystems/indexedDB'
import {localStorageFS} from '../files/filesystems/localStorage'
import {fileSystemUtility, migrationTestData} from '../files/filesystems/fileSystemUtility'
import './styles/preload.css'
const _paq = window._paq = window._paq || []
const _paq = (window._paq = window._paq || [])
export const Preload = () => {
const [supported, setSupported] = useState<boolean>(true)
const [error, setError] = useState<boolean>(false)
const [showDownloader, setShowDownloader] = useState<boolean>(false)
@ -18,12 +17,19 @@ export const Preload = () => {
const remixIndexedDB = useRef<fileSystem>(new indexedDBFileSystem())
const localStorageFileSystem = useRef<fileSystem>(new localStorageFS())
// url parameters to e2e test the fallbacks and error warnings
const testmigrationFallback = useRef<boolean>(window.location.hash.includes('e2e_testmigration_fallback=true') && window.location.host === '127.0.0.1:8080' && window.location.protocol === 'http:')
const testmigrationResult = useRef<boolean>(window.location.hash.includes('e2e_testmigration=true') && window.location.host === '127.0.0.1:8080' && window.location.protocol === 'http:')
const testBlockStorage = useRef<boolean>(window.location.hash.includes('e2e_testblock_storage=true') && window.location.host === '127.0.0.1:8080' && window.location.protocol === 'http:')
const testmigrationFallback = useRef<boolean>(
window.location.hash.includes('e2e_testmigration_fallback=true') && window.location.host === '127.0.0.1:8080' && window.location.protocol === 'http:'
)
const testmigrationResult = useRef<boolean>(
window.location.hash.includes('e2e_testmigration=true') && window.location.host === '127.0.0.1:8080' && window.location.protocol === 'http:'
)
const testBlockStorage = useRef<boolean>(
window.location.hash.includes('e2e_testblock_storage=true') && window.location.host === '127.0.0.1:8080' && window.location.protocol === 'http:'
)
function loadAppComponent() {
import('../../app').then((AppComponent) => {
import('../../app')
.then((AppComponent) => {
const appComponent = new AppComponent.default()
appComponent.run().then(() => {
render(
@ -33,7 +39,8 @@ export const Preload = () => {
document.getElementById('root')
)
})
}).catch(err => {
})
.catch((err) => {
_paq.push(['trackEvent', 'Preload', 'error', err && err.message])
console.error('Error loading Remix:', err)
setError(true)
@ -51,12 +58,15 @@ export const Preload = () => {
setShowDownloader(false)
const fsUtility = new fileSystemUtility()
const migrationResult = await fsUtility.migrate(localStorageFileSystem.current, remixIndexedDB.current)
_paq.push(['trackEvent', 'Migrate', 'result', migrationResult?'success' : 'fail'])
_paq.push(['trackEvent', 'Migrate', 'result', migrationResult ? 'success' : 'fail'])
await setFileSystems()
}
const setFileSystems = async() => {
const fsLoaded = await remixFileSystems.current.setFileSystem([(testmigrationFallback.current || testBlockStorage.current)? null: remixIndexedDB.current, testBlockStorage.current? null:localStorageFileSystem.current])
const setFileSystems = async () => {
const fsLoaded = await remixFileSystems.current.setFileSystem([
testmigrationFallback.current || testBlockStorage.current ? null : remixIndexedDB.current,
testBlockStorage.current ? null : localStorageFileSystem.current
])
if (fsLoaded) {
console.log(fsLoaded.name + ' activated')
_paq.push(['trackEvent', 'Storage', 'activate', fsLoaded.name])
@ -67,7 +77,7 @@ export const Preload = () => {
}
}
const testmigration = async() => {
const testmigration = async () => {
if (testmigrationResult.current) {
const fsUtility = new fileSystemUtility()
fsUtility.populateWorkspace(migrationTestData, remixFileSystems.current.fileSystems['localstorage'].fs)
@ -76,69 +86,89 @@ export const Preload = () => {
useEffect(() => {
async function loadStorage() {
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(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 testmigration()
remixIndexedDB.current.loaded && await remixIndexedDB.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 && await setFileSystems()
remixIndexedDB.current.loaded && (await remixIndexedDB.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 && (await setFileSystems())
}
loadStorage()
}, [])
return <>
<div className='preload-container'>
<div className='preload-logo pb-4'>
return (
<>
<div className="preload-container">
<div className="preload-logo pb-4">
{logo}
<div className="info-secondary splash">
REMIX IDE
<br />
<span className='version'> v{packageJson.version}</span>
<span className="version"> v{packageJson.version}</span>
</div>
</div>
{!supported ?
<div className='preload-info-container alert alert-warning'>
Your browser does not support any of the filesystems required by Remix.
Either change the settings in your browser or use a supported browser.
</div> : null}
{error ?
<div className='preload-info-container alert alert-danger text-left'>
An unknown error has occurred while loading the application.<br></br>
{!supported ? (
<div className="preload-info-container alert alert-warning">
Your browser does not support any of the filesystems required by Remix. Either change the settings in your browser or use a supported browser.
</div>
) : null}
{error ? (
<div className="preload-info-container alert alert-danger text-left">
An unknown error has occurred while loading the application.
<br></br>
Doing a hard refresh might fix this issue:<br></br>
<div className='pt-2'>
Windows:<br></br>
- Chrome: CTRL + F5 or CTRL + Reload Button<br></br>
- Firefox: CTRL + SHIFT + R or CTRL + F5<br></br>
<div className="pt-2">
Windows:<br></br>- Chrome: CTRL + F5 or CTRL + Reload Button
<br></br>- Firefox: CTRL + SHIFT + R or CTRL + F5<br></br>
</div>
<div className='pt-2'>
MacOS:<br></br>
- Chrome & FireFox: CMD + SHIFT + R or SHIFT + Reload Button<br></br>
<div className="pt-2">
MacOS:<br></br>- Chrome & FireFox: CMD + SHIFT + R or SHIFT + Reload Button<br></br>
</div>
<div className='pt-2'>
Linux:<br></br>
- Chrome & FireFox: CTRL + SHIFT + R<br></br>
<div className="pt-2">
Linux:<br></br>- Chrome & FireFox: CTRL + SHIFT + R<br></br>
</div>
</div> : null}
{showDownloader ?
<div className='preload-info-container alert alert-info'>
</div>
) : null}
{showDownloader ? (
<div className="preload-info-container alert alert-info">
This app will be updated now. Please download a backup of your files now to make sure you don't lose your work.
<br></br>
You don't need to do anything else, your files will be available when the app loads.
<div onClick={async () => { await downloadBackup() }} data-id='downloadbackup-btn' className='btn btn-primary mt-1'>download backup</div>
<div onClick={async () => { await migrateAndLoad() }} data-id='skipbackup-btn' className='btn btn-primary mt-1'>skip backup</div>
</div> : null}
{(supported && !error && !showDownloader) ?
<div
onClick={async () => {
await downloadBackup()
}}
data-id="downloadbackup-btn"
className="btn btn-primary mt-1"
>
download backup
</div>
<div
onClick={async () => {
await migrateAndLoad()
}}
data-id="skipbackup-btn"
className="btn btn-primary mt-1"
>
skip backup
</div>
</div>
) : null}
{supported && !error && !showDownloader ? (
<div>
<i className="fas fa-spinner fa-spin fa-2x"></i>
</div> : null}
</div>
) : null}
</div>
</>
)
}
const logo = <svg id="Ebene_2" data-name="Ebene 2" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 105 100">
const logo = (
<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="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" />
</svg>
</svg>
)

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

@ -1,11 +1,11 @@
// eslint-disable-next-line no-use-before-define
import React from 'react'
import packageJson from '../../../../../package.json'
import { Plugin } from '@remixproject/engine'
import { EventEmitter } from 'events'
import { IconRecord, RemixUiVerticalIconsPanel } from '@remix-ui/vertical-icons-panel'
import { Profile } from '@remixproject/plugin-utils'
import { PluginViewWrapper } from '@remix-ui/helper'
import {Plugin} from '@remixproject/engine'
import {EventEmitter} from 'events'
import {IconRecord, RemixUiVerticalIconsPanel} from '@remix-ui/vertical-icons-panel'
import {Profile} from '@remixproject/plugin-utils'
import {PluginViewWrapper} from '@remix-ui/helper'
const profile = {
name: 'menuicons',
@ -21,44 +21,51 @@ export class VerticalIcons extends Plugin {
htmlElement: HTMLDivElement
icons: Record<string, IconRecord> = {}
dispatch: React.Dispatch<any> = () => {}
constructor () {
constructor() {
super(profile)
this.events = new EventEmitter()
this.htmlElement = document.createElement('div')
this.htmlElement.setAttribute('id', 'icon-panel')
}
renderComponent () {
const fixedOrder = ['filePanel', 'search', 'solidity','udapp', 'debugger', 'solidityStaticAnalysis', 'solidityUnitTesting', 'pluginManager']
renderComponent() {
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,
isRequired: fixedOrder.indexOf(value.profile.name) > -1
}}).sort((a,b) => {
}
})
.sort((a, b) => {
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)
})
const sorted: IconRecord[] = [
...required,
...divived.filter((value) => { return !value.isRequired })
...divived.filter((value) => {
return !value.isRequired
})
]
this.dispatch({
verticalIconsPlugin: this,
icons: sorted
})
}
setDispatch (dispatch: React.Dispatch<any>) {
setDispatch(dispatch: React.Dispatch<any>) {
this.dispatch = dispatch
}
onActivation () {
onActivation() {
this.renderComponent()
this.on('sidePanel', 'focusChanged', (name: string) => {
Object.keys(this.icons).map((o) => {
@ -69,7 +76,7 @@ export class VerticalIcons extends Plugin {
})
}
async linkContent (profile: Profile) {
async linkContent(profile: Profile) {
if (!profile.icon) return
if (!profile.kind) profile.kind = 'none'
this.icons[profile.name] = {
@ -81,7 +88,7 @@ export class VerticalIcons extends Plugin {
this.renderComponent()
}
unlinkContent (profile: Profile) {
unlinkContent(profile: Profile) {
delete this.icons[profile.name]
this.renderComponent()
}
@ -95,7 +102,7 @@ export class VerticalIcons extends Plugin {
* Set an icon as active
* @param {string} name Name of profile of the module to activate
*/
select (name: string) {
select(name: string) {
// TODO: Only keep `this.emit` (issue#2210)
this.emit('showContent', name)
this.events.emit('showContent', name)
@ -105,22 +112,21 @@ export class VerticalIcons extends Plugin {
* Toggles the side panel for plugin
* @param {string} name Name of profile of the module to activate
*/
toggle (name: string) {
toggle(name: string) {
// TODO: Only keep `this.emit` (issue#2210)
this.emit('toggleContent', name)
this.events.emit('toggleContent', name)
}
updateComponent(state: any){
return <RemixUiVerticalIconsPanel
verticalIconsPlugin={state.verticalIconsPlugin}
icons={state.icons}
/>
updateComponent(state: any) {
return <RemixUiVerticalIconsPanel verticalIconsPlugin={state.verticalIconsPlugin} icons={state.icons} />
}
render() {
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',
description: 'service - editor',
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 {
@ -51,7 +51,8 @@ class Editor extends Plugin {
rs: 'rust',
cairo: 'cairo',
ts: 'typescript',
move: 'move'
move: 'move',
circom: 'circom'
}
this.activated = false
@ -174,8 +175,8 @@ class Editor extends Plugin {
}
this.saveTimeout = window.setTimeout(() => {
this.triggerEvent('contentChanged', [])
this.triggerEvent('requiringToSaveCurrentfile', [])
this.triggerEvent('contentChanged', [currentFile, input])
this.triggerEvent('requiringToSaveCurrentfile', [currentFile])
}, 500)
}
@ -578,6 +579,10 @@ class Editor extends Plugin {
this.clearDecorationsByPlugin(session, from, 'lineTextPerFile', this.registeredDecorations, this.currentDecorations)
}
}
getPositionAt(offset) {
return this.api.getPositionAt(offset)
}
}
module.exports = Editor

@ -1,3 +1,4 @@
'use strict'
import { saveAs } from 'file-saver'
import JSZip from 'jszip'
@ -23,8 +24,8 @@ const profile = {
icon: 'assets/img/fileManager.webp',
permission: true,
version: packageJson.version,
methods: ['closeAllFiles', 'closeFile', 'file', 'exists', 'open', 'writeFile', 'readFile', 'copyFile', 'copyDir', 'rename', 'mkdir',
'readdir', 'dirList', 'fileList', 'remove', 'getCurrentFile', 'getFile', 'selectFolder', 'setFile', 'switchFile', 'refresh',
methods: ['closeAllFiles', 'closeFile', 'file', 'exists', 'open', 'writeFile', 'writeMultipleFiles', 'readFile', 'copyFile', 'copyDir', 'rename', 'mkdir',
'readdir', 'dirList', 'fileList', 'remove', 'getCurrentFile', 'getFile', 'getFolder', 'setFile', 'switchFile', 'refresh',
'getProviderOf', 'getProviderByName', 'getPathFromUrl', 'getUrlFromPath', 'saveCurrentFile', 'setBatchFiles', 'isGitRepo'],
kind: 'file-system'
}
@ -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
* @param {string} path path of the file
@ -353,7 +386,7 @@ class FileManager extends Plugin {
async zipDir(dirPath, zip) {
const filesAndFolders = await this.readdir(dirPath)
for(let path in filesAndFolders) {
for (let path in filesAndFolders) {
if (filesAndFolders[path].isDirectory) await this.zipDir(path, zip)
else {
path = this.normalize(path)
@ -369,7 +402,7 @@ class FileManager extends Plugin {
if (await this.isDirectory(path)) {
const zip = new JSZip()
await this.zipDir(path, zip)
const content = await zip.generateAsync({type: 'blob'})
const content = await zip.generateAsync({ type: 'blob' })
saveAs(content, `${downloadFileName}.zip`)
} else {
path = this.normalize(path)
@ -589,7 +622,7 @@ class FileManager extends Plugin {
// TODO : Add permission
// TODO : Change Provider to Promise
return new Promise((resolve, reject) => {
provider.set(path, content, (error) => {
provider.set(path, content, async (error) => {
if (error) reject(error)
this.syncEditor(path)
this.emit('fileSaved', path)
@ -759,14 +792,14 @@ class FileManager extends Plugin {
return collectList(path)
}
async fileList (dirPath) {
async fileList(dirPath) {
const paths: any = await this.readdir(dirPath)
for( const path in paths)
if(paths[path].isDirectory) delete paths[path]
for (const path in paths)
if (paths[path].isDirectory) delete paths[path]
return Object.keys(paths)
}
isRemixDActive () {
isRemixDActive() {
return this.appManager.isActive('remixd')
}
@ -781,7 +814,7 @@ class FileManager extends Plugin {
provider.get(currentFile, (error, oldContent) => {
provider.set(currentFile, input, (error) => {
if (error) {
if (error.message ) this.call('notification', 'toast',
if (error.message) this.call('notification', 'toast',
error.message.indexOf(
'LocalStorage is full') !== -1 ? storageFullMessage()
: error.message
@ -805,10 +838,10 @@ class FileManager extends Plugin {
if (path !== currentFile) return
const provider = this.fileProviderOf(currentFile)
if (provider) {
try{
try {
const content = await provider.get(currentFile)
if(content) this.editor.setText(currentFile, content)
}catch(error){
if (content) this.editor.setText(currentFile, content)
} catch (error) {
console.log(error)
}
} else {
@ -829,7 +862,7 @@ class FileManager extends Plugin {
}
await self.syncEditor(fileProvider + file)
} else {
try{
try {
const name = await helper.createNonClashingNameAsync(file, self._deps.filesProviders[fileProvider])
if (helper.checkSpecialChars(name)) {
this.call('notification', 'alert', {
@ -844,7 +877,7 @@ class FileManager extends Plugin {
}
self.syncEditor(fileProvider + name)
}
}catch(error){
} catch (error) {
if (error) {
this.call('notification', 'alert', {
id: 'fileManagerAlert',
@ -870,13 +903,64 @@ class FileManager extends Plugin {
}
}
async isGitRepo (): Promise<boolean> {
async isGitRepo(): Promise<boolean> {
const path = '.git'
const exists = await this.exists(path)
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
* @param {string} src path of the source file
@ -897,7 +981,7 @@ class FileManager extends Plugin {
const fileName = helper.extractNameFromKey(src)
if (await this.exists(dest + '/' + fileName)) {
throw createError({ code: 'EEXIST', message: `Cannot move ${src}. File already exists at destination ${dest}`})
throw createError({ code: 'EEXIST', message: `Cannot move ${src}. File already exists at destination ${dest}` })
}
await this.copyFile(src, dest, fileName)
await this.remove(src)
@ -925,9 +1009,15 @@ class FileManager extends Plugin {
await this._handleIsDir(dest, `Cannot move content into ${dest}. Path is not directory.`)
const dirName = helper.extractNameFromKey(src)
if (await this.exists(dest + '/' + dirName) || src === dest) {
throw createError({ code: 'EEXIST', message: `Cannot move ${src}. Folder already exists at destination ${dest}`})
throw createError({ code: 'EEXIST', message: `Cannot move ${src}. Folder already exists at destination ${dest}` })
}
const provider = this.fileProviderOf(src)
if (provider.isSubDirectory(src, dest)) {
this.call('notification', 'toast', recursivePasteToastMsg())
return false
}
await this.copyDir(src, dest, dirName)
await this.inDepthCopy(src, dest, dirName)
await this.remove(src)
} catch (e) {

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

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

@ -1,11 +1,11 @@
import { Plugin } from '@remixproject/engine'
import { LibraryProfile, MethodApi, StatusEvents } from '@remixproject/plugin-utils'
import { AppModal } from '@remix-ui/app'
import { AlertModal } from '@remix-ui/app'
import { dispatchModalInterface } from '@remix-ui/app'
import {Plugin} from '@remixproject/engine'
import {LibraryProfile, MethodApi, StatusEvents} from '@remixproject/plugin-utils'
import {AppModal} from '@remix-ui/app'
import {AlertModal} from '@remix-ui/app'
import {dispatchModalInterface} from '@remix-ui/app'
interface INotificationApi {
events: StatusEvents,
events: StatusEvents
methods: {
modal: (args: AppModal) => void
alert: (args: AlertModal) => void
@ -13,7 +13,7 @@ interface INotificationApi {
}
}
const profile:LibraryProfile<INotificationApi> = {
const profile: LibraryProfile<INotificationApi> = {
name: 'notification',
displayName: 'Notification',
description: 'Displays notifications',
@ -22,23 +22,23 @@ const profile:LibraryProfile<INotificationApi> = {
export class NotificationPlugin extends Plugin implements MethodApi<INotificationApi> {
dispatcher: dispatchModalInterface
constructor () {
constructor() {
super(profile)
}
setDispatcher (dispatcher: dispatchModalInterface) {
setDispatcher(dispatcher: dispatchModalInterface) {
this.dispatcher = dispatcher
}
async modal (args: AppModal) {
async modal(args: AppModal) {
return this.dispatcher.modal(args)
}
async alert (args: AlertModal) {
async alert(args: AlertModal) {
return this.dispatcher.alert(args)
}
async toast (message: string | JSX.Element) {
async toast(message: string | JSX.Element) {
this.dispatcher.toast(message)
}
}

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

@ -1,37 +1,83 @@
'use strict'
import { Plugin } from '@remixproject/engine'
import { sourceMappingDecoder } from '@remix-project/remix-debug'
import { CompilerAbstract } from '@remix-project/remix-solidity'
import { CompilationResult } from '@remix-project/remix-solidity'
import {Plugin} from '@remixproject/engine'
import {sourceMappingDecoder} from '@remix-project/remix-debug'
import {CompilerAbstract} from '@remix-project/remix-solidity'
import {CompilationResult} from '@remix-project/remix-solidity'
import CodeParserGasService from './services/code-parser-gas-service'
import CodeParserCompiler from './services/code-parser-compiler'
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 { Profile } from '@remixproject/plugin-utils'
import { ContractDefinitionAstNode, EventDefinitionAstNode, FunctionCallAstNode, FunctionDefinitionAstNode, IdentifierAstNode, ImportDirectiveAstNode, ModifierDefinitionAstNode, SourceUnitAstNode, StructDefinitionAstNode, VariableDeclarationAstNode } from '@remix-project/remix-analyzer'
import { lastCompilationResult, RemixApi } from '@remixproject/plugin-api'
import { antlr } from './types'
import { ParseResult } from './types/antlr-types'
import {Profile} from '@remixproject/plugin-utils'
import {
ContractDefinitionAstNode,
EventDefinitionAstNode,
FunctionCallAstNode,
FunctionDefinitionAstNode,
IdentifierAstNode,
ImportDirectiveAstNode,
ModifierDefinitionAstNode,
SourceUnitAstNode,
StructDefinitionAstNode,
VariableDeclarationAstNode
} from '@remix-project/remix-analyzer'
import {lastCompilationResult, RemixApi} from '@remixproject/plugin-api'
import {antlr} from './types'
import {ParseResult} from './types/antlr-types'
const profile: Profile = {
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: [],
version: '0.0.1'
}
export function isNodeDefinition(node: genericASTNode) {
return node.nodeType === 'ContractDefinition' ||
return (
node.nodeType === 'ContractDefinition' ||
node.nodeType === 'FunctionDefinition' ||
node.nodeType === 'ModifierDefinition' ||
node.nodeType === 'VariableDeclaration' ||
node.nodeType === 'StructDefinition' ||
node.nodeType === 'EventDefinition'
)
}
export type genericASTNode =
ContractDefinitionAstNode
| ContractDefinitionAstNode
| FunctionDefinitionAstNode
| ModifierDefinitionAstNode
| VariableDeclarationAstNode
@ -51,13 +97,12 @@ interface declarationIndexNode {
}
interface codeParserIndex {
declarations: declarationIndexNode,
flatReferences: flatReferenceIndexNode,
declarations: declarationIndexNode
flatReferences: flatReferenceIndexNode
nodesPerFile: any
}
export class CodeParser extends Plugin {
compilerAbstract: CompilerAbstract
currentFile: string
nodeIndex: codeParserIndex
@ -80,7 +125,6 @@ export class CodeParser extends Plugin {
debuggerIsOn: boolean = false
constructor(astWalker: any) {
super(profile)
this.astWalker = astWalker
@ -106,7 +150,6 @@ export class CodeParser extends Plugin {
}
async onActivation() {
this.gasService = new CodeParserGasService(this)
this.compilerService = new CodeParserCompiler(this)
this.antlrService = new CodeParserAntlrService(this)
@ -173,7 +216,7 @@ export class CodeParser extends Plugin {
})
}
async reload(){
async reload() {
await this.call('editor', 'discardLineTexts')
await this.call('fileDecorator', 'clearFileDecorators')
await this.call('editor', 'clearErrorMarkers', [this.currentFile])
@ -189,7 +232,7 @@ export class CodeParser extends Plugin {
}
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) {
if (compilationResult && compilationResult.sources) {
const callback = (node: genericASTNode) => {
if (node && ("referencedDeclaration" in node) && node.referencedDeclaration) {
if (node && 'referencedDeclaration' in node && node.referencedDeclaration) {
if (!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) {
this.astWalker.walkFull(compilationResult.sources[s].ast, callback)
}
}
}
// NODE HELPERS
@ -232,15 +273,13 @@ export class CodeParser extends Plugin {
return '(' + params.toString() + ')'
}
_flatNodeList(contractNode: ContractDefinitionAstNode, fileName: string, inScope: boolean, compilatioResult: any) {
const index = {}
const contractName: string = contractNode.name
const callback = (node) => {
if (inScope && node.scope !== contractNode.id
&& !(node.nodeType === 'EnumDefinition' || node.nodeType === 'EventDefinition' || node.nodeType === 'ModifierDefinition'))
if (inScope && node.scope !== contractNode.id && !(node.nodeType === 'EnumDefinition' || node.nodeType === 'EventDefinition' || node.nodeType === 'ModifierDefinition'))
return
if (inScope) node.isClassNode = true;
if (inScope) node.isClassNode = true
node.gasEstimate = this._getContractGasEstimate(node, contractName, fileName, compilatioResult)
node.functionName = node.name + this._getInputParams(node)
node.contractName = contractName
@ -261,7 +300,10 @@ export class CodeParser extends Plugin {
if (node.nodeType === 'ContractDefinition') {
const flatNodes = this._flatNodeList(node, fileName, false, 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 baseNodesWithBaseContractScope = {}
if (node.linearizedBaseContracts) {
@ -271,19 +313,16 @@ export class CodeParser extends Plugin {
const callback = (node) => {
node.contractName = (baseContract as any).name
node.contractId = (baseContract as any).id
node.isBaseNode = true;
node.isBaseNode = true
baseNodes[node.id] = node
if ((node.scope && node.scope === baseContract.id)
|| node.nodeType === 'EnumDefinition'
|| node.nodeType === 'EventDefinition'
) {
if ((node.scope && node.scope === baseContract.id) || node.nodeType === 'EnumDefinition' || node.nodeType === 'EventDefinition') {
baseNodesWithBaseContractScope[node.id] = node
}
if (node.members) {
for (const member of node.members) {
member.contractName = (baseContract as any).name
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)
}
if (node.nodeType === 'ImportDirective') {
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)
for (const subNode of importedNode.nodes) {
nodesByContract.imports[subNode.id] = subNode
@ -312,7 +350,6 @@ export class CodeParser extends Plugin {
}
_getContractGasEstimate(node: ContractDefinitionAstNode | FunctionDefinitionAstNode, contractName: string, fileName: string, compilationResult: lastCompilationResult) {
const contracts = compilationResult.data.contracts && compilationResult.data.contracts[this.currentFile]
for (const name in contracts) {
if (name === contractName) {
@ -331,11 +368,11 @@ export class CodeParser extends Plugin {
} else if (visibility === 'private' || visibility === 'internal') {
executionCost = estimationObj === null ? '-' : estimationObj.internal[fn]
}
return { executionCost }
return {executionCost}
} else {
return {
creationCost: estimationObj === null ? '-' : estimationObj.creation.totalCost,
codeDepositCost: estimationObj === null ? '-' : estimationObj.creation.codeDepositCost,
codeDepositCost: estimationObj === null ? '-' : estimationObj.creation.codeDepositCost
}
}
}
@ -351,14 +388,24 @@ export class CodeParser extends Plugin {
*/
async nodesAtPosition(position: number, type = ''): Promise<genericASTNode[]> {
let lastCompilationResult = this.compilerAbstract
if(this.debuggerIsOn) {
if (this.debuggerIsOn) {
lastCompilationResult = await this.call('compilerArtefacts', 'get', '__last')
this.currentFile = await this.call('fileManager', 'file')
}
if (!lastCompilationResult) return []
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]) {
const nodes: genericASTNode[] = sourceMappingDecoder.nodesAtPosition(type, position, lastCompilationResult.data.sources[this.currentFile] || lastCompilationResult.data.sources[urlFromPath.file])
if (
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 []
@ -417,7 +464,7 @@ export class CodeParser extends Plugin {
* @returns
*/
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 null
@ -430,56 +477,84 @@ export class CodeParser extends Plugin {
*/
async definitionAtPosition(position: number) {
const nodes = await this.nodesAtPosition(position)
let nodeDefinition: any
const nodeDefinition = {
'ast': null,
'parser': null
}
let node: genericASTNode
if (nodes && nodes.length && !this.errorState) {
if (nodes && nodes.length) {
node = nodes[nodes.length - 1]
nodeDefinition = node
let astNodeDefinition = node
if (!isNodeDefinition(node)) {
nodeDefinition = await this.declarationOf(node) || node
astNodeDefinition = (await this.declarationOf(node)) || node
}
if (node.nodeType === 'ImportDirective') {
for (const key in this.nodeIndex.flatReferences) {
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()
let parserNodeDefinition = null
if (astNodes && astNodes.length) {
for (const node of astNodes) {
if (node.range[0] <= position && node.range[1] >= position) {
if (nodeDefinition && nodeDefinition.range[0] < node.range[0]) {
nodeDefinition = node
if (parserNodeDefinition && parserNodeDefinition.range[0] < node.range[0]) {
parserNodeDefinition = node
}
if (!parserNodeDefinition) parserNodeDefinition = node
}
}
if (!nodeDefinition) nodeDefinition = node
if (parserNodeDefinition && parserNodeDefinition.type && parserNodeDefinition.type === 'Identifier') {
const nodeForIdentifier = await this.findIdentifier(parserNodeDefinition)
if (nodeForIdentifier) parserNodeDefinition = nodeForIdentifier
}
nodeDefinition.parser = parserNodeDefinition
}
if (nodeDefinition && nodeDefinition.type && nodeDefinition.type === 'Identifier') {
const nodeForIdentifier = await this.findIdentifier(nodeDefinition)
if (nodeForIdentifier) nodeDefinition = nodeForIdentifier
/* 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) {
if (this.nodeIndex.nodesPerFile
&& this.nodeIndex.nodesPerFile[this.currentFile]
&& this.nodeIndex.nodesPerFile[this.currentFile].contracts[contractName]
&& this.nodeIndex.nodesPerFile[this.currentFile].contracts[contractName].contractNodes) {
if (
this.nodeIndex.nodesPerFile &&
this.nodeIndex.nodesPerFile[this.currentFile] &&
this.nodeIndex.nodesPerFile[this.currentFile].contracts[contractName] &&
this.nodeIndex.nodesPerFile[this.currentFile].contracts[contractName].contractNodes
) {
return this.nodeIndex.nodesPerFile[this.currentFile].contracts[contractName]
}
return false
}
async getCurrentFileNodes() {
if (this.nodeIndex.nodesPerFile
&& this.nodeIndex.nodesPerFile[this.currentFile]) {
if (this.nodeIndex.nodesPerFile && this.nodeIndex.nodesPerFile[this.currentFile]) {
return this.nodeIndex.nodesPerFile[this.currentFile]
}
return false
@ -535,8 +610,6 @@ export class CodeParser extends Plugin {
return imported
}
/**
*
* @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)
const current = this.nodeIndex.flatReferences[node.referencedDeclaration]
results.push(current)
@ -586,8 +659,6 @@ export class CodeParser extends Plugin {
return this.nodeIndex.flatReferences
}
/**
*
* @param node
@ -629,9 +700,9 @@ export class CodeParser extends Plugin {
* @returns
*/
async getNodeDocumentation(node: genericASTNode) {
if (("documentation" in node) && node.documentation && (node.documentation as any).text) {
let text = '';
(node.documentation as any).text.split('\n').forEach(line => {
if ('documentation' in node && node.documentation && (node.documentation as any).text) {
let text = ''
;(node.documentation as any).text.split('\n').forEach((line) => {
text += `${line.trim()}\n`
})
return text
@ -651,11 +722,9 @@ export class CodeParser extends Plugin {
} else {
if (node.typeName && node.typeName.name) {
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}`
}
else {
} else {
return `${nodeName}${nodeName}`
}
}
@ -667,7 +736,7 @@ export class CodeParser extends Plugin {
* @returns
*/
async getFunctionParamaters(node: any) {
const localParam = (node.parameters && node.parameters.parameters) || (node.parameters)
const localParam = (node.parameters && node.parameters.parameters) || node.parameters
if (localParam) {
const params = []
for (const param of localParam) {
@ -683,7 +752,7 @@ export class CodeParser extends Plugin {
* @returns
*/
async getFunctionReturnParameters(node: any) {
const localParam = (node.returnParameters && node.returnParameters.parameters)
const localParam = node.returnParameters && node.returnParameters.parameters
if (localParam) {
const params = []
for (const param of localParam) {
@ -692,7 +761,4 @@ export class CodeParser extends Plugin {
return `(${params.join(', ')})`
}
}
}

@ -29,14 +29,14 @@ export default class CodeParserAntlrService {
parserThresholdSampleAmount = 3
cache: {
[name: string]: {
text: string,
ast: antlr.ParseResult | null,
duration?: number,
parsingEnabled?: boolean,
blocks?: BlockDefinition[],
text: string
ast: antlr.ParseResult | null
duration?: number
parsingEnabled?: boolean
blocks?: BlockDefinition[]
blockDurations?: number[]
}
} = {};
} = {}
constructor(plugin: CodeParser) {
this.plugin = plugin
this.createWorker()
@ -46,8 +46,8 @@ export default class CodeParserAntlrService {
this.worker = new Worker(new URL('./antlr-worker', import.meta.url))
this.worker.postMessage({
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
this.worker.addEventListener('message', function (ev) {
@ -60,14 +60,15 @@ export default class CodeParserAntlrService {
ast: ev.data.ast,
duration: ev.data.duration,
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)
}
break;
break
}
});
})
}
setFileParsingState(file: string) {
@ -97,7 +98,6 @@ export default class CodeParserAntlrService {
clearInterval(this.workerTimer)
}
async parseWithWorker(text: string, file: string) {
this.parserStartTime = Date.now()
this.worker.postMessage({
@ -105,9 +105,8 @@ export default class CodeParserAntlrService {
text,
timestamp: this.parserStartTime,
file,
parsingEnabled: (this.cache[file] && this.cache[file].parsingEnabled) || true
});
parsingEnabled: (this.cache[file] && this.cache[file].parsingEnabled) || true,
})
}
async parseSolidity(text: string) {
@ -125,13 +124,13 @@ export default class CodeParserAntlrService {
try {
this.plugin.currentFile = await this.plugin.call('fileManager', 'file')
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]) {
this.cache[this.plugin.currentFile] = {
text: '',
ast: null,
parsingEnabled: true,
blockDurations: []
blockDurations: [],
}
}
if (this.cache[this.plugin.currentFile] && this.cache[this.plugin.currentFile].text !== fileContent) {
@ -155,8 +154,8 @@ export default class CodeParserAntlrService {
async listAstNodes() {
this.plugin.currentFile = await this.plugin.call('fileManager', 'file')
if (!this.cache[this.plugin.currentFile]) return
const nodes: AstNode[] = [];
(SolidityParser as any).visit(this.cache[this.plugin.currentFile].ast, {
const nodes: AstNode[] = []
;(SolidityParser as any).visit(this.cache[this.plugin.currentFile].ast, {
StateVariableDeclaration: (node: antlr.StateVariableDeclaration) => {
if (node.variables) {
for (const variable of node.variables) {
@ -196,13 +195,11 @@ export default class CodeParserAntlrService {
},
StructDefinition: function (node: antlr.StructDefinition) {
nodes.push({ ...node, nodeType: node.type, id: null, src: null })
}
},
})
return nodes
}
/**
*
* @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) {
checkLastNode(node)
},
Identifier: function (node: antlr.Identifier) {
checkLastNode(node)
}
},
})
if (lastNode && lastNode.expression) {
return lastNode.expression
@ -244,12 +241,15 @@ export default class CodeParserAntlrService {
}
}
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 {
const startTime = Date.now()
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){
this.cache[this.plugin.currentFile].blockDurations = [...this.cache[this.plugin.currentFile].blockDurations.slice(-this.parserThresholdSampleAmount), Date.now() - startTime]
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.setFileParsingState(this.plugin.currentFile)
}
if (blocks) this.cache[this.plugin.currentFile].blocks = blocks
@ -285,5 +285,4 @@ export default class CodeParserAntlrService {
const block = walkAst(blocks)
return block
}
}

@ -99,11 +99,11 @@ export default class CodeParserCompiler {
await this.clearDecorators(result.getSourceCode().sources)
}
if (!data.sources) return
if (data.sources && Object.keys(data.sources).length === 0) return
this.plugin.compilerAbstract = new CompilerAbstract('soljson', data, source, input)
this.errorState = false
this.plugin.nodeIndex = {
declarations: {},
flatReferences: {},
@ -113,7 +113,10 @@ export default class CodeParserCompiler {
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
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()
this.plugin.emit('astFinished')
}

@ -42,7 +42,10 @@ export default class CodeParserGasService {
}
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
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)

@ -17,7 +17,7 @@ export default class CodeParserImports {
this.init()
}
async getImports() {
async getImports(){
return this.data
}
@ -28,43 +28,36 @@ export default class CodeParserImports {
.filter(x => x !== '')
.map(x => x.replace('./node_modules/', ''))
.filter(x => {
if (x.includes('@openzeppelin')) {
if(x.includes('@openzeppelin')) {
return !x.includes('mock')
} else {
}else{
return true
}
})
// get unique first words of the values in the array
this.data.packages = [...new Set(this.data.modules.map(x => x.split('/')[0]))]
}
setFileTree = async () => {
if (isElectron()) {
const files = await this.plugin.call('fs', 'glob', '/', '**/*.sol')
// only get path property of files
this.data.files = files.map(x => x.path)
this.data.files = files.map((x) => x.path)
} else {
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) => {
console.log('getDirectorySEARCH', dir)
let result = []
let files = {}
try {
if (await this.plugin.call('fileManager', 'exists', dir)) {
files = await this.plugin.call('fileManager', 'readdir', dir)
}
} catch (e) { }
} catch (e) {}
const fileArray = this.normalize(files)
for (const fi of fileArray) {
@ -77,12 +70,10 @@ export default class CodeParserImports {
}
}
}
return result
}
normalize = filesList => {
console.log('normalize', filesList)
const folders = []
const files = []
Object.keys(filesList || {}).forEach(key => {

@ -1,9 +1,9 @@
import React from 'react' // eslint-disable-line
import { FormattedMessage } from 'react-intl'
import { Plugin } from '@remixproject/engine'
import { AppModal } from '@remix-ui/app'
import { PermissionHandlerDialog, PermissionHandlerValue } from '@remix-ui/permission-handler'
import { Profile } from '@remixproject/plugin-utils'
import {FormattedMessage} from 'react-intl'
import {Plugin} from '@remixproject/engine'
import {AppModal} from '@remix-ui/app'
import {PermissionHandlerDialog, PermissionHandlerValue} from '@remix-ui/permission-handler'
import {Profile} from '@remixproject/plugin-utils'
const profile = {
name: 'permissionhandler',
@ -46,15 +46,11 @@ 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) {
set
? this.sessionPermissions[to.name][method][from.name] = {}
: delete this.sessionPermissions[to.name][method][from.name]
set ? (this.sessionPermissions[to.name][method][from.name] = {}) : delete this.sessionPermissions[to.name][method][from.name]
} else {
set
? this.permissions[to.name][method][from.name] = {}
: delete this.permissions[to.name][method][from.name]
set ? (this.permissions[to.name][method][from.name] = {}) : delete this.permissions[to.name][method][from.name]
}
}
@ -93,7 +89,7 @@ export class PermissionHandlerPlugin extends Plugin {
if (!this.permissions[to.name][method][from.name]) return this.openPermission(from, to, method, message, sensitiveCall)
}
const { allow, hash } = sensitiveCall ? this.sessionPermissions[to.name][method][from.name] : this.permissions[to.name][method][from.name]
const {allow, hash} = sensitiveCall ? this.sessionPermissions[to.name][method][from.name] : this.permissions[to.name][method][from.name]
if (!allow) {
const warning = this.notAllowWarning(from, to, method)
this.call('notification', 'toast', warning)
@ -124,10 +120,10 @@ export class PermissionHandlerPlugin extends Plugin {
}
const modal: AppModal = {
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>,
okLabel: <FormattedMessage id='permissionHandler.accept' />,
cancelLabel: <FormattedMessage id='permissionHandler.decline' />
okLabel: <FormattedMessage id="permissionHandler.accept" />,
cancelLabel: <FormattedMessage id="permissionHandler.decline" />
}
const result = await this.call('notification', 'modal', modal)

@ -1,12 +1,12 @@
/* eslint-disable no-unused-vars */
import React, { useRef, useState, useEffect } from 'react' // eslint-disable-line
import React, {useRef, useState, useEffect} from 'react' // eslint-disable-line
import isElectron from 'is-electron'
import { WebsocketPlugin } from '@remixproject/engine-web'
import {WebsocketPlugin} from '@remixproject/engine-web'
import * as packageJson from '../../../../../package.json'
// eslint-disable-next-line @nrwl/nx/enforce-module-boundaries
import { version as remixdVersion } from '../../../../../libs/remixd/package.json'
import { PluginManager } from '@remixproject/engine'
import { AppModal, AlertModal } from '@remix-ui/app'
import {version as remixdVersion} from '../../../../../libs/remixd/package.json'
import {PluginManager} from '@remixproject/engine'
import {AppModal, AlertModal} from '@remix-ui/app'
const LOCALHOST = ' - connect to localhost - '
@ -19,10 +19,10 @@ const profile = {
description: 'Using Remixd daemon, allow to access file system',
kind: 'other',
version: packageJson.version,
repo: "https://github.com/ethereum/remix-project/tree/master/libs/remixd",
maintainedBy: "Remix",
documentation: "https://remix-ide.readthedocs.io/en/latest/remixd.html",
authorContact: ""
repo: 'https://github.com/ethereum/remix-project/tree/master/libs/remixd',
maintainedBy: 'Remix',
documentation: 'https://remix-ide.readthedocs.io/en/latest/remixd.html',
authorContact: ''
}
export class RemixdHandle extends WebsocketPlugin {
@ -63,7 +63,6 @@ export class RemixdHandle extends WebsocketPlugin {
}
await this.appManager.deactivatePlugin('remixd')
}
async callPluginMethod(key: string, payload?: any[]) {
@ -92,7 +91,8 @@ export class RemixdHandle extends WebsocketPlugin {
this.canceled()
} else {
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)
const alert: AlertModal = {
id: 'connectionAlert',
@ -103,8 +103,8 @@ export class RemixdHandle extends WebsocketPlugin {
}
}, 3000)
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) {
await this.appManager.activatePlugin(plugin)
}
@ -119,7 +119,7 @@ export class RemixdHandle extends WebsocketPlugin {
title: 'Access file system using remixd',
message: remixdDialog(),
okLabel: 'Connect',
cancelLabel: 'Cancel',
cancelLabel: 'Cancel'
}
const result = await this.call('notification', 'modal', mod)
if (result) {
@ -127,7 +127,8 @@ export class RemixdHandle extends WebsocketPlugin {
this.localhostProvider.preInit()
super.activate()
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.'))
} else {
connection()
@ -136,14 +137,15 @@ export class RemixdHandle extends WebsocketPlugin {
} catch (error) {
connection(error)
}
}
else {
} else {
await this.canceled()
}
} else {
try {
super.activate()
setTimeout(() => { connection() }, 2000)
setTimeout(() => {
connection()
}, 2000)
} catch (error) {
connection(error)
}
@ -154,37 +156,53 @@ export class RemixdHandle extends WebsocketPlugin {
function remixdDialog() {
const commandText = 'remixd'
const fullCommandText = 'remixd -s <path-to-the-shared-folder> -u <remix-ide-instance-URL>'
return (<>
<div className=''>
<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>.
return (
<>
<div className="">
<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 className='mb-2 text-break'>
Remixd <a target="_blank" href="https://remix-ide.readthedocs.io/en/latest/remixd.html">documentation</a>.
<div className="mb-2 text-break">
Remixd{' '}
<a target="_blank" href="https://remix-ide.readthedocs.io/en/latest/remixd.html">
documentation
</a>
.
</div>
<div className='mb-2 text-break'>
<div className="mb-2 text-break">
The remixd command is:
<br /><b>{commandText}</b>
<br />
<b>{commandText}</b>
</div>
<div className='mb-2 text-break'>
The remixd command without options uses the terminal's current directory as the shared directory and the shared Remix domain can only be https://remix.ethereum.org, https://remix-alpha.ethereum.org, or https://remix-beta.ethereum.org
<div className="mb-2 text-break">
The remixd command without options uses the terminal's current directory as the shared directory and the shared Remix domain can only be https://remix.ethereum.org,
https://remix-alpha.ethereum.org, or https://remix-beta.ethereum.org
</div>
<div className='mb-2 text-break'>
<div className="mb-2 text-break">
Example command with flags: <br />
<b>{fullCommandText}</b>
</div>
<div className='mb-2 text-break'>
For info about ports, see <a target="_blank" href="https://remix-ide.readthedocs.io/en/latest/remixd.html#ports-usage">Remixd ports usage</a>
</div>
<div className='mb-2 text-break'>
This feature is still in Alpha. We recommend to keep a backup of the shared folder.
<div className="mb-2 text-break">
For info about ports, see{' '}
<a target="_blank" href="https://remix-ide.readthedocs.io/en/latest/remixd.html#ports-usage">
Remixd ports usage
</a>
</div>
<div 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">
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>
</div>
</div>
</>)
</>
)
}

@ -1,9 +1,9 @@
import React from 'react' // eslint-disable-line
import { format } from 'util'
import { Plugin } from '@remixproject/engine'
import { compile } from '@remix-project/remix-solidity'
import { TransactionConfig } from 'web3-core'
const _paq = window._paq = window._paq || [] //eslint-disable-line
import {format} from 'util'
import {Plugin} from '@remixproject/engine'
import {compile} from '@remix-project/remix-solidity'
import {TransactionConfig} from 'web3-core'
const _paq = (window._paq = window._paq || []) //eslint-disable-line
const profile = {
name: 'solidity-script',
@ -13,17 +13,16 @@ const profile = {
}
export class SolidityScript extends Plugin {
constructor () {
constructor() {
super(profile)
}
async execute (path: string, functionName: string = 'run') {
async execute(path: string, functionName: string = 'run') {
_paq.push(['trackEvent', 'SolidityScript', 'execute', 'script'])
this.call('terminal', 'log', `Running free function '${functionName}' from ${path}...`)
let content = await this.call('fileManager', 'readFile', path)
const params = await this.call('solidity', 'getCompilerParameters')
content = `
// SPDX-License-Identifier: GPL-3.0
@ -38,11 +37,13 @@ export class SolidityScript extends Plugin {
${functionName}();
}
}`
const targets = { 'script.sol': { content } }
const targets = {'script.sol': {content}}
// compile
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) {
@ -83,9 +84,12 @@ export class SolidityScript extends Plugin {
const hhlogs = await web3.eth.getHHLogsForTx(receiptCall.transactionHash)
if (hhlogs && hhlogs.length) {
const finalLogs = <div><div><b>console.log:</b></div>
{
hhlogs.map((log) => {
const finalLogs = (
<div>
<div>
<b>console.log:</b>
</div>
{hhlogs.map((log) => {
let formattedLog
// 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
@ -99,6 +103,7 @@ export class SolidityScript extends Plugin {
return <div>{formattedLog}</div>
})}
</div>
)
_paq.push(['trackEvent', 'udapp', 'hardhat', 'console.log'])
this.call('terminal', 'logHtml', finalLogs)
}

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

Loading…
Cancel
Save