commit
ba81a4c74f
@ -0,0 +1,20 @@ |
||||
|
||||
name: Running Solidity Unit Tests |
||||
on: [push] |
||||
|
||||
jobs: |
||||
run_sol_contracts_job: |
||||
runs-on: ubuntu-latest |
||||
name: A job to run solidity unit tests on github actions CI |
||||
steps: |
||||
- name: Checkout |
||||
uses: actions/checkout@v2 |
||||
- name: Environment Setup |
||||
uses: actions/setup-node@v3 |
||||
with: |
||||
node-version: 14.17.6 |
||||
- name: Run SUT Action |
||||
uses: EthereumRemix/sol-test@v1 |
||||
with: |
||||
test-path: 'apps/remix-ide/contracts/tests' |
||||
compiler-version: '0.8.15' |
@ -1,4 +0,0 @@ |
||||
# Add files here to ignore them from prettier formatting |
||||
|
||||
/dist |
||||
/coverage |
@ -1,3 +0,0 @@ |
||||
{ |
||||
"singleQuote": true |
||||
} |
@ -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" |
After Width: | Height: | Size: 2.9 KiB |
@ -0,0 +1,51 @@ |
||||
import React from "react" |
||||
import { |
||||
HashRouter as Router, |
||||
Route, |
||||
Routes, |
||||
RouteProps, |
||||
} from "react-router-dom" |
||||
|
||||
import { ErrorView, HomeView, ReceiptsView, CaptureKeyView } from "./views" |
||||
import { DefaultLayout } from "./layouts" |
||||
|
||||
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> |
||||
) |
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 |
||||
}; |
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" |
||||
} |
||||
] |
||||
} |
@ -0,0 +1,41 @@ |
||||
import { NightwatchBrowser } from 'nightwatch' |
||||
import EventEmitter from 'events' |
||||
|
||||
class ConnectToExternalHttpProvider extends EventEmitter { |
||||
command(this: NightwatchBrowser, url: string, identifier: string): NightwatchBrowser { |
||||
this.api.element('xpath', `//*[@class='udapp_environment' and contains(.,'${identifier}')]`, |
||||
(result) => { |
||||
if (result.status as any === -1) { |
||||
console.log("No connection to external provider found. Adding one.", url) |
||||
browser |
||||
.click({ |
||||
locateStrategy: 'css selector', |
||||
selector: '[data-id="basic-http-provider-modal-footer-ok-react"]', |
||||
abortOnFailure: false, |
||||
suppressNotFoundErrors: true, |
||||
timeout: 5000 |
||||
}) |
||||
.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"]', url) |
||||
.modalFooterOKClick('basic-http-provider') |
||||
.perform((done) => { |
||||
done() |
||||
this.emit('complete') |
||||
}) |
||||
} else { |
||||
this.api.perform((done) => { |
||||
done() |
||||
this.emit('complete') |
||||
}) |
||||
} |
||||
} |
||||
) |
||||
return this |
||||
} |
||||
} |
||||
|
||||
module.exports = ConnectToExternalHttpProvider |
@ -0,0 +1,19 @@ |
||||
import { NightwatchBrowser } from 'nightwatch' |
||||
import EventEmitter from 'events' |
||||
|
||||
class GetBrowserLogs extends EventEmitter { |
||||
command(this: NightwatchBrowser): NightwatchBrowser { |
||||
this.api.getLog('browser', function (logs) { |
||||
if (logs && Array.isArray(logs)) { |
||||
logs.forEach(function (log) { |
||||
console.log(log) |
||||
} |
||||
) |
||||
} |
||||
}).perform(() => { |
||||
this.emit('complete') |
||||
}) |
||||
return this |
||||
} |
||||
} |
||||
module.exports = GetBrowserLogs |
@ -0,0 +1,18 @@ |
||||
import { NightwatchBrowser } from 'nightwatch' |
||||
import EventEmitter from 'events' |
||||
|
||||
class switchEnvironment extends EventEmitter { |
||||
command (this: NightwatchBrowser, provider: string): NightwatchBrowser { |
||||
this.api.useCss().waitForElementVisible('[data-id="settingsSelectEnvOptions"]') |
||||
.click('[data-id="settingsSelectEnvOptions"] button') |
||||
.waitForElementVisible(`[data-id="dropdown-item-${provider}"]`) |
||||
.click(`[data-id="dropdown-item-${provider}"]`) |
||||
.perform((done) => { |
||||
done() |
||||
this.emit('complete') |
||||
}) |
||||
return this |
||||
} |
||||
} |
||||
|
||||
module.exports = switchEnvironment |
@ -0,0 +1,20 @@ |
||||
import { NightwatchBrowser } from 'nightwatch' |
||||
import EventEmitter from 'events' |
||||
|
||||
class switchWorkspace extends EventEmitter { |
||||
command (this: NightwatchBrowser, workspaceName: string): NightwatchBrowser { |
||||
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') |
||||
}) |
||||
return this |
||||
} |
||||
} |
||||
|
||||
module.exports = switchWorkspace |
@ -0,0 +1,173 @@ |
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
const testContract = { |
||||
name: 'contracts/test.sol', |
||||
content: ` |
||||
// SPDX-License-Identifier: GPL-3.0
|
||||
pragma solidity >=0.7.0 <0.9.0; |
||||
|
||||
import "contracts/base.sol"; |
||||
import "contracts/import1.sol"; |
||||
|
||||
contract test is base { |
||||
string public publicstring; |
||||
string private privatestring; |
||||
string internal internalstring; |
||||
|
||||
struct TestBookDefinition {
|
||||
string title; |
||||
string author; |
||||
uint book_id; |
||||
} |
||||
TestBookDefinition public mybook; |
||||
enum MyEnum{ SMALL, MEDIUM, LARGE } |
||||
event MyEvent(uint abc); |
||||
importcontract importedcontract; |
||||
|
||||
modifier costs(uint price) { |
||||
if (msg.value >= price) { |
||||
_; |
||||
} |
||||
} |
||||
constructor(){
|
||||
|
||||
} |
||||
|
||||
function testing() public view { |
||||
|
||||
} |
||||
|
||||
function myprivatefunction() private { |
||||
|
||||
} |
||||
|
||||
function myinternalfunction() internal { |
||||
|
||||
} |
||||
|
||||
function myexternalfunction() external { |
||||
|
||||
} |
||||
}`}
|
||||
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
const baseContract = { |
||||
name: 'contracts/base.sol', |
||||
content: ` |
||||
// SPDX-License-Identifier: GPL-3.0
|
||||
pragma solidity >=0.7.0 <0.9.0; |
||||
|
||||
import "contracts/baseofbase.sol"; |
||||
|
||||
contract base is baseofbase { |
||||
event BaseEvent(address indexed _from, uint _value); |
||||
enum BaseEnum{ SMALL, MEDIUM, LARGE } |
||||
struct Book {
|
||||
string title; |
||||
string author; |
||||
uint book_id; |
||||
} |
||||
Book public book; |
||||
}`}
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
const baseOfBaseContract = { |
||||
name: 'contracts/baseofbase.sol', |
||||
content: ` |
||||
// SPDX-License-Identifier: GPL-3.0
|
||||
pragma solidity >=0.7.0 <0.9.0; |
||||
|
||||
contract baseofbase { |
||||
|
||||
struct BaseBook {
|
||||
string title; |
||||
string author; |
||||
uint book_id; |
||||
} |
||||
BaseBook public basebook; |
||||
|
||||
string private basestring; |
||||
string internal internalbasestring; |
||||
|
||||
function privatebase() private { |
||||
|
||||
} |
||||
|
||||
function internalbasefunction() internal { |
||||
|
||||
} |
||||
|
||||
function publicbasefunction() public { |
||||
|
||||
} |
||||
function externalbasefunction() external { |
||||
} |
||||
}`}
|
||||
|
||||
const import1Contract = { |
||||
name: 'contracts/import1.sol', |
||||
content: ` |
||||
// SPDX-License-Identifier: MIT
|
||||
pragma solidity >=0.7.0 <0.9.0; |
||||
|
||||
import "contracts/importbase.sol"; |
||||
import "contracts/secondimport.sol"; |
||||
|
||||
contract importcontract is importbase { |
||||
struct ImportedBook {
|
||||
string title; |
||||
string author; |
||||
uint book_id; |
||||
} |
||||
ImportedBook public importedbook; |
||||
|
||||
string private importprivatestring; |
||||
string internal internalimportstring; |
||||
string public importpublicstring; |
||||
|
||||
function privateimport() private { |
||||
|
||||
} |
||||
|
||||
function internalimport() internal { |
||||
|
||||
} |
||||
|
||||
function publicimport() public { |
||||
|
||||
} |
||||
|
||||
function externalimport() external { |
||||
} |
||||
}`}
|
||||
|
||||
const importbase = { |
||||
name: 'contracts/importbase.sol', |
||||
content: ` |
||||
// SPDX-License-Identifier: MIT
|
||||
pragma solidity >=0.7.0 <0.9.0; |
||||
|
||||
contract importbase { |
||||
string public importbasestring; |
||||
} |
||||
`}
|
||||
|
||||
const secondimport = { |
||||
name: 'contracts/secondimport.sol', |
||||
content: ` |
||||
// SPDX-License-Identifier: MIT
|
||||
pragma solidity >=0.7.0 <0.9.0; |
||||
|
||||
contract secondimport { |
||||
string public secondimportstring; |
||||
} |
||||
`}
|
||||
|
||||
export default { |
||||
testContract, |
||||
baseContract, |
||||
baseOfBaseContract, |
||||
import1Contract, |
||||
importbase, |
||||
secondimport |
||||
} |
@ -0,0 +1 @@ |
||||
// @ts-ignore
|
File diff suppressed because one or more lines are too long
@ -0,0 +1,526 @@ |
||||
'use strict' |
||||
import { NightwatchBrowser } from 'nightwatch' |
||||
import init from '../helpers/init' |
||||
import examples from '../examples/editor-test-contracts' |
||||
|
||||
const autoCompleteLineElement = (name: string) => { |
||||
return `//*[@class='editor-widget suggest-widget visible']//*[@class='contents' and contains(.,'${name}')]` |
||||
} |
||||
|
||||
module.exports = { |
||||
'@disabled': true, |
||||
before: function (browser: NightwatchBrowser, done: VoidFunction) { |
||||
init(browser, done, 'http://127.0.0.1:8080', false) |
||||
}, |
||||
|
||||
'Should enable settings': function (browser: NightwatchBrowser) { |
||||
browser |
||||
.clickLaunchIcon('settings') |
||||
.click('[data-id="settingsAutoCompleteLabel"]') |
||||
.click('[data-id="settingsShowGasLabel"]') |
||||
.click('[data-id="displayErrorsLabel"]') |
||||
}, |
||||
|
||||
'Should add test and base files #group1': function (browser: NightwatchBrowser) { |
||||
browser.addFile(examples.testContract.name, examples.testContract) |
||||
.addFile(examples.baseContract.name, examples.baseContract) |
||||
.addFile(examples.import1Contract.name, examples.import1Contract) |
||||
.addFile(examples.baseOfBaseContract.name, examples.baseOfBaseContract) |
||||
.addFile(examples.secondimport.name, examples.secondimport) |
||||
.addFile(examples.importbase.name, examples.importbase) |
||||
.openFile(examples.testContract.name) |
||||
}, |
||||
'Should put cursor in the () of the function #group1': function (browser: NightwatchBrowser) { |
||||
browser.scrollToLine(36) |
||||
const path = "//*[@class='view-line' and contains(.,'myprivatefunction') and contains(.,'private')]//span//span[contains(.,'(')]" |
||||
browser.waitForElementVisible('#editorView') |
||||
.useXpath() |
||||
.click(path).pause(1000) |
||||
}, |
||||
'Should complete variable declaration types in a function definition #group1': function (browser: NightwatchBrowser) { |
||||
browser |
||||
.perform(function () { |
||||
const actions = this.actions({ async: true }); |
||||
return actions. |
||||
sendKeys('uint25') |
||||
}) |
||||
.waitForElementPresent(autoCompleteLineElement('uint256')) |
||||
.click(autoCompleteLineElement('uint256')) |
||||
.perform(function () { |
||||
const actions = this.actions({ async: true }); |
||||
return actions. |
||||
sendKeys(' abc') |
||||
.sendKeys(this.Keys.ENTER) // we need to split lines for FF texts to pass because the screen is too narrow
|
||||
.sendKeys(', testb') |
||||
}) |
||||
.waitForElementPresent(autoCompleteLineElement('"TestBookDefinition"')) |
||||
.click(autoCompleteLineElement('"TestBookDefinition"')) |
||||
.perform(function () { |
||||
const actions = this.actions({ async: true }); |
||||
return actions. |
||||
sendKeys(' memo') |
||||
}) |
||||
.waitForElementPresent(autoCompleteLineElement('memory')) |
||||
.click(autoCompleteLineElement('memory')) |
||||
.perform(function () { |
||||
const actions = this.actions({ async: true }); |
||||
return actions. |
||||
sendKeys(' btextbook') |
||||
.sendKeys(this.Keys.ENTER) |
||||
.sendKeys(', BaseB') |
||||
}) |
||||
.waitForElementPresent(autoCompleteLineElement('"BaseBook"')) |
||||
.click(autoCompleteLineElement('"BaseBook"')) |
||||
.perform(function () { |
||||
const actions = this.actions({ async: true }); |
||||
return actions. |
||||
sendKeys(' stor') |
||||
}) |
||||
.waitForElementPresent(autoCompleteLineElement('storage')) |
||||
.click(autoCompleteLineElement('storage')) |
||||
.perform(function () { |
||||
const actions = this.actions({ async: true }); |
||||
return actions. |
||||
sendKeys(' localbbook') |
||||
}).pause(3000) |
||||
}, |
||||
'Should put cursor at the end of function #group1': function (browser: NightwatchBrowser) { |
||||
|
||||
const path = "//*[@class='view-line' and contains(.,'localbbook') and contains(.,'private')]//span//span[contains(.,'{')]" |
||||
browser |
||||
.useXpath() |
||||
.click(path).pause(1000) |
||||
.perform(function () { |
||||
const actions = this.actions({ async: true }); |
||||
return actions. |
||||
// right arrow key
|
||||
sendKeys(this.Keys.ARROW_RIGHT). |
||||
sendKeys(this.Keys.ARROW_RIGHT) |
||||
}) |
||||
}, |
||||
'Should autcomplete address types #group1': function (browser: NightwatchBrowser) { |
||||
browser |
||||
.perform(function () { |
||||
const actions = this.actions({ async: true }); |
||||
return actions. |
||||
sendKeys('addre') |
||||
}) |
||||
.waitForElementPresent(autoCompleteLineElement('address')) |
||||
.click(autoCompleteLineElement('address')) |
||||
.perform(function () { |
||||
const actions = this.actions({ async: true }); |
||||
return actions. |
||||
sendKeys(' someaddress;') |
||||
.sendKeys(this.Keys.ENTER) |
||||
}).pause(2000) |
||||
.perform(function () { |
||||
const actions = this.actions({ async: true }); |
||||
return actions. |
||||
sendKeys('someaddress.') |
||||
}) |
||||
.waitForElementVisible(autoCompleteLineElement('balance')) |
||||
.waitForElementVisible(autoCompleteLineElement('send')) |
||||
.waitForElementVisible(autoCompleteLineElement('transfer')) |
||||
.waitForElementVisible(autoCompleteLineElement('code')) |
||||
.click(autoCompleteLineElement('balance')) |
||||
.perform(function () { |
||||
const actions = this.actions({ async: true }); |
||||
return actions |
||||
.sendKeys(this.Keys.ENTER) |
||||
}) |
||||
}, |
||||
'Should autcomplete array types #group1': function (browser: NightwatchBrowser) { |
||||
browser |
||||
.perform(function () { |
||||
const actions = this.actions({ async: true }); |
||||
return actions. |
||||
sendKeys('uin') |
||||
}) |
||||
.waitForElementPresent(autoCompleteLineElement('uint')) |
||||
.click(autoCompleteLineElement('uint')) |
||||
.perform(function () { |
||||
const actions = this.actions({ async: true }); |
||||
return actions. |
||||
sendKeys('[] mem') |
||||
}) |
||||
.waitForElementVisible(autoCompleteLineElement('memory')) |
||||
.click(autoCompleteLineElement('memory')) |
||||
.perform(function () { |
||||
const actions = this.actions({ async: true }); |
||||
return actions. |
||||
sendKeys(' somearray;') |
||||
} |
||||
).pause(2000) |
||||
.perform(function () { |
||||
const actions = this.actions({ async: true }); |
||||
return actions |
||||
.sendKeys(this.Keys.ENTER) |
||||
.sendKeys('somearray.') |
||||
}) |
||||
.waitForElementVisible(autoCompleteLineElement('push')) |
||||
.waitForElementVisible(autoCompleteLineElement('pop')) |
||||
.waitForElementVisible(autoCompleteLineElement('length')) |
||||
.click(autoCompleteLineElement('length')) |
||||
.perform(function () { |
||||
const actions = this.actions({ async: true }); |
||||
return actions |
||||
.sendKeys(this.Keys.ENTER) |
||||
}) |
||||
}, |
||||
'Should see and autocomplete second import because it was imported by the first import #group1': function (browser: NightwatchBrowser) { |
||||
browser |
||||
.perform(function () { |
||||
const actions = this.actions({ async: true }); |
||||
return actions. |
||||
sendKeys('secondi') |
||||
}) |
||||
.waitForElementPresent(autoCompleteLineElement('secondimport')) |
||||
.click(autoCompleteLineElement('secondimport')) |
||||
.perform(function () { |
||||
const actions = this.actions({ async: true }); |
||||
return actions. |
||||
sendKeys(' sec;') |
||||
.sendKeys(this.Keys.ENTER) |
||||
}).pause(3000) |
||||
.perform(function () { |
||||
const actions = this.actions({ async: true }); |
||||
return actions. |
||||
sendKeys('sec.') |
||||
}) |
||||
.waitForElementVisible(autoCompleteLineElement('secondimportstring')) |
||||
.click(autoCompleteLineElement('secondimportstring')) |
||||
.perform(function () { |
||||
const actions = this.actions({ async: true }); |
||||
return actions. |
||||
sendKeys(';') |
||||
.sendKeys(this.Keys.ENTER) |
||||
}) |
||||
|
||||
}, |
||||
'Should see and autocomplete imported local class #group1': function (browser: NightwatchBrowser) { |
||||
browser |
||||
.perform(function () { |
||||
const actions = this.actions({ async: true }); |
||||
return actions. |
||||
sendKeys('import') |
||||
}) |
||||
.waitForElementPresent(autoCompleteLineElement('importedcontract')) |
||||
.click(autoCompleteLineElement('importedcontract')) |
||||
.perform(function () { |
||||
const actions = this.actions({ async: true }); |
||||
return actions. |
||||
sendKeys('.') |
||||
}) |
||||
.waitForElementVisible(autoCompleteLineElement('externalimport')) |
||||
.waitForElementVisible(autoCompleteLineElement('importbasestring')) |
||||
.waitForElementVisible(autoCompleteLineElement('importedbook')) |
||||
.waitForElementVisible(autoCompleteLineElement('importpublicstring')) |
||||
.waitForElementVisible(autoCompleteLineElement('publicimport')) |
||||
// no private
|
||||
.waitForElementNotPresent(autoCompleteLineElement('importprivatestring')) |
||||
.waitForElementNotPresent(autoCompleteLineElement('privateimport')) |
||||
// no internal
|
||||
.waitForElementNotPresent(autoCompleteLineElement('importinternalstring')) |
||||
.waitForElementNotPresent(autoCompleteLineElement('internalimport')) |
||||
.click(autoCompleteLineElement('importbasestring')) |
||||
.perform(function () { |
||||
const actions = this.actions({ async: true }); |
||||
return actions. |
||||
sendKeys(';') |
||||
.sendKeys(this.Keys.ENTER) |
||||
}) |
||||
|
||||
}, |
||||
'Should autocomplete derived and local event when not using this. #group1': function (browser: NightwatchBrowser) { |
||||
browser.perform(function () { |
||||
const actions = this.actions({ async: true }); |
||||
return actions. |
||||
sendKeys('emit base') |
||||
}) |
||||
.waitForElementVisible(autoCompleteLineElement('BaseEvent')) |
||||
.click(autoCompleteLineElement('BaseEvent')) |
||||
.perform(function () { |
||||
const actions = this.actions({ async: true }); |
||||
return actions |
||||
.sendKeys('msg.sender') |
||||
.sendKeys(this.Keys.TAB) |
||||
.sendKeys(this.Keys.TAB) // somehow this is needed to get the cursor to the next parameter, only for selenium
|
||||
.sendKeys('3232') |
||||
.sendKeys(this.Keys.TAB) |
||||
.sendKeys(this.Keys.ENTER) |
||||
}) |
||||
.perform(function () { |
||||
const actions = this.actions({ async: true }); |
||||
return actions. |
||||
sendKeys('emit MyEv') |
||||
}) |
||||
.waitForElementVisible(autoCompleteLineElement('MyEvent')) |
||||
.click(autoCompleteLineElement('MyEvent')) |
||||
.perform(function () { |
||||
const actions = this.actions({ async: true }); |
||||
return actions |
||||
.sendKeys('3232') |
||||
.sendKeys(this.Keys.TAB) |
||||
.sendKeys(this.Keys.ENTER) |
||||
}) |
||||
}, |
||||
|
||||
'Should type and get msg options #group1': function (browser: NightwatchBrowser) { |
||||
browser. |
||||
perform(function () { |
||||
const actions = this.actions({ async: true }); |
||||
return actions. |
||||
sendKeys(this.Keys.ENTER). |
||||
sendKeys('msg.') |
||||
}) |
||||
.waitForElementVisible(autoCompleteLineElement('sender')) |
||||
.waitForElementVisible(autoCompleteLineElement('data')) |
||||
.waitForElementVisible(autoCompleteLineElement('value')) |
||||
.waitForElementVisible(autoCompleteLineElement('gas')) |
||||
.waitForElementVisible(autoCompleteLineElement('sig')) |
||||
.click(autoCompleteLineElement('sender')) |
||||
.perform(function () { |
||||
const actions = this.actions({ async: true }); |
||||
return actions. |
||||
sendKeys('.') |
||||
}) |
||||
.waitForElementVisible(autoCompleteLineElement('balance')) |
||||
.waitForElementVisible(autoCompleteLineElement('code')) |
||||
.waitForElementVisible(autoCompleteLineElement('codehash')) |
||||
.waitForElementVisible(autoCompleteLineElement('send')) |
||||
.waitForElementVisible(autoCompleteLineElement('transfer')) |
||||
.click(autoCompleteLineElement('balance')) |
||||
.perform(function () { |
||||
const actions = this.actions({ async: true }); |
||||
return actions. |
||||
sendKeys(this.Keys.ENTER) |
||||
}) |
||||
}, |
||||
'Should bo and get book #group1': function (browser: NightwatchBrowser) { |
||||
browser. |
||||
perform(function () { |
||||
const actions = this.actions({ async: true }); |
||||
return actions. |
||||
sendKeys(this.Keys.ENTER). |
||||
sendKeys('bo') |
||||
}) |
||||
.waitForElementVisible(autoCompleteLineElement('book')) |
||||
.click(autoCompleteLineElement('book')) |
||||
}, |
||||
'Should autcomplete derived struct #group1': function (browser: NightwatchBrowser) { |
||||
browser.perform(function () { |
||||
const actions = this.actions({ async: true }); |
||||
return actions. |
||||
sendKeys('.') |
||||
}) |
||||
.waitForElementVisible(autoCompleteLineElement('author')) |
||||
.waitForElementVisible(autoCompleteLineElement('book_id')) |
||||
.waitForElementVisible(autoCompleteLineElement('title')) |
||||
.click(autoCompleteLineElement('title')) |
||||
.perform(function () { |
||||
const actions = this.actions({ async: true }); |
||||
return actions. |
||||
sendKeys(';') |
||||
.sendKeys(this.Keys.ENTER) |
||||
}) |
||||
}, |
||||
'Should bo and get basebook #group1': function (browser: NightwatchBrowser) { |
||||
browser. |
||||
perform(function () { |
||||
const actions = this.actions({ async: true }); |
||||
return actions. |
||||
sendKeys(this.Keys.ENTER). |
||||
sendKeys('base') |
||||
}) |
||||
.waitForElementVisible(autoCompleteLineElement('basebook')) |
||||
.click(autoCompleteLineElement('basebook')) |
||||
}, |
||||
'Should autcomplete derived struct from base class #group1': function (browser: NightwatchBrowser) { |
||||
browser.perform(function () { |
||||
const actions = this.actions({ async: true }); |
||||
return actions. |
||||
sendKeys('.') |
||||
}) |
||||
.waitForElementVisible(autoCompleteLineElement('author')) |
||||
.waitForElementVisible(autoCompleteLineElement('book_id')) |
||||
.waitForElementVisible(autoCompleteLineElement('title')) |
||||
.click(autoCompleteLineElement('title')) |
||||
.perform(function () { |
||||
const actions = this.actions({ async: true }); |
||||
return actions. |
||||
sendKeys(';') |
||||
.sendKeys(this.Keys.ENTER) |
||||
}) |
||||
}, |
||||
'Should block scoped localbbook #group1': function (browser: NightwatchBrowser) { |
||||
browser.pause(4000). |
||||
perform(function () { |
||||
const actions = this.actions({ async: true }); |
||||
return actions. |
||||
sendKeys(this.Keys.ENTER). |
||||
sendKeys('localb') |
||||
}) |
||||
.waitForElementVisible(autoCompleteLineElement('localbbook')) |
||||
.click(autoCompleteLineElement('localbbook')) |
||||
}, |
||||
'Should autcomplete derived struct from block localbbook #group1': function (browser: NightwatchBrowser) { |
||||
browser.perform(function () { |
||||
const actions = this.actions({ async: true }); |
||||
return actions. |
||||
sendKeys('.') |
||||
}) |
||||
.waitForElementVisible(autoCompleteLineElement('author')) |
||||
.waitForElementVisible(autoCompleteLineElement('book_id')) |
||||
.waitForElementVisible(autoCompleteLineElement('title')) |
||||
.click(autoCompleteLineElement('title')) |
||||
.perform(function () { |
||||
const actions = this.actions({ async: true }); |
||||
return actions. |
||||
sendKeys(';') |
||||
.sendKeys(this.Keys.ENTER) |
||||
}) |
||||
}, |
||||
'Should block scoped btextbook #group1': function (browser: NightwatchBrowser) { |
||||
browser. |
||||
perform(function () { |
||||
const actions = this.actions({ async: true }); |
||||
return actions. |
||||
sendKeys(this.Keys.ENTER). |
||||
sendKeys('btext') |
||||
}) |
||||
.waitForElementVisible(autoCompleteLineElement('btextbook')) |
||||
.click(autoCompleteLineElement('btextbook')) |
||||
}, |
||||
'Should autcomplete derived struct from block btextbook #group1': function (browser: NightwatchBrowser) { |
||||
browser.perform(function () { |
||||
const actions = this.actions({ async: true }); |
||||
return actions. |
||||
sendKeys('.') |
||||
}) |
||||
.waitForElementVisible(autoCompleteLineElement('author')) |
||||
.waitForElementVisible(autoCompleteLineElement('book_id')) |
||||
.waitForElementVisible(autoCompleteLineElement('title')) |
||||
.click(autoCompleteLineElement('title')) |
||||
.perform(function () { |
||||
const actions = this.actions({ async: true }); |
||||
return actions. |
||||
sendKeys(';') |
||||
.sendKeys(this.Keys.ENTER) |
||||
}) |
||||
}, |
||||
'Should find private and internal local functions #group1': function (browser: NightwatchBrowser) { |
||||
browser.perform(function () { |
||||
const actions = this.actions({ async: true }); |
||||
return actions. |
||||
sendKeys('my') |
||||
}) |
||||
.waitForElementVisible(autoCompleteLineElement('myprivatefunction')) |
||||
.waitForElementVisible(autoCompleteLineElement('myinternalfunction')) |
||||
.waitForElementVisible(autoCompleteLineElement('memory')) |
||||
.click(autoCompleteLineElement('myinternalfunction')) |
||||
.perform(function () { |
||||
const actions = this.actions({ async: true }); |
||||
return actions. |
||||
sendKeys(this.Keys.ENTER) |
||||
}) |
||||
}, |
||||
'Should find internal functions and var from base and owner #group1': function (browser: NightwatchBrowser) { |
||||
browser.perform(function () { |
||||
const actions = this.actions({ async: true }); |
||||
return actions. |
||||
sendKeys('intern') |
||||
}) |
||||
.waitForElementVisible(autoCompleteLineElement('internalbasefunction')) |
||||
.waitForElementVisible(autoCompleteLineElement('internalstring')) |
||||
.waitForElementVisible(autoCompleteLineElement('internalbasestring')) |
||||
// keyword internal
|
||||
.waitForElementVisible(autoCompleteLineElement('internal keyword')) |
||||
.click(autoCompleteLineElement('internalbasefunction')) |
||||
.perform(function () { |
||||
const actions = this.actions({ async: true }); |
||||
return actions |
||||
.sendKeys(this.Keys.ENTER) |
||||
}) |
||||
}, |
||||
|
||||
'Should not find external functions without this. #group1': function (browser: NightwatchBrowser) { |
||||
browser.perform(function () { |
||||
const actions = this.actions({ async: true }); |
||||
return actions. |
||||
sendKeys('extern') |
||||
}) |
||||
.waitForElementNotPresent(autoCompleteLineElement('externalbasefunction')) |
||||
.waitForElementNotPresent(autoCompleteLineElement('myexternalfunction')) |
||||
// keyword internal
|
||||
.waitForElementVisible(autoCompleteLineElement('external keyword')) |
||||
.perform(function () { |
||||
const actions = this.actions({ async: true }); |
||||
return actions |
||||
.sendKeys(this.Keys.BACK_SPACE) |
||||
.sendKeys(this.Keys.BACK_SPACE) |
||||
.sendKeys(this.Keys.BACK_SPACE) |
||||
.sendKeys(this.Keys.BACK_SPACE) |
||||
.sendKeys(this.Keys.BACK_SPACE) |
||||
.sendKeys(this.Keys.BACK_SPACE) |
||||
.sendKeys(this.Keys.BACK_SPACE) |
||||
}) |
||||
}, |
||||
'Should find external functions using this. #group1': function (browser: NightwatchBrowser) { |
||||
browser. |
||||
perform(function () { |
||||
const actions = this.actions({ async: true }); |
||||
return actions. |
||||
sendKeys(this.Keys.ENTER). |
||||
sendKeys('this.') |
||||
}) |
||||
.waitForElementVisible(autoCompleteLineElement('externalbasefunction')) |
||||
.waitForElementVisible(autoCompleteLineElement('myexternalfunction')) |
||||
}, |
||||
'Should find public functions and vars using this. but not private & other types of nodes #group1': function (browser: NightwatchBrowser) { |
||||
browser |
||||
.waitForElementVisible(autoCompleteLineElement('"publicbasefunction"')) |
||||
.waitForElementVisible(autoCompleteLineElement('"publicstring"')) |
||||
.waitForElementVisible(autoCompleteLineElement('"basebook"')) |
||||
.waitForElementVisible(autoCompleteLineElement('"mybook"')) |
||||
.waitForElementVisible(autoCompleteLineElement('"testing"')) |
||||
// but no private functions or vars or other types of nodes
|
||||
.waitForElementNotPresent(autoCompleteLineElement('"private"')) |
||||
.waitForElementNotPresent(autoCompleteLineElement('"BaseEvent"')) |
||||
.waitForElementNotPresent(autoCompleteLineElement('"BaseEnum"')) |
||||
.waitForElementNotPresent(autoCompleteLineElement('"TestBookDefinition"')) |
||||
.click(autoCompleteLineElement('"publicbasefunction"')) |
||||
.perform(function () { |
||||
const actions = this.actions({ async: true }); |
||||
return actions |
||||
.sendKeys(this.Keys.ENTER) |
||||
}) |
||||
}, |
||||
'Should autocomplete local and derived ENUMS #group1': function (browser: NightwatchBrowser) { |
||||
browser.perform(function () { |
||||
const actions = this.actions({ async: true }); |
||||
return actions. |
||||
sendKeys('BaseEnum.') |
||||
}) |
||||
.waitForElementVisible(autoCompleteLineElement('SMALL')) |
||||
.waitForElementVisible(autoCompleteLineElement('MEDIUM')) |
||||
.waitForElementVisible(autoCompleteLineElement('LARGE')) |
||||
.click(autoCompleteLineElement('SMALL')) |
||||
.perform(function () { |
||||
const actions = this.actions({ async: true }); |
||||
return actions. |
||||
sendKeys(';') |
||||
.sendKeys(this.Keys.ENTER) |
||||
.sendKeys('MyEnum.') |
||||
}) |
||||
.waitForElementVisible(autoCompleteLineElement('SMALL')) |
||||
.waitForElementVisible(autoCompleteLineElement('MEDIUM')) |
||||
.waitForElementVisible(autoCompleteLineElement('LARGE')) |
||||
.click(autoCompleteLineElement('SMALL')) |
||||
.perform(function () { |
||||
const actions = this.actions({ async: true }); |
||||
return actions. |
||||
sendKeys(';') |
||||
.sendKeys(this.Keys.ENTER) |
||||
}) |
||||
} |
||||
} |
@ -0,0 +1,244 @@ |
||||
'use strict' |
||||
import { NightwatchBrowser } from 'nightwatch' |
||||
import init from '../helpers/init' |
||||
|
||||
const checkEditorHoverContent = (browser: NightwatchBrowser, path: string, expectedContent: string, offsetLeft: number = 0) => { |
||||
browser.useXpath() |
||||
.useXpath() |
||||
.moveToElement('//body', 0, 0) // always move away from the hover before the next test in case we hover in the same line on a different element
|
||||
.waitForElementVisible(path) |
||||
.moveToElement(path, offsetLeft, 0) |
||||
.useCss() |
||||
.waitForElementContainsText('.monaco-hover-content', expectedContent).pause(1000) |
||||
} |
||||
|
||||
module.exports = { |
||||
'@disabled': true, |
||||
before: function (browser: NightwatchBrowser, done: VoidFunction) { |
||||
init(browser, done, 'http://127.0.0.1:8080', false) |
||||
}, |
||||
|
||||
'Should enable settings': function (browser: NightwatchBrowser) { |
||||
browser |
||||
.clickLaunchIcon('settings') |
||||
.click('[data-id="settingsAutoCompleteLabel"]') |
||||
.click('[data-id="settingsShowGasLabel"]') |
||||
.click('[data-id="displayErrorsLabel"]') |
||||
}, |
||||
|
||||
'Should load the test file': function (browser: NightwatchBrowser) { |
||||
browser.openFile('contracts') |
||||
.openFile('contracts/3_Ballot.sol') |
||||
.waitForElementVisible('#editorView') |
||||
.setEditorValue(BallotWithARefToOwner) |
||||
.pause(4000) // wait for the compiler to finish
|
||||
.scrollToLine(37) |
||||
}, |
||||
'Should show hover over contract in editor #group1': function (browser: NightwatchBrowser) { |
||||
const path = "//*[contains(text(),'BallotHoverTest')]" |
||||
checkEditorHoverContent(browser, path, 'contract BallotHoverTest is BallotHoverTest') |
||||
checkEditorHoverContent(browser, path, 'contracts/3_Ballot.sol 10:0') |
||||
checkEditorHoverContent(browser, path, '@title Ballot') |
||||
}, |
||||
'Should show hover over var definition in editor #group1': function (browser: NightwatchBrowser) { |
||||
const path = "//*[@class='view-line' and contains(.,'chairperson') and contains(.,'address') and contains(.,'public')]//span//span[contains(.,'chairperson')]" |
||||
const expectedContent = 'address public chairperson' |
||||
checkEditorHoverContent(browser, path, expectedContent) |
||||
}, |
||||
'Should show hover over constructor in editor #group1': function (browser: NightwatchBrowser) { |
||||
const path: string = "//*[@class='view-line' and contains(.,'constructor') and contains(.,'bytes32') and contains(.,'memory')]//span//span[contains(.,'constructor')]" |
||||
const expectedContent = 'Estimated creation cost: infinite gas Estimated code deposit cost:' |
||||
checkEditorHoverContent(browser, path, expectedContent) |
||||
}, |
||||
'Should show hover over function in editor #group1': function (browser: NightwatchBrowser) { |
||||
browser.scrollToLine(58) |
||||
const path: string = "//*[@class='view-line' and contains(.,'giveRightToVote(address') and contains(.,'function') and contains(.,'public')]//span//span[contains(.,'giveRightToVote')]" |
||||
let expectedContent = 'Estimated execution cost' |
||||
checkEditorHoverContent(browser, path, expectedContent) |
||||
expectedContent = 'function giveRightToVote (address internal voter) public nonpayable returns ()' |
||||
checkEditorHoverContent(browser, path, expectedContent) |
||||
expectedContent = "@dev Give 'voter' the right to vote on this ballot. May only be called by 'chairperson'" |
||||
checkEditorHoverContent(browser, path, expectedContent) |
||||
}, |
||||
'Should show hover over var components in editor #group1': function (browser: NightwatchBrowser) { |
||||
browser.scrollToLine(37) |
||||
let path = "//*[@class='view-line' and contains(.,'voters') and contains(.,'weight')]//span//span[contains(.,'voters')]" |
||||
let expectedContent = 'mapping(address => struct BallotHoverTest.Voter) public voters' |
||||
checkEditorHoverContent(browser, path, expectedContent, 15) |
||||
path = "//*[@class='view-line' and contains(.,'voters') and contains(.,'weight')]//span//span[contains(.,'chairperson')]" |
||||
expectedContent = 'address public chairperson' |
||||
checkEditorHoverContent(browser, path, expectedContent, 3) |
||||
path = "//*[@class='view-line' and contains(.,'voters') and contains(.,'weight')]//span//span[contains(.,'weight')]" |
||||
expectedContent = 'uint256 internal weight' |
||||
checkEditorHoverContent(browser, path, expectedContent) |
||||
}, |
||||
'Should show hover over new contract creation in editor #group1': function (browser: NightwatchBrowser) { |
||||
let path = "//*[@class='view-line' and contains(.,'Owner') and contains(.,'new')]//span//span[contains(.,'cowner')]" |
||||
let expectedContent = 'contract Owner internal cowner' |
||||
checkEditorHoverContent(browser, path, expectedContent, 10) |
||||
path = "//*[@class='view-line' and contains(.,'Owner') and contains(.,'new')]//span//span[contains(.,'Owner')]" |
||||
expectedContent = 'contract Owner is Owner' |
||||
checkEditorHoverContent(browser, path, expectedContent, 10) |
||||
}, |
||||
'Should show hover over external class member in editor #group1': function (browser: NightwatchBrowser) { |
||||
const path = "//*[@class='view-line' and contains(.,'getOwner') and contains(.,'cowner')]//span//span[contains(.,'getOwner')]" |
||||
let expectedContent = 'function getOwner () external view returns (address internal )' |
||||
checkEditorHoverContent(browser, path, expectedContent, 0) |
||||
expectedContent = 'contracts/2_Owner.sol' |
||||
checkEditorHoverContent(browser, path, expectedContent, 0) |
||||
expectedContent = '@dev Return owner address' |
||||
checkEditorHoverContent(browser, path, expectedContent, 0) |
||||
}, |
||||
'Should show hover over struct definition in editor #group1': function (browser: NightwatchBrowser) { |
||||
browser.scrollToLine(5) |
||||
const path = "//*[@class='view-line' and contains(.,'Voter') and contains(.,'struct')]//span//span[contains(.,'Voter')]" |
||||
const expectedContent = 'StructDefinition' |
||||
checkEditorHoverContent(browser, path, expectedContent) |
||||
} |
||||
} |
||||
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
const BallotWithARefToOwner = `// SPDX-License-Identifier: GPL-3.0
|
||||
|
||||
pragma solidity >=0.7.0 <0.9.0; |
||||
|
||||
import "./2_Owner.sol"; |
||||
|
||||
/** |
||||
* @title Ballot |
||||
* @dev Implements voting process along with vote delegation |
||||
*/ |
||||
contract BallotHoverTest { |
||||
Owner cowner; |
||||
struct Voter { |
||||
uint weight; // weight is accumulated by delegation
|
||||
bool voted; // if true, that person already voted
|
||||
address delegate; // person delegated to
|
||||
uint vote; // index of the voted proposal
|
||||
} |
||||
|
||||
struct Proposal { |
||||
// If you can limit the length to a certain number of bytes,
|
||||
// always use one of bytes1 to bytes32 because they are much cheaper
|
||||
bytes32 name; // short name (up to 32 bytes)
|
||||
uint voteCount; // number of accumulated votes
|
||||
} |
||||
|
||||
address public chairperson; |
||||
|
||||
mapping(address => Voter) public voters; |
||||
|
||||
Proposal[] public proposals; |
||||
|
||||
/** |
||||
* @dev Create a new ballot to choose one of 'proposalNames'. |
||||
* @param proposalNames names of proposals |
||||
*/ |
||||
constructor(bytes32[] memory proposalNames) { |
||||
cowner = new Owner(); |
||||
cowner.getOwner(); |
||||
chairperson = msg.sender; |
||||
voters[chairperson].weight = 1; |
||||
|
||||
for (uint i = 0; i < proposalNames.length; i++) { |
||||
// 'Proposal({...})' creates a temporary
|
||||
// Proposal object and 'proposals.push(...)'
|
||||
// appends it to the end of 'proposals'.
|
||||
proposals.push(Proposal({ |
||||
name: proposalNames[i], |
||||
voteCount: 0 |
||||
})); |
||||
} |
||||
} |
||||
|
||||
/** |
||||
* @dev Give 'voter' the right to vote on this ballot. May only be called by 'chairperson'. |
||||
* @param voter address of voter |
||||
*/ |
||||
function giveRightToVote(address voter) public { |
||||
require( |
||||
msg.sender == chairperson, |
||||
"Only chairperson can give right to vote." |
||||
); |
||||
require( |
||||
!voters[voter].voted, |
||||
"The voter already voted." |
||||
); |
||||
require(voters[voter].weight == 0); |
||||
voters[voter].weight = 1; |
||||
} |
||||
|
||||
/** |
||||
* @dev Delegate your vote to the voter 'to'. |
||||
* @param to address to which vote is delegated |
||||
*/ |
||||
function delegate(address to) public { |
||||
Voter storage sender = voters[msg.sender]; |
||||
require(!sender.voted, "You already voted."); |
||||
require(to != msg.sender, "Self-delegation is disallowed."); |
||||
|
||||
while (voters[to].delegate != address(0)) { |
||||
to = voters[to].delegate; |
||||
|
||||
// We found a loop in the delegation, not allowed.
|
||||
require(to != msg.sender, "Found loop in delegation."); |
||||
} |
||||
sender.voted = true; |
||||
sender.delegate = to; |
||||
Voter storage delegate_ = voters[to]; |
||||
if (delegate_.voted) { |
||||
// If the delegate already voted,
|
||||
// directly add to the number of votes
|
||||
proposals[delegate_.vote].voteCount += sender.weight; |
||||
} else { |
||||
// If the delegate did not vote yet,
|
||||
// add to her weight.
|
||||
delegate_.weight += sender.weight; |
||||
} |
||||
} |
||||
|
||||
/** |
||||
* @dev Give your vote (including votes delegated to you) to proposal 'proposals[proposal].name'. |
||||
* @param proposal index of proposal in the proposals array |
||||
*/ |
||||
function vote(uint proposal) public { |
||||
Voter storage sender = voters[msg.sender]; |
||||
require(sender.weight != 0, "Has no right to vote"); |
||||
require(!sender.voted, "Already voted."); |
||||
sender.voted = true; |
||||
sender.vote = proposal; |
||||
|
||||
// If 'proposal' is out of the range of the array,
|
||||
// this will throw automatically and revert all
|
||||
// changes.
|
||||
proposals[proposal].voteCount += sender.weight; |
||||
} |
||||
|
||||
/** |
||||
* @dev Computes the winning proposal taking all previous votes into account. |
||||
* @return winningProposal_ index of winning proposal in the proposals array |
||||
*/ |
||||
function winningProposal() public view |
||||
returns (uint winningProposal_) |
||||
{ |
||||
uint winningVoteCount = 0; |
||||
for (uint p = 0; p < proposals.length; p++) { |
||||
if (proposals[p].voteCount > winningVoteCount) { |
||||
winningVoteCount = proposals[p].voteCount; |
||||
winningProposal_ = p; |
||||
} |
||||
} |
||||
} |
||||
|
||||
/** |
||||
* @dev Calls winningProposal() function to get the index of the winner contained in the proposals array and then |
||||
* @return winnerName_ the name of the winner |
||||
*/ |
||||
function winnerName() public view |
||||
returns (bytes32 winnerName_) |
||||
{ |
||||
winnerName_ = proposals[winningProposal()].name; |
||||
} |
||||
} |
||||
` |
@ -0,0 +1,203 @@ |
||||
'use strict' |
||||
import { NightwatchBrowser } from 'nightwatch' |
||||
import init from '../helpers/init' |
||||
|
||||
const openReferences = (browser: NightwatchBrowser, path: string) => { |
||||
(browser as any).useXpath() |
||||
.useXpath() |
||||
.waitForElementVisible(path) |
||||
.click(path) |
||||
.perform(function () { |
||||
const actions = this.actions({ async: true }); |
||||
return actions. |
||||
keyDown(this.Keys.SHIFT). |
||||
sendKeys(this.Keys.F12) |
||||
}) |
||||
} |
||||
|
||||
module.exports = { |
||||
before: function (browser: NightwatchBrowser, done: VoidFunction) { |
||||
init(browser, done, 'http://127.0.0.1:8080', false) |
||||
}, |
||||
|
||||
'Should enable settings': function (browser: NightwatchBrowser) { |
||||
browser |
||||
.clickLaunchIcon('settings') |
||||
.click('[data-id="settingsAutoCompleteLabel"]') |
||||
.click('[data-id="settingsShowGasLabel"]') |
||||
.click('[data-id="displayErrorsLabel"]') |
||||
}, |
||||
'Should load the test file': function (browser: NightwatchBrowser) { |
||||
browser.openFile('contracts') |
||||
.openFile('contracts/3_Ballot.sol') |
||||
.waitForElementVisible('#editorView') |
||||
.setEditorValue(BallotWithARefToOwner) |
||||
.pause(10000) // wait for the compiler to finish
|
||||
.scrollToLine(37) |
||||
}, |
||||
'Should show local references': function (browser: NightwatchBrowser) { |
||||
browser.scrollToLine(48) |
||||
const path = "//*[@class='view-line' and contains(.,'length') and contains(.,'proposalNames')]//span//span[contains(.,'proposalNames')]" |
||||
openReferences(browser, path) |
||||
browser.waitForElementVisible("//*[@class='monaco-highlighted-label referenceMatch']//span[contains(.,'length; i++')]") |
||||
.waitForElementVisible("//*[@class='monaco-highlighted-label referenceMatch']//span[contains(.,'name:')]") |
||||
.waitForElementVisible("//*[@class='monaco-highlighted-label referenceMatch']//span[contains(.,'constructor')]") |
||||
.keys(browser.Keys.ESCAPE) |
||||
}, |
||||
'Should show references of getOwner': function (browser: NightwatchBrowser) { |
||||
browser.scrollToLine(39) |
||||
const path = "//*[@class='view-line' and contains(.,'getOwner') and contains(.,'cowner')]//span//span[contains(.,'getOwner')]" |
||||
openReferences(browser, path) |
||||
browser.useXpath() |
||||
.waitForElementVisible("//*[@class='monaco-highlighted-label']//span[contains(.,'2_Owner.sol')]") |
||||
.waitForElementVisible("//*[@class='monaco-highlighted-label']//span[contains(.,'3_Ballot.sol')]") |
||||
.waitForElementVisible("//*[@class='monaco-highlighted-label referenceMatch']//span[contains(.,'cowner.getOwner')]") |
||||
.waitForElementVisible("//*[contains(@class, 'results-loaded') and contains(., 'References (2)')]") |
||||
.keys(browser.Keys.ESCAPE) |
||||
} |
||||
} |
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
const BallotWithARefToOwner = `// SPDX-License-Identifier: GPL-3.0
|
||||
|
||||
pragma solidity >=0.7.0 <0.9.0; |
||||
|
||||
import "./2_Owner.sol"; |
||||
|
||||
/** |
||||
* @title Ballot |
||||
* @dev Implements voting process along with vote delegation |
||||
*/ |
||||
contract BallotHoverTest { |
||||
Owner cowner; |
||||
struct Voter { |
||||
uint weight; // weight is accumulated by delegation
|
||||
bool voted; // if true, that person already voted
|
||||
address delegate; // person delegated to
|
||||
uint vote; // index of the voted proposal
|
||||
} |
||||
|
||||
struct Proposal { |
||||
// If you can limit the length to a certain number of bytes,
|
||||
// always use one of bytes1 to bytes32 because they are much cheaper
|
||||
bytes32 name; // short name (up to 32 bytes)
|
||||
uint voteCount; // number of accumulated votes
|
||||
} |
||||
|
||||
address public chairperson; |
||||
|
||||
mapping(address => Voter) public voters; |
||||
|
||||
Proposal[] public proposals; |
||||
|
||||
/** |
||||
* @dev Create a new ballot to choose one of 'proposalNames'. |
||||
* @param proposalNames names of proposals |
||||
*/ |
||||
constructor(bytes32[] memory proposalNames) { |
||||
cowner = new Owner(); |
||||
cowner.getOwner(); |
||||
chairperson = msg.sender; |
||||
voters[chairperson].weight = 1; |
||||
|
||||
for (uint i = 0; i < proposalNames.length; i++) { |
||||
// 'Proposal({...})' creates a temporary
|
||||
// Proposal object and 'proposals.push(...)'
|
||||
// appends it to the end of 'proposals'.
|
||||
proposals.push(Proposal({ |
||||
name: proposalNames[i], |
||||
voteCount: 0 |
||||
})); |
||||
} |
||||
} |
||||
|
||||
/** |
||||
* @dev Give 'voter' the right to vote on this ballot. May only be called by 'chairperson'. |
||||
* @param voter address of voter |
||||
*/ |
||||
function giveRightToVote(address voter) public { |
||||
require( |
||||
msg.sender == chairperson, |
||||
"Only chairperson can give right to vote." |
||||
); |
||||
require( |
||||
!voters[voter].voted, |
||||
"The voter already voted." |
||||
); |
||||
require(voters[voter].weight == 0); |
||||
voters[voter].weight = 1; |
||||
} |
||||
|
||||
/** |
||||
* @dev Delegate your vote to the voter 'to'. |
||||
* @param to address to which vote is delegated |
||||
*/ |
||||
function delegate(address to) public { |
||||
Voter storage sender = voters[msg.sender]; |
||||
require(!sender.voted, "You already voted."); |
||||
require(to != msg.sender, "Self-delegation is disallowed."); |
||||
|
||||
while (voters[to].delegate != address(0)) { |
||||
to = voters[to].delegate; |
||||
|
||||
// We found a loop in the delegation, not allowed.
|
||||
require(to != msg.sender, "Found loop in delegation."); |
||||
} |
||||
sender.voted = true; |
||||
sender.delegate = to; |
||||
Voter storage delegate_ = voters[to]; |
||||
if (delegate_.voted) { |
||||
// If the delegate already voted,
|
||||
// directly add to the number of votes
|
||||
proposals[delegate_.vote].voteCount += sender.weight; |
||||
} else { |
||||
// If the delegate did not vote yet,
|
||||
// add to her weight.
|
||||
delegate_.weight += sender.weight; |
||||
} |
||||
} |
||||
|
||||
/** |
||||
* @dev Give your vote (including votes delegated to you) to proposal 'proposals[proposal].name'. |
||||
* @param proposal index of proposal in the proposals array |
||||
*/ |
||||
function vote(uint proposal) public { |
||||
Voter storage sender = voters[msg.sender]; |
||||
require(sender.weight != 0, "Has no right to vote"); |
||||
require(!sender.voted, "Already voted."); |
||||
sender.voted = true; |
||||
sender.vote = proposal; |
||||
|
||||
// If 'proposal' is out of the range of the array,
|
||||
// this will throw automatically and revert all
|
||||
// changes.
|
||||
proposals[proposal].voteCount += sender.weight; |
||||
} |
||||
|
||||
/** |
||||
* @dev Computes the winning proposal taking all previous votes into account. |
||||
* @return winningProposal_ index of winning proposal in the proposals array |
||||
*/ |
||||
function winningProposal() public view |
||||
returns (uint winningProposal_) |
||||
{ |
||||
uint winningVoteCount = 0; |
||||
for (uint p = 0; p < proposals.length; p++) { |
||||
if (proposals[p].voteCount > winningVoteCount) { |
||||
winningVoteCount = proposals[p].voteCount; |
||||
winningProposal_ = p; |
||||
} |
||||
} |
||||
} |
||||
|
||||
/** |
||||
* @dev Calls winningProposal() function to get the index of the winner contained in the proposals array and then |
||||
* @return winnerName_ the name of the winner |
||||
*/ |
||||
function winnerName() public view |
||||
returns (bytes32 winnerName_) |
||||
{ |
||||
winnerName_ = proposals[winningProposal()].name; |
||||
} |
||||
} |
||||
` |
@ -0,0 +1,91 @@ |
||||
'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 enable settings': function (browser: NightwatchBrowser) { |
||||
browser |
||||
.clickLaunchIcon('settings') |
||||
.click('[data-id="settingsAutoCompleteLabel"]') |
||||
.click('[data-id="settingsShowGasLabel"]') |
||||
.click('[data-id="displayErrorsLabel"]') |
||||
}, |
||||
'Should add error marker': function (browser: NightwatchBrowser) { |
||||
browser |
||||
.openFile('contracts') |
||||
.openFile('contracts/1_Storage.sol') |
||||
.addFile('scripts/adderror.ts', {content: addErrorMarker}) |
||||
.pause(4000) |
||||
.executeScriptInTerminal('remix.exeCurrent()') |
||||
.pause(4000) |
||||
.openFile('contracts/1_Storage.sol') |
||||
.useXpath() |
||||
.waitForElementVisible("//*[@class='cdr squiggly-error']") |
||||
.waitForElementVisible("//*[@class='cdr squiggly-warning']") |
||||
}, |
||||
'Should clear error marker': function (browser: NightwatchBrowser) { |
||||
browser
|
||||
.useCss() |
||||
.addFile('scripts/clear.ts', {content: clearMarkers}) |
||||
.pause(4000) |
||||
.executeScriptInTerminal('remix.exeCurrent()') |
||||
.pause(4000) |
||||
.openFile('contracts/1_Storage.sol') |
||||
.useXpath() |
||||
.waitForElementNotPresent("//*[@class='cdr squiggly-error']") |
||||
.waitForElementNotPresent("//*[@class='cdr squiggly-warning']") |
||||
} |
||||
} |
||||
|
||||
const clearMarkers =` |
||||
(async () => { |
||||
await remix.call('editor', 'clearErrorMarkers' as any, ['contracts/1_Storage.sol']) |
||||
})()` |
||||
|
||||
const addErrorMarker = ` |
||||
(async () => { |
||||
|
||||
|
||||
let errors = [ |
||||
{ |
||||
position: { |
||||
start: { |
||||
line: 10, |
||||
column: 1, |
||||
}, |
||||
end: { |
||||
line: 10, |
||||
column: 10 |
||||
} |
||||
}, |
||||
message: 'testing', |
||||
severity: 'error', |
||||
file: 'contracts/1_Storage.sol' |
||||
}, |
||||
{ |
||||
position: { |
||||
start: { |
||||
line: 18, |
||||
column: 1, |
||||
}, |
||||
end: { |
||||
line: 18, |
||||
column: 10 |
||||
} |
||||
}, |
||||
message: 'testing2', |
||||
severity: 'warning', |
||||
file: 'contracts/1_Storage.sol' |
||||
}, |
||||
] |
||||
|
||||
|
||||
await remix.call('editor', 'addErrorMarker' as any, errors) |
||||
|
||||
|
||||
})()` |
@ -0,0 +1,76 @@ |
||||
'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 enable settings': function (browser: NightwatchBrowser) { |
||||
browser |
||||
.clickLaunchIcon('settings') |
||||
.click('[data-id="settingsAutoCompleteLabel"]') |
||||
.click('[data-id="settingsShowGasLabel"]') |
||||
.click('[data-id="displayErrorsLabel"]') |
||||
}, |
||||
'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,42 @@ |
||||
'use strict' |
||||
|
||||
import { NightwatchBrowser } from 'nightwatch' |
||||
import init from '../helpers/init' |
||||
|
||||
const sources = [] |
||||
|
||||
module.exports = { |
||||
before: function (browser: NightwatchBrowser, done: VoidFunction) { |
||||
init(browser, done) |
||||
}, |
||||
'@sources': function () { |
||||
return sources |
||||
}, |
||||
'Deploy SampleERC721 whose bytecode is very similar to ERC721': function (browser: NightwatchBrowser) { |
||||
browser.clickLaunchIcon('filePanel') |
||||
.click('*[data-id="workspaceCreate"]') |
||||
// create contract
|
||||
.waitForElementVisible('*[data-id="modalDialogCustomPromptTextCreate"]') |
||||
.waitForElementVisible('[data-id="fileSystemModalDialogModalFooter-react"] > button') |
||||
// eslint-disable-next-line dot-notation
|
||||
.execute(function () { document.querySelector('*[data-id="modalDialogCustomPromptTextCreate"]')['value'] = 'workspace_erc721' }) |
||||
.click('select[id="wstemplate"]') |
||||
.click('select[id="wstemplate"] option[value=ozerc721]') |
||||
.waitForElementPresent('[data-id="fileSystemModalDialogModalFooter-react"] .modal-ok') |
||||
.execute(function () { (document.querySelector('[data-id="fileSystemModalDialogModalFooter-react"] .modal-ok') as HTMLElement).click() }) |
||||
.pause(100) |
||||
.waitForElementVisible('*[data-id="treeViewLitreeViewItemcontracts"]') |
||||
.waitForElementVisible('*[data-id="treeViewLitreeViewItemcontracts/MyToken.sol"]') |
||||
.openFile('contracts/MyToken.sol') |
||||
.verifyContracts(['MyToken']) |
||||
// deploy contract
|
||||
.clickLaunchIcon('udapp') |
||||
.selectContract('MyToken') |
||||
.createContract('') |
||||
.testFunction('last', |
||||
{ |
||||
status: 'true Transaction mined and execution succeed', |
||||
'decoded input': {} |
||||
}).end() |
||||
} |
||||
} |
@ -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; |
||||
} |
||||
}` |
@ -0,0 +1,125 @@ |
||||
|
||||
'use strict' |
||||
import { NightwatchBrowser } from 'nightwatch' |
||||
import init from '../helpers/init' |
||||
|
||||
module.exports = { |
||||
|
||||
before: function (browser: NightwatchBrowser, done: VoidFunction) { |
||||
init(browser, done) |
||||
}, |
||||
|
||||
'Test decorators with script': function (browser: NightwatchBrowser) { |
||||
browser |
||||
.openFile('contracts') |
||||
.openFile('contracts/2_Owner.sol') |
||||
.openFile('contracts/1_Storage.sol') |
||||
.openFile('contracts/3_Ballot.sol') |
||||
.addFile('scripts/decorators.ts', { content: testScriptSet }) |
||||
.pause(2000) |
||||
.executeScriptInTerminal('remix.exeCurrent()') |
||||
.pause(4000) |
||||
.useXpath() |
||||
.waitForElementContainsText('//*[@id="fileExplorerView"]//*[@data-id="file-decoration-error-contracts/2_Owner.sol"]', '2') |
||||
.waitForElementContainsText('//*[@class="mainview"]//*[@data-id="file-decoration-error-contracts/2_Owner.sol"]', '2') |
||||
.waitForElementContainsText('//*[@id="fileExplorerView"]//*[@data-id="file-decoration-custom-contracts/2_Owner.sol"]', 'U') |
||||
.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') |
||||
.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 testScriptSet = ` |
||||
(async () => { |
||||
remix.call('fileDecorator' as any, 'clearFileDecorators') |
||||
let decorator: any = { |
||||
path: 'contracts/2_Owner.sol', |
||||
isDirectory: false, |
||||
fileStateType: 'ERROR', |
||||
fileStateLabelClass: 'text-danger', |
||||
fileStateIconClass: '', |
||||
fileStateIcon: '', |
||||
text: '2', |
||||
bubble: true, |
||||
comment: 'error on owner', |
||||
} |
||||
let decorator2: any = { |
||||
path: 'contracts/2_Owner.sol', |
||||
isDirectory: false, |
||||
fileStateType: 'CUSTOM', |
||||
fileStateLabelClass: 'text-success', |
||||
fileStateIconClass: 'text-success', |
||||
fileStateIcon: 'U', |
||||
text: '', |
||||
bubble: true, |
||||
comment: 'modified', |
||||
} |
||||
await remix.call('fileDecorator' as any, 'setFileDecorators', [decorator, decorator2]) |
||||
|
||||
decorator = { |
||||
path: 'contracts/1_Storage.sol', |
||||
isDirectory: false, |
||||
fileStateType: 'WARNING', |
||||
fileStateLabelClass: 'text-warning', |
||||
fileStateIconClass: '', |
||||
fileStateIcon: '', |
||||
text: '2', |
||||
bubble: true, |
||||
comment: 'warning on storage', |
||||
} |
||||
await remix.call('fileDecorator' as any, 'setFileDecorators', decorator) |
||||
|
||||
decorator = { |
||||
path: 'contracts/3_Ballot.sol', |
||||
isDirectory: false, |
||||
fileStateType: 'CUSTOM', |
||||
fileStateLabelClass: '', |
||||
fileStateIconClass: '', |
||||
fileStateIcon: 'customtext', |
||||
text: 'with text', |
||||
bubble: true, |
||||
comment: 'custom comment', |
||||
} |
||||
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') |
||||
|
||||
|
||||
})()` |
@ -0,0 +1,287 @@ |
||||
'use strict' |
||||
import { NightwatchBrowser } from 'nightwatch' |
||||
import init from '../helpers/init' |
||||
|
||||
let firstProxyAddress: string |
||||
let lastProxyAddress: string |
||||
module.exports = { |
||||
before: function (browser: NightwatchBrowser, done: VoidFunction) { |
||||
init(browser, done) |
||||
}, |
||||
|
||||
'@sources': function () { |
||||
return sources |
||||
}, |
||||
|
||||
'Should show deploy proxy option for UUPS upgradeable contract': function (browser: NightwatchBrowser) { |
||||
browser |
||||
.addFile('myTokenV1.sol', sources[0]['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"]') |
||||
.waitForElementPresent('[data-id="contractGUIUpgradeImplementationLabel"]') |
||||
}, |
||||
|
||||
'Should show upgrade proxy option for child contract inheriting UUPS parent contract': function (browser: NightwatchBrowser) { |
||||
browser |
||||
.addFile('myTokenV2.sol', sources[1]['myTokenV2.sol']) |
||||
.clickLaunchIcon('solidity') |
||||
.pause(2000) |
||||
.click('[data-id="compilerContainerCompileBtn"]') |
||||
.waitForElementPresent('select[id="compiledContracts"] option[value=MyTokenV2]', 60000) |
||||
.clickLaunchIcon('udapp') |
||||
.click('select.udapp_contractNames') |
||||
.click('select.udapp_contractNames option[value=MyTokenV2]') |
||||
.waitForElementPresent('[data-id="contractGUIDeployWithProxyLabel"]') |
||||
.waitForElementPresent('[data-id="contractGUIUpgradeImplementationLabel"]') |
||||
}, |
||||
|
||||
'Should deploy proxy without initialize parameters': function (browser: NightwatchBrowser) { |
||||
browser |
||||
.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="contractGUIDeployWithProxyLabel"]') |
||||
.click('[data-id="contractGUIDeployWithProxyLabel"]') |
||||
.createContract('') |
||||
.waitForElementContainsText('[data-id="udappNotifyModalDialogModalTitle-react"]', 'Deploy Implementation & Proxy (ERC1967)') |
||||
.waitForElementVisible('[data-id="udappNotify-modal-footer-ok-react"]') |
||||
.click('[data-id="udappNotify-modal-footer-ok-react"]') |
||||
.waitForElementContainsText('[data-id="confirmProxyDeploymentModalDialogModalTitle-react"]', 'Confirm Deploy Proxy (ERC1967)') |
||||
.waitForElementVisible('[data-id="confirmProxyDeployment-modal-footer-ok-react"]') |
||||
.click('[data-id="confirmProxyDeployment-modal-footer-ok-react"]') |
||||
.waitForElementPresent('[data-id="universalDappUiTitleExpander0"]') |
||||
.waitForElementPresent('[data-id="universalDappUiTitleExpander1"]') |
||||
}, |
||||
|
||||
'Should interact with deployed contract via ERC1967 (proxy)': function (browser: NightwatchBrowser) { |
||||
browser |
||||
.getAddressAtPosition(1, (address) => { |
||||
firstProxyAddress = address |
||||
}) |
||||
.clickInstance(1) |
||||
.perform((done) => { |
||||
browser.testConstantFunction(firstProxyAddress, 'name - call', null, '0:\nstring: MyToken').perform(() => { |
||||
done() |
||||
}) |
||||
}) |
||||
.perform((done) => { |
||||
browser.testConstantFunction(firstProxyAddress, 'symbol - call', null, '0:\nstring: MTK').perform(() => { |
||||
done() |
||||
}) |
||||
}) |
||||
}, |
||||
|
||||
'Should deploy proxy with initialize parameters': function (browser: NightwatchBrowser) { |
||||
browser |
||||
.waitForElementPresent('[data-id="deployAndRunClearInstances"]') |
||||
.click('[data-id="deployAndRunClearInstances"]') |
||||
.addFile('initializeProxy.sol', sources[2]['initializeProxy.sol']) |
||||
.clickLaunchIcon('solidity') |
||||
.pause(2000) |
||||
.click('[data-id="compilerContainerCompileBtn"]') |
||||
.waitForElementPresent('select[id="compiledContracts"] option[value=MyInitializedToken]', 60000) |
||||
.clickLaunchIcon('udapp') |
||||
.click('select.udapp_contractNames') |
||||
.click('select.udapp_contractNames option[value=MyInitializedToken]') |
||||
.waitForElementPresent('[data-id="contractGUIDeployWithProxyLabel"]') |
||||
.click('[data-id="contractGUIDeployWithProxyLabel"]') |
||||
.waitForElementPresent('input[title="tokenName"]') |
||||
.waitForElementPresent('input[title="tokenSymbol"]') |
||||
.setValue('input[title="tokenName"]', 'Remix') |
||||
.setValue('input[title="tokenSymbol"]', "R") |
||||
.createContract('') |
||||
.waitForElementContainsText('[data-id="udappNotifyModalDialogModalTitle-react"]', 'Deploy Implementation & Proxy (ERC1967)') |
||||
.waitForElementVisible('[data-id="udappNotify-modal-footer-ok-react"]') |
||||
.click('[data-id="udappNotify-modal-footer-ok-react"]') |
||||
.waitForElementContainsText('[data-id="confirmProxyDeploymentModalDialogModalTitle-react"]', 'Confirm Deploy Proxy (ERC1967)') |
||||
.waitForElementVisible('[data-id="confirmProxyDeployment-modal-footer-ok-react"]') |
||||
.click('[data-id="confirmProxyDeployment-modal-footer-ok-react"]') |
||||
.waitForElementPresent('[data-id="universalDappUiTitleExpander0"]') |
||||
.waitForElementPresent('[data-id="universalDappUiTitleExpander1"]') |
||||
}, |
||||
|
||||
'Should interact with initialized contract to verify parameters': function (browser: NightwatchBrowser) { |
||||
browser |
||||
.getAddressAtPosition(1, (address) => { |
||||
lastProxyAddress = address |
||||
}) |
||||
.clickInstance(1) |
||||
.perform((done) => { |
||||
browser.testConstantFunction(lastProxyAddress, 'name - call', null, '0:\nstring: Remix').perform(() => { |
||||
done() |
||||
}) |
||||
}) |
||||
.perform((done) => { |
||||
browser.testConstantFunction(lastProxyAddress, 'symbol - call', null, '0:\nstring: R').perform(() => { |
||||
done() |
||||
}) |
||||
}) |
||||
}, |
||||
|
||||
'Should upgrade contract using last deployed proxy address (MyTokenV1 to MyTokenV2)': function (browser: NightwatchBrowser) { |
||||
browser |
||||
.waitForElementPresent('[data-id="deployAndRunClearInstances"]') |
||||
.click('[data-id="deployAndRunClearInstances"]') |
||||
.openFile('myTokenV2.sol') |
||||
.clickLaunchIcon('solidity') |
||||
.pause(2000) |
||||
.click('[data-id="compilerContainerCompileBtn"]') |
||||
.waitForElementPresent('select[id="compiledContracts"] option[value=MyTokenV2]', 60000) |
||||
.clickLaunchIcon('udapp') |
||||
.click('select.udapp_contractNames') |
||||
.click('select.udapp_contractNames option[value=MyTokenV2]') |
||||
.waitForElementPresent('[data-id="contractGUIUpgradeImplementationLabel"]') |
||||
.click('[data-id="contractGUIUpgradeImplementationLabel"]') |
||||
.waitForElementPresent('[data-id="contractGUIProxyAddressLabel"]') |
||||
.click('[data-id="contractGUIProxyAddressLabel"]') |
||||
.waitForElementPresent('[data-id="lastDeployedERC1967Address"]') |
||||
.assert.containsText('[data-id="lastDeployedERC1967Address"]', lastProxyAddress) |
||||
.createContract('') |
||||
.waitForElementContainsText('[data-id="udappNotifyModalDialogModalTitle-react"]', 'Deploy Implementation & Update Proxy') |
||||
.waitForElementVisible('[data-id="udappNotify-modal-footer-ok-react"]') |
||||
.click('[data-id="udappNotify-modal-footer-ok-react"]') |
||||
.waitForElementContainsText('[data-id="confirmProxyDeploymentModalDialogModalTitle-react"]', 'Confirm Update Proxy (ERC1967)') |
||||
.waitForElementVisible('[data-id="confirmProxyDeployment-modal-footer-ok-react"]') |
||||
.click('[data-id="confirmProxyDeployment-modal-footer-ok-react"]') |
||||
.waitForElementPresent('[data-id="universalDappUiTitleExpander0"]') |
||||
.waitForElementPresent('[data-id="universalDappUiTitleExpander1"]') |
||||
}, |
||||
|
||||
'Should interact with upgraded function in contract MyTokenV2': function (browser: NightwatchBrowser) { |
||||
browser |
||||
.clickInstance(1) |
||||
.perform((done) => { |
||||
browser.testConstantFunction(lastProxyAddress, 'version - call', null, '0:\nstring: MyTokenV2!').perform(() => { |
||||
done() |
||||
}) |
||||
}) |
||||
}, |
||||
|
||||
'Should upgrade contract by providing proxy address in input field (MyTokenV1 to MyTokenV2)': function (browser: NightwatchBrowser) { |
||||
browser |
||||
.waitForElementPresent('[data-id="deployAndRunClearInstances"]') |
||||
.click('[data-id="deployAndRunClearInstances"]') |
||||
.openFile('myTokenV2.sol') |
||||
.clickLaunchIcon('solidity') |
||||
.pause(2000) |
||||
.click('[data-id="compilerContainerCompileBtn"]') |
||||
.waitForElementPresent('select[id="compiledContracts"] option[value=MyTokenV2]', 60000) |
||||
.clickLaunchIcon('udapp') |
||||
.click('select.udapp_contractNames') |
||||
.click('select.udapp_contractNames option[value=MyTokenV2]') |
||||
.waitForElementPresent('[data-id="contractGUIUpgradeImplementationLabel"]') |
||||
.waitForElementPresent('[data-id="contractGUIProxyAddressLabel"]') |
||||
.click('[data-id="contractGUIProxyAddressLabel"]') |
||||
.waitForElementPresent('[data-id="ERC1967AddressInput"]') |
||||
.setValue('[data-id="ERC1967AddressInput"]', firstProxyAddress) |
||||
.createContract('') |
||||
.waitForElementContainsText('[data-id="udappNotifyModalDialogModalTitle-react"]', 'Deploy Implementation & Update Proxy') |
||||
.waitForElementVisible('[data-id="udappNotify-modal-footer-ok-react"]') |
||||
.click('[data-id="udappNotify-modal-footer-ok-react"]') |
||||
.waitForElementContainsText('[data-id="confirmProxyDeploymentModalDialogModalTitle-react"]', 'Confirm Update Proxy (ERC1967)') |
||||
.waitForElementVisible('[data-id="confirmProxyDeployment-modal-footer-ok-react"]') |
||||
.click('[data-id="confirmProxyDeployment-modal-footer-ok-react"]') |
||||
.waitForElementPresent('[data-id="universalDappUiTitleExpander0"]') |
||||
.waitForElementPresent('[data-id="universalDappUiTitleExpander1"]') |
||||
}, |
||||
|
||||
'Should interact with upgraded contract through provided proxy address': function (browser: NightwatchBrowser) { |
||||
browser |
||||
.clickInstance(1) |
||||
.perform((done) => { |
||||
browser.testConstantFunction(firstProxyAddress, 'version - call', null, '0:\nstring: MyTokenV2!').perform(() => { |
||||
done() |
||||
}) |
||||
}) |
||||
.end() |
||||
} |
||||
} |
||||
|
||||
const sources = [ |
||||
{ |
||||
'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 |
||||
{} |
||||
} |
||||
` |
||||
} |
||||
}, { |
||||
'myTokenV2.sol': { |
||||
content: ` |
||||
import "./myTokenV1.sol"; |
||||
|
||||
contract MyTokenV2 is MyToken { |
||||
function version () public view returns (string memory) { |
||||
return "MyTokenV2!"; |
||||
} |
||||
} |
||||
` |
||||
} |
||||
}, { |
||||
'initializeProxy.sol': { |
||||
content: ` |
||||
// SPDX-License-Identifier: MIT
|
||||
pragma solidity ^0.8.4; |
||||
|
||||
import "@openzeppelin/contracts-upgradeable/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 MyInitializedToken is Initializable, ERC721Upgradeable, OwnableUpgradeable, UUPSUpgradeable { |
||||
/// @custom:oz-upgrades-unsafe-allow constructor
|
||||
constructor() { |
||||
_disableInitializers(); |
||||
} |
||||
|
||||
function initialize(string memory tokenName, string memory tokenSymbol) initializer public { |
||||
__ERC721_init(tokenName, tokenSymbol); |
||||
__Ownable_init(); |
||||
__UUPSUpgradeable_init(); |
||||
} |
||||
|
||||
function _authorizeUpgrade(address newImplementation) |
||||
internal |
||||
onlyOwner |
||||
override |
||||
{} |
||||
} |
||||
` |
||||
} |
||||
} |
||||
] |
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in new issue