Merge branch 'master' of https://github.com/ethereum/remix-project into editorcontext_merge

pull/5370/head
bunsenstraat 2 years ago
commit a5d786bff0
  1. 102
      .circleci/config.yml
  2. 2
      README.md
  3. 13
      apps/etherscan/.babelrc
  4. 16
      apps/etherscan/.browserslistrc
  5. 34
      apps/etherscan/.eslintrc.json
  6. 7
      apps/etherscan/src/app/App.css
  7. 24
      apps/etherscan/src/app/AppContext.tsx
  8. 153
      apps/etherscan/src/app/app.tsx
  9. 160
      apps/etherscan/src/app/components/HeaderWithSettings.tsx
  10. 37
      apps/etherscan/src/app/components/SubmitButton.tsx
  11. 2
      apps/etherscan/src/app/components/index.ts
  12. 37
      apps/etherscan/src/app/hooks/useLocalStorage.tsx
  13. 19
      apps/etherscan/src/app/layouts/Default.tsx
  14. 1
      apps/etherscan/src/app/layouts/index.ts
  15. 17
      apps/etherscan/src/app/logo.svg
  16. 51
      apps/etherscan/src/app/routes.tsx
  17. 11
      apps/etherscan/src/app/star.svg
  18. 6
      apps/etherscan/src/app/types/Receipt.ts
  19. 1
      apps/etherscan/src/app/types/ThemeType.ts
  20. 2
      apps/etherscan/src/app/types/index.ts
  21. 1
      apps/etherscan/src/app/utils/index.ts
  22. 32
      apps/etherscan/src/app/utils/utilities.ts
  23. 59
      apps/etherscan/src/app/views/CaptureKeyView.tsx
  24. 31
      apps/etherscan/src/app/views/ErrorView.tsx
  25. 36
      apps/etherscan/src/app/views/HomeView.tsx
  26. 135
      apps/etherscan/src/app/views/ReceiptsView.tsx
  27. 302
      apps/etherscan/src/app/views/VerifyView.tsx
  28. 4
      apps/etherscan/src/app/views/index.ts
  29. 0
      apps/etherscan/src/assets/.gitkeep
  30. 3
      apps/etherscan/src/environments/environment.prod.ts
  31. 6
      apps/etherscan/src/environments/environment.ts
  32. BIN
      apps/etherscan/src/favicon.ico
  33. 14
      apps/etherscan/src/index.html
  34. 7
      apps/etherscan/src/main.tsx
  35. 7
      apps/etherscan/src/polyfills.ts
  36. 1
      apps/etherscan/src/styles.css
  37. 14
      apps/etherscan/tsconfig.app.json
  38. 20
      apps/etherscan/tsconfig.json
  39. 8
      apps/remix-ide-e2e/nightwatch.ts
  40. 3
      apps/remix-ide-e2e/src/commands/getEditorValue.ts
  41. 4
      apps/remix-ide-e2e/src/commands/journalChildIncludes.ts
  42. 8
      apps/remix-ide-e2e/src/commands/openFile.ts
  43. 2
      apps/remix-ide-e2e/src/commands/switchWorkspace.ts
  44. 11
      apps/remix-ide-e2e/src/commands/testFunction.ts
  45. 17
      apps/remix-ide-e2e/src/helpers/init.ts
  46. 7
      apps/remix-ide-e2e/src/select_tests.sh
  47. 32
      apps/remix-ide-e2e/src/tests/ballot.test.ts
  48. 69
      apps/remix-ide-e2e/src/tests/editor_line_text.test.ts
  49. 107
      apps/remix-ide-e2e/src/tests/etherscan_api.ts
  50. 11
      apps/remix-ide-e2e/src/tests/fileExplorer.test.ts
  51. 60
      apps/remix-ide-e2e/src/tests/file_decorator.test.ts
  52. 2
      apps/remix-ide-e2e/src/tests/pluginManager.test.ts
  53. 1
      apps/remix-ide-e2e/src/tests/publishContract.test.ts
  54. 2
      apps/remix-ide-e2e/src/tests/remixd.test.ts
  55. 48
      apps/remix-ide-e2e/src/tests/runAndDeploy.test.ts
  56. 40
      apps/remix-ide-e2e/src/tests/search.test.ts
  57. 12
      apps/remix-ide-e2e/src/tests/solidityUnittests.test.ts
  58. 42
      apps/remix-ide-e2e/src/tests/terminal.test.ts
  59. 4
      apps/remix-ide-e2e/src/tests/transactionExecution.test.ts
  60. 6
      apps/remix-ide-e2e/src/tests/txListener.test.ts
  61. 137
      apps/remix-ide-e2e/src/tests/url.test.ts
  62. 96
      apps/remix-ide-e2e/src/tests/vyper_api.ts
  63. 111
      apps/remix-ide-e2e/src/tests/workspace.test.ts
  64. 3
      apps/remix-ide/ci/browser_test.sh
  65. 25
      apps/remix-ide/ci/browser_tests_etherscan_plugin.sh
  66. 25
      apps/remix-ide/ci/browser_tests_vyper_plugin.sh
  67. 28
      apps/remix-ide/src/app.js
  68. 2
      apps/remix-ide/src/app/panels/file-panel.js
  69. 4
      apps/remix-ide/src/app/panels/terminal.js
  70. 52
      apps/remix-ide/src/app/plugins/file-decorator.ts
  71. 3
      apps/remix-ide/src/app/tabs/abstract-provider.tsx
  72. 5
      apps/remix-ide/src/app/tabs/web3-provider.js
  73. 2
      apps/remix-ide/src/blockchain/execution-context.js
  74. 15
      apps/remix-ide/src/remixAppManager.js
  75. 3
      apps/solidity-compiler/src/app/compiler-api.ts
  76. 13
      apps/vyper/.babelrc
  77. 16
      apps/vyper/.browserslistrc
  78. 18
      apps/vyper/.eslintrc
  79. 124
      apps/vyper/src/app/app.css
  80. 118
      apps/vyper/src/app/app.tsx
  81. 75
      apps/vyper/src/app/components/CompilerButton.tsx
  82. 36
      apps/vyper/src/app/components/LocalUrl.tsx
  83. 70
      apps/vyper/src/app/components/VyperResult.tsx
  84. 19
      apps/vyper/src/app/components/WarnRemote.tsx
  85. 161
      apps/vyper/src/app/examples/ballot.tsx
  86. 14
      apps/vyper/src/app/logo.svg
  87. 11
      apps/vyper/src/app/star.svg
  88. 139
      apps/vyper/src/app/utils/compiler.tsx
  89. 12
      apps/vyper/src/app/utils/index.ts
  90. 71
      apps/vyper/src/app/utils/remix-client.tsx
  91. 0
      apps/vyper/src/assets/.gitkeep
  92. 3
      apps/vyper/src/environments/environment.prod.ts
  93. 6
      apps/vyper/src/environments/environment.ts
  94. BIN
      apps/vyper/src/favicon.ico
  95. 14
      apps/vyper/src/index.html
  96. 7
      apps/vyper/src/main.tsx
  97. 7
      apps/vyper/src/polyfills.ts
  98. 1
      apps/vyper/src/styles.css
  99. 14
      apps/vyper/tsconfig.app.json
  100. 20
      apps/vyper/tsconfig.json
  101. Some files were not shown because too many files have changed in this diff Show More

@ -8,7 +8,7 @@ parameters:
type: boolean
default: false
orbs:
browser-tools: circleci/browser-tools@1.2.3
browser-tools: circleci/browser-tools@1.3.0
jobs:
build:
docker:
@ -267,6 +267,94 @@ jobs:
- store_artifacts:
path: ./reports/screenshots
remix-ide-vyper-plugin:
docker:
# specify the version you desire here
- image: cimg/node:14.17.6-browsers
# Specify service dependencies here if necessary
# CircleCI maintains a library of pre-built images
# documented at https://circleci.com/docs/2.0/circleci-images/
resource_class: xlarge
# - image: circleci/mongo:3.4.4
environment:
- COMMIT_AUTHOR_EMAIL: "yann@ethereum.org"
- COMMIT_AUTHOR: "Circle CI"
working_directory: ~/remix-project
parallelism: 10
steps:
- browser-tools/install-chrome
- browser-tools/install-chromedriver
- run:
command: |
google-chrome --version
chromedriver --version
java -jar /usr/local/bin/selenium.jar --version
name: Check install
- checkout
- checkout
- attach_workspace:
at: .
- run: unzip ./persist/dist.zip
- restore_cache:
keys:
- v1-deps-{{ checksum "yarn.lock" }}
- run: yarn install
- run:
name: Start Selenium
command: java -jar /usr/local/bin/selenium.jar
background: true
- run: npx nx build vyper
- run: ./apps/remix-ide/ci/browser_tests_vyper_plugin.sh
- store_test_results:
path: ./reports/tests
- store_artifacts:
path: ./reports/screenshots
remix-ide-etherscan-plugin:
docker:
# specify the version you desire here
- image: cimg/node:14.17.6-browsers
# Specify service dependencies here if necessary
# CircleCI maintains a library of pre-built images
# documented at https://circleci.com/docs/2.0/circleci-images/
resource_class: xlarge
# - image: circleci/mongo:3.4.4
environment:
- COMMIT_AUTHOR_EMAIL: "yann@ethereum.org"
- COMMIT_AUTHOR: "Circle CI"
working_directory: ~/remix-project
parallelism: 10
steps:
- browser-tools/install-chrome
- browser-tools/install-chromedriver
- run:
command: |
google-chrome --version
chromedriver --version
java -jar /usr/local/bin/selenium.jar --version
name: Check install
- checkout
- checkout
- attach_workspace:
at: .
- run: unzip ./persist/dist.zip
- restore_cache:
keys:
- v1-deps-{{ checksum "yarn.lock" }}
- run: yarn install
- run:
name: Start Selenium
command: java -jar /usr/local/bin/selenium.jar
background: true
- run: npx nx build etherscan
- run: ./apps/remix-ide/ci/browser_tests_etherscan_plugin.sh
- store_test_results:
path: ./reports/tests
- store_artifacts:
path: ./reports/screenshots
remix-ide-plugin-api:
docker:
# specify the version you desire here
@ -420,6 +508,12 @@ workflows:
- remix-ide-plugin-api:
requires:
- build
- remix-ide-vyper-plugin:
requires:
- build
- remix-ide-etherscan-plugin:
requires:
- build
- remix-ide-chrome:
requires:
- build
@ -433,6 +527,8 @@ workflows:
- remix-ide-chrome
- remix-ide-firefox
- remix-ide-plugin-api
- remix-ide-vyper-plugin
- remix-ide-etherscan-plugin
filters:
branches:
only: remix_live
@ -443,6 +539,8 @@ workflows:
- remix-ide-chrome
- remix-ide-firefox
- remix-ide-plugin-api
- remix-ide-vyper-plugin
- remix-ide-etherscan-plugin
filters:
branches:
only: master
@ -453,6 +551,8 @@ workflows:
- remix-ide-chrome
- remix-ide-firefox
- remix-ide-plugin-api
- remix-ide-vyper-plugin
- remix-ide-etherscan-plugin
filters:
branches:
only: remix_beta

@ -213,7 +213,7 @@ To do this you need to:
browser.waitForElementPresent('*[data-id="verticalIconsKindfilePanel"]')
```
- add '@disable': true to the test file you want to split:
- add '@disabled': true to the test file you want to split:
```
module.exports = {

@ -0,0 +1,13 @@
{
"presets": [
[
"@nrwl/react/babel", {
"runtime": "automatic"
}
]
],
"plugins": [
]
}

@ -0,0 +1,16 @@
# This file is used by:
# 1. autoprefixer to adjust CSS to support the below specified browsers
# 2. babel preset-env to adjust included polyfills
#
# For additional information regarding the format and rule options, please see:
# https://github.com/browserslist/browserslist#queries
#
# If you need to support different browsers in production, you may tweak the list below.
last 1 Chrome version
last 1 Firefox version
last 2 Edge major versions
last 2 Safari major version
last 2 iOS major versions
Firefox ESR
not IE 9-11 # For IE 9-11 support, remove 'not'.

@ -0,0 +1,34 @@
{
"extends": [
"plugin:@nrwl/nx/react",
"../../.eslintrc.json"
],
"ignorePatterns": [
"!**/*"
],
"overrides": [
{
"files": [
"*.ts",
"*.tsx",
"*.js",
"*.jsx"
],
"rules": {}
},
{
"files": [
"*.ts",
"*.tsx"
],
"rules": {}
},
{
"files": [
"*.js",
"*.jsx"
],
"rules": {}
}
]
}

@ -0,0 +1,7 @@
body {
margin: 0;
}
#root {
padding: 5px;
}

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

@ -0,0 +1,153 @@
import React, { useState, useEffect, useRef } from "react"
import {
CompilationFileSources,
CompilationResult,
} from "@remixproject/plugin-api"
import { PluginClient } from "@remixproject/plugin";
import { createClient } from "@remixproject/plugin-webview";
import { AppContext } from "./AppContext"
import { DisplayRoutes } from "./routes"
import { useLocalStorage } from "./hooks/useLocalStorage"
import { getReceiptStatus, getEtherScanApi, getNetworkName } from "./utils"
import { Receipt, ThemeType } from "./types"
import "./App.css"
export const getNewContractNames = (compilationResult: CompilationResult) => {
const compiledContracts = compilationResult.contracts
let result: string[] = []
for (const file of Object.keys(compiledContracts)) {
const newContractNames = Object.keys(compiledContracts[file])
result = [...result, ...newContractNames]
}
return result
}
const App = () => {
const [apiKey, setAPIKey] = useLocalStorage("apiKey", "")
const [clientInstance, setClientInstance] = useState(undefined as any)
const [receipts, setReceipts] = useLocalStorage("receipts", [])
const [contracts, setContracts] = useState([] as string[])
const [themeType, setThemeType] = useState("dark" as ThemeType)
const clientInstanceRef = useRef(clientInstance)
clientInstanceRef.current = clientInstance
const contractsRef = useRef(contracts)
contractsRef.current = contracts
useEffect(() => {
const client = new PluginClient()
createClient(client)
const loadClient = async () => {
await client.onload()
setClientInstance(client)
client.on("solidity",
"compilationFinished",
(
fileName: string,
source: CompilationFileSources,
languageVersion: string,
data: CompilationResult
) => {
const newContractsNames = getNewContractNames(data)
const newContractsToSave: string[] = [
...contractsRef.current,
...newContractsNames,
]
const uniqueContracts: string[] = [...new Set(newContractsToSave)]
setContracts(uniqueContracts)
}
)
//const currentTheme = await client.call("theme", "currentTheme")
//setThemeType(currentTheme.quality)
//client.on("theme", "themeChanged", (theme) => {
// setThemeType(theme.quality)
//})
}
loadClient()
}, [])
useEffect(() => {
if (!clientInstance) {
return
}
const receiptsNotVerified: Receipt[] = receipts.filter((item: Receipt) => {
return item.status !== "Verified"
})
if (receiptsNotVerified.length > 0) {
let timer1 = setInterval(() => {
for (const item in receiptsNotVerified) {
}
receiptsNotVerified.forEach(async (item) => {
if (!clientInstanceRef.current) {
return {}
}
const network = await getNetworkName(clientInstanceRef.current)
if (network === "vm") {
return {}
}
const status = await getReceiptStatus(
item.guid,
apiKey,
getEtherScanApi(network)
)
if (status === "Pass - Verified") {
const newReceipts = receipts.map((currentReceipt: Receipt) => {
if (currentReceipt.guid === item.guid) {
return {
...currentReceipt,
status: "Verified",
}
}
return currentReceipt
})
clearInterval(timer1)
setReceipts(newReceipts)
return () => {
clearInterval(timer1)
}
}
return {}
})
}, 5000)
}
}, [receipts, clientInstance, apiKey, setReceipts])
return (
<AppContext.Provider
value={{
apiKey,
setAPIKey,
clientInstance,
receipts,
setReceipts,
contracts,
setContracts,
themeType,
setThemeType,
}}
>
<DisplayRoutes />
</AppContext.Provider>
)
}
export default App

@ -0,0 +1,160 @@
import React from "react"
import { NavLink } from "react-router-dom"
import { AppContext } from "../AppContext"
import { ThemeType } from "../types"
interface Props {
title?: string
showBackButton?: boolean
from: string
}
interface IconProps {
from: string
themeType: ThemeType
}
const HomeIcon: React.FC<IconProps> = ({ from, themeType }: IconProps) => {
return (
<NavLink
data-id="home"
data-toggle="tooltip"
data-placement="top"
title="Home"
to={{
pathname: "/"
}}
state={ from }
style={isActive => {
return {
...(isActive ? getStyleFilterIcon(themeType) : {}), ...{ marginRight: "0.4em" }
}
}}
>
<svg
style={{ filter: "invert(0.5)" }}
width="1em"
height="1em"
viewBox="0 0 16 16"
className="bi bi-house-door-fill"
fill="currentColor"
xmlns="http://www.w3.org/2000/svg"
>
<path d="M6.5 10.995V14.5a.5.5 0 0 1-.5.5H2a.5.5 0 0 1-.5-.5v-7a.5.5 0 0 1 .146-.354l6-6a.5.5 0 0 1 .708 0l6 6a.5.5 0 0 1 .146.354v7a.5.5 0 0 1-.5.5h-4a.5.5 0 0 1-.5-.5V11c0-.25-.25-.5-.5-.5H7c-.25 0-.5.25-.5.495z" />
<path
fillRule="evenodd"
d="M13 2.5V6l-2-2V2.5a.5.5 0 0 1 .5-.5h1a.5.5 0 0 1 .5.5z"
/>
</svg>
</NavLink>
)
}
const SettingsIcon: React.FC<IconProps> = ({ from, themeType }: IconProps) => {
return (
<NavLink
data-toggle="tooltip"
data-placement="top"
title="Settings"
to={{
pathname: "/settings",
}}
state= {from}
style={isActive => {
return {
...(isActive ? getStyleFilterIcon(themeType) : {}), ...{ marginRight: "0.4em" }
}
}}
>
<svg
style={{ filter: "invert(0.5)" }}
width="1em"
height="1em"
viewBox="0 0 16 16"
className="bi bi-gear-fill"
fill="currentColor"
xmlns="http://www.w3.org/2000/svg"
>
<path
fillRule="evenodd"
d="M9.405 1.05c-.413-1.4-2.397-1.4-2.81 0l-.1.34a1.464 1.464 0 0 1-2.105.872l-.31-.17c-1.283-.698-2.686.705-1.987 1.987l.169.311c.446.82.023 1.841-.872 2.105l-.34.1c-1.4.413-1.4 2.397 0 2.81l.34.1a1.464 1.464 0 0 1 .872 2.105l-.17.31c-.698 1.283.705 2.686 1.987 1.987l.311-.169a1.464 1.464 0 0 1 2.105.872l.1.34c.413 1.4 2.397 1.4 2.81 0l.1-.34a1.464 1.464 0 0 1 2.105-.872l.31.17c1.283.698 2.686-.705 1.987-1.987l-.169-.311a1.464 1.464 0 0 1 .872-2.105l.34-.1c1.4-.413 1.4-2.397 0-2.81l-.34-.1a1.464 1.464 0 0 1-.872-2.105l.17-.31c.698-1.283-.705-2.686-1.987-1.987l-.311.169a1.464 1.464 0 0 1-2.105-.872l-.1-.34zM8 10.93a2.929 2.929 0 1 0 0-5.86 2.929 2.929 0 0 0 0 5.858z"
/>
<path
fillRule="evenodd"
d="M9.405 1.05c-.413-1.4-2.397-1.4-2.81 0l-.1.34a1.464 1.464 0 0 1-2.105.872l-.31-.17c-1.283-.698-2.686.705-1.987 1.987l.169.311c.446.82.023 1.841-.872 2.105l-.34.1c-1.4.413-1.4 2.397 0 2.81l.34.1a1.464 1.464 0 0 1 .872 2.105l-.17.31c-.698 1.283.705 2.686 1.987 1.987l.311-.169a1.464 1.464 0 0 1 2.105.872l.1.34c.413 1.4 2.397 1.4 2.81 0l.1-.34a1.464 1.464 0 0 1 2.105-.872l.31.17c1.283.698 2.686-.705 1.987-1.987l-.169-.311a1.464 1.464 0 0 1 .872-2.105l.34-.1c1.4-.413 1.4-2.397 0-2.81l-.34-.1a1.464 1.464 0 0 1-.872-2.105l.17-.31c.698-1.283-.705-2.686-1.987-1.987l-.311.169a1.464 1.464 0 0 1-2.105-.872l-.1-.34zM8 10.93a2.929 2.929 0 1 0 0-5.86 2.929 2.929 0 0 0 0 5.858z"
/>
</svg>
</NavLink>
)
}
const getStyleFilterIcon = (themeType: ThemeType) => {
const invert = themeType === "dark" ? 1 : 0
const brightness = themeType === "dark" ? "150" : "0" // should be >100 for icons with color
return {
filter: `invert(${invert}) grayscale(1) brightness(${brightness}%)`,
}
}
const ReceiptsIcon: React.FC<IconProps> = ({ from, themeType }: IconProps) => {
return (
<NavLink
data-toggle="tooltip"
data-placement="top"
title="Receipts"
to={{
pathname: "/receipts",
}}
state= { from }
style={isActive => {
return {
...(isActive ? getStyleFilterIcon(themeType) : {}), ...{ marginRight: "0.4em" }
}
}}
>
<svg
style={{ filter: "invert(0.5)" }}
width="1em"
height="1em"
viewBox="0 0 16 16"
className="bi bi-receipt"
fill="currentColor"
xmlns="http://www.w3.org/2000/svg"
>
<path
fillRule="evenodd"
d="M1.92.506a.5.5 0 0 1 .434.14L3 1.293l.646-.647a.5.5 0 0 1 .708 0L5 1.293l.646-.647a.5.5 0 0 1 .708 0L7 1.293l.646-.647a.5.5 0 0 1 .708 0L9 1.293l.646-.647a.5.5 0 0 1 .708 0l.646.647.646-.647a.5.5 0 0 1 .708 0l.646.647.646-.647a.5.5 0 0 1 .801.13l.5 1A.5.5 0 0 1 15 2v12a.5.5 0 0 1-.053.224l-.5 1a.5.5 0 0 1-.8.13L13 14.707l-.646.647a.5.5 0 0 1-.708 0L11 14.707l-.646.647a.5.5 0 0 1-.708 0L9 14.707l-.646.647a.5.5 0 0 1-.708 0L7 14.707l-.646.647a.5.5 0 0 1-.708 0L5 14.707l-.646.647a.5.5 0 0 1-.708 0L3 14.707l-.646.647a.5.5 0 0 1-.801-.13l-.5-1A.5.5 0 0 1 1 14V2a.5.5 0 0 1 .053-.224l.5-1a.5.5 0 0 1 .367-.27zm.217 1.338L2 2.118v11.764l.137.274.51-.51a.5.5 0 0 1 .707 0l.646.647.646-.646a.5.5 0 0 1 .708 0l.646.646.646-.646a.5.5 0 0 1 .708 0l.646.646.646-.646a.5.5 0 0 1 .708 0l.646.646.646-.646a.5.5 0 0 1 .708 0l.646.646.646-.646a.5.5 0 0 1 .708 0l.509.509.137-.274V2.118l-.137-.274-.51.51a.5.5 0 0 1-.707 0L12 1.707l-.646.647a.5.5 0 0 1-.708 0L10 1.707l-.646.647a.5.5 0 0 1-.708 0L8 1.707l-.646.647a.5.5 0 0 1-.708 0L6 1.707l-.646.647a.5.5 0 0 1-.708 0L4 1.707l-.646.647a.5.5 0 0 1-.708 0l-.509-.51z"
/>
<path
fillRule="evenodd"
d="M3 4.5a.5.5 0 0 1 .5-.5h6a.5.5 0 1 1 0 1h-6a.5.5 0 0 1-.5-.5zm0 2a.5.5 0 0 1 .5-.5h6a.5.5 0 1 1 0 1h-6a.5.5 0 0 1-.5-.5zm0 2a.5.5 0 0 1 .5-.5h6a.5.5 0 1 1 0 1h-6a.5.5 0 0 1-.5-.5zm0 2a.5.5 0 0 1 .5-.5h6a.5.5 0 0 1 0 1h-6a.5.5 0 0 1-.5-.5zm8-6a.5.5 0 0 1 .5-.5h1a.5.5 0 0 1 0 1h-1a.5.5 0 0 1-.5-.5zm0 2a.5.5 0 0 1 .5-.5h1a.5.5 0 0 1 0 1h-1a.5.5 0 0 1-.5-.5zm0 2a.5.5 0 0 1 .5-.5h1a.5.5 0 0 1 0 1h-1a.5.5 0 0 1-.5-.5zm0 2a.5.5 0 0 1 .5-.5h1a.5.5 0 0 1 0 1h-1a.5.5 0 0 1-.5-.5z"
/>
</svg>
</NavLink>
)
}
export const HeaderWithSettings: React.FC<Props> = ({
title = "",
showBackButton = false,
from,
}) => {
return (
<AppContext.Consumer>
{({ themeType }) => (
<div>
<h6>{title}</h6>
<div style={{ float: "right" }}>
<HomeIcon from={from} themeType={themeType} />
<ReceiptsIcon from={from} themeType={themeType} />
<SettingsIcon from={from} themeType={themeType} />
</div>
</div>
)}
</AppContext.Consumer>
)
}

@ -0,0 +1,37 @@
import React from "react"
interface Props {
text: string
isSubmitting?: boolean
dataId?: string
}
export const SubmitButton: React.FC<Props> = ({
text,
dataId,
isSubmitting = false,
}) => {
return (
<button
data-id={dataId}
style={{ padding: "0.25rem 0.4rem", marginRight: "0.5em" }}
type="submit"
className="btn btn-primary"
disabled={isSubmitting}
>
{!isSubmitting && text}
{isSubmitting && (
<div>
<span
className="spinner-border spinner-border-sm"
role="status"
aria-hidden="true"
style={{ marginRight: "0.3em" }}
/>
Verifying... Please wait
</div>
)}
</button>
)
}

@ -0,0 +1,2 @@
export { HeaderWithSettings } from "./HeaderWithSettings"
export { SubmitButton } from "./SubmitButton"

@ -0,0 +1,37 @@
import { useState } from "react"
export function useLocalStorage(key: string, initialValue: any) {
// State to store our value
// Pass initial state function to useState so logic is only executed once
const [storedValue, setStoredValue] = useState(() => {
try {
// Get from local storage by key
const item = window.localStorage.getItem(key)
// Parse stored json or if none return initialValue
return item ? JSON.parse(item) : initialValue
} catch (error) {
// If error also return initialValue
console.error(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
// Save state
setStoredValue(valueToStore)
// Save to local storage
window.localStorage.setItem(key, JSON.stringify(valueToStore))
} catch (error) {
// A more advanced implementation would handle the error case
console.error(error)
}
}
return [storedValue, setValue]
}

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

@ -0,0 +1 @@
export { DefaultLayout } from "./Default"

@ -0,0 +1,17 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg width="262px" height="163px" viewBox="0 0 262 163" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<g id="Styles-&amp;-Quick-Wins" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
<g id="Nx---Quick-Wins" transform="translate(-476.000000, -1284.000000)" fill-rule="nonzero">
<g id="Logos" transform="translate(-11.000000, 782.000000)">
<g id="Nx_Flat_White" transform="translate(487.000000, 502.000000)">
<polygon id="Path" fill="#FFFFFF" points="130.68 104.59 97.49 52.71 97.44 96.3 40.24 0 0 0 0 162.57 39.79 162.57 39.92 66.39 96.53 158.26"></polygon>
<polygon id="Path" fill="#FFFFFF" points="97.5 41.79 137.24 41.79 137.33 41.33 137.33 0 97.54 0 97.49 41.33"></polygon>
<path d="M198.66,86.86 C189.139872,86.6795216 180.538723,92.516445 177.19,101.43 C182.764789,93.0931021 193.379673,89.7432211 202.73,93.37 C207.05,95.13 212.73,97.97 217.23,96.45 C212.950306,90.4438814 206.034895,86.8725952 198.66,86.86 L198.66,86.86 Z" id="Path" fill="#96D8E9"></path>
<path d="M243.75,106.42 C243.75,101.55 241.1,100.42 235.6,98.42 C231.52,97 226.89,95.4 223.52,91 C222.86,90.13 222.25,89.15 221.6,88.11 C220.14382,85.4164099 218.169266,83.037429 215.79,81.11 C212.58,78.75 208.37,77.6 202.91,77.6 C191.954261,77.6076705 182.084192,84.2206169 177.91,94.35 C183.186964,87.0278244 191.956716,83.0605026 200.940147,83.9314609 C209.923578,84.8024193 217.767888,90.3805017 221.54,98.58 C223.424615,101.689762 227.141337,103.174819 230.65,102.22 C236.02,101.07 235.65,106.15 243.76,107.87 L243.75,106.42 Z" id="Path" fill="#48C4E5"></path>
<path d="M261.46,105.38 L261.46,105.27 C261.34,73.03 235.17,45.45 202.91,45.45 C183.207085,45.4363165 164.821777,55.3450614 154,71.81 L153.79,71.45 L137.23,45.45 L97.5,45.4499858 L135.25,104.57 L98.41,162.57 L137,162.57 L153.79,136.78 L170.88,162.57 L209.48,162.57 L174.48,107.49 C173.899005,106.416838 173.583536,105.220114 173.56,104 C173.557346,96.2203871 176.64661,88.7586448 182.147627,83.2576275 C187.648645,77.7566101 195.110387,74.6673462 202.89,74.67 C219.11,74.67 221.82,84.37 225.32,88.93 C232.23,97.93 246.03,93.99 246.03,105.73 L246.03,105.73 C246.071086,108.480945 247.576662,111.001004 249.979593,112.340896 C252.382524,113.680787 255.317747,113.636949 257.679593,112.225896 C260.041438,110.814842 261.471086,108.250945 261.43,105.5 L261.43,105.5 L261.43,105.38 L261.46,105.38 Z" id="Path" fill="#FFFFFF"></path>
<path d="M261.5,113.68 C261.892278,116.421801 261.504116,119.218653 260.38,121.75 C258.18,126.84 254.51,125.14 254.51,125.14 C254.51,125.14 251.35,123.6 253.27,120.65 C255.4,117.36 259.61,117.74 261.5,113.68 Z" id="Path" fill="#FFFFFF"></path>
</g>
</g>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 2.9 KiB

@ -0,0 +1,51 @@
import React from "react"
import {
BrowserRouter as Router,
Route,
Routes,
RouteProps,
} from "react-router-dom"
import { ErrorView, HomeView, ReceiptsView, CaptureKeyView } from "./views"
import { DefaultLayout } from "./layouts"
interface Props extends RouteProps {
component: any // TODO: new (props: any) => React.Component
from: string
}
const RouteWithHeader = ({ component: Component, ...rest }: Props) => {
return (
<Route
{...rest}
>
<DefaultLayout {...rest}>
<Component />
</DefaultLayout>
</Route>
)
}
export const DisplayRoutes = () => (
<Router>
<Routes>
<Route
path="/"
element={<DefaultLayout from="/">
<HomeView />
</DefaultLayout>} />
<Route path="/error"
element={<ErrorView />} />
<Route
path="/receipts"
element={<DefaultLayout from="/receipts">
<ReceiptsView />
</DefaultLayout>} />
<Route
path="/settings"
element={<DefaultLayout from="/settings">
<CaptureKeyView />
</DefaultLayout>} />
</Routes>
</Router>
)

@ -0,0 +1,11 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg
className="material-icons"
xmlns="http://www.w3.org/2000/svg"
width="24"
height="24"
viewBox="0 0 24 24"
>
<path d="M0 0h24v24H0z" fill="none" />
<path d="M12 17.27L18.18 21l-1.64-7.03L22 9.24l-7.19-.61L12 2 9.19 8.63 2 9.24l5.46 4.73L5.82 21z" />
</svg>

After

Width:  |  Height:  |  Size: 347 B

@ -0,0 +1,6 @@
export type ReceiptStatus = "Verified" | "Queue"
export interface Receipt {
guid: string
status: ReceiptStatus
}

@ -0,0 +1 @@
export type ThemeType = "dark" | "light"

@ -0,0 +1,2 @@
export * from "./Receipt"
export * from "./ThemeType"

@ -0,0 +1 @@
export * from "./utilities"

@ -0,0 +1,32 @@
import { PluginClient } from "@remixproject/plugin"
import axios from 'axios'
type RemixClient = PluginClient
export const getEtherScanApi = (network: string) => {
return network === "main"
? `https://api.etherscan.io/api`
: `https://api-${network}.etherscan.io/api`
}
export const getNetworkName = async (client: RemixClient) => {
const network = await client.call("network", "detectNetwork")
if (!network) {
throw new Error("no known network to verify against")
}
return network.name!.toLowerCase()
}
export const getReceiptStatus = async (
receiptGuid: string,
apiKey: string,
etherscanApi: string
) => {
const params = `guid=${receiptGuid}&module=contract&action=checkverifystatus&apiKey=${apiKey}`
try {
const response = await axios.get(`${etherscanApi}?${params}`)
const { result } = response.data
return result
} catch (error) {
console.error(error)
}
}

@ -0,0 +1,59 @@
import React from "react"
import { Formik, ErrorMessage, Field } from "formik"
import { useNavigate, useLocation } from "react-router-dom"
import { AppContext } from "../AppContext"
import { SubmitButton } from "../components"
export const CaptureKeyView: React.FC = () => {
const location = useLocation()
const navigate = useNavigate()
return (
<AppContext.Consumer>
{({ apiKey, setAPIKey }) => (
<Formik
initialValues={{ apiKey }}
validate={(values) => {
const errors = {} as any
if (!values.apiKey) {
errors.apiKey = "Required"
}
return errors
}}
onSubmit={(values) => {
setAPIKey(values.apiKey)
navigate((location.state as any).from)
}}
>
{({ errors, touched, handleSubmit }) => (
<form onSubmit={handleSubmit}>
<div className="form-group" style={{ marginBottom: "0.5rem" }}>
<label htmlFor="apikey">Please Enter your API key</label>
<Field
className={
errors.apiKey && touched.apiKey
? "form-control form-control-sm is-invalid"
: "form-control form-control-sm"
}
type="text"
name="apiKey"
placeholder="Example: GM1T20XY6JGSAPWKDCYZ7B2FJXKTJRFVGZ"
/>
<ErrorMessage
className="invalid-feedback"
name="apiKey"
component="div"
/>
</div>
<div>
<SubmitButton text="Save API key" dataId="save-api-key" />
</div>
</form>
)}
</Formik>
)}
</AppContext.Consumer>
)
}

@ -0,0 +1,31 @@
import React from "react"
export const ErrorView: React.FC = () => {
return (
<div
style={{
width: "100%",
display: "flex",
flexDirection: "column",
alignItems: "center",
}}
>
<img
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/ethereum/remix-project/issues"
>
Here
</a>
</h5>
</div>
)
}

@ -0,0 +1,36 @@
import React from "react"
import { Navigate } from "react-router-dom"
import { AppContext } from "../AppContext"
import { Receipt } from "../types"
import { VerifyView } from "./VerifyView"
export const HomeView: React.FC = () => {
// const [hasError, setHasError] = useState(false)
return (
<AppContext.Consumer>
{({ apiKey, clientInstance, setReceipts, receipts, contracts }) =>
!apiKey ? (
<Navigate
to={{
pathname: "/settings"
}}
/>
) : (
<VerifyView
contracts={contracts}
client={clientInstance}
apiKey={apiKey}
onVerifiedContract={(receipt: Receipt) => {
const newReceipts = [...receipts, receipt]
setReceipts(newReceipts)
}}
/>
)
}
</AppContext.Consumer>
)
}

@ -0,0 +1,135 @@
import React, { useState } from "react"
import { Formik, ErrorMessage, Field } from "formik"
import { getEtherScanApi, getNetworkName, getReceiptStatus } from "../utils"
import { Receipt } from "../types"
import { AppContext } from "../AppContext"
import { SubmitButton } from "../components"
import { Navigate } from "react-router-dom"
interface FormValues {
receiptGuid: string
}
export const ReceiptsView: React.FC = () => {
const [results, setResults] = useState("")
const onGetReceiptStatus = async (
values: FormValues,
clientInstance: any,
apiKey: string
) => {
try {
const network = await getNetworkName(clientInstance)
if (network === "vm") {
setResults("Cannot verify in the selected network")
return
}
const etherscanApi = getEtherScanApi(network)
const result = await getReceiptStatus(
values.receiptGuid,
apiKey,
etherscanApi
)
setResults(result)
} catch (error: any) {
setResults(error.message)
}
}
return (
<AppContext.Consumer>
{({ apiKey, clientInstance, receipts }) =>
!apiKey ? (
<Navigate
to={{
pathname: "/settings"
}}
/>
) : (
<div>
<Formik
initialValues={{ receiptGuid: "" }}
validate={(values) => {
const errors = {} as any
if (!values.receiptGuid) {
errors.receiptGuid = "Required"
}
return errors
}}
onSubmit={(values) =>
onGetReceiptStatus(values, clientInstance, apiKey)
}
>
{({ errors, touched, handleSubmit }) => (
<form onSubmit={handleSubmit}>
<div
className="form-group"
style={{ marginBottom: "0.5rem" }}
>
<h6>Get your Receipt GUID status</h6>
<label htmlFor="receiptGuid">Receipt GUID</label>
<Field
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"
/>
</div>
<SubmitButton text="Check" />
</form>
)}
</Formik>
<div
style={{
marginTop: "2em",
fontSize: "0.8em",
textAlign: "center",
}}
dangerouslySetInnerHTML={{ __html: results }}
/>
<ReceiptsTable receipts={receipts} />
</div>
)
}
</AppContext.Consumer>
)
}
const ReceiptsTable: React.FC<{ receipts: Receipt[] }> = ({ receipts }) => {
return (
<div className="table-responsive" style={{ fontSize: "0.7em" }}>
<h6>Receipts</h6>
<table className="table table-sm">
<thead>
<tr>
<th scope="col">Guid</th>
<th scope="col">Status</th>
</tr>
</thead>
<tbody>
{receipts &&
receipts.length > 0 &&
receipts.map((item: Receipt, index) => {
return (
<tr key={item.guid}>
<td>{item.guid}</td>
<td>{item.status}</td>
</tr>
)
})}
</tbody>
</table>
</div>
)
}

@ -0,0 +1,302 @@
import React, { useState } from "react"
import {
PluginClient,
} from "@remixproject/plugin"
import { Formik, ErrorMessage, Field } from "formik"
import { getNetworkName, getEtherScanApi, getReceiptStatus } from "../utils"
import { SubmitButton } from "../components"
import { Receipt } from "../types"
import { CompilationResult } from "@remixproject/plugin-api"
import axios from 'axios'
interface Props {
client: PluginClient
apiKey: string
onVerifiedContract: (receipt: Receipt) => void
contracts: string[]
}
interface FormValues {
contractName: string
contractArguments: string
contractAddress: string
}
export const getContractFileName = (
compilationResult: CompilationResult,
contractName: string
) => {
const compiledContracts = compilationResult.contracts
let fileName = ""
for (const file of Object.keys(compiledContracts)) {
for (const contract of Object.keys(compiledContracts[file])) {
if (contract === contractName) {
fileName = file
break
}
}
}
return fileName
}
export const getContractMetadata = (
compilationResult: CompilationResult,
contractName: string
) => {
const compiledContracts = compilationResult.contracts
let contractMetadata = ""
for (const file of Object.keys(compiledContracts)) {
for (const contract of Object.keys(compiledContracts[file])) {
if (contract === contractName) {
contractMetadata = compiledContracts[file][contract].metadata
if (contractMetadata) {
break
}
}
}
}
return contractMetadata
}
export const VerifyView: React.FC<Props> = ({
apiKey,
client,
contracts,
onVerifiedContract,
}) => {
const [results, setResults] = useState("")
const onVerifyContract = async (values: FormValues) => {
const compilationResult = (await client.call(
"solidity",
"getCompilationResult"
)) as any
if (!compilationResult) {
throw new Error("no compilation result available")
}
const contractArguments = values.contractArguments.replace("0x", "")
const verify = async (
apiKeyParam: string,
contractAddress: string,
contractArgumentsParam: string,
contractName: string,
compilationResultParam: any
) => {
const network = await getNetworkName(client)
if (network === "vm") {
return "Cannot verify in the selected network"
}
const etherscanApi = getEtherScanApi(network)
try {
const contractMetadata = getContractMetadata(
compilationResultParam.data,
contractName
)
if (!contractMetadata) {
return "Please recompile contract"
}
const contractMetadataParsed = JSON.parse(contractMetadata)
const fileName = getContractFileName(
compilationResultParam.data,
contractName
)
const jsonInput = {
language: 'Solidity',
sources: compilationResultParam.source.sources,
settings: {
optimizer: {
enabled: contractMetadataParsed.settings.optimizer.enabled,
runs: contractMetadataParsed.settings.optimizer.runs
}
}
}
const data: { [key: string]: string | any } = {
apikey: apiKeyParam, // A valid API-Key is required
module: "contract", // Do not change
action: "verifysourcecode", // Do not change
codeformat: "solidity-standard-json-input",
contractaddress: contractAddress, // Contract Address starts with 0x...
sourceCode: JSON.stringify(jsonInput),
contractname: fileName + ':' + contractName,
compilerversion: `v${contractMetadataParsed.compiler.version}`, // see http://etherscan.io/solcversions for list of support versions
constructorArguements: contractArgumentsParam, // if applicable
}
const body = new FormData()
Object.keys(data).forEach((key) => body.append(key, data[key]))
client.emit("statusChanged", {
key: "loading",
type: "info",
title: "Verifying ...",
})
const response = await axios.post(etherscanApi, body)
const { message, result, status } = await response.data
if (message === "OK" && status === "1") {
resetAfter10Seconds()
const receiptStatus = await getReceiptStatus(
result,
apiKey,
etherscanApi
)
onVerifiedContract({
guid: result,
status: receiptStatus,
})
return `Contract verified correctly <br> Receipt GUID ${result}`
}
if (message === "NOTOK") {
client.emit("statusChanged", {
key: "failed",
type: "error",
title: result,
})
resetAfter10Seconds()
}
return result
} catch (error) {
console.error(error)
setResults("Something wrong happened, try again")
}
}
const resetAfter10Seconds = () => {
setTimeout(() => {
client.emit("statusChanged", { key: "none" })
setResults("")
}, 10000)
}
const verificationResult = await verify(
apiKey,
values.contractAddress,
contractArguments,
values.contractName,
compilationResult
)
setResults(verificationResult)
}
return (
<div>
<Formik
initialValues={{
contractName: "",
contractArguments: "",
contractAddress: "",
}}
validate={(values) => {
const errors = {} as any
if (!values.contractName) {
errors.contractName = "Required"
}
if (!values.contractAddress) {
errors.contractAddress = "Required"
}
if (values.contractAddress.trim() === "") {
errors.contractAddress = "Please enter a valid contract address"
}
return errors
}}
onSubmit={(values) => onVerifyContract(values)}
>
{({ errors, touched, handleSubmit, isSubmitting }) => (
<form onSubmit={handleSubmit}>
<div className="form-group">
<h6>Verify your smart contracts</h6>
<label htmlFor="contractName">Contract</label>
<Field
as="select"
className={
errors.contractName && touched.contractName
? "form-control form-control-sm is-invalid"
: "form-control form-control-sm"
}
name="contractName"
>
<option disabled={true} value="">
Select a contract
</option>
{contracts.map((item) => (
<option key={item} value={item}>
{item}
</option>
))}
</Field>
<ErrorMessage
className="invalid-feedback"
name="contractName"
component="div"
/>
</div>
<div className="form-group">
<label htmlFor="contractArguments">Constructor Arguments</label>
<Field
className={
errors.contractArguments && touched.contractArguments
? "form-control form-control-sm is-invalid"
: "form-control form-control-sm"
}
type="text"
name="contractArguments"
placeholder="hex encoded"
/>
<ErrorMessage
className="invalid-feedback"
name="contractArguments"
component="div"
/>
</div>
<div className="form-group">
<label htmlFor="contractAddress">Contract Address</label>
<Field
className={
errors.contractAddress && touched.contractAddress
? "form-control form-control-sm is-invalid"
: "form-control form-control-sm"
}
type="text"
name="contractAddress"
placeholder="i.e. 0x11b79afc03baf25c631dd70169bb6a3160b2706e"
/>
<ErrorMessage
className="invalid-feedback"
name="contractAddress"
component="div"
/>
</div>
<SubmitButton dataId="verify-contract" text="Verify Contract" isSubmitting={isSubmitting} />
</form>
)}
</Formik>
<div data-id="verify-result"
style={{ marginTop: "2em", fontSize: "0.8em", textAlign: "center" }}
dangerouslySetInnerHTML={{ __html: results }}
/>
{/* <div style={{ display: "block", textAlign: "center", marginTop: "1em" }}>
<Link to="/receipts">View Receipts</Link>
</div> */}
</div>
)
}

@ -0,0 +1,4 @@
export { HomeView } from "./HomeView"
export { ErrorView } from "./ErrorView"
export { ReceiptsView } from "./ReceiptsView"
export { CaptureKeyView } from "./CaptureKeyView"

@ -0,0 +1,3 @@
export const environment = {
production: true
};

@ -0,0 +1,6 @@
// This file can be replaced during build by using the `fileReplacements` array.
// When building for production, this file is replaced with `environment.prod.ts`.
export const environment = {
production: false
};

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

@ -0,0 +1,14 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<title>Etherscan</title>
<base href="/" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<link rel="icon" type="image/x-icon" href="favicon.ico" />
</head>
<body>
<div id="root"></div>
</body>
</html>

@ -0,0 +1,7 @@
import { StrictMode } from 'react';
import * as ReactDOM from 'react-dom';
import App from './app/app';
ReactDOM.render(<StrictMode><App /></StrictMode>, 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 @@
/* You can add global styles to this file, and also import other style files */

@ -0,0 +1,14 @@
{
"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": ["**/*.spec.ts", "**/*.spec.tsx"],
"include": ["**/*.js", "**/*.jsx", "**/*.ts", "**/*.tsx"]
}

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

@ -29,7 +29,13 @@ module.exports = {
javascriptEnabled: true,
acceptSslCerts: true,
'goog:chromeOptions': {
args: ['window-size=2560,1440', 'start-fullscreen', '--no-sandbox', '--headless', '--verbose']
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",
]
}
}
},

@ -9,11 +9,10 @@ class GetEditorValue extends EventEmitter {
return elem.currentContent()
}, [], (result) => {
done()
const value = typeof result.value === 'string' ? result.value : null
callback(value)
this.emit('complete')
done()
})
})
return this

@ -1,4 +1,4 @@
import { NightwatchBrowser } from 'nightwatch'
import { ELEMENT_KEY, NightwatchBrowser } from 'nightwatch'
import EventEmitter from 'events'
/*
@ -11,7 +11,7 @@ class JournalChildIncludes extends EventEmitter {
let occurence = 0
this.api.elements('css selector', '*[data-id="terminalJournal"]', (res) => {
Array.isArray(res.value) && res.value.forEach(function (jsonWebElement) {
const jsonWebElementId = jsonWebElement.ELEMENT || jsonWebElement[Object.keys(jsonWebElement)[0]]
const jsonWebElementId = jsonWebElement[ELEMENT_KEY] || jsonWebElement[Object.keys(jsonWebElement)[0]]
browser.elementIdText(jsonWebElementId, (jsonElement) => {
const text = jsonElement.value

@ -23,10 +23,14 @@ function openFile (browser: NightwatchBrowser, name: string, done: VoidFunction)
browser.element('css selector', '[data-id="verticalIconsKindfilePanel"] img[data-id="selected"]', (result) => {
if (result.status === 0) {
done()
} else browser.clickLaunchIcon('filePanel').perform(done())
} else browser.clickLaunchIcon('filePanel').perform(() => {
done()
})
})
} else {
browser.clickLaunchIcon('filePanel').perform(done())
browser.clickLaunchIcon('filePanel').perform(() => {
done()
})
}
})

@ -6,7 +6,9 @@ class switchWorkspace extends EventEmitter {
this.api.waitForElementVisible('[data-id="workspacesSelect"]')
.click('[data-id="workspacesSelect"]')
.waitForElementVisible(`[data-id="dropdown-item-${workspaceName}"]`)
.pause(2000)
.click(`[data-id="dropdown-item-${workspaceName}"]`)
.pause(3000)
.perform((done) => {
done()
this.emit('complete')

@ -1,4 +1,4 @@
import { NightwatchBrowser, NightwatchTestFunctionExpectedInput } from 'nightwatch'
import { ELEMENT_KEY, NightwatchBrowser, NightwatchTestFunctionExpectedInput } from 'nightwatch'
import EventEmitter from 'events'
const deepequal = require('deep-equal')
@ -22,14 +22,17 @@ class TestFunction extends EventEmitter {
})
})
.perform((done) => {
browser.waitForElementVisible(`[data-id="block_tx${txHash}"]`, 60000)
browser
.waitForElementVisible(`[data-id="block_tx${txHash}"]`, 60000)
.moveToElement(`[data-id="block_tx${txHash}"]`, 0, 0)
.pause(2000)
.click(`[data-id="block_tx${txHash}"]`)
.pause(3000)
.waitForElementVisible(`*[data-id="txLoggerTable${txHash}"]`, 60000)
// fetch and format transaction logs as key => pair object
.elements('css selector', `*[data-shared="key_${txHash}"]`, (res) => {
Array.isArray(res.value) && res.value.forEach(function (jsonWebElement) {
const jsonWebElementId: string = jsonWebElement.ELEMENT || jsonWebElement[Object.keys(jsonWebElement)[0]]
const jsonWebElementId: string = jsonWebElement[ELEMENT_KEY] || jsonWebElement[Object.keys(jsonWebElement)[0]]
browser.elementIdText(jsonWebElementId, (jsonElement) => {
const key = typeof jsonElement.value === 'string' ? jsonElement.value.trim() : null
@ -40,7 +43,7 @@ class TestFunction extends EventEmitter {
})
.elements('css selector', `*[data-shared="pair_${txHash}"]`, (res) => {
Array.isArray(res.value) && res.value.forEach(function (jsonWebElement, index) {
const jsonWebElementId = jsonWebElement.ELEMENT || jsonWebElement[Object.keys(jsonWebElement)[0]]
const jsonWebElementId = jsonWebElement[ELEMENT_KEY] || jsonWebElement[Object.keys(jsonWebElement)[0]]
browser.elementIdText(jsonWebElementId, (jsonElement) => {
let value = jsonElement.value

@ -2,13 +2,28 @@ import { NightwatchBrowser } from 'nightwatch'
require('dotenv').config()
export default function (browser: NightwatchBrowser, callback: VoidFunction, url?: string, preloadPlugins = true): void {
type LoadPlugin = {
name: string
url: string
}
export default function (browser: NightwatchBrowser, callback: VoidFunction, url?: string, preloadPlugins = true, loadPlugin?: LoadPlugin): void {
browser
.url(url || 'http://127.0.0.1:8080')
.pause(6000)
.switchBrowserTab(0)
.waitForElementVisible('[id="remixTourSkipbtn"]')
.click('[id="remixTourSkipbtn"]')
.perform((done) => {
if (!loadPlugin) return done()
browser.execute(function (loadPlugin) { // override a plugin url for testing purpose
localStorage.setItem('test-plugin-name', loadPlugin.name)
localStorage.setItem('test-plugin-url', loadPlugin.url)
}, [loadPlugin])
.refresh()
.pause(6000)
.perform(done())
})
.maximizeWindow()
.fullscreenWindow(() => {
if (preloadPlugins) {

@ -3,7 +3,7 @@
# Bash Menu Script Example
PS3='Select a browser: '
BROWSERS=( "chrome" "firefox" "exit" )
BROWSERS=( "chrome" "chrome headless" "firefox" "exit" )
select opt in "${BROWSERS[@]}"
do
case $opt in
@ -12,6 +12,11 @@ do
BROWSER="chromeDesktop"
break
;;
"chrome headless")
echo "Chrome headless selected"
BROWSER="chrome"
break
;;
"firefox")
echo "Firefox selected"
BROWSER="firefoxDesktop"

@ -134,6 +134,23 @@ module.exports = {
.sendKeys('*[data-id$="scConfigFilePathInput"]', browser.Keys.ENTER)
.openFile('Untitled.sol')
.verifyContracts(['Ballot'], {wait: 2000, runs: '300'})
},
'Compile and deploy sample yul file': function (browser: NightwatchBrowser) {
browser
.addFile('sample.yul', {content: yulSample})
.clickLaunchIcon('solidity')
.waitForElementVisible('*[data-id="scConfigExpander"]')
.click('*[data-id="scManualConfiguration"]')
.waitForElementVisible('select[id="compilierLanguageSelector"]', 10000)
.click('select[id="compilierLanguageSelector"]')
.click('select[id="compilierLanguageSelector"] option[value=Yul]')
.waitForElementContainsText('[data-id="compiledContracts"]', 'Contract', 60000)
.clickLaunchIcon('udapp')
.click('*[data-id="Deploy - transact (not payable)"]')
.waitForElementPresent('*[data-id="universalDappUiContractActionWrapper"]', 60000)
.journalLastChildIncludes('Contract.(constructor)')
.journalLastChildIncludes('data: 0x602...0565b')
.end()
}
}
@ -388,3 +405,18 @@ const configFile = `
}
}
`
const yulSample = `
object "Contract" {
code {
function power(base, exponent) -> result
{
result := 1
for { let i := 0 } lt(i, exponent) { i := add(i, 1) }
{
result := mul(result, base)
}
}
}
}
`

@ -0,0 +1,69 @@
'use strict'
import { NightwatchBrowser } from 'nightwatch'
import init from '../helpers/init'
module.exports = {
before: function (browser: NightwatchBrowser, done: VoidFunction) {
init(browser, done, 'http://127.0.0.1:8080', true)
},
'Should add line texts': function (browser: NightwatchBrowser) {
browser
.openFile('contracts')
.openFile('contracts/1_Storage.sol')
.addFile('scripts/addlinetext.ts', {content: addLineText})
.pause(4000)
.executeScriptInTerminal('remix.exeCurrent()')
.pause(4000)
.openFile('contracts/1_Storage.sol')
.useXpath()
.waitForElementVisible("//*[@class='view-line' and contains(.,'contract')]//span//span[contains(.,'mylinetext1')]")
.waitForElementVisible("//*[@class='view-line' and contains(.,'function')]//span//span[contains(.,'mylinetext2')]")
}
}
const addLineText = `
(async () => {
await remix.call('editor', 'discardLineTexts' as any)
let linetext = {
content: 'mylinetext1',
position: {
start: {
line: 9,
column: 1,
}
},
hide: false,
className: 'text-muted small',
afterContentClassName: 'text-muted small fas fa-gas-pump pl-4',
hoverMessage: [{
value: 'hovering1',
},
],
}
await remix.call('editor', 'addLineText' as any, linetext, 'contracts/1_Storage.sol')
linetext = {
content: 'mylinetext2',
position: {
start: {
line: 17,
column: 1,
}
},
hide: false,
className: 'text-muted small',
afterContentClassName: 'text-muted small fas fa-gas-pump pl-4',
hoverMessage: [{
value: 'hovering2',
},
],
}
await remix.call('editor', 'addLineText' as any, linetext, 'contracts/1_Storage.sol')
})()`

@ -0,0 +1,107 @@
'use strict'
import { NightwatchBrowser } from 'nightwatch'
import init from '../helpers/init'
declare global {
interface Window { testplugin: { name: string, url: string }; }
}
module.exports = {
'@disabled': true,
before: function (browser: NightwatchBrowser, done: VoidFunction) {
init(browser, done, null, true, { name: 'etherscan', url: 'http://127.0.0.1:5003'})
},
'Should load etherscan plugin #group1': function (browser: NightwatchBrowser) {
browser.clickLaunchIcon('pluginManager')
.scrollAndClick('[data-id="pluginManagerComponentActivateButtonetherscan"]')
.clickLaunchIcon('etherscan')
.pause(5000)
// @ts-ignore
.frame(0)
.waitForElementVisible('input[name="apiKey"]')
.setValue('input[name="apiKey"]', '2HKUX5ZVASZIKWJM8MIQVCRUVZ6JAWT531')
.click('[data-id="save-api-key"]')
},
'Should verify a contract (contract is already verified) #group1': function (browser: NightwatchBrowser) {
browser
.frameParent()
.clickLaunchIcon('udapp') // switch to Goerli
.switchEnvironment('External Http Provider')
.waitForElementPresent('[data-id="basic-http-provider-modal-footer-ok-react"]')
.execute(() => {
(document.querySelector('*[data-id="basic-http-providerModalDialogContainer-react"] input[data-id="modalDialogCustomPromp"]') as any).focus()
}, [], () => {})
.setValue('[data-id="modalDialogCustomPromp"]', 'https://remix-goerli.ethdevops.io')
.modalFooterOKClick('basic-http-provider')
.clickLaunchIcon('solidity') // compile
.testContracts('Owner_1.sol', { content: verifiedContract }, ['Owner'])
.clickLaunchIcon('etherscan') // start etherscan verification
// @ts-ignore
.frame(0)
.click('[data-id="home"]')
.setValue('select[name="contractName"]', 'Owner')
.setValue('*[name="contractAddress"]', '0x9981c9d00103da481c3c65b22a79582a3e3ff50b')
.click('[data-id="verify-contract"]')
.waitForElementVisible('[data-id="verify-result"]')
.waitForElementContainsText('[data-id="verify-result"]', 'Contract source code already verified')
}
}
const verifiedContract = `
// SPDX-License-Identifier: GPL-3.0
pragma solidity >=0.7.0 <0.9.0;
/**
* @title Owner
* @dev Set & change owner
*/
contract Owner {
address private owner;
// event for EVM logging
event OwnerSet(address indexed oldOwner, address indexed newOwner);
// modifier to check if caller is owner
modifier isOwner() {
// If the first argument of 'require' evaluates to 'false', execution terminates and all
// changes to the state and to Ether balances are reverted.
// This used to consume all gas in old EVM versions, but not anymore.
// It is often a good idea to use 'require' to check if functions are called correctly.
// As a second argument, you can also provide an explanation about what went wrong.
require(msg.sender == owner, "Caller is not owner");
_;
}
function getInt() public returns (uint) {
return 123498;
}
/**
* @dev Set contract deployer as owner
*/
constructor() {
owner = msg.sender; // 'msg.sender' is sender of current call, contract deployer for a constructor
emit OwnerSet(address(0), owner);
}
/**
* @dev Change owner
* @param newOwner address of new owner
*/
function changeOwner(address newOwner) public isOwner {
emit OwnerSet(owner, newOwner);
owner = newOwner;
}
/**
* @dev Return owner address
* @return address of owner
*/
function getOwner() external view returns (address) {
return owner;
}
}`

@ -99,15 +99,16 @@ module.exports = {
})
},
'Should open local filesystem explorer #group2': !function (browser: NightwatchBrowser) {
browser.waitForElementVisible('div[data-id="remixIdeSidePanel"]')
'Should open local filesystem explorer #group2': function (browser: NightwatchBrowser) {
browser
.waitForElementVisible('div[data-id="remixIdeSidePanel"]')
.clickLaunchIcon('filePanel')
.waitForElementVisible('*[data-id="filePanelFileExplorerTree"]')
.click('[data-id="remixUIWorkspaceExplorer"]')
.waitForElementPresent('*[data-id="fileExplorerFileUpload"]')
.setValue('*[data-id="fileExplorerFileUpload"]', testData.testFile1)
.setValue('*[data-id="fileExplorerFileUpload"]', testData.testFile2)
.setValue('*[data-id="fileExplorerFileUpload"]', testData.testFile3)
.uploadFile('*[data-id="fileExplorerFileUpload"]', testData.testFile1)
.uploadFile('*[data-id="fileExplorerFileUpload"]', testData.testFile2)
.uploadFile('*[data-id="fileExplorerFileUpload"]', testData.testFile3)
.waitForElementVisible('[data-id="treeViewLitreeViewItemeditor.test.js"]')
.waitForElementVisible('[data-id="treeViewLitreeViewItemfileExplorer.test.js"]')
.waitForElementVisible('[data-id="treeViewLitreeViewItemgeneralSettings.test.js"]')

@ -15,9 +15,9 @@ module.exports = {
.openFile('contracts/2_Owner.sol')
.openFile('contracts/1_Storage.sol')
.openFile('contracts/3_Ballot.sol')
.addFile('scripts/decorators.ts', { content: testScript })
.addFile('scripts/decorators.ts', { content: testScriptSet })
.pause(2000)
.executeScript('remix.exeCurrent()')
.executeScriptInTerminal('remix.exeCurrent()')
.pause(4000)
.useXpath()
.waitForElementContainsText('//*[@id="fileExplorerView"]//*[@data-id="file-decoration-error-contracts/2_Owner.sol"]', '2')
@ -26,16 +26,35 @@ module.exports = {
.waitForElementContainsText('//*[@class="mainview"]//*[@data-id="file-decoration-custom-contracts/2_Owner.sol"]', 'U')
.waitForElementContainsText('//*[@id="fileExplorerView"]//*[@data-id="file-decoration-warning-contracts/1_Storage.sol"]', '2')
.waitForElementContainsText('//*[@class="mainview"]//*[@data-id="file-decoration-warning-contracts/1_Storage.sol"]', '2')
.useCss()
.waitForElementNotPresent('[data-id="file-decoration-custom-contracts/3_Ballot.sol"]', 10000)
.useXpath()
.waitForElementContainsText('//*[@id="fileExplorerView"]//*[@data-id="file-decoration-custom-contracts/3_Ballot.sol"]', 'customtext')
.waitForElementContainsText('//*[@class="mainview"]//*[@data-id="file-decoration-custom-contracts/3_Ballot.sol"]', 'customtext')
.moveToElement('//*[@id="fileExplorerView"]//*[@data-id="file-decoration-error-contracts/2_Owner.sol"]', 0, 0)
.waitForElementVisible('//*[@id="error-tooltip-contracts/2_Owner.sol"]')
.waitForElementContainsText('//*[@id="error-tooltip-contracts/2_Owner.sol"]', 'error on owner')
},
'clear ballot decorator': function (browser: NightwatchBrowser) {
browser
.useCss()
.addFile('scripts/clearballot.ts', { content: testScriptClearBallot })
.pause(2000)
.executeScriptInTerminal('remix.exeCurrent()')
.pause(4000)
.waitForElementNotPresent('[data-id="file-decoration-custom-contracts/3_Ballot.sol"]', 10000)
},
'clear all decorators': function (browser: NightwatchBrowser) {
browser
.addFile('scripts/clearall.ts', { content: testScriptClear })
.pause(2000)
.executeScriptInTerminal('remix.exeCurrent()')
.pause(4000)
.waitForElementNotPresent('[data-id="file-decoration-error-contracts/2_Owner.sol"]', 10000)
.waitForElementNotPresent('[data-id="file-decoration-warning-contracts/1_Storage.sol"]', 10000)
}
}
const testScript = `
const testScriptSet = `
(async () => {
remix.call('fileDecorator' as any, 'clearFileDecorators')
let decorator: any = {
@ -46,7 +65,6 @@ const testScript = `
fileStateIconClass: '',
fileStateIcon: '',
text: '2',
owner: 'code-parser',
bubble: true,
comment: 'error on owner',
}
@ -58,11 +76,10 @@ const testScript = `
fileStateIconClass: 'text-success',
fileStateIcon: 'U',
text: '',
owner: 'code-parser',
bubble: true,
comment: 'modified',
}
remix.call('fileDecorator' as any, 'setFileDecorators', [decorator, decorator2])
await remix.call('fileDecorator' as any, 'setFileDecorators', [decorator, decorator2])
decorator = {
path: 'contracts/1_Storage.sol',
@ -72,11 +89,10 @@ const testScript = `
fileStateIconClass: '',
fileStateIcon: '',
text: '2',
owner: 'code-parser',
bubble: true,
comment: 'warning on storage',
}
remix.call('fileDecorator' as any, 'setFileDecorators', decorator)
await remix.call('fileDecorator' as any, 'setFileDecorators', decorator)
decorator = {
path: 'contracts/3_Ballot.sol',
@ -85,13 +101,25 @@ const testScript = `
fileStateLabelClass: '',
fileStateIconClass: '',
fileStateIcon: 'customtext',
text: 'w',
owner: 'dgit',
text: 'with text',
bubble: true,
comment: '',
comment: 'custom comment',
}
remix.call('fileDecorator' as any, 'setFileDecorators', decorator)
await remix.call('fileDecorator' as any, 'setFileDecorators', decorator)
})()`
const testScriptClearBallot = `
(async () => {
await remix.call('fileDecorator' as any, 'clearFileDecorators', 'contracts/3_Ballot.sol')
})()`
const testScriptClear = `
(async () => {
await remix.call('fileDecorator' as any, 'clearAllFileDecorators')
remix.call('fileDecorator' as any, 'clearFileDecorators', 'dgit')
})()`

@ -109,7 +109,7 @@ module.exports = {
'Should load back installed plugins after reload': function (browser: NightwatchBrowser) {
browser
.waitForElementVisible('*[data-id="remixIdeSidePanel"]')
.waitForElementVisible('*[data-id="remixIdeSidePanel"]',3000)
.waitForElementVisible('*[data-id="pluginManagerComponentPluginManager"]')
.getInstalledPlugins((plugins) => {
browser.refresh()

@ -3,6 +3,7 @@ import { NightwatchBrowser } from 'nightwatch'
import init from '../helpers/init'
module.exports = {
'@disabled': true,
before: function (browser: NightwatchBrowser, done: VoidFunction) {
init(browser, done)
},

@ -55,6 +55,7 @@ module.exports = {
},
'start Remixd': function (browser) {
startRemixd(browser)
},
'run Remixd tests #group4': function (browser) {
runTests(browser)
@ -131,6 +132,7 @@ function startRemixd (browser: NightwatchBrowser) {
.waitForElementVisible('*[data-id="remixdConnect-modal-footer-ok-react"]', 2000)
.pause(2000)
.click('*[data-id="remixdConnect-modal-footer-ok-react"]')
.pause(10000)
// .click('*[data-id="workspacesModalDialog-modal-footer-ok-react"]')
}

@ -65,6 +65,29 @@ module.exports = {
})
},
'Should show and update balance for deployed contract on JavascriptVM #group3': function (browser: NightwatchBrowser) {
let instanceAddress
browser.waitForElementVisible('*[data-id="remixIdeSidePanel"]')
.clickLaunchIcon('filePanel')
.addFile('checkBalance.sol', sources[0]['checkBalance.sol'])
.clickLaunchIcon('udapp')
.setValue('*[data-id="dandrValue"]', '111')
.waitForElementVisible('*[data-id="Deploy - transact (payable)"]', 45000)
.click('*[data-id="Deploy - transact (payable)"]')
.pause(1000)
.clickInstance(1)
.pause(1000)
.getAddressAtPosition(1, (address) => {
instanceAddress = address
browser
.waitForElementVisible(`#instance${instanceAddress} [data-id="instanceContractBal"]`)
.assert.containsText(`#instance${instanceAddress} [data-id="instanceContractBal"]`, 'Balance: 0.000000000000000111 ETH')
.clickFunction('sendSomeEther - transact (not payable)', { types: 'uint256 num', values: '2' })
.pause(1000)
.assert.containsText(`#instance${instanceAddress} [data-id="instanceContractBal"]`, 'Balance: 0.000000000000000109 ETH')
})
},
'Should run low level interaction (fallback function) #group3': function (browser: NightwatchBrowser) {
browser.waitForElementPresent('*[data-id="remixIdeSidePanel"]')
.clickInstance(0)
@ -80,7 +103,7 @@ module.exports = {
.end()
},
'Should connect to Goerli Test Network using MetaMask': '' + function (browser: NightwatchBrowser) {
'Should connect to Goerli Test Network using MetaMask': !function (browser: NightwatchBrowser) {
browser.waitForElementPresent('*[data-id="remixIdeSidePanel"]')
.setupMetamask(passphrase, password)
.click('.network-indicator__down-arrow')
@ -101,7 +124,7 @@ module.exports = {
.switchBrowserTab(0)
},
'Should deploy contract on Goerli Test Network using MetaMask': '' + function (browser: NightwatchBrowser) {
'Should deploy contract on Goerli Test Network using MetaMask': !function (browser: NightwatchBrowser) {
browser.waitForElementPresent('*[data-id="runTabSelectAccount"] option')
.clickLaunchIcon('filePanel')
.openFile('Greet.sol')
@ -118,7 +141,7 @@ module.exports = {
.switchBrowserTab(0)
},
'Should run low level interaction (fallback function) on Goerli Test Network using MetaMask': '' + function (browser: NightwatchBrowser) {
'Should run low level interaction (fallback function) on Goerli Test Network using MetaMask': !function (browser: NightwatchBrowser) {
browser.waitForElementPresent('*[data-id="remixIdeSidePanel"]')
.waitForElementPresent('*[data-id="universalDappUiTitleExpander"]')
.click('*[data-id="universalDappUiTitleExpander"]')
@ -135,7 +158,7 @@ module.exports = {
.end()
},
'Should connect to Ethereum Main Network using MetaMask': '' + function (browser: NightwatchBrowser) {
'Should connect to Ethereum Main Network using MetaMask': !function (browser: NightwatchBrowser) {
browser.waitForElementPresent('*[data-id="remixIdeSidePanel"]')
.switchBrowserTab(2)
.waitForElementPresent('.network-indicator__down-arrow')
@ -153,7 +176,7 @@ module.exports = {
.assert.containsText('*[data-id="settingsNetworkEnv"]', 'Main (1) network')
},
'Should deploy contract on Ethereum Main Network using MetaMask': '' + function (browser: NightwatchBrowser) {
'Should deploy contract on Ethereum Main Network using MetaMask': !function (browser: NightwatchBrowser) {
browser.waitForElementPresent('*[data-id="runTabSelectAccount"] option')
.clickLaunchIcon('filePanel')
.openFile('Greet.sol')
@ -173,7 +196,7 @@ module.exports = {
* - Ropsten node for retrieving the trace and storage
*
*/
'Should debug Ropsten transaction with source highlighting using the source verifier service and MetaMask': '' + function (browser: NightwatchBrowser) {
'Should debug Ropsten transaction with source highlighting using the source verifier service and MetaMask': !function (browser: NightwatchBrowser) {
browser.waitForElementPresent('*[data-id="remixIdeSidePanel"]')
.waitForElementVisible('*[data-id="remixIdeIconPanel"]', 10000)
.switchBrowserTab(2)
@ -198,7 +221,7 @@ module.exports = {
.assert.containsText('*[data-id="solidityLocals"]', 'to: 0x6C3CCC7FBA111707D5A1AAF2758E9D4F4AC5E7B1')
},
'Call web3.eth.getAccounts() using Injected Provider (Metamask)': '' + function (browser: NightwatchBrowser) {
'Call web3.eth.getAccounts() using Injected Provider (Metamask)': !function (browser: NightwatchBrowser) {
browser
.executeScriptInTerminal('web3.eth.getAccounts()')
.pause(2000)
@ -224,6 +247,17 @@ const sources = [
message = _message;
}
}`
},
'checkBalance.sol': {
content: `pragma solidity ^0.8.0;
contract checkBalance {
constructor () payable {}
function sendSomeEther(uint256 num) public {
payable(msg.sender).transfer(num);
}
}`
}
}
]

@ -4,10 +4,11 @@ import { NightwatchBrowser } from 'nightwatch'
import init from '../helpers/init'
module.exports = {
'@disabled': true,
before: function (browser: NightwatchBrowser, done: VoidFunction) {
init(browser, done, 'http://127.0.0.1:8080', true)
},
'Should find text': function (browser: NightwatchBrowser) {
'Should find text #group1': function (browser: NightwatchBrowser) {
browser.waitForElementVisible('*[data-id="remixIdeSidePanel"]')
.click('*[plugin="search"]').waitForElementVisible('*[id="search_input"]')
.waitForElementVisible('*[id="search_include"]')
@ -26,7 +27,7 @@ module.exports = {
Array.isArray(res.value) && browser.assert.equal(res.value.length, 6)
})
},
'Should find text with exclude': function (browser: NightwatchBrowser) {
'Should find text with exclude #group1': function (browser: NightwatchBrowser) {
browser
.clearValue('*[id="search_input"]')
.setValue('*[id="search_input"]', 'contract').pause(1000)
@ -42,7 +43,7 @@ module.exports = {
.clearValue('*[id="search_include"]').setValue('*[id="search_include"]', '*.sol, *.js, *.txt')
.clearValue('*[id="search_exclude"]').setValue('*[id="search_exclude"]', '.*/**/*')
},
'Should find regex': function (browser: NightwatchBrowser) {
'Should find regex #group1': function (browser: NightwatchBrowser) {
browser
.waitForElementVisible('*[id="search_input"]')
.clearValue('*[id="search_input"]').pause(2000)
@ -57,7 +58,7 @@ module.exports = {
Array.isArray(res.value) && browser.assert.equal(res.value.length, 4)
})
},
'Should find matchcase': function (browser: NightwatchBrowser) {
'Should find matchcase #group1': function (browser: NightwatchBrowser) {
browser
.waitForElementVisible('*[data-id="search_use_regex"]').click('*[data-id="search_use_regex"]')
.waitForElementVisible('*[data-id="search_case_sensitive"]').click('*[data-id="search_case_sensitive"]').pause(4000)
@ -71,7 +72,7 @@ module.exports = {
})
.waitForElementContainsText('*[data-id="search_results"]', 'STORAGE.TEST.JS', 60000)
},
'Should find matchword': function (browser: NightwatchBrowser) {
'Should find matchword #group1': function (browser: NightwatchBrowser) {
browser
.waitForElementVisible('*[data-id="search_case_sensitive"]').click('*[data-id="search_case_sensitive"]')
.waitForElementVisible('*[data-id="search_whole_word"]').click('*[data-id="search_whole_word"]').pause(2000)
@ -81,7 +82,7 @@ module.exports = {
Array.isArray(res.value) && browser.assert.equal(res.value.length, 15)
})
},
'Should replace text': function (browser: NightwatchBrowser) {
'Should replace text #group1': function (browser: NightwatchBrowser) {
browser
.waitForElementVisible('*[data-id="toggle_replace"]').click('*[data-id="toggle_replace"]')
.waitForElementVisible('*[id="search_replace"]')
@ -95,7 +96,7 @@ module.exports = {
browser.assert.ok(content.includes('replacing deployer for a constructor'), 'should replace text ok')
})
},
'Should replace text without confirmation': function (browser: NightwatchBrowser) {
'Should replace text without confirmation #group1': function (browser: NightwatchBrowser) {
browser.click('*[data-id="confirm_replace_label"]').pause(500)
.clearValue('*[id="search_input"]')
.setValue('*[id="search_input"]', 'replacing').sendKeys('*[id="search_input"]', browser.Keys.ENTER).pause(1000)
@ -108,7 +109,7 @@ module.exports = {
browser.assert.ok(content.includes('replacing2 deployer for a constructor'), 'should replace text ok')
})
},
'Should replace all & undo': function (browser: NightwatchBrowser) {
'Should replace all & undo #group1': function (browser: NightwatchBrowser) {
browser
.clearValue('*[id="search_input"]')
.setValue('*[id="search_input"]', 'storage').sendKeys('*[id="search_input"]', browser.Keys.ENTER)
@ -127,7 +128,7 @@ module.exports = {
browser.assert.ok(content.includes('title Storage'), 'should undo text ok')
})
},
'Should replace all & undo & switch between files': function (browser: NightwatchBrowser) {
'Should replace all & undo & switch between files #group1': function (browser: NightwatchBrowser) {
browser.waitForElementVisible('*[id="search_input"]')
.clearValue('*[id="search_input"]')
.setValue('*[id="search_input"]', 'storage').sendKeys('*[id="search_input"]', browser.Keys.ENTER)
@ -163,14 +164,20 @@ module.exports = {
browser.assert.ok(content.includes("Storage' contract"), 'should replace text ok')
})
},
'Should hide button when edited content is the same': function (browser: NightwatchBrowser) {
'Should hide button when edited content is the same #group2': function (browser: NightwatchBrowser) {
browser.waitForElementVisible('*[data-id="remixIdeSidePanel"]')
.addFile('test.sol', { content: '123' })
.click('*[plugin="search"]').waitForElementVisible('*[id="search_input"]')
.click('*[plugin="search"]')
.waitForElementVisible('*[id="search_input"]')
.waitForElementVisible('*[data-id="toggle_replace"]')
.click('*[data-id="toggle_replace"]')
.clearValue('*[id="search_input"]')
.setValue('*[id="search_input"]', '123').sendKeys('*[id="search_input"]', browser.Keys.ENTER)
.setValue('*[id="search_input"]', '123')
.sendKeys('*[id="search_input"]', browser.Keys.ENTER)
.waitForElementVisible('*[id="search_replace"]')
.clearValue('*[id="search_replace"]')
.setValue('*[id="search_replace"]', '456').pause(1000)
.click('*[data-id="confirm_replace_label"]').pause(500)
.waitForElementVisible('*[data-id="replace-all-test.sol"]')
.click('*[data-id="replace-all-test.sol"]').pause(2000)
.getEditorValue((content) => {
@ -181,13 +188,14 @@ module.exports = {
.getEditorValue((content) => {
browser.assert.ok(content.includes('123'), 'should have text ok')
}
).pause(1000)
).pause(5000)
.waitForElementNotPresent('*[data-id="undo-replace-test.sol"]')
},
'Should disable/enable button when edited content changed': function (browser: NightwatchBrowser) {
'Should disable/enable button when edited content changed #group2': function (browser: NightwatchBrowser) {
browser
.waitForElementVisible('*[id="search_input"]')
.clearValue('*[id="search_input"]')
.clearValue('*[id="search_input"]')
.setValue('*[id="search_input"]', '123').sendKeys('*[id="search_input"]', browser.Keys.ENTER)
.clearValue('*[id="search_replace"]')
.setValue('*[id="search_replace"]', 'replaced').pause(1000)
@ -201,7 +209,7 @@ module.exports = {
.getEditorValue((content) => {
browser.assert.ok(content.includes('changed'), 'should have text ok')
}
).pause(1000)
).pause(5000)
.waitForElementVisible('*[data-id="undo-replace-test.sol"]')
.getAttribute('[data-id="undo-replace-test.sol"]', 'disabled', (result) => {
browser.assert.equal(result.value, 'true', 'should be disabled')
@ -222,7 +230,7 @@ module.exports = {
.waitForElementNotPresent('*[data-id="undo-replace-test.sol"]')
},
'should clear search': function (browser: NightwatchBrowser) {
'should clear search #group2': function (browser: NightwatchBrowser) {
browser
.waitForElementVisible('*[id="search_input"]')
.setValue('*[id="search_input"]', 'nodata').sendKeys('*[id="search_input"]', browser.Keys.ENTER).pause(1000)

@ -177,10 +177,16 @@ module.exports = {
// creating a new workspace
.click('*[data-id="workspaceCreate"]')
.waitForElementVisible('*[data-id="modalDialogCustomPromptTextCreate"]')
// eslint-disable-next-line dot-notation
.execute(function () { document.querySelector('*[data-id="modalDialogCustomPromptTextCreate"]')['value'] = 'workspace_new' })
.click('*[data-id="fileSystemModalDialogContainer-react"] input[data-id="modalDialogCustomPromptTextCreate"]')
.setValue('*[data-id="fileSystemModalDialogContainer-react"] input[data-id="modalDialogCustomPromptTextCreate"]', 'workspace_new')
.pause(2000)
.getValue('*[data-id="fileSystemModalDialogContainer-react"] input[data-id="modalDialogCustomPromptTextCreate"]', (result) => {
console.log(result)
browser.assert.equal(result.value, 'workspace_new')
})
.waitForElementVisible('*[data-id="fileSystem-modal-footer-ok-react"]')
.execute(function () { (document.querySelector('[data-id="fileSystem-modal-footer-ok-react"]') as HTMLElement).click() })
.click('*[data-id="fileSystem-modal-footer-ok-react"]')
.pause(3000)
.currentWorkspaceIs('workspace_new')
.waitForElementVisible('li[data-id="treeViewLitreeViewItem.deps/remix-tests/remix_tests.sol"]')
.waitForElementVisible('li[data-id="treeViewLitreeViewItem.deps/remix-tests/remix_accounts.sol"]')

@ -7,7 +7,6 @@ module.exports = {
before: function (browser: NightwatchBrowser, done: VoidFunction) {
init(browser, done, 'http://127.0.0.1:8080?plugins=solidity,udapp', false)
},
'Should execution a simple console command #group1 #group999': function (browser: NightwatchBrowser) {
browser
.waitForElementVisible('*[data-id="terminalCli"]', 10000)
@ -164,10 +163,9 @@ module.exports = {
'Should print hardhat logs #group4': function (browser: NightwatchBrowser) {
browser
.click('*[data-id="terminalClearConsole"]') // clear the terminal
.addFile('printHardhatlog.sol', { content: hardhatLog })
.clickLaunchIcon('solidity')
.waitForElementVisible('[for="autoCompile"]')
.click('[for="autoCompile"]')
.clickLaunchIcon('udapp')
.testContracts('printHardhatlog.sol', { content: hardhatLog }, ['OwnerTest'])
.clickLaunchIcon('udapp')
.click('*[data-id="deployAndRunClearInstances"]')
@ -202,6 +200,30 @@ module.exports = {
.sendKeys('body', [browser.Keys.CONTROL, browser.Keys.SHIFT, 's'])
.pause(15000)
.journalLastChildIncludes('147')
},
'Should run a script which log transaction and block using web3.js and ethers #group7': function (browser: NightwatchBrowser) {
browser
.clickLaunchIcon('udapp')
.switchEnvironment('External Http Provider')
.waitForElementPresent('[data-id="basic-http-provider-modal-footer-ok-react"]')
.execute(() => {
(document.querySelector('*[data-id="basic-http-providerModalDialogContainer-react"] input[data-id="modalDialogCustomPromp"]') as any).focus()
}, [], () => {})
.setValue('[data-id="modalDialogCustomPromp"]', 'https://remix-goerli.ethdevops.io')
.modalFooterOKClick('basic-http-provider')
.clickLaunchIcon('filePanel')
.openFile('README.txt')
.addFile('scripts/log_tx_block.js', { content: scriptBlockAndTransaction } )
.pause(1000)
.executeScriptInTerminal('remix.execute(\'scripts/log_tx_block.js\')')
.pause(10000)
// check if the input of the transaction is being logged (web3 call)
.journalChildIncludes('0x775526410000000000000000000000000000000000000000000000000000000000000060464c0335b2f1609abd9de25141c0a3b49db516fc7375970dc737c32b986e88e3000000000000000000000000000000000000000000000000000000000000039e000000000000000000000000000000000000000000000000000000000000000602926b30b10e7a514d92bc71e085f5bff2687fac2856ae43ef7621bf1756fa370516d310bec5727543089be9a4d5f68471174ee528e95a2520b0ca36c2b6c6eb0000000000000000000000000000000000000000000000000000000000046f49036f5e4ea4dd042801c8841e3db8e654124305da0f11824fc1db60c405dbb39f00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000')
// check if the logsBloom is being logged (web3 call)
.journalChildIncludes('0x00000000000000000000000000100000000000000000020000000000002000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000000000000040000000060000000000000000000000000000000000000000000000000000000000000000000000000000000400000000000000100000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000001')
// check if the logsBloom is being logged (ethers.js call)
.journalChildIncludes('"hex":"0x025cd8"')
}
}
@ -607,3 +629,17 @@ const scriptAutoExec = {
})()
`
}
const scriptBlockAndTransaction = `
// Right click on the script name and hit "Run" to execute
(async () => {
try {
web3.eth.getTransaction('0x022ccd55747677ac50f8d9dfd1bf5b843fa2f36438a28c1d0a0958e057bb3e2a').then(console.log)
web3.eth.getBlock('7367447').then(console.log);
let ethersProvider = new ethers.providers.Web3Provider(web3Provider)
ethersProvider.getBlock(7367447).then(console.log)
} catch (e) {
console.log(e.message)
}
})()
`

@ -23,8 +23,8 @@ module.exports = {
status: 'true Transaction mined and execution succeed',
'decoded output': { 0: 'uint256: 8' }
})
.pause(500)
.checkTerminalFilter('0x12332162e2e31397dc1e07ed0a1cf08f728e9b4487c6f9ed79d2f39410c92782', '')
.pause(120000)
//.checkTerminalFilter('0x12332162e2e31397dc1e07ed0a1cf08f728e9b4487c6f9ed79d2f39410c92782', '')
.clickFunction('g - transact (not payable)')
.testFunction('last',
{

@ -9,13 +9,14 @@ const sources = [
]
module.exports = {
'@disabled': true,
before: function (browser: NightwatchBrowser, done: VoidFunction) {
init(browser, done)
},
'@sources': function () {
return sources
},
'The sequence: Compiling / Deploying / Compiling another contract / calling the first contract - should display in the log the transaction with all the decoded information': function (browser: NightwatchBrowser) {
'The sequence: Compiling / Deploying / Compiling another contract / calling the first contract - should display in the log the transaction with all the decoded information #group1': function (browser: NightwatchBrowser) {
// https://github.com/ethereum/remix-ide/issues/2864
browser
.waitForElementVisible('*[data-id="remixIdeIconPanel"]', 10000)
@ -36,8 +37,7 @@ module.exports = {
.clickLaunchIcon('solidity')
.testContracts('Untitled1.sol', sources[1]['Untitled1.sol'], ['test'])
.clickLaunchIcon('udapp')
.clickFunction('delegate - transact (not payable)', { types: 'address to', values: '' })
.pause(5000)
.clickFunction('delegate - transact (not payable)', { types: 'address to', values: '"0x4b0897b0513fdc7c541b6d9d7e929c4e5364d2db"' })
.testFunction('last',
{
status: 'false Transaction mined but execution failed',

@ -5,10 +5,43 @@ import init from '../helpers/init'
import examples from '../examples/example-contracts'
const sources = [
{ 'Untitled.sol': { content: examples.ballot.content } }
{ 'Untitled.sol': { content: examples.ballot.content } },
{
'myTokenV1.sol': {
content: `
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.4;
import "@openzeppelin/contracts-upgradeable/token/ERC721/ERC721Upgradeable.sol";
import "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol";
import "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol";
import "@openzeppelin/contracts-upgradeable/proxy/utils/UUPSUpgradeable.sol";
contract MyToken is Initializable, ERC721Upgradeable, OwnableUpgradeable, UUPSUpgradeable {
/// @custom:oz-upgrades-unsafe-allow constructor
constructor() {
_disableInitializers();
}
function initialize() initializer public {
__ERC721_init("MyToken", "MTK");
__Ownable_init();
__UUPSUpgradeable_init();
}
function _authorizeUpgrade(address newImplementation)
internal
onlyOwner
override
{}
}
`
}
}
]
module.exports = {
'@disabled': true,
before: function (browser: NightwatchBrowser, done: VoidFunction) {
init(browser, done, 'http://127.0.0.1:8080/#optimize=true&runs=300&evmVersion=istanbul&version=soljson-v0.7.4+commit.3f05b770.js', true)
},
@ -17,7 +50,7 @@ module.exports = {
return sources
},
'Should load the code from URL params (code param)': function (browser: NightwatchBrowser) {
'Should load the code from URL params (code param) #group1': function (browser: NightwatchBrowser) {
browser
.waitForElementVisible('[for="autoCompile"]')
.click('[for="autoCompile"]') // we set it too false in the local storage
@ -38,7 +71,7 @@ module.exports = {
})
},
'Should load the code from URL params (url param)': function (browser: NightwatchBrowser) {
'Should load the code from URL params (url param) #group1': function (browser: NightwatchBrowser) {
browser
.pause(5000)
.url('http://127.0.0.1:8080/#optimize=true&runs=300&evmVersion=istanbul&version=soljson-v0.7.4+commit.3f05b770.js&url=https://github.com/ethereum/remix-project/blob/master/apps/remix-ide/contracts/app/solidity/mode.sol')
@ -52,7 +85,36 @@ module.exports = {
})
},
'Should load the code from URL & code params': function (browser: NightwatchBrowser) {
'Should load Etherscan verified contractss from URL "address" param) #group2': function (browser: NightwatchBrowser) {
browser
.pause(5000)
.url('http://127.0.0.1:8080/#address=0x56db08fb78bc6689a1ef66efd079083fed0e4915')
.refresh()
.pause(7000)
.currentWorkspaceIs('etherscan-code-sample')
.assert.elementPresent('*[data-id=treeViewLitreeViewItemropsten]')
.assert.elementPresent('*[data-id=treeViewLitreeViewItemrinkeby]')
.assert.elementPresent('*[data-id="treeViewLitreeViewItemrinkeby/0x56db08fb78bc6689a1ef66efd079083fed0e4915"]')
.assert.elementPresent('*[data-id="treeViewLitreeViewItemrinkeby/0x56db08fb78bc6689a1ef66efd079083fed0e4915/Sample.sol"]')
.getEditorValue((content) => {
browser.assert.ok(content && content.indexOf(
'contract Sample {') !== -1)
})
.url('http://127.0.0.1:8080/#address=0xdac17f958d2ee523a2206206994597c13d831ec7')
.refresh()
.pause(7000)
.currentWorkspaceIs('etherscan-code-sample')
.assert.elementPresent('*[data-id=treeViewLitreeViewItemmainnet]')
.assert.elementPresent('*[data-id="treeViewLitreeViewItemmainnet/0xdac17f958d2ee523a2206206994597c13d831ec7"]')
.assert.elementPresent('*[data-id="treeViewLitreeViewItemmainnet/0xdac17f958d2ee523a2206206994597c13d831ec7/TetherToken.sol"]')
.getEditorValue((content) => {
browser.assert.ok(content && content.indexOf(
'contract TetherToken is Pausable, StandardToken, BlackList {') !== -1)
})
},
'Should load the code from URL & code params #group1': function (browser: NightwatchBrowser) {
browser
.pause(5000)
.url('http://127.0.0.1:8080/#optimize=true&runs=300&evmVersion=istanbul&version=soljson-v0.7.4+commit.3f05b770.js&url=https://github.com/ethereum/remix-project/blob/master/apps/remix-ide/contracts/app/solidity/mode.sol&code=cHJhZ21hIHNvbGlkaXR5ID49MC42LjAgPDAuNy4wOwoKaW1wb3J0ICJodHRwczovL2dpdGh1Yi5jb20vT3BlblplcHBlbGluL29wZW56ZXBwZWxpbi1jb250cmFjdHMvYmxvYi9tYXN0ZXIvY29udHJhY3RzL2FjY2Vzcy9Pd25hYmxlLnNvbCI7Cgpjb250cmFjdCBHZXRQYWlkIGlzIE93bmFibGUgewogIGZ1bmN0aW9uIHdpdGhkcmF3KCkgZXh0ZXJuYWwgb25seU93bmVyIHsKICB9Cn0')
@ -76,7 +138,58 @@ module.exports = {
.openFile('ethereum/remix-project/apps/remix-ide/contracts/app/solidity/mode.sol')
},
'Should load using URL compiler params': function (browser: NightwatchBrowser) {
'Should load the code from language & code params #group1': function (browser: NightwatchBrowser) {
browser
.pause(5000)
.url('http://127.0.0.1:8080/#language=yul&version=soljson-v0.8.7+commit.e28d00a7.js&code=Ly8gQSBjb250cmFjdCBjb25zaXN0cyBvZiBhIHNpbmdsZSBvYmplY3Qgd2l0aCBzdWItb2JqZWN0cyByZXByZXNlbnRpbmcKLy8gdGhlIGNvZGUgdG8gYmUgZGVwbG95ZWQgb3Igb3RoZXIgY29udHJhY3RzIGl0IGNhbiBjcmVhdGUuCi8vIFRoZSBzaW5nbGUgImNvZGUiIG5vZGUgaXMgdGhlIGV4ZWN1dGFibGUgY29kZSBvZiB0aGUgb2JqZWN0LgovLyBFdmVyeSAob3RoZXIpIG5hbWVkIG9iamVjdCBvciBkYXRhIHNlY3Rpb24gaXMgc2VyaWFsaXplZCBhbmQKLy8gbWFkZSBhY2Nlc3NpYmxlIHRvIHRoZSBzcGVjaWFsIGJ1aWx0LWluIGZ1bmN0aW9ucyBkYXRhY29weSAvIGRhdGFvZmZzZXQgLyBkYXRhc2l6ZQovLyBUaGUgY3VycmVudCBvYmplY3QsIHN1Yi1vYmplY3RzIGFuZCBkYXRhIGl0ZW1zIGluc2lkZSB0aGUgY3VycmVudCBvYmplY3QKLy8gYXJlIGluIHNjb3BlLgpvYmplY3QgIkNvbnRyYWN0MSIgewogICAgLy8gVGhpcyBpcyB0aGUgY29uc3RydWN0b3IgY29kZSBvZiB0aGUgY29udHJhY3QuCiAgICBjb2RlIHsKICAgICAgICBmdW5jdGlvbiBhbGxvY2F0ZShzaXplKSAtPiBwdHIgewogICAgICAgICAgICBwdHIgOj0gbWxvYWQoMHg0MCkKICAgICAgICAgICAgaWYgaXN6ZXJvKHB0cikgeyBwdHIgOj0gMHg2MCB9CiAgICAgICAgICAgIG1zdG9yZSgweDQwLCBhZGQocHRyLCBzaXplKSkKICAgICAgICB9CgogICAgICAgIC8vIGZpcnN0IGNyZWF0ZSAiQ29udHJhY3QyIgogICAgICAgIGxldCBzaXplIDo9IGRhdGFzaXplKCJDb250cmFjdDIiKQogICAgICAgIGxldCBvZmZzZXQgOj0gYWxsb2NhdGUoc2l6ZSkKICAgICAgICAvLyBUaGlzIHdpbGwgdHVybiBpbnRvIGNvZGVjb3B5IGZvciBFVk0KICAgICAgICBkYXRhY29weShvZmZzZXQsIGRhdGFvZmZzZXQoIkNvbnRyYWN0MiIpLCBzaXplKQogICAgICAgIC8vIGNvbnN0cnVjdG9yIHBhcmFtZXRlciBpcyBhIHNpbmdsZSBudW1iZXIgMHgxMjM0CiAgICAgICAgbXN0b3JlKGFkZChvZmZzZXQsIHNpemUpLCAweDEyMzQpCiAgICAgICAgcG9wKGNyZWF0ZShvZmZzZXQsIGFkZChzaXplLCAzMiksIDApKQoKICAgICAgICAvLyBub3cgcmV0dXJuIHRoZSBydW50aW1lIG9iamVjdCAodGhlIGN1cnJlbnRseQogICAgICAgIC8vIGV4ZWN1dGluZyBjb2RlIGlzIHRoZSBjb25zdHJ1Y3RvciBjb2RlKQogICAgICAgIHNpemUgOj0gZGF0YXNpemUoIkNvbnRyYWN0MV9kZXBsb3llZCIpCiAgICAgICAgb2Zmc2V0IDo9IGFsbG9jYXRlKHNpemUpCiAgICAgICAgLy8gVGhpcyB3aWxsIHR1cm4gaW50byBhIG1lbW9yeS0+bWVtb3J5IGNvcHkgZm9yIEV3YXNtIGFuZAogICAgICAgIC8vIGEgY29kZWNvcHkgZm9yIEVWTQogICAgICAgIGRhdGFjb3B5KG9mZnNldCwgZGF0YW9mZnNldCgiQ29udHJhY3QxX2RlcGxveWVkIiksIHNpemUpCiAgICAgICAgcmV0dXJuKG9mZnNldCwgc2l6ZSkKICAgIH0KCiAgICBkYXRhICJUYWJsZTIiIGhleCI0MTIzIgoKICAgIG9iamVjdCAiQ29udHJhY3QxX2RlcGxveWVkIiB7CiAgICAgICAgY29kZSB7CiAgICAgICAgICAgIGZ1bmN0aW9uIGFsbG9jYXRlKHNpemUpIC0+IHB0ciB7CiAgICAgICAgICAgICAgICBwdHIgOj0gbWxvYWQoMHg0MCkKICAgICAgICAgICAgICAgIGlmIGlzemVybyhwdHIpIHsgcHRyIDo9IDB4NjAgfQogICAgICAgICAgICAgICAgbXN0b3JlKDB4NDAsIGFkZChwdHIsIHNpemUpKQogICAgICAgICAgICB9CgogICAgICAgICAgICAvLyBydW50aW1lIGNvZGUKCiAgICAgICAgICAgIG1zdG9yZSgwLCAiSGVsbG8sIFdvcmxkISIpCiAgICAgICAgICAgIHJldHVybigwLCAweDIwKQogICAgICAgIH0KICAgIH0KCiAgICAvLyBFbWJlZGRlZCBvYmplY3QuIFVzZSBjYXNlIGlzIHRoYXQgdGhlIG91dHNpZGUgaXMgYSBmYWN0b3J5IGNvbnRyYWN0LAogICAgLy8gYW5kIENvbnRyYWN0MiBpcyB0aGUgY29kZSB0byBiZSBjcmVhdGVkIGJ5IHRoZSBmYWN0b3J5CiAgICBvYmplY3QgIkNvbnRyYWN0MiIgewogICAgICAgIGNvZGUgewogICAgICAgICAgICAvLyBjb2RlIGhlcmUgLi4uCiAgICAgICAgfQoKICAgICAgICBvYmplY3QgIkNvbnRyYWN0Ml9kZXBsb3llZCIgewogICAgICAgICAgICBjb2RlIHsKICAgICAgICAgICAgICAgIC8vIGNvZGUgaGVyZSAuLi4KICAgICAgICAgICAgfQogICAgICAgIH0KCiAgICAgICAgZGF0YSAiVGFibGUxIiBoZXgiNDEyMyIKICAgIH0KfQ&optimize=false&runs=200&evmVersion=null')
.refresh()
.pause(5000)
.clickLaunchIcon('filePanel')
.currentWorkspaceIs('code-sample')
.waitForElementVisible('*[data-id="treeViewLitreeViewItemcontract-eaa022e37e.yul"]', 6000)
.openFile('contract-eaa022e37e.yul')
.getEditorValue((content) => {
browser.assert.ok(content && content.indexOf(
'object "Contract1" {') !== -1)
})
},
'Should select deploy with proxy option from URL params #group1': function (browser: NightwatchBrowser) {
browser
.url('http://127.0.0.1:8080/#optimize=false&runs=200&deployProxy=true')
.refresh()
.pause(5000)
.switchWorkspace('default_workspace')
.addFile('myTokenV1.sol', sources[1]['myTokenV1.sol'])
.clickLaunchIcon('solidity')
.pause(2000)
.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]')
.waitForElementPresent('[data-id="contractGUIDeployWithProxyLabel"]')
.expect.element('[data-id="contractGUIDeployWithProxy"]').to.be.selected
},
'Should select upgrade with proxy option from URL params #group1': function (browser: NightwatchBrowser) {
browser
.url('http://127.0.0.1:8080/#optimize=false&runs=200&upgradeProxy=true')
.refresh()
.pause(5000)
.openFile('myTokenV1.sol')
.clickLaunchIcon('solidity')
.pause(2000)
.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]')
.waitForElementPresent('[data-id="contractGUIUpgradeImplementationLabel"]')
.expect.element('[data-id="contractGUIUpgradeImplementation"]').to.be.selected
},
'Should load using various URL compiler params #group1': function (browser: NightwatchBrowser) {
browser
.pause(5000)
.url('http://127.0.0.1:8080/#optimize=true&runs=300&autoCompile=true&evmVersion=istanbul&version=soljson-v0.7.4+commit.3f05b770.js&language=Yul')
@ -90,9 +203,19 @@ module.exports = {
.verify.elementPresent('#optimize:checked')
.verify.elementPresent('#autoCompile:checked')
.verify.attributeEquals('#runs', 'value', '300')
.url('http://127.0.0.1:8080/#version=0.8.7')
.refresh()
.pause(5000)
.clickLaunchIcon('solidity')
.assert.containsText('#versionSelector option[data-id="selected"]', '0.8.7+commit.e28d00a7')
.url('http://127.0.0.1:8080/#version=0.8.15+commit.e14f2714')
.refresh()
.pause(5000)
.clickLaunchIcon('solidity')
.assert.containsText('#versionSelector option[data-id="selected"]', '0.8.15+commit.e14f2714')
},
'Should load using compiler from link passed in remix URL': function (browser: NightwatchBrowser) {
'Should load using compiler from link passed in remix URL #group1': function (browser: NightwatchBrowser) {
browser
.url('http://127.0.0.1:8080/#version=https://solidity-blog.s3.eu-central-1.amazonaws.com/data/08preview/soljson.js&optimize=false')
.refresh()
@ -110,7 +233,7 @@ module.exports = {
.verify.attributeEquals('#runs', 'value', '200')
},
'Should load json files from link passed in remix URL': function (browser: NightwatchBrowser) {
'Should load json files from link passed in remix URL #group1': function (browser: NightwatchBrowser) {
browser
.url('http://127.0.0.1:8080/#optimize=false&runs=200&evmVersion=null&version=soljson-v0.6.12+commit.27d51765.js&url=https://raw.githubusercontent.com/EthVM/evm-source-verification/main/contracts/1/0x011e5846975c6463a8c6337eecf3cbf64e328884/input.json')
.refresh()

@ -0,0 +1,96 @@
'use strict'
import { NightwatchBrowser } from 'nightwatch'
import init from '../helpers/init'
declare global {
interface Window { testplugin: { name: string, url: string }; }
}
module.exports = {
'@disabled': true,
before: function (browser: NightwatchBrowser, done: VoidFunction) {
init(browser, done, null, true, { name: 'vyper', url: 'http://127.0.0.1:5002'})
},
'Should connect to vyper plugin #group1': function (browser: NightwatchBrowser) {
browser.clickLaunchIcon('pluginManager')
.scrollAndClick('[data-id="pluginManagerComponentActivateButtonvyper"]')
.clickLaunchIcon('vyper')
.pause(5000)
// @ts-ignore
.frame(0)
},
'Should add the Ballot.vy #group1': function (browser: NightwatchBrowser) {
browser.click('button[data-id="add-ballot"]')
.frameParent()
.openFile('ballot.vy')
},
'Compile ballot.vy should error #group1': function (browser: NightwatchBrowser) {
browser.clickLaunchIcon('vyper')
// @ts-ignore
.frame(0)
.click('[data-id="remote-compiler"]')
.click('[data-id="compile"]')
.assert.containsText('[data-id="error-message"]', 'unexpected indent')
},
'Compile test contract should success #group1': function (browser: NightwatchBrowser) {
let contractAddress
browser
.frameParent()
.addFile('test.vy', { content: testContract })
.clickLaunchIcon('vyper')
// @ts-ignore
.frame(0)
.click('[data-id="compile"]')
.frameParent()
.clickLaunchIcon('udapp')
.createContract('')
.clickInstance(0)
.clickFunction('totalPokemonCount - call')
.getAddressAtPosition(0, (address) => {
console.log('Vyper contract ' + address)
contractAddress = address
})
.perform((done) => {
browser.verifyCallReturnValue(contractAddress, ['0:uint256: 0'])
.perform(() => done())
})
}
}
const testContract = `
# @version >=0.2.4 <0.3.0
DNA_DIGITS: constant(uint256) = 16
DNA_MODULUS: constant(uint256) = 10 ** DNA_DIGITS
# add HP_LIMIT
struct Pokemon:
name: String[32]
dna: uint256
HP: uint256
matches: uint256
wins: uint256
totalPokemonCount: public(uint256)
pokemonList: HashMap[uint256, Pokemon]
@pure
@internal
def _generateRandomDNA(_name: String[32]) -> uint256:
random: uint256 = convert(keccak256(_name), uint256)
return random % DNA_MODULUS
# modify _createPokemon
@internal
def _createPokemon(_name: String[32], _dna: uint256, _HP: uint256):
self.pokemonList[self.totalPokemonCount] = Pokemon({
name: _name,
dna: _dna,
HP: _HP,
matches: 0,
wins: 0
})
self.totalPokemonCount += 1`

@ -33,7 +33,7 @@ module.exports = {
// WORKSPACE TEMPLATES E2E START
'Should create Remix default workspace with files': function (browser: NightwatchBrowser) {
'Should create Remix default workspace with files #group1': function (browser: NightwatchBrowser) {
browser
.clickLaunchIcon('filePanel')
.click('*[data-id="workspaceCreate"]')
@ -44,12 +44,12 @@ module.exports = {
.waitForElementPresent('[data-id="fileSystemModalDialogModalFooter-react"] .modal-ok')
.execute(function () { (document.querySelector('[data-id="fileSystemModalDialogModalFooter-react"] .modal-ok') as HTMLElement).click() })
.pause(1000)
.assert.elementPresent('*[data-id="treeViewLitreeViewItemcontracts"]')
.assert.elementPresent('*[data-id="treeViewLitreeViewItemcontracts/1_Storage.sol"]')
.assert.elementPresent('*[data-id="treeViewLitreeViewItemcontracts/2_Owner.sol"]')
.assert.elementPresent('*[data-id="treeViewLitreeViewItemcontracts/3_Ballot.sol"]')
.assert.elementPresent('*[data-id="treeViewLitreeViewItemscripts"]')
.assert.elementPresent('*[data-id="treeViewLitreeViewItemscripts/deploy_with_web3.ts"]')
.waitForElementVisible('*[data-id="treeViewLitreeViewItemcontracts"]')
.waitForElementVisible('*[data-id="treeViewLitreeViewItemcontracts/1_Storage.sol"]')
.waitForElementVisible('*[data-id="treeViewLitreeViewItemcontracts/2_Owner.sol"]')
.waitForElementVisible('*[data-id="treeViewLitreeViewItemcontracts/3_Ballot.sol"]')
.waitForElementVisible('*[data-id="treeViewLitreeViewItemscripts"]')
.waitForElementVisible('*[data-id="treeViewLitreeViewItemscripts/deploy_with_web3.ts"]')
// check js and ts files are not transformed
.click('*[data-id="treeViewLitreeViewItemscripts/deploy_with_web3.ts"]')
@ -57,40 +57,40 @@ module.exports = {
browser.assert.ok(content.indexOf(`import { deploy } from './web3-lib'`) !== -1,
'Incorrect content')
})
.assert.elementPresent('*[data-id="treeViewLitreeViewItemscripts/deploy_with_ethers.ts"]')
.waitForElementVisible('*[data-id="treeViewLitreeViewItemscripts/deploy_with_ethers.ts"]')
.click('*[data-id="treeViewLitreeViewItemscripts/deploy_with_ethers.ts"]')
.pause(100)
.getEditorValue((content) => {
browser.assert.ok(content.indexOf(`import { deploy } from './ethers-lib'`) !== -1,
'Incorrect content')
})
.assert.elementPresent('*[data-id="treeViewLitreeViewItemscripts/web3-lib.ts"]')
.waitForElementVisible('*[data-id="treeViewLitreeViewItemscripts/web3-lib.ts"]')
.click('*[data-id="treeViewLitreeViewItemscripts/web3-lib.ts"]')
.pause(2000)
.getEditorValue((content) => {
browser.assert.ok(content.indexOf(`export const deploy = async (contractName: string, args: Array<any>, from?: string, gas?: number): Promise<Options> => {`) !== -1,
'Incorrect content')
})
.assert.elementPresent('*[data-id="treeViewLitreeViewItemscripts/ethers-lib.ts"]')
.waitForElementVisible('*[data-id="treeViewLitreeViewItemscripts/ethers-lib.ts"]')
.click('*[data-id="treeViewLitreeViewItemscripts/ethers-lib.ts"]')
.pause(100)
.getEditorValue((content) => {
browser.assert.ok(content.indexOf(`export const deploy = async (contractName: string, args: Array<any>, accountIndex?: number): Promise<ethers.Contract> => {`) !== -1,
'Incorrect content')
})
.assert.elementPresent('*[data-id="treeViewLitreeViewItemtests"]')
.assert.elementPresent('*[data-id="treeViewLitreeViewItemtests/storage.test.js"]')
.waitForElementVisible('*[data-id="treeViewLitreeViewItemtests"]')
.waitForElementVisible('*[data-id="treeViewLitreeViewItemtests/storage.test.js"]')
.click('*[data-id="treeViewLitreeViewItemtests/storage.test.js"]')
.pause(100)
.getEditorValue((content) => {
browser.assert.ok(content.indexOf(`const { expect } = require("chai");`) !== -1,
'Incorrect content')
})
.assert.elementPresent('*[data-id="treeViewLitreeViewItemtests/Ballot_test.sol"]')
.assert.elementPresent('*[data-id="treeViewLitreeViewItemREADME.txt"]')
.waitForElementVisible('*[data-id="treeViewLitreeViewItemtests/Ballot_test.sol"]')
.waitForElementVisible('*[data-id="treeViewLitreeViewItemREADME.txt"]')
},
'Should create blank workspace with no files': function (browser: NightwatchBrowser) {
'Should create blank workspace with no files #group1': function (browser: NightwatchBrowser) {
browser
.click('*[data-id="workspaceCreate"]')
.waitForElementVisible('*[data-id="modalDialogCustomPromptTextCreate"]')
@ -102,7 +102,7 @@ module.exports = {
.waitForElementPresent('[data-id="fileSystemModalDialogModalFooter-react"] .modal-ok')
.execute(function () { (document.querySelector('[data-id="fileSystemModalDialogModalFooter-react"] .modal-ok') as HTMLElement).click() })
.pause(100)
.assert.elementPresent('*[data-id="treeViewUltreeViewMenu"]')
.waitForElementPresent('*[data-id="treeViewUltreeViewMenu"]')
.execute(function () {
const fileList = document.querySelector('*[data-id="treeViewUltreeViewMenu"]')
return fileList.getElementsByTagName('li').length;
@ -111,7 +111,7 @@ module.exports = {
});
},
'Should create ERC20 workspace with files': function (browser: NightwatchBrowser) {
'Should create ERC20 workspace with files #group1': function (browser: NightwatchBrowser) {
browser
.click('*[data-id="workspaceCreate"]')
.waitForElementVisible('*[data-id="modalDialogCustomPromptTextCreate"]')
@ -123,43 +123,43 @@ module.exports = {
.waitForElementPresent('[data-id="fileSystemModalDialogModalFooter-react"] .modal-ok')
.execute(function () { (document.querySelector('[data-id="fileSystemModalDialogModalFooter-react"] .modal-ok') as HTMLElement).click() })
.pause(100)
.assert.elementPresent('*[data-id="treeViewLitreeViewItemcontracts"]')
.assert.elementPresent('*[data-id="treeViewLitreeViewItemcontracts/SampleERC20.sol"]')
.assert.elementPresent('*[data-id="treeViewLitreeViewItemscripts"]')
.assert.elementPresent('*[data-id="treeViewLitreeViewItemscripts/deploy_with_web3.ts"]')
.waitForElementVisible('*[data-id="treeViewLitreeViewItemcontracts"]')
.waitForElementVisible('*[data-id="treeViewLitreeViewItemcontracts/SampleERC20.sol"]')
.waitForElementVisible('*[data-id="treeViewLitreeViewItemscripts"]')
.waitForElementVisible('*[data-id="treeViewLitreeViewItemscripts/deploy_with_web3.ts"]')
// check js and ts files are not transformed
.click('*[data-id="treeViewLitreeViewItemscripts/deploy_with_web3.ts"]')
.pause(5000)
.getEditorValue((content) => {
browser.assert.ok(content.indexOf(`import { deploy } from './web3-lib'`) !== -1,
'Incorrect content')
})
.assert.elementPresent('*[data-id="treeViewLitreeViewItemscripts/deploy_with_ethers.ts"]')
.waitForElementVisible('*[data-id="treeViewLitreeViewItemscripts/deploy_with_ethers.ts"]')
.click('*[data-id="treeViewLitreeViewItemscripts/deploy_with_ethers.ts"]')
.pause(100)
.pause(4000)
.getEditorValue((content) => {
browser.assert.ok(content.indexOf(`import { deploy } from './ethers-lib'`) !== -1,
'Incorrect content')
})
.assert.elementPresent('*[data-id="treeViewLitreeViewItemscripts/web3-lib.ts"]')
.waitForElementVisible('*[data-id="treeViewLitreeViewItemscripts/web3-lib.ts"]')
.click('*[data-id="treeViewLitreeViewItemscripts/web3-lib.ts"]')
.pause(100)
.pause(4000)
.getEditorValue((content) => {
browser.assert.ok(content.indexOf(`export const deploy = async (contractName: string, args: Array<any>, from?: string, gas?: number): Promise<Options> => {`) !== -1,
'Incorrect content')
})
.assert.elementPresent('*[data-id="treeViewLitreeViewItemscripts/ethers-lib.ts"]')
.waitForElementVisible('*[data-id="treeViewLitreeViewItemscripts/ethers-lib.ts"]')
.click('*[data-id="treeViewLitreeViewItemscripts/ethers-lib.ts"]')
.pause(100)
.pause(4000)
.getEditorValue((content) => {
browser.assert.ok(content.indexOf(`export const deploy = async (contractName: string, args: Array<any>, accountIndex?: number): Promise<ethers.Contract> => {`) !== -1,
'Incorrect content')
})
.assert.elementPresent('*[data-id="treeViewLitreeViewItemtests"]')
.assert.elementPresent('*[data-id="treeViewLitreeViewItemtests/SampleERC20_test.sol"]')
.waitForElementVisible('*[data-id="treeViewLitreeViewItemtests"]')
.waitForElementVisible('*[data-id="treeViewLitreeViewItemtests/SampleERC20_test.sol"]')
},
'Should create ERC721 workspace with files': function (browser: NightwatchBrowser) {
'Should create ERC721 workspace with files #group1': function (browser: NightwatchBrowser) {
browser
.click('*[data-id="workspaceCreate"]')
.waitForElementVisible('*[data-id="modalDialogCustomPromptTextCreate"]')
@ -171,72 +171,70 @@ module.exports = {
.waitForElementPresent('[data-id="fileSystemModalDialogModalFooter-react"] .modal-ok')
.execute(function () { (document.querySelector('[data-id="fileSystemModalDialogModalFooter-react"] .modal-ok') as HTMLElement).click() })
.pause(100)
.assert.elementPresent('*[data-id="treeViewLitreeViewItemcontracts"]')
.assert.elementPresent('*[data-id="treeViewLitreeViewItemcontracts/SampleERC721.sol"]')
.assert.elementPresent('*[data-id="treeViewLitreeViewItemscripts"]')
.assert.elementPresent('*[data-id="treeViewLitreeViewItemscripts/deploy_with_web3.ts"]')
.waitForElementVisible('*[data-id="treeViewLitreeViewItemcontracts"]')
.waitForElementVisible('*[data-id="treeViewLitreeViewItemcontracts/SampleERC721.sol"]')
.waitForElementVisible('*[data-id="treeViewLitreeViewItemscripts"]')
.waitForElementVisible('*[data-id="treeViewLitreeViewItemscripts/deploy_with_web3.ts"]')
// check js and ts files are not transformed
.click('*[data-id="treeViewLitreeViewItemscripts/deploy_with_web3.ts"]')
.pause(4000)
.getEditorValue((content) => {
browser.assert.ok(content.indexOf(`import { deploy } from './web3-lib'`) !== -1,
'Incorrect content')
})
.assert.elementPresent('*[data-id="treeViewLitreeViewItemscripts/deploy_with_ethers.ts"]')
.waitForElementVisible('*[data-id="treeViewLitreeViewItemscripts/deploy_with_ethers.ts"]')
.click('*[data-id="treeViewLitreeViewItemscripts/deploy_with_ethers.ts"]')
.pause(100)
.pause(4000)
.getEditorValue((content) => {
browser.assert.ok(content.indexOf(`import { deploy } from './ethers-lib'`) !== -1,
'Incorrect content')
})
.assert.elementPresent('*[data-id="treeViewLitreeViewItemscripts/web3-lib.ts"]')
.waitForElementVisible('*[data-id="treeViewLitreeViewItemscripts/web3-lib.ts"]')
.click('*[data-id="treeViewLitreeViewItemscripts/web3-lib.ts"]')
.pause(100)
.pause(4000)
.getEditorValue((content) => {
browser.assert.ok(content.indexOf(`export const deploy = async (contractName: string, args: Array<any>, from?: string, gas?: number): Promise<Options> => {`) !== -1,
'Incorrect content')
})
.assert.elementPresent('*[data-id="treeViewLitreeViewItemscripts/ethers-lib.ts"]')
.waitForElementVisible('*[data-id="treeViewLitreeViewItemscripts/ethers-lib.ts"]')
.click('*[data-id="treeViewLitreeViewItemscripts/ethers-lib.ts"]')
.pause(100)
.pause(4000)
.getEditorValue((content) => {
browser.assert.ok(content.indexOf(`export const deploy = async (contractName: string, args: Array<any>, accountIndex?: number): Promise<ethers.Contract> => {`) !== -1,
'Incorrect content')
})
.assert.elementPresent('*[data-id="treeViewLitreeViewItemtests"]')
.assert.elementPresent('*[data-id="treeViewLitreeViewItemtests/SampleERC721_test.sol"]')
.waitForElementVisible('*[data-id="treeViewLitreeViewItemtests"]')
.waitForElementVisible('*[data-id="treeViewLitreeViewItemtests/SampleERC721_test.sol"]')
},
// WORKSPACE TEMPLATES E2E END
'Should create two workspace and switch to the first one': function (browser: NightwatchBrowser) {
'Should create two workspace and switch to the first one #group1': function (browser: NightwatchBrowser) {
browser
.click('*[data-id="workspaceCreate"]') // create workspace_name
.click('*[data-id="workspaceCreate"]')
.waitForElementVisible('*[data-id="modalDialogCustomPromptTextCreate"]')
.waitForElementVisible('[data-id="fileSystemModalDialogModalFooter-react"] > button')
.click('*[data-id="modalDialogCustomPromptTextCreate"]')
.clearValue('*[data-id="modalDialogCustomPromptTextCreate"]')
.setValue('*[data-id="modalDialogCustomPromptTextCreate"]', 'workspace_name')
.click('*[data-id="fileSystemModalDialogContainer-react"] input[data-id="modalDialogCustomPromptTextCreate"]')
.setValue('*[data-id="fileSystemModalDialogContainer-react"] input[data-id="modalDialogCustomPromptTextCreate"]', 'workspace_name')
.waitForElementPresent('[data-id="fileSystemModalDialogModalFooter-react"] .modal-ok')
.click('[data-id="fileSystemModalDialogModalFooter-react"] .modal-ok')
.waitForElementVisible('*[data-id="treeViewLitreeViewItemtests"]')
.pause(1000)
.addFile('test.sol', { content: 'test' })
.waitForElementVisible('*[data-id="treeViewLitreeViewItemtest.sol"]')
.click('*[data-id="workspaceCreate"]') // create workspace_name_1
.click('*[data-id="workspaceCreate"]')
.waitForElementVisible('*[data-id="modalDialogCustomPromptTextCreate"]')
.waitForElementVisible('[data-id="fileSystemModalDialogModalFooter-react"] > button')
.click('*[data-id="modalDialogCustomPromptTextCreate"]')
.clearValue('*[data-id="modalDialogCustomPromptTextCreate"]')
.setValue('*[data-id="modalDialogCustomPromptTextCreate"]', 'workspace_name_1')
.click('*[data-id="fileSystemModalDialogContainer-react"] input[data-id="modalDialogCustomPromptTextCreate"]')
.setValue('*[data-id="fileSystemModalDialogContainer-react"] input[data-id="modalDialogCustomPromptTextCreate"]', 'workspace_name_1')
.waitForElementPresent('[data-id="fileSystemModalDialogModalFooter-react"] .modal-ok')
.click('[data-id="fileSystemModalDialogModalFooter-react"] .modal-ok')
.pause(2000)
.waitForElementVisible('*[data-id="treeViewLitreeViewItemtests"]')
.pause(2000)
.waitForElementNotPresent('*[data-id="treeViewLitreeViewItemtest.sol"]')
.pause(2000)
.switchWorkspace('workspace_name')
.waitForElementVisible('*[data-id="treeViewLitreeViewItemtests"]')
.currentWorkspaceIs('workspace_name')
},
'Should rename a workspace #group1': function (browser: NightwatchBrowser) {
@ -251,9 +249,11 @@ module.exports = {
.click('[data-id="fileSystemModalDialogModalFooter-react"] .modal-ok')
.switchWorkspace('workspace_name_1')
.pause(2000)
.currentWorkspaceIs('workspace_name_1')
.waitForElementNotPresent('*[data-id="treeViewLitreeViewItemtest.sol"]')
.switchWorkspace('workspace_name_renamed')
.pause(2000)
.currentWorkspaceIs('workspace_name_renamed')
.waitForElementVisible('*[data-id="treeViewLitreeViewItemtest.sol"]')
},
@ -272,6 +272,7 @@ module.exports = {
'Should clone a repository #group2': function (browser: NightwatchBrowser) {
browser
.clickLaunchIcon('filePanel')
.waitForElementVisible('[data-id="cloneGitRepository"]')
.click('[data-id="cloneGitRepository"]')
.waitForElementVisible('[data-id="fileSystemModalDialogModalBody-react"]')

@ -14,8 +14,7 @@ yarn run remixd &
sleep 5
yarn run build:e2e
TESTFILES=$(grep -IRiL "\'@disabled\': \?true" "dist/apps/remix-ide-e2e/src/tests" | grep "\.spec\|\.test" | sort | circleci tests split )
TESTFILES=$(grep -IRiL "@disabled" "dist/apps/remix-ide-e2e/src/tests" | grep "\.spec\|\.test" | sort | circleci tests split )
for TESTFILE in $TESTFILES; do
npx nightwatch --config dist/apps/remix-ide-e2e/nightwatch.js $TESTFILE --env=$1 || TEST_EXITCODE=1
done

@ -0,0 +1,25 @@
#!/usr/bin/env bash
set -e
BUILD_ID=${CIRCLE_BUILD_NUM:-${TRAVIS_JOB_NUMBER}}
echo "$BUILD_ID"
TEST_EXITCODE=0
yarn run serve:production &
npx nx serve etherscan &
sleep 5
yarn run build:e2e
TESTFILES=$(grep -IRiL "\'@disabled\': \?true" "dist/apps/remix-ide-e2e/src/tests" | grep "etherscan_api" | sort | circleci tests split )
for TESTFILE in $TESTFILES; do
npx nightwatch --config dist/apps/remix-ide-e2e/nightwatch.js $TESTFILE --env=chrome || TEST_EXITCODE=1
done
echo "$TEST_EXITCODE"
if [ "$TEST_EXITCODE" -eq 1 ]
then
exit 1
fi

@ -0,0 +1,25 @@
#!/usr/bin/env bash
set -e
BUILD_ID=${CIRCLE_BUILD_NUM:-${TRAVIS_JOB_NUMBER}}
echo "$BUILD_ID"
TEST_EXITCODE=0
yarn run serve:production &
npx nx serve vyper &
sleep 5
yarn run build:e2e
TESTFILES=$(grep -IRiL "\'@disabled\': \?true" "dist/apps/remix-ide-e2e/src/tests" | grep "vyper_api" | sort | circleci tests split )
for TESTFILE in $TESTFILES; do
npx nightwatch --config dist/apps/remix-ide-e2e/nightwatch.js $TESTFILE --env=chrome || TEST_EXITCODE=1
done
echo "$TEST_EXITCODE"
if [ "$TEST_EXITCODE" -eq 1 ]
then
exit 1
fi

@ -33,6 +33,7 @@ import { FoundryProvider } from './app/tabs/foundry-provider'
import { ExternalHttpProvider } from './app/tabs/external-http-provider'
import { Injected0ptimismProvider } from './app/tabs/injected-optimism-provider'
import { InjectedArbitrumOneProvider } from './app/tabs/injected-arbitrum-one-provider'
import { FileDecorator } from './app/plugins/file-decorator'
const isElectron = require('is-electron')
@ -158,6 +159,9 @@ class AppComponent {
// ----------------- Storage plugin ---------------------------------
const storagePlugin = new StoragePlugin()
// ------- FILE DECORATOR PLUGIN ------------------
const fileDecorator = new FileDecorator()
//----- search
const search = new SearchPlugin()
@ -244,6 +248,7 @@ class AppComponent {
fetchAndCompile,
dGitProvider,
storagePlugin,
fileDecorator,
hardhatProvider,
ganacheProvider,
foundryProvider,
@ -415,6 +420,29 @@ class AppComponent {
this.appManager.call(...callDetails).catch(console.error)
}
}
if (params.calls) {
const calls = params.calls.split("///");
// call all functions in the list, one after the other
for (const call of calls) {
const callDetails = call.split("//");
if (callDetails.length > 1) {
this.appManager.call(
"notification",
"toast",
`initiating ${callDetails[0]} ...`
);
// @todo(remove the timeout when activatePlugin is on 0.3.0)
try {
await this.appManager.call(...callDetails)
} catch (e) {
console.error(e)
}
}
}
}
})
.catch(console.error)
}

@ -57,7 +57,7 @@ module.exports = class Filepanel extends ViewPlugin {
this.slitherHandle = new SlitherHandle()
this.workspaces = []
this.appManager = appManager
this.currentWorkspaceMetadata = {}
this.currentWorkspaceMetadata = null
}
render () {

@ -100,8 +100,8 @@ class Terminal extends Plugin {
this.terminalApi.logHtml(html)
}
log (message) {
this.terminalApi.log(message)
log (message, type) {
this.terminalApi.log(message, type)
}
setDispatch(dispatch) {

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

@ -101,7 +101,7 @@ export abstract class AbstractProvider extends Plugin {
reject('Unable to connect')
}
}, 2000)
await this.provider.ready
await this.provider.detectNetwork() // this throws if the network cannot be detected
this.connected = true
} catch (e) {
this.switchAway(true)
@ -116,6 +116,7 @@ export abstract class AbstractProvider extends Plugin {
}
private async switchAway (showError) {
if (!this.provider) return
this.provider = null
this.blocked = true
this.connected = false

@ -29,7 +29,10 @@ export class Web3ProviderModule extends Plugin {
const provider = this.blockchain.web3().currentProvider
// see https://github.com/ethereum/web3.js/pull/1018/files#diff-d25786686c1053b786cc2626dc6e048675050593c0ebaafbf0814e1996f22022R129
provider[provider.sendAsync ? 'sendAsync' : 'send'](payload, async (error, message) => {
if (error) return reject(error)
if (error) {
this.call('terminal', 'log', error.data ? error.data : error, 'error')
return reject(error.data ? error.data : error)
}
if (payload.method === 'eth_sendTransaction') {
if (payload.params.length && !payload.params[0].to && message.result) {
setTimeout(async () => {

@ -156,7 +156,7 @@ export class ExecutionContext {
return cb()
} else {
if (injectedProvider && injectedProvider._metamask && injectedProvider._metamask.isUnlocked) {
if (!await injectedProvider._metamask.isUnlocked()) this.call('notification', 'toast', 'Please make sure the injected provider is unlocked (e.g Metamask).')
if (!await injectedProvider._metamask.isUnlocked()) infoCb('Please make sure the injected provider is unlocked (e.g Metamask).')
}
this.askPermission()
this.executionContext = context

@ -4,14 +4,17 @@ import { QueryParams } from '@remix-project/remix-lib'
import { IframePlugin } from '@remixproject/engine-web'
const _paq = window._paq = window._paq || []
// requiredModule removes the plugin from the plugin manager list on UI
const requiredModules = [ // services + layout views + system views
'manager', 'config', 'compilerArtefacts', 'compilerMetadata', 'contextualListener', 'editor', 'offsetToLineColumnConverter', 'network', 'theme',
'fileManager', 'contentImport', 'blockchain', 'web3Provider', 'scriptRunner', 'fetchAndCompile', 'mainPanel', 'hiddenPanel', 'sidePanel', 'menuicons',
'filePanel', 'terminal', 'settings', 'pluginManager', 'tabs', 'udapp', 'dGitProvider', 'solidity', 'solidity-logic', 'gistHandler', 'layout',
'notification', 'permissionhandler', 'walkthrough', 'storage', 'restorebackupzip', 'link-libraries', 'deploy-libraries', 'openzeppelin-proxy',
'hardhat-provider', 'compileAndRun', 'search', 'recorder']
'hardhat-provider', 'ganache-provider', 'foundry-provider', 'basic-http-provider', 'injected-optimism-provider', 'injected-arbitrum-one-provider',
'compileAndRun', 'search', 'recorder', 'fileDecorator']
const dependentModules = ['git', 'hardhat', 'truffle', 'slither'] // module which shouldn't be manually activated (e.g git is activated by remixd)
// dependentModules shouldn't be manually activated (e.g hardhat is activated by remixd)
const dependentModules = ['hardhat', 'truffle', 'slither']
const sensitiveCalls = {
'fileManager': ['writeFile', 'copyFile', 'rename', 'copyDir'],
@ -20,7 +23,9 @@ const sensitiveCalls = {
}
export function isNative(name) {
const nativePlugins = ['vyper', 'workshops', 'debugger', 'remixd', 'menuicons', 'solidity', 'solidity-logic', 'solidityStaticAnalysis', 'solidityUnitTesting', 'layout', 'notification', 'hardhat-provider', 'ganache-provider', 'foundry-provider', 'basic-http-provider', 'injected-optimism-provider', 'injected-arbitrum-one-provider']
// nativePlugin allows to bypass the permission request
const nativePlugins = ['vyper', 'workshops', 'debugger', 'remixd', 'menuicons', 'solidity', 'solidity-logic', 'solidityStaticAnalysis', 'solidityUnitTesting',
'layout', 'notification', 'hardhat-provider', 'ganache-provider', 'foundry-provider', 'basic-http-provider', 'injected-optimism-provider', 'injected-arbitrum-one-provider']
return nativePlugins.includes(name) || requiredModules.includes(name)
}
@ -141,9 +146,11 @@ export class RemixAppManager extends PluginManager {
}
}
}
const testPluginName = localStorage.getItem('test-plugin-name')
const testPluginUrl = localStorage.getItem('test-plugin-url')
return plugins.map(plugin => {
if (plugin.name === testPluginName) plugin.url = testPluginUrl
return new IframePlugin(plugin)
// return new IframeReactPlugin(plugin)
})
}

@ -223,9 +223,10 @@ export const CompilerApiMixin = (Base) => class extends Base {
}
this.compiler.event.register('loadingCompiler', this.data.eventHandlers.onLoadingCompiler)
this.data.eventHandlers.onCompilerLoaded = () => {
this.data.eventHandlers.onCompilerLoaded = (version) => {
this.data.loading = false
this.statusChanged({ key: 'none' })
this.emit('compilerLoaded', version)
}
this.compiler.event.register('compilerLoaded', this.data.eventHandlers.onCompilerLoaded)

@ -0,0 +1,13 @@
{
"presets": [
[
"@nrwl/react/babel", {
"runtime": "automatic"
}
]
],
"plugins": [
]
}

@ -0,0 +1,16 @@
# This file is used by:
# 1. autoprefixer to adjust CSS to support the below specified browsers
# 2. babel preset-env to adjust included polyfills
#
# For additional information regarding the format and rule options, please see:
# https://github.com/browserslist/browserslist#queries
#
# If you need to support different browsers in production, you may tweak the list below.
last 1 Chrome version
last 1 Firefox version
last 2 Edge major versions
last 2 Safari major version
last 2 iOS major versions
Firefox ESR
not IE 9-11 # For IE 9-11 support, remove 'not'.

@ -0,0 +1,18 @@
{
"env": {
"browser": true,
"es6": true
},
"extends": "../../.eslintrc.json",
"globals": {
"Atomics": "readonly",
"SharedArrayBuffer": "readonly"
},
"parserOptions": {
"ecmaVersion": 11,
"sourceType": "module"
},
"rules": {
"standard/no-callback-literal": "off"
}
}

@ -0,0 +1,124 @@
* {
box-sizing: border-box;
}
html, body, #root, main {
height: 100%;
margin: 0;
}
#vyper-plugin header {
display: flex;
justify-content: space-between;
align-items: center;
height: 50px;
padding: 5px 15px;
}
#vyper-plugin header a,
#vyper-plugin header .title {
display: flex;
height: 60%;
}
#vyper-plugin header svg,
#vyper-plugin header img {
width: auto;
height: 100%;
}
#vyper-plugin section {
display: flex;
flex-direction: column;
align-items: center;
height: calc(100% - 50px);
}
.btn-group-toggle {
width: 90%;
text-transform: uppercase;
margin: 10px 0;
}
.btn-group-toggle .btn {
cursor: pointer;
font-size: 0.8rem;
}
#local-url {
width: 90%;
}
#compile-btn {
width: 90%;
}
#compile-btn * {
width: 100%;
}
#result {
flex: 1;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
width: 100%;
padding-top: 10px;
overflow: hidden;
}
#result.error {
justify-content: flex-start;
}
#result.error svg {
width: 40px;
height: 40px;
margin: 10px;
}
#result nav {
width: 100%;
flex-wrap: nowrap;
justify-content: space-evenly;
}
#result nav a {
padding: 0.5rem 1rem;
font-size: 0.8rem;
}
#result .tab-content {
flex: 1;
width: 100%;
padding: 15px;
overflow: auto;
}
#result .tab-pane.active {
height: 100%;
display: flex;
flex-direction: column;
align-items: stretch;
}
#result .copy {
padding: 5x;
font-size: 0.8rem;
margin: 10px;
}
#result p {
text-align: center;
width: 100% !important;
}
#result .react-json-view {
overflow-y: auto;
}
#result textarea {
border: none;
height: 100%;
width: 100%;
}

@ -0,0 +1,118 @@
import React, { useState, useEffect } from 'react'
import { VyperCompilationOutput, remixClient } from './utils'
import { CompilationResult } from '@remixproject/plugin-api'
// Components
import CompilerButton from './components/CompilerButton'
import WarnRemote from './components/WarnRemote'
import VyperResult from './components/VyperResult'
import LocalUrlInput from './components/LocalUrl'
import ToggleButtonGroup from 'react-bootstrap/ToggleButtonGroup'
import ToggleButton from 'react-bootstrap/ToggleButton'
import vyperLogo from './logo.svg'
import './app.css'
interface AppState {
status: 'idle' | 'inProgress'
environment: 'remote' | 'local'
compilationResult?: CompilationResult
localUrl: string
}
interface OutputMap {
[fileName: string]: VyperCompilationOutput
}
const App: React.FC = () => {
const [contract, setContract] = useState<string>()
const [output, setOutput] = useState<OutputMap>({})
const [state, setState] = useState<AppState>({
status: 'idle',
environment: 'local',
localUrl: 'http://localhost:8000/compile'
})
useEffect(() => {
async function start() {
try {
await remixClient.loaded()
remixClient.onFileChange(name => setContract(name))
const name = await remixClient.getContractName()
setContract(name)
} catch (err) {
console.log(err)
}
}
start()
}, [])
/** Update the environment state value */
function setEnvironment(environment: 'local' | 'remote') {
setState({ ...state, environment })
}
function setLocalUrl(url: string) {
setState({ ...state, localUrl: url })
}
function compilerUrl() {
return state.environment === 'remote'
? 'https://vyper.remixproject.org/compile'
: state.localUrl
}
return (
<main id="vyper-plugin">
<header className="bg-light">
<div className="title">
<img src={vyperLogo} alt="Vyper logo" />
<h4>yper Compiler</h4>
</div>
<a
rel="noopener noreferrer"
href="https://github.com/GrandSchtroumpf/vyper-remix"
target="_blank"
>
<i className="fab fa-github"></i>
</a>
</header>
<section>
<ToggleButtonGroup
name="remote"
onChange={setEnvironment}
type="radio"
value={state.environment}
>
<ToggleButton data-id="remote-compiler" variant="secondary" name="remote" value="remote">
Remote Compiler
</ToggleButton>
<ToggleButton data-id="local-compiler" variant="secondary" name="local" value="local">
Local Compiler
</ToggleButton>
</ToggleButtonGroup>
<LocalUrlInput
url={state.localUrl}
setUrl={setLocalUrl}
environment={state.environment}
/>
<WarnRemote environment={state.environment} />
<div id="compile-btn">
<CompilerButton
compilerUrl={compilerUrl()}
contract={contract}
setOutput={(name, update) =>
setOutput({ ...output, [name]: update })
}
/>
</div>
<article id="result">
<VyperResult output={contract ? output[contract] : undefined} />
</article>
</section>
</main>
)
}
export default App

@ -0,0 +1,75 @@
import React from 'react'
import {
isVyper,
compile,
toStandardOutput,
VyperCompilationOutput,
isCompilationError,
remixClient
} from '../utils'
import Button from 'react-bootstrap/Button'
interface Props {
compilerUrl: string
contract?: string,
setOutput: (name: string, output: VyperCompilationOutput) => void
}
function CompilerButton({ contract, setOutput, compilerUrl }: Props) {
if (!contract || !contract) {
return <Button disabled>No contract selected</Button>
}
if (!isVyper(contract)) {
return <Button disabled>Not a vyper contract</Button>
}
/** Compile a Contract */
async function compileContract() {
try {
const _contract = await remixClient.getContract()
remixClient.changeStatus({
key: 'loading',
type: 'info',
title: 'Compiling'
})
const output = await compile(compilerUrl, _contract)
setOutput(_contract.name, output)
// ERROR
if (isCompilationError(output)) {
const line = output.line
const lineColumnPos = {
start: { line: line - 1 },
end: { line: line - 1 }
}
remixClient.highlight(lineColumnPos as any, _contract.name, '#e0b4b4')
throw new Error(output.message)
}
// SUCCESS
remixClient.discardHighlight()
remixClient.changeStatus({
key: 'succeed',
type: 'success',
title: 'succeed'
})
const data = toStandardOutput(_contract.name, output)
remixClient.compilationFinish(_contract.name, _contract.content, data)
} catch (err: any) {
remixClient.changeStatus({
key: 'failed',
type: 'error',
title: err.message
})
console.error(err)
}
}
return (
<Button data-id="compile" onClick={compileContract} variant="primary">
Compile {contract}
</Button>
)
}
export default CompilerButton

@ -0,0 +1,36 @@
import React from 'react'
import Form from 'react-bootstrap/Form'
interface Props {
url: string
setUrl: (url: string) => void,
environment: 'remote' | 'local'
}
function LocalUrlInput({ url, setUrl, environment }: Props) {
if (environment === 'remote') {
return <></>
}
function updateUrl(event: React.FocusEvent<HTMLInputElement>) {
setUrl(event.target.value)
}
return (
<Form id="local-url">
<Form.Group controlId="localUrl">
<Form.Label>Local Compiler Url</Form.Label>
<Form.Control onBlur={updateUrl}
defaultValue={url}
type="email"
placeholder="eg http://localhost:8000/compile" />
<Form.Text className="text-muted">
The url to your local compiler
</Form.Text>
</Form.Group>
</Form>
)
}
export default LocalUrlInput;

@ -0,0 +1,70 @@
import React, { useState } from 'react';
import {
VyperCompilationResult,
VyperCompilationOutput,
isCompilationError,
remixClient
} from '../utils';
import Tabs from 'react-bootstrap/Tabs'
import Tab from 'react-bootstrap/Tab'
import { Ballot } from '../examples/ballot';
import Button from 'react-bootstrap/Button';
import JSONTree from 'react-json-view'
import { CopyToClipboard } from '@remix-ui/clipboard'
interface VyperResultProps {
output?: VyperCompilationOutput;
}
function VyperResult({ output }: VyperResultProps) {
const [ active, setActive ] = useState<keyof VyperCompilationResult>('abi');
if (!output) return (
<div id="result">
<p>No contract compiled yet.</p>
<Button data-id="add-ballot" variant="info" onClick={() => remixClient.loadContract(Ballot)}>
Create Ballot.vy example
</Button>
</div>
)
if (isCompilationError(output)) {
return (
<div id="result" className="error">
<i className="fas fa-exclamation-circle text-danger"></i>
<p data-id="error-message" className="alert alert-danger">{output.message}</p>
</div>)
}
return (
<Tabs id="result" activeKey={active} onSelect={(key: any) => setActive(key)}>
<Tab eventKey="abi" title="ABI">
<CopyToClipboard getContent={() => JSON.stringify(output.abi)}>
<Button variant="info" className="copy">Copy ABI</Button>
</CopyToClipboard>
<JSONTree src={output.abi} />
</Tab>
<Tab eventKey="bytecode" title="Bytecode">
<CopyToClipboard getContent={() => output.bytecode}>
<Button variant="info" className="copy">Copy Bytecode</Button>
</CopyToClipboard>
<textarea defaultValue={output.bytecode}></textarea>
</Tab>
<Tab eventKey="bytecode_runtime" title="Runtime Bytecode">
<CopyToClipboard getContent={() => output.bytecode_runtime}>
<Button variant="info" className="copy">Copy Runtime Bytecode</Button>
</CopyToClipboard>
<textarea defaultValue={output.bytecode_runtime}></textarea>
</Tab>
<Tab eventKey="ir" title="LLL">
<CopyToClipboard getContent={() => output.ir}>
<Button variant="info" className="copy">Copy LLL Code</Button>
</CopyToClipboard>
<textarea defaultValue={output.ir}></textarea>
</Tab>
</Tabs>
);
}
export default VyperResult;

@ -0,0 +1,19 @@
import React from 'react'
interface Props {
environment: 'remote' | 'local'
}
function WarnRemoteLabel({ environment }: Props) {
if (environment === 'local') {
return <></>
}
return (
<div className="alert alert-warning">It is really important to not use the remote compiler for production environment.
Please only use it for testing purpose and prefer to using a local compiler for production like environment.</div>
)
}
export default WarnRemoteLabel;

@ -0,0 +1,161 @@
export const Ballot = {
name: 'browser/ballot.vy',
content: `# Voting with delegation.
# Information about voters
struct Voter:
# weight is accumulated by delegation
weight: int128
# if true, that person already voted (which includes voting by delegating)
voted: bool
# person delegated to
delegate: address
# index of the voted proposal, which is not meaningful unless 'voted' is True.
vote: int128
# Users can create proposals
struct Proposal:
# short name (up to 32 bytes)
name: bytes32
# number of accumulated votes
voteCount: int128
voters: public(map(address, Voter))
proposals: public(map(int128, Proposal))
voterCount: public(int128)
chairperson: public(address)
int128Proposals: public(int128)
@public
@constant
def delegated(addr: address) -> bool:
return self.voters[addr].delegate != ZERO_ADDRESS
@public
@constant
def directlyVoted(addr: address) -> bool:
return self.voters[addr].voted and (self.voters[addr].delegate == ZERO_ADDRESS)
# Setup global variables
@public
def __init__(_proposalNames: bytes32[2]):
self.chairperson = msg.sender
self.voterCount = 0
for i in range(2):
self.proposals[i] = Proposal({
name: _proposalNames[i],
voteCount: 0
})
self.int128Proposals += 1
# Give a 'voter' the right to vote on this ballot.
# This may only be called by the 'chairperson'.
@public
def giveRightToVote(voter: address):
# Throws if the sender is not the chairperson.
assert msg.sender == self.chairperson
# Throws if the voter has already voted.
assert not self.voters[voter].voted
# Throws if the voter's voting weight isn't 0.
assert self.voters[voter].weight == 0
self.voters[voter].weight = 1
self.voterCount += 1
# Used by 'delegate' below, and can be called by anyone.
@public
def forwardWeight(delegate_with_weight_to_forward: address):
assert self.delegated(delegate_with_weight_to_forward)
# Throw if there is nothing to do:
assert self.voters[delegate_with_weight_to_forward].weight > 0
target: address = self.voters[delegate_with_weight_to_forward].delegate
for i in range(4):
if self.delegated(target):
target = self.voters[target].delegate
# The following effectively detects cycles of length <= 5,
# in which the delegation is given back to the delegator.
# This could be done for any int128ber of loops,
# or even infinitely with a while loop.
# However, cycles aren't actually problematic for correctness;
# they just result in spoiled votes.
# So, in the production version, this should instead be
# the responsibility of the contract's client, and this
# check should be removed.
assert target != delegate_with_weight_to_forward
else:
# Weight will be moved to someone who directly voted or
# hasn't voted.
break
weight_to_forward: int128 = self.voters[delegate_with_weight_to_forward].weight
self.voters[delegate_with_weight_to_forward].weight = 0
self.voters[target].weight += weight_to_forward
if self.directlyVoted(target):
self.proposals[self.voters[target].vote].voteCount += weight_to_forward
self.voters[target].weight = 0
# To reiterate: if target is also a delegate, this function will need
# to be called again, similarly to as above.
# Delegate your vote to the voter 'to'.
@public
def delegate(to: address):
# Throws if the sender has already voted
assert not self.voters[msg.sender].voted
# Throws if the sender tries to delegate their vote to themselves or to
# the default address value of 0x0000000000000000000000000000000000000000
# (the latter might not be problematic, but I don't want to think about it).
assert to != msg.sender
assert to != ZERO_ADDRESS
self.voters[msg.sender].voted = True
self.voters[msg.sender].delegate = to
# This call will throw if and only if this delegation would cause a loop
# of length <= 5 that ends up delegating back to the delegator.
self.forwardWeight(msg.sender)
# Give your vote (including votes delegated to you)
# to proposal 'proposals[proposal].name'.
@public
def vote(proposal: int128):
# can't vote twice
assert not self.voters[msg.sender].voted
# can only vote on legitimate proposals
assert proposal < self.int128Proposals
self.voters[msg.sender].vote = proposal
self.voters[msg.sender].voted = True
# transfer msg.sender's weight to proposal
self.proposals[proposal].voteCount += self.voters[msg.sender].weight
self.voters[msg.sender].weight = 0
# Computes the winning proposal taking all
# previous votes into account.
@public
@constant
def winningProposal() -> int128:
winning_vote_count: int128 = 0
winning_proposal: int128 = 0
for i in range(2):
if self.proposals[i].voteCount > winning_vote_count:
winning_vote_count = self.proposals[i].voteCount
winning_proposal = i
return winning_proposal
# Calls winningProposal() function to get the index
# of the winner contained in the proposals array and then
# returns the name of the winner
@public
@constant
def winnerName() -> bytes32:
return self.proposals[self.winningProposal()].name
`
}

@ -0,0 +1,14 @@
<svg id="Flat_Logo" data-name="Flat Logo" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 2048 1773.62">
<title>vyper-logo-flat</title>
/*add logo description for screen readers*/
<desc>The vyper logo, consists of a letter V superimposed on the letter A, in shades of gray, black and white.</desc>
<polyline points="1024 886.81 768 1330.22 1024 1773.62 1280 1330.22 1024 886.81" style="fill:#333"/>
<polyline points="1280 443.41 1024 886.81 1280 1330.22 1536 886.81 1280 443.41" style="fill:#666"/>
<polyline points="768 443.41 512 886.81 768 1330.22 1024 886.81 768 443.41" style="fill:#666"/>
<polyline points="1536 0 1280 443.41 1536 886.81 1792 443.41 1536 0" style="fill:#8c8c8c"/>
<polyline points="1152 221.7 896 221.7 768 443.41 1024 886.81 1280 443.41 1152 221.7" style="fill:#8c8c8c"/>
<polyline points="512 0 256 443.41 512 886.81 768 443.41 512 0" style="fill:#8c8c8c"/>
<polyline points="2048 0 1536 0 1792 443.4 2048 0" style="fill:#b2b2b2"/>
<polyline points="512 0 0 0 256 443.4 512 0" style="fill:#b2b2b2"/>
/*Author: @yudetamago, last update: @evertonmelo*/
</svg>

After

Width:  |  Height:  |  Size: 1.1 KiB

@ -0,0 +1,11 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg
className="material-icons"
xmlns="http://www.w3.org/2000/svg"
width="24"
height="24"
viewBox="0 0 24 24"
>
<path d="M0 0h24v24H0z" fill="none" />
<path d="M12 17.27L18.18 21l-1.64-7.03L22 9.24l-7.19-.61L12 2 9.19 8.63 2 9.24l5.46 4.73L5.82 21z" />
</svg>

After

Width:  |  Height:  |  Size: 347 B

@ -0,0 +1,139 @@
import { CompilationResult, ABIDescription } from "@remixproject/plugin-api";
export interface Contract {
name: string;
content: string;
}
export interface VyperCompilationResult {
status: 'success',
bytecode: string,
bytecode_runtime: string,
abi: ABIDescription[],
ir: string,
method_identifiers: {
[method: string]: string
}
}
export interface VyperCompilationError {
status: 'failed'
column: number
line: number
message: string
}
export type VyperCompilationOutput = VyperCompilationResult | VyperCompilationError
/** Check if the output is an error */
export function isCompilationError(output: VyperCompilationOutput): output is VyperCompilationError {
return output.status === 'failed'
}
/**
* Compile the a contract
* @param url The url of the compiler
* @param contract The name and content of the contract
*/
export async function compile(url: string, contract: Contract): Promise<VyperCompilationOutput> {
if (!contract.name) {
throw new Error('Set your Vyper contract file.')
}
const extension = contract.name.split('.')[1]
if (extension !== 'vy') {
throw new Error('Use extension .vy for Vyper.')
}
const response = await fetch(url, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ code: contract.content })
})
if (response.status === 404) {
throw new Error(`Vyper compiler not found at "${url}".`)
}
/*if (response.status === 400) {
throw new Error(`Vyper compilation failed: ${response.statusText}`)
}*/
return response.json()
}
/**
* Transform Vyper Output to Solidity-like Compiler output
* @param name Name of the contract file
* @param compilationResult Result returned by the compiler
*/
export function toStandardOutput(fileName: string, compilationResult: VyperCompilationResult): CompilationResult {
const contractName = fileName.split('/').slice(-1)[0].split('.')[0];
const methodIdentifiers = JSON.parse(JSON.stringify(compilationResult['method_identifiers']).replace(/0x/g,''));
return {
sources: {
[fileName]: {
id: 1,
ast: {} as any,
legacyAST: {} as any
}
},
contracts: {
[fileName]: {
// If the language used has no contract names, this field should equal to an empty string
[contractName]: {
// The Ethereum Contract ABI. If empty, it is represented as an empty array.
// See https://github.com/ethereum/wiki/wiki/Ethereum-Contract-ABI
abi: compilationResult['abi'],
evm: {
bytecode: {
linkReferences: {},
object: compilationResult['bytecode'].replace('0x',''),
opcodes: ""
},
deployedBytecode: {
linkReferences: {},
object: compilationResult['bytecode_runtime'].replace('0x',''),
opcodes: ""
},
methodIdentifiers: methodIdentifiers
}
}
} as any
}
};
}
/*
export function createCompilationResultMessage(name: string, result: any) {
if(result.status == 'success') {
return {
bytecode: this.state.compilationResult['bytecode'],
bytecode_runtime: this.state.compilationResult['bytecode_runtime'],
abi: JSON.stringify(this.state.compilationResult['abi'], null , "\t"),
ir: this.state.compilationResult['ir']
}
} else if(result.status == 'failed' && result.column && result.line) {
const header = `${name}:${result.line}:${result.column}`
const body = this.state.compilationResult.message.split(/\r\n|\r|\n/)
const arr = [header].concat(body).join("\n")
return {
bytecode: arr,
bytecode_runtime: arr,
abi: arr,
ir: arr
}
} else if(result.status == 'failed') {
const message = this.state.compilationResult.message
return {
bytecode: message,
bytecode_runtime: message,
abi: message,
ir: message
}
}
return {
bytecode: "",
bytecode_runtime: "",
abi: "",
ir: ""
}
}
*/

@ -0,0 +1,12 @@
export * from './compiler'
export * from './remix-client'
export function contractName(fileName: string): string {
const parts = fileName.split('/')
return parts[parts.length - 1]
}
export function isVyper(name: string): boolean {
const parts = name.split('.')
return parts[parts.length - 1] === 'vy'
}

@ -0,0 +1,71 @@
import { HighlightPosition, CompilationResult, RemixApi } from '@remixproject/plugin-api';
import { Api, Status } from '@remixproject/plugin-utils';
import { createClient } from '@remixproject/plugin-webview'
import { PluginClient } from '@remixproject/plugin';
import { Contract } from './compiler';
export class RemixClient extends PluginClient {
private client = createClient<Api, Readonly<RemixApi>>(this);
loaded() {
return this.client.onload()
}
/** Emit an event when file changed */
async onFileChange(cb: (contract: string) => any) {
this.client.on('fileManager', 'currentFileChanged', async (name: string) => {
if (!name) return
cb(name)
})
}
/** Load Ballot contract example into the file manager */
async loadContract({name, content}: Contract) {
try {
await this.client.call('fileManager', 'setFile', name, content)
await this.client.call('fileManager', 'switchFile', name)
} catch (err) {
console.log(err)
}
}
/** Update the status of the plugin in remix */
changeStatus(status: Status) {
this.client.emit('statusChanged', status);
}
/** Highlight a part of the editor */
highlight(lineColumnPos: HighlightPosition, name: string, color: string) {
return this.client.call('editor', 'highlight', lineColumnPos, name, color)
}
/** Remove current Hightlight */
discardHighlight() {
return this.client.call('editor', 'discardHighlight')
}
/** Get the name of the current contract */
async getContractName(): Promise<string> {
await this.client.onload()
return this.client.call('fileManager', 'getCurrentFile')
}
/** Get the current contract file */
async getContract(): Promise<Contract> {
const name = await this.getContractName()
if (!name) throw new Error('No contract selected yet')
const content = await this.client.call('fileManager', 'getFile', name)
return {
name,
content,
}
}
/** Emit an event to Remix with compilation result */
compilationFinish(title: string, content: string, data: CompilationResult) {
this.client.emit('compilationFinished', title, content, 'vyper', data);
}
}
export const remixClient = new RemixClient()
// export const RemixClientContext = React.createContext(new RemixClient())

@ -0,0 +1,3 @@
export const environment = {
production: true
};

@ -0,0 +1,6 @@
// This file can be replaced during build by using the `fileReplacements` array.
// When building for production, this file is replaced with `environment.prod.ts`.
export const environment = {
production: false
};

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

@ -0,0 +1,14 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<title>Vyper</title>
<base href="/" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<link rel="icon" type="image/x-icon" href="favicon.ico" />
</head>
<body>
<div id="root"></div>
</body>
</html>

@ -0,0 +1,7 @@
import { StrictMode } from 'react';
import * as ReactDOM from 'react-dom';
import App from './app/app';
ReactDOM.render(<StrictMode><App /></StrictMode>, 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 @@
/* You can add global styles to this file, and also import other style files */

@ -0,0 +1,14 @@
{
"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": ["**/*.spec.ts", "**/*.spec.tsx"],
"include": ["**/*.js", "**/*.jsx", "**/*.ts", "**/*.tsx"]
}

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

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

Loading…
Cancel
Save