Merge branch 'master' into pluginupdate

pull/5370/head
bunsenstraat 2 years ago committed by GitHub
commit def2b24721
  1. 2
      .circleci/config.yml
  2. 18
      apps/etherscan/.babelrc
  3. 5
      apps/etherscan/src/app/RemixPlugin.tsx
  4. 4
      apps/etherscan/src/app/app.tsx
  5. 21
      apps/etherscan/src/app/utils/utilities.ts
  6. 4
      apps/etherscan/src/app/utils/verify.ts
  7. 2
      apps/etherscan/src/app/views/ReceiptsView.tsx
  8. 104
      apps/etherscan/webpack.config.js
  9. 4
      apps/remix-ide-e2e/src/tests/etherscan_api.ts
  10. 21
      apps/remix-ide-e2e/src/tests/proxy.test.ts
  11. 2
      apps/remix-ide-e2e/src/tests/runAndDeploy.test.ts
  12. 17
      apps/remix-ide-e2e/src/tests/solidityImport.test.ts
  13. 37
      apps/remix-ide-e2e/src/tests/terminal.test.ts
  14. 4
      apps/remix-ide-e2e/src/tests/workspace.test.ts
  15. 55
      apps/remix-ide-e2e/src/tests/workspace_git.test.ts
  16. 1
      apps/remix-ide/ci/browser_test.sh
  17. 1
      apps/remix-ide/ci/browser_tests_plugin_api.sh
  18. 16
      apps/remix-ide/src/app.js
  19. 2
      apps/remix-ide/src/app/components/preload.tsx
  20. 19
      apps/remix-ide/src/app/editor/editor.js
  21. 34
      apps/remix-ide/src/app/files/fileManager.ts
  22. 61
      apps/remix-ide/src/app/plugins/solidity-umlgen.tsx
  23. 19
      apps/remix-ide/src/app/providers/abstract-provider.tsx
  24. 106
      apps/remix-ide/src/app/providers/custom-vm-fork-provider.tsx
  25. 29
      apps/remix-ide/src/app/providers/goerli-vm-fork-provider.tsx
  26. 1
      apps/remix-ide/src/app/providers/injected-L2-provider.tsx
  27. 12
      apps/remix-ide/src/app/providers/injected-provider.tsx
  28. 29
      apps/remix-ide/src/app/providers/mainnet-vm-fork-provider.tsx
  29. 29
      apps/remix-ide/src/app/providers/sepolia-vm-fork-provider.tsx
  30. 40
      apps/remix-ide/src/app/providers/vm-provider.tsx
  31. 8
      apps/remix-ide/src/app/tabs/locales/en/filePanel.json
  32. 2
      apps/remix-ide/src/app/tabs/locales/en/home.json
  33. 2
      apps/remix-ide/src/app/tabs/locales/zh/filePanel.json
  34. 2
      apps/remix-ide/src/app/tabs/locales/zh/home.json
  35. 14
      apps/remix-ide/src/app/udapp/run-tab.js
  36. 13
      apps/remix-ide/src/assets/js/loader.js
  37. 39
      apps/remix-ide/src/blockchain/blockchain.js
  38. 14
      apps/remix-ide/src/blockchain/execution-context.js
  39. 4
      apps/remix-ide/src/blockchain/providers/vm.js
  40. 2
      apps/remix-ide/src/blockchain/providers/worker-vm.ts
  41. 1
      apps/remix-ide/src/remixEngine.js
  42. 56
      libs/remix-core-plugin/src/lib/compiler-content-imports.ts
  43. 22
      libs/remix-core-plugin/src/types/contract.ts
  44. 7
      libs/remix-lib/src/execution/txRunnerVM.ts
  45. 7
      libs/remix-simulator/src/genesis.ts
  46. 4
      libs/remix-simulator/src/methods/transactions.ts
  47. 6
      libs/remix-simulator/src/provider.ts
  48. 67
      libs/remix-simulator/src/vm-context.ts
  49. 46
      libs/remix-ui/helper/src/lib/components/custom-dropdown.tsx
  50. 25
      libs/remix-ui/helper/src/lib/helper-components.tsx
  51. 12
      libs/remix-ui/helper/src/lib/remix-ui-helper.ts
  52. 6
      libs/remix-ui/home-tab/src/lib/components/homeTabTitle.tsx
  53. 4
      libs/remix-ui/modal-dialog/src/lib/remix-ui-modal-dialog.tsx
  54. 4
      libs/remix-ui/modal-dialog/src/lib/types/index.ts
  55. 6
      libs/remix-ui/run-tab/src/lib/actions/actions.ts
  56. 62
      libs/remix-ui/run-tab/src/lib/actions/deploy.ts
  57. 14
      libs/remix-ui/run-tab/src/lib/actions/events.ts
  58. 6
      libs/remix-ui/run-tab/src/lib/actions/index.ts
  59. 30
      libs/remix-ui/run-tab/src/lib/actions/payload.ts
  60. 11
      libs/remix-ui/run-tab/src/lib/components/account.tsx
  61. 10
      libs/remix-ui/run-tab/src/lib/components/contractDropdownUI.tsx
  62. 162
      libs/remix-ui/run-tab/src/lib/components/contractGUI.tsx
  63. 2
      libs/remix-ui/run-tab/src/lib/components/instanceContainerUI.tsx
  64. 4
      libs/remix-ui/run-tab/src/lib/constants/index.ts
  65. 2
      libs/remix-ui/run-tab/src/lib/css/run-tab.css
  66. 148
      libs/remix-ui/run-tab/src/lib/reducers/runTab.ts
  67. 12
      libs/remix-ui/run-tab/src/lib/run-tab.tsx
  68. 6
      libs/remix-ui/run-tab/src/lib/types/blockchain.d.ts
  69. 122
      libs/remix-ui/run-tab/src/lib/types/index.ts
  70. 6
      libs/remix-ui/solidity-uml-gen/src/lib/css/solidity-uml-gen.css
  71. 123
      libs/remix-ui/solidity-uml-gen/src/lib/solidity-uml-gen.tsx
  72. 16
      libs/remix-ui/solidity-uml-gen/src/types/index.ts
  73. 12
      libs/remix-ui/workspace/src/lib/actions/events.ts
  74. 12
      libs/remix-ui/workspace/src/lib/actions/workspace.ts
  75. 51
      libs/remix-ui/workspace/src/lib/components/workspace-hamburger-item.tsx
  76. 345
      libs/remix-ui/workspace/src/lib/components/workspace-hamburger.tsx
  77. 1
      libs/remix-ui/workspace/src/lib/contexts/index.ts
  78. 13
      libs/remix-ui/workspace/src/lib/css/remix-ui-workspace.css
  79. 7
      libs/remix-ui/workspace/src/lib/providers/FileSystemProvider.tsx
  80. 28
      libs/remix-ui/workspace/src/lib/remix-ui-workspace.tsx
  81. 2
      libs/remix-ui/workspace/src/lib/utils/index.ts
  82. 94
      libs/remix-url-resolver/src/resolve.ts
  83. 18
      libs/remixd/src/services/remixdClient.ts
  84. 2
      libs/remixd/src/types/index.ts
  85. 3
      package.json
  86. 715
      yarn.lock

@ -24,7 +24,7 @@ jobs:
key: v1-deps-{{ checksum "yarn.lock" }}
paths:
- node_modules
- run: NX_BIN_URL=http://127.0.0.1:8080/assets/js NX_WASM_URL=http://127.0.0.1:8080/assets/js yarn build:production
- run: NX_BIN_URL=http://127.0.0.1:8080/assets/js NX_WASM_URL=http://127.0.0.1:8080/assets/js NPM_URL=http://localhost:9090/ yarn build:production
- run: yarn nx build remix-ide-e2e-src-local-plugin & yarn run build:libs
- run: yarn nx run debugger:build:production
- run: yarn nx run solidity-compiler:build:production

@ -1,13 +1,9 @@
{
"presets": [
[
"@nrwl/react/babel", {
"runtime": "automatic"
}
]
],
"plugins": [
"presets": ["@babel/preset-env", ["@babel/preset-react",
{"runtime": "automatic"}
]],
"plugins": ["@babel/plugin-proposal-class-properties", "@babel/plugin-transform-runtime", "@babel/plugin-proposal-nullish-coalescing-operator"],
"ignore": [
"**/node_modules/**"
]
}
}

@ -23,9 +23,8 @@ export class RemixClient extends PluginClient {
const etherscanApi = getEtherScanApi(network)
const receiptStatus = await getReceiptStatus(receiptGuid, apiKey, etherscanApi)
return {
status: receiptStatus,
message: receiptStatus,
succeed: true
message: receiptStatus.result,
succeed: receiptStatus.status === '0' ? false : true
}
} catch (e: any){
return {

@ -89,7 +89,7 @@ const App = () => {
})
if (receiptsNotVerified.length > 0) {
let timer1 = setInterval(() => {
const timer1 = setInterval(() => {
for (const item in receiptsNotVerified) {
}
@ -106,7 +106,7 @@ const App = () => {
apiKey,
getEtherScanApi(network)
)
if (status === "Pass - Verified") {
if (status.result === "Pass - Verified") {
const newReceipts = receipts.map((currentReceipt: Receipt) => {
if (currentReceipt.guid === item.guid) {
return {

@ -2,6 +2,17 @@ import { PluginClient } from "@remixproject/plugin"
import axios from 'axios'
type RemixClient = PluginClient
/*
status: 0=Error, 1=Pass
message: OK, NOTOK
result: explanation
*/
export type receiptStatus = {
result: string
message: string
status: string
}
export const getEtherScanApi = (network: string) => {
return network === "main"
? `https://api.etherscan.io/api`
@ -20,12 +31,16 @@ export const getReceiptStatus = async (
receiptGuid: string,
apiKey: string,
etherscanApi: string
) => {
): Promise<receiptStatus> => {
const params = `guid=${receiptGuid}&module=contract&action=checkverifystatus&apiKey=${apiKey}`
try {
const response = await axios.get(`${etherscanApi}?${params}`)
const { result } = response.data
return result
const { result, message, status } = response.data
return {
result,
message,
status,
}
} catch (error) {
console.error(error)
}

@ -100,7 +100,7 @@ export const verify = async (
const returnValue = {
guid: result,
status: receiptStatus,
status: receiptStatus.result,
message: `Verification process started correctly. Receipt GUID ${result}`,
succeed: true
}
@ -169,4 +169,4 @@ export const verify = async (
}
}
return contractMetadata
}
}

@ -30,7 +30,7 @@ export const ReceiptsView: React.FC = () => {
apiKey,
etherscanApi
)
setResults(result)
setResults(result.result)
} catch (error: any) {
setResults(error.message)
}

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

@ -54,7 +54,7 @@ module.exports = {
.clickLaunchIcon('filePanel')
.addFile('receiptStatusScript.ts', { content: receiptStatusScript })
.click('*[data-id="play-editor"]') // run the script
.waitForElementContainsText('*[data-id="terminalJournal"]', 'Pass - Verified', 60000)
.waitForElementContainsText('*[data-id="terminalJournal"]', 'Already Verified', 60000)
}
}
@ -119,7 +119,7 @@ const receiptStatusScript = `
const receiptStatus = async () => {
try {
const apikey = '2HKUX5ZVASZIKWJM8MIQVCRUVZ6JAWT531'
const ret = await remix.call('etherscan' as any, 'receiptStatus', 'n1qtqfn8jggwqv9uvni5zzectnztqbxqqvizznvl4vg1pndb9v', apikey)
const ret = await remix.call('etherscan' as any, 'receiptStatus', 'tsrrzmayenrslvixnvhdv7fbbp6kk1xuqkg667aqlesblpkimt', apikey)
console.log(ret)
} catch (e) {
console.log(e.message)

@ -4,6 +4,8 @@ import init from '../helpers/init'
let firstProxyAddress: string
let lastProxyAddress: string
let shortenedFirstAddress: string
let shortenedLastAddress: string
module.exports = {
'@disabled': true,
before: function (browser: NightwatchBrowser, done: VoidFunction) {
@ -94,6 +96,7 @@ module.exports = {
browser
.getAddressAtPosition(1, (address) => {
firstProxyAddress = address
shortenedFirstAddress = address.slice(0, 5) + '...' + address.slice(address.length - 5, address.length)
})
.clickInstance(1)
.perform((done) => {
@ -143,6 +146,7 @@ module.exports = {
browser
.getAddressAtPosition(1, (address) => {
lastProxyAddress = address
shortenedLastAddress = address.slice(0, 5) + '...' + address.slice(address.length - 5, address.length)
})
.clickInstance(1)
.perform((done) => {
@ -157,7 +161,7 @@ module.exports = {
})
},
'Should upgrade contract using last deployed proxy address (MyTokenV1 to MyTokenV2) #group1': function (browser: NightwatchBrowser) {
'Should upgrade contract by selecting a previously deployed proxy address from dropdown (MyTokenV1 to MyTokenV2) #group1': function (browser: NightwatchBrowser) {
browser
.waitForElementPresent('[data-id="deployAndRunClearInstances"]')
.click('[data-id="deployAndRunClearInstances"]')
@ -171,10 +175,12 @@ module.exports = {
.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)
.waitForElementPresent('[data-id="toggleProxyAddressDropdown"]')
.click('[data-id="toggleProxyAddressDropdown"]')
.waitForElementVisible('[data-id="proxy-dropdown-items"]')
.assert.textContains('[data-id="proxy-dropdown-items"]', shortenedFirstAddress)
.assert.textContains('[data-id="proxy-dropdown-items"]', shortenedLastAddress)
.click('[data-id="proxyAddress1"]')
.createContract('')
.waitForElementContainsText('[data-id="udappNotifyModalDialogModalTitle-react"]', 'Deploy Implementation & Update Proxy')
.waitForElementVisible('[data-id="udappNotify-modal-footer-ok-react"]')
@ -212,9 +218,8 @@ module.exports = {
.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"]')
.waitForElementPresent('[data-id="toggleProxyAddressDropdown"]')
.clearValue('[data-id="ERC1967AddressInput"]')
.setValue('[data-id="ERC1967AddressInput"]', firstProxyAddress)
.createContract('')
.waitForElementContainsText('[data-id="udappNotifyModalDialogModalTitle-react"]', 'Deploy Implementation & Update Proxy')

@ -32,7 +32,7 @@ module.exports = {
'Should sign message using account key #group2': function (browser: NightwatchBrowser) {
browser.waitForElementVisible('*[data-id="settingsRemixRunSignMsg"]')
.switchEnvironment('vm-berlin')
.switchEnvironment('vm-merge')
.pause(2000)
.click('*[data-id="settingsRemixRunSignMsg"]')
.pause(2000)

@ -34,19 +34,6 @@ module.exports = {
timeout: 120000,
suppressNotFoundErrors: true
})
.click('[data-id="compilerContainerCompileBtn"]')
.isVisible({
selector: "//span[contains(.,'not found Untitled11')]",
locateStrategy: 'xpath',
timeout: 120000,
suppressNotFoundErrors: true
})
.click('[data-id="compilerContainerCompileBtn"]')
.waitForElementVisible({
selector: "//span[contains(.,'not found Untitled11')]",
locateStrategy: 'xpath',
timeout: 120000,
})
},
@ -135,11 +122,11 @@ const sources = [
'Untitled.sol': { content: 'contract test1 {} contract test2 {}' }
},
{
'Untitled1.sol': { content: 'import "./Untitled2.sol"; contract test6 {}' },
'Untitled1.sol': { content: 'import "/Untitled2.sol"; contract test6 {}' },
'Untitled2.sol': { content: 'contract test4 {} contract test5 {}' }
},
{
'Untitled3.sol': { content: 'import "./Untitled11.sol"; contract test6 {}' }
'Untitled3.sol': { content: 'import "/Untitled11.sol"; contract test6 {}' }
},
{
'Untitled4.sol': { content: 'import "https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/token/ERC20/ERC20.sol"; contract test7 {}' }

File diff suppressed because one or more lines are too long

@ -410,12 +410,12 @@ module.exports = {
browser
.useXpath()
.waitForElementPresent({
selector: '//i[@data-icon="workspaceDropdownMenuIcon"]',
selector: '//i[@data-id="workspaceDropdownMenuIcon"]',
locateStrategy: 'xpath',
})
.click('//*[@id="workspacesMenuDropdown"]/span/i')
.waitForElementVisible('//*[@id="workspacesMenuDropdown"]/div/ul')
.click('//*[@id="workspacesMenuDropdown"]/div/ul/a[3]') // rename workspace_name
.click('//*[@id="workspacesMenuDropdown"]/div/ul/a[4]') // rename workspace_name
.useCss()
.waitForElementVisible('*[data-id="treeViewLitreeViewItemtests"]')
.waitForElementVisible('*[data-id="modalDialogCustomPromptTextRename"]')

@ -58,19 +58,15 @@ module.exports = {
.waitForElementContainsText('[data-id="workspaceGitBranchesDropdown"]', 'main')
},
// CLONE REPOSITORY E2E START
'Should clone a repository #group2': function (browser: NightwatchBrowser) {
browser
.clickLaunchIcon('filePanel')
.useXpath()
.click('//*[@id="workspacesMenuDropdown"]/span/i')
.waitForElementVisible('//*[@id="workspacesMenuDropdown"]/div/ul/a[5]')
.click('//*[@id="workspacesMenuDropdown"]/div/ul/a[5]')
.useCss()
.waitForElementVisible('[data-id="workspaceMenuDropdown"]')
.click('[data-id="workspaceMenuDropdown"]')
.waitForElementVisible('[data-id="workspaceclone"]')
.click('[data-id="workspaceclone"]')
.waitForElementVisible('[data-id="fileSystemModalDialogModalBody-react"]')
.click('[data-id="fileSystemModalDialogModalBody-react"]')
.waitForElementVisible('[data-id="modalDialogCustomPromptTextClone"]')
@ -93,11 +89,9 @@ module.exports = {
'Should display non-clashing names for duplicate clone #group2': '' + function (browser: NightwatchBrowser) {
browser
.useXpath()
.click('//*[@id="workspacesMenuDropdown"]/span/i')
.waitForElementVisible('//*[@id="workspacesMenuDropdown"]/div/ul/a[5]')
.click('//*[@id="workspacesMenuDropdown"]/div/ul/a[5]')
.useCss()
.click('[data-id="workspaceMenuDropdown"]')
.waitForElementVisible('[data-id="workspaceclone"]')
.click('[data-id="workspaceclone"]')
.waitForElementVisible('[data-id="fileSystemModalDialogModalBody-react"]')
.click('[data-id="fileSystemModalDialogModalBody-react"]')
.waitForElementVisible('[data-id="modalDialogCustomPromptTextClone"]')
@ -105,11 +99,9 @@ module.exports = {
.click('[data-id="fileSystem-modal-footer-ok-react"]')
.pause(5000)
.waitForElementContainsText('[data-id="workspacesSelect"]', 'awesome-remix1')
.useXpath()
.click('//*[@id="workspacesMenuDropdown"]/span/i')
.waitForElementVisible('//*[@id="workspacesMenuDropdown"]/div/ul/a[5]')
.click('//*[@id="workspacesMenuDropdown"]/div/ul/a[5]')
.useCss()
.click('[data-id="workspaceMenuDropdown"]')
.waitForElementVisible('[data-id="workspaceclone"]')
.click('[data-id="workspaceclone"]')
.waitForElementVisible('[data-id="fileSystemModalDialogModalBody-react"]')
.click('[data-id="fileSystemModalDialogModalBody-react"]')
.waitForElementVisible('[data-id="modalDialogCustomPromptTextClone"]')
@ -117,10 +109,8 @@ module.exports = {
.click('[data-id="fileSystem-modal-footer-ok-react"]')
.pause(5000)
.waitForElementContainsText('[data-id="workspacesSelect"]', 'awesome-remix2')
.useXpath()
.click('//*[@id="workspacesMenuDropdown"]/span/i')
.waitForElementVisible('//*[@id="workspacesMenuDropdown"]/div/ul/a[5]')
.useCss()
.click('[data-id="workspaceMenuDropdown"]')
.waitForElementVisible('[data-id="workspaceDropdownMenuIcon]"')
.waitForElementVisible('[data-id="fileSystemModalDialogModalBody-react"]')
.click('[data-id="fileSystemModalDialogModalBody-react"]')
.waitForElementVisible('[data-id="modalDialogCustomPromptTextClone"]')
@ -136,15 +126,10 @@ module.exports = {
'Should display error message in modal for failed clone #group2': function (browser: NightwatchBrowser) {
browser
.useXpath()
.waitForElementPresent({
selector: '//i[@data-icon="workspaceDropdownMenuIcon"]',
locateStrategy: 'xpath',
})
.click('//*[@id="workspacesMenuDropdown"]/span/i')
.waitForElementVisible('//*[@id="workspacesMenuDropdown"]/div/ul/a[5]')
.click('//*[@id="workspacesMenuDropdown"]/div/ul/a[5]')
.useCss()
.waitForElementVisible('[data-id="workspaceDropdownMenuIcon"]')
.click('[data-id="workspaceMenuDropdown"]')
.waitForElementVisible('[data-id="workspaceclone"]')
.click('[data-id="workspaceclone"]')
.waitForElementVisible('[data-id="fileSystemModalDialogModalBody-react"]')
.click('[data-id="fileSystemModalDialogModalBody-react"]')
.waitForElementVisible('[data-id="modalDialogCustomPromptTextClone"]')
@ -163,11 +148,9 @@ module.exports = {
browser
.clickLaunchIcon('filePanel')
.waitForElementNotVisible('[data-id="workspaceGitPanel"]')
.useXpath()
.click('//*[@id="workspacesMenuDropdown"]/span/i')
.waitForElementVisible('//*[@id="workspacesMenuDropdown"]/div/ul/a[5]')
.click('//*[@id="workspacesMenuDropdown"]/div/ul/a[5]')
.useCss()
.click('[data-id="workspaceMenuDropdown"]')
.waitForElementVisible('[data-id="workspaceclone"]')
.click('[data-id="workspaceclone"]')
.waitForElementVisible('[data-id="fileSystemModalDialogModalBody-react"]')
.click('[data-id="fileSystemModalDialogModalBody-react"]')
.waitForElementVisible('[data-id="modalDialogCustomPromptTextClone"]')

@ -8,6 +8,7 @@ BUILD_ID=${CIRCLE_BUILD_NUM:-${TRAVIS_JOB_NUMBER}}
echo "$BUILD_ID"
TEST_EXITCODE=0
yarn run ganache-cli &
npx http-server -p 9090 --cors='*' ./node_modules &
yarn run serve:production &
echo 'sharing folder: ' $PWD '/apps/remix-ide/contracts' &

@ -7,6 +7,7 @@ echo "$BUILD_ID"
TEST_EXITCODE=0
yarn run ganache-cli &
npx http-server -p 9090 --cors='*' ./node_modules &
yarn run serve:production &
npx nx serve remix-ide-e2e-src-local-plugin &

@ -27,7 +27,11 @@ import { StoragePlugin } from './app/plugins/storage'
import { Layout } from './app/panels/layout'
import { NotificationPlugin } from './app/plugins/notification'
import { Blockchain } from './blockchain/blockchain.js'
import { BerlinVMProvider, LondonVMProvider } from './app/providers/vm-provider'
import { MergeVMProvider, LondonVMProvider, BerlinVMProvider} from './app/providers/vm-provider'
import { MainnetForkVMProvider } from './app/providers/mainnet-vm-fork-provider'
import { SepoliaForkVMProvider } from './app/providers/sepolia-vm-fork-provider'
import { GoerliForkVMProvider } from './app/providers/goerli-vm-fork-provider'
import { CustomForkVMProvider } from './app/providers/custom-vm-fork-provider'
import { HardhatProvider } from './app/providers/hardhat-provider'
import { GanacheProvider } from './app/providers/ganache-provider'
import { FoundryProvider } from './app/providers/foundry-provider'
@ -203,6 +207,11 @@ class AppComponent {
const networkModule = new NetworkModule(blockchain)
// ----------------- represent the current selected web3 provider ----
const web3Provider = new Web3ProviderModule(blockchain)
const vmProviderCustomFork = new CustomForkVMProvider(blockchain)
const vmProviderMainnetFork = new MainnetForkVMProvider(blockchain)
const vmProviderSepoliaFork = new SepoliaForkVMProvider(blockchain)
const vmProviderGoerliFork = new GoerliForkVMProvider(blockchain)
const vmProviderMerge = new MergeVMProvider(blockchain)
const vmProviderBerlin = new BerlinVMProvider(blockchain)
const vmProviderLondon = new LondonVMProvider(blockchain)
const hardhatProvider = new HardhatProvider(blockchain)
@ -271,8 +280,13 @@ class AppComponent {
fetchAndCompile,
dGitProvider,
storagePlugin,
vmProviderMerge,
vmProviderBerlin,
vmProviderLondon,
vmProviderSepoliaFork,
vmProviderGoerliFork,
vmProviderMainnetFork,
vmProviderCustomFork,
hardhatProvider,
ganacheProvider,
foundryProvider,

@ -35,7 +35,7 @@ export const Preload = () => {
})
}).catch(err => {
_paq.push(['trackEvent', 'Preload', 'error', err && err.message])
console.log('Error loading Remix:', err)
console.error('Error loading Remix:', err)
setError(true)
})
}

@ -13,7 +13,7 @@ const profile = {
name: 'editor',
description: 'service - editor',
version: packageJson.version,
methods: ['highlight', 'discardHighlight', 'clearAnnotations', 'addLineText', 'discardLineTexts', 'addAnnotation', 'gotoLine', 'revealRange', 'getCursorPosition', 'open', 'addModel','addErrorMarker', 'clearErrorMarkers'],
methods: ['highlight', 'discardHighlight', 'clearAnnotations', 'addLineText', 'discardLineTexts', 'addAnnotation', 'gotoLine', 'revealRange', 'getCursorPosition', 'open', 'addModel','addErrorMarker', 'clearErrorMarkers', 'getText'],
}
class Editor extends Plugin {
@ -280,11 +280,22 @@ class Editor extends Plugin {
/**
* Set the text in the current session, if any.
* @param {string} url Address of the text to replace.
* @param {string} text New text to be place.
*/
setText (text) {
if (this.currentFile && this.sessions[this.currentFile]) {
this.sessions[this.currentFile].setValue(text)
setText (url, text) {
if (this.sessions[url]) {
this.sessions[url].setValue(text)
}
}
/**
* Get the text in the current session, if any.
* @param {string} url Address of the content to retrieve.
*/
getText (url) {
if (this.sessions[url]) {
return this.sessions[url].getValue()
}
}

@ -1,5 +1,6 @@
'use strict'
import { saveAs } from 'file-saver'
import JSZip from 'jszip'
import { Plugin } from '@remixproject/engine'
import * as packageJson from '../../../../../package.json'
import Registry from '../state/registry'
@ -344,12 +345,31 @@ class FileManager extends Plugin {
}
}
async zipDir(dirPath, zip) {
const filesAndFolders = await this.readdir(dirPath)
for(let path in filesAndFolders) {
if (filesAndFolders[path].isDirectory) await this.zipDir(path, zip)
else {
path = this.normalize(path)
const content: any = await this.readFile(path)
zip.file(path, content)
}
}
}
async download(path) {
try {
const fileName = helper.extractNameFromKey(path)
path = this.normalize(path)
const content: any = await this.readFile(path)
saveAs(new Blob([content]), fileName)
const downloadFileName = helper.extractNameFromKey(path)
if (await this.isDirectory(path)) {
const zip = new JSZip()
await this.zipDir(path, zip)
const content = await zip.generateAsync({type: 'blob'})
saveAs(content, `${downloadFileName}.zip`)
} else {
path = this.normalize(path)
const content: any = await this.readFile(path)
saveAs(new Blob([content]), downloadFileName)
}
} catch (e) {
throw new Error(e)
}
@ -377,9 +397,9 @@ class FileManager extends Plugin {
/**
* Get the list of files in the directory
* @param {string} path path of the directory
* @returns {string[]} list of the file/directory name in this directory
* @returns {Object} list of the file/directory name in this directory e.g; {contracts/1_Storage.sol:{isDirectory: false}}
*/
async readdir(path) {
async readdir(path): Promise<Record<string, Record<string, boolean>>> {
try {
path = this.normalize(path)
path = this.limitPluginScope(path)
@ -775,7 +795,7 @@ class FileManager extends Plugin {
if (provider) {
try{
const content = await provider.get(currentFile)
if(content) this.editor.setText(content)
if(content) this.editor.setText(currentFile, content)
}catch(error){
console.log(error)
}

@ -3,7 +3,7 @@ import { ViewPlugin } from '@remixproject/engine-web'
import React from 'react'
// eslint-disable-next-line @nrwl/nx/enforce-module-boundaries
import { RemixUiSolidityUmlGen } from '@remix-ui/solidity-uml-gen'
import { ISolidityUmlGen } from 'libs/remix-ui/solidity-uml-gen/src/types'
import { ISolidityUmlGen, ThemeQualityType, ThemeSummary } from 'libs/remix-ui/solidity-uml-gen/src/types'
import { RemixAppManager } from 'libs/remix-ui/plugin-manager/src/types'
import { concatSourceFiles, getDependencyGraph } from 'libs/remix-ui/solidity-compiler/src/lib/logic/flattenerUtilities'
import { convertUmlClasses2Dot } from 'sol2uml/lib/converterClasses2Dot'
@ -22,13 +22,33 @@ const profile = {
events: [],
}
const themeCollection = [
{ themeName: 'HackerOwl', backgroundColor: '--body-bg', actualHex: '#011628'},
{ themeName: 'Cerulean', backgroundColor: '--body-bg', actualHex: '#fff'},
{ themeName: 'Cyborg', backgroundColor: '--body-bg', actualHex: '#060606'},
{ themeName: 'Dark', backgroundColor: '--body-bg', actualHex: '#222336'},
{ themeName: 'Flatly', backgroundColor: '--body-bg', actualHex: '#fff'},
{ themeName: 'Black', backgroundColor: '--body-bg', actualHex: '#1a1a1a'},
{ themeName: 'Light', backgroundColor: '--body-bg', actualHex: '#eef1f6'},
{ themeName: 'Midcentuary', backgroundColor: '--body-bg', actualHex: '#DBE2E0'},
{ themeName: 'Spacelab', backgroundColor: '--body-bg', actualHex: '#fff'},
{ themeName: 'Candy', backgroundColor: '--body-bg', actualHex: '#d5efff'},
]
/**
* add context menu which will offer download as pdf and download png.
* add menu under the first download button to download
*/
export class SolidityUmlGen extends ViewPlugin implements ISolidityUmlGen {
element: HTMLDivElement
currentFile: string
svgPayload: string
updatedSvg: string
currentlySelectedTheme: string
themeName: string
loading: boolean
themeCollection: ThemeSummary[]
appManager: RemixAppManager
dispatch: React.Dispatch<any> = () => {}
@ -39,14 +59,18 @@ export class SolidityUmlGen extends ViewPlugin implements ISolidityUmlGen {
this.updatedSvg = ''
this.loading = false
this.currentlySelectedTheme = ''
this.themeName = ''
this.themeCollection = themeCollection
this.appManager = appManager
this.element = document.createElement('div')
this.element.setAttribute('id', 'sol-uml-gen')
}
onActivation(): void {
if (this.currentFile.length < 1)
this.on('solidity', 'compilationFinished', async (file, source, languageVersion, data, input, version) => {
const currentTheme: ThemeQualityType = await this.call('theme', 'currentTheme')
this.currentlySelectedTheme = currentTheme.quality
this.themeName = currentTheme.name
let result = ''
try {
if (data.sources && Object.keys(data.sources).length > 1) { // we should flatten first as there are multiple asts
@ -56,12 +80,11 @@ export class SolidityUmlGen extends ViewPlugin implements ISolidityUmlGen {
const umlClasses = convertAST2UmlClasses(ast, this.currentFile)
const umlDot = convertUmlClasses2Dot(umlClasses)
const payload = vizRenderStringSync(umlDot)
const currentTheme = await this.call('theme', 'currentTheme')
this.currentlySelectedTheme = currentTheme.quality
this.updatedSvg = payload
this.renderComponent()
await this.call('tabs', 'focus', 'solidityumlgen')
} catch (error) {
console.log({ error })
console.log('error', error)
}
})
this.on('theme', 'themeChanged', (theme) => {
@ -72,13 +95,16 @@ export class SolidityUmlGen extends ViewPlugin implements ISolidityUmlGen {
async mangleSvgPayload(svgPayload: string) : Promise<string> {
const parser = new DOMParser()
const themeQuality = await this.call('theme', 'currentTheme')
const themeQuality: ThemeQualityType = await this.call('theme', 'currentTheme')
const parsedDocument = parser.parseFromString(svgPayload, 'image/svg+xml')
const res = parsedDocument.documentElement
parsedDocument.bgColor = '#cccabc'
res.style.filter = themeQuality.quality === 'dark' ? 'invert(1)' : 'invert(0)'
const element = parsedDocument.getElementsByTagName('svg')
themeCollection.forEach((theme) => {
if (theme.themeName === themeQuality.name) {
parsedDocument.documentElement.setAttribute('style', `background-color: var(${themeQuality.name === theme.themeName ? theme.backgroundColor : '--body-bg'})`)
element[0].setAttribute('fill', theme.actualHex)
}
})
const stringifiedSvg = new XMLSerializer().serializeToString(parsedDocument)
console.log({ parsedDocument, themeQuality, stringifiedSvg })
return stringifiedSvg
}
@ -87,6 +113,7 @@ export class SolidityUmlGen extends ViewPlugin implements ISolidityUmlGen {
}
generateCustomAction = async (action: customAction) => {
this.updatedSvg = this.updatedSvg.startsWith('<?xml') ? '' : this.updatedSvg
this.currentFile = action.path[0]
await this.generateUml(action.path[0])
}
@ -105,14 +132,11 @@ export class SolidityUmlGen extends ViewPlugin implements ISolidityUmlGen {
* @returns {Promise<string>}
*/
async flattenContract (source: any, filePath: string, data: any) {
const hold = { data, source, filePath }
const ast = data.sources
const dependencyGraph = getDependencyGraph(ast, filePath)
const dependencyGraph = getDependencyGraph(data.sources, filePath)
const sorted = dependencyGraph.isEmpty()
? [filePath]
: dependencyGraph.sort().reverse()
const sources = source.sources
const result = concatSourceFiles(sorted, sources)
const result = concatSourceFiles(sorted, source.sources)
await this.call('fileManager', 'writeFile', `${filePath}_flattened.sol`, result)
return result
}
@ -143,16 +167,19 @@ export class SolidityUmlGen extends ViewPlugin implements ISolidityUmlGen {
...this,
updatedSvg: this.updatedSvg,
loading: this.loading,
themeSelected: this.currentlySelectedTheme
themeSelected: this.currentlySelectedTheme,
themeName: this.themeName,
themeCollection: this.themeCollection
})
}
updateComponent(state: any) {
return <RemixUiSolidityUmlGen
plugin={state}
updatedSvg={state.updatedSvg}
loading={state.loading}
themeSelected={state.currentlySelectedTheme}
themeName={state.themeName}
themeCollection={state.themeCollection}
/>
}
}

@ -19,12 +19,20 @@ export type JsonDataResult = {
export type RejectRequest = (error: Error) => void
export type SuccessRequest = (data: JsonDataResult) => void
export abstract class AbstractProvider extends Plugin {
export interface IProvider {
options: { [id: string] : any }
init(): Promise<{ [id: string] : any }>
body(): JSX.Element
sendAsync (data: JsonDataRequest): Promise<JsonDataResult>
}
export abstract class AbstractProvider extends Plugin implements IProvider {
provider: ethers.providers.JsonRpcProvider
blockchain: Blockchain
defaultUrl: string
connected: boolean
nodeUrl: string
options: { [id: string] : any } = {}
constructor (profile, blockchain, defaultUrl) {
super(profile)
@ -41,7 +49,7 @@ export abstract class AbstractProvider extends Plugin {
this.provider = null
}
async init () {
async init () {
this.nodeUrl = await ((): Promise<string> => {
return new Promise((resolve, reject) => {
const modalContent: AppModal = {
@ -80,9 +88,12 @@ export abstract class AbstractProvider extends Plugin {
})
})()
this.provider = new ethers.providers.JsonRpcProvider(this.nodeUrl)
return {
nodeUrl: this.nodeUrl
}
}
sendAsync (data: JsonDataRequest): Promise<any> {
sendAsync (data: JsonDataRequest): Promise<JsonDataResult> {
// eslint-disable-next-line no-async-promise-executor
return new Promise(async (resolve, reject) => {
if (!this.provider) return reject(new Error('provider node set'))
@ -102,7 +113,7 @@ export abstract class AbstractProvider extends Plugin {
}
this.call('notification', 'alert', modalContent)
}
await this.call('udapp', 'setEnvironmentMode', { context: 'vm-london'})
await this.call('udapp', 'setEnvironmentMode', { context: 'vm-merge'})
return
}

@ -0,0 +1,106 @@
import React, { useRef } from 'react' // eslint-disable-line
import * as packageJson from '../../../../../package.json'
import { AppModal, ModalTypes } from '@remix-ui/app'
import { BasicVMProvider } from './vm-provider'
import { Hardfork } from '@ethereumjs/common'
export class CustomForkVMProvider extends BasicVMProvider {
nodeUrl: string
blockNumber: number | 'latest'
inputs: any
constructor (blockchain) {
super({
name: 'vm-custom-fork',
displayName: 'Custom fork - Remix VM',
kind: 'provider',
description: 'Custom fork - Remix VM',
methods: ['sendAsync', 'init'],
version: packageJson.version
}, blockchain)
this.blockchain = blockchain
this.fork = ''
this.nodeUrl = ''
this.blockNumber = 'latest'
this.inputs = {}
}
async init () {
const body = () => {
return <div>
<span>Please provide information about the custom fork. If the node URL is not provided, the VM will start with an empty state.</span>
<div>
<label className="mt-3 mb-1">Node URL</label>
<input data-id="CustomForkNodeUrl" name="nodeUrl" type="text" className="border form-control border-right-0" />
</div>
<div>
<label className="mt-3 mb-1">Block number (or "latest")</label>
<input data-id="CustomForkBlockNumber" name="blockNumber" type="text" defaultValue="latest" placeholder='block number or "latest"' className="border form-control border-right-0" />
</div>
<div>
<label className="mt-3 mb-1">EVM</label>
<select data-id="CustomForkEvmType" name="evmType" className="border form-control border-right-0">
{Object.keys(Hardfork).map((value, index) => {
return <option value={Hardfork[value]} key={index}>{value}</option>
})}
</select>
</div>
</div>
}
const result = await ((): Promise<any> => {
return new Promise((resolve, reject) => {
const modalContent: AppModal = {
id: this.profile.name,
title: this.profile.displayName,
message: body(),
validationFn: (data: any) => {
if(data.nodeUrl !== '' && !data.nodeUrl.startsWith("http")) {
return {
valid: false,
message: 'node URL should be a valid URL'
}
}
if (data.blockNumber !== 'latest' && isNaN(data.blockNumber)) {
return {
valid: false,
message: 'blockNumber should be a number or "latest"'
}
}
return {
valid: true,
message: ''
}
},
modalType: ModalTypes.form,
okLabel: 'Connect',
cancelLabel: 'Cancel',
okFn: (value: string) => {
setTimeout(() => resolve(value), 0)
},
cancelFn: () => {
setTimeout(() => reject(new Error('Canceled')), 0)
},
hideFn: () => {
setTimeout(() => reject(new Error('Hide')), 0)
}
}
return this.call('notification', 'modal', modalContent)
})
})()
this.fork = result.evmType
this.nodeUrl = result.nodeUrl
if (this.nodeUrl) {
const block = result.blockNumber
this.blockNumber = block === 'latest' ? 'latest' : parseInt(block)
} else {
this.nodeUrl = undefined
this.blockNumber = undefined
}
return {
'fork': this.fork,
'nodeUrl': this.nodeUrl,
'blockNumber': this.blockNumber
}
}
}

@ -0,0 +1,29 @@
import * as packageJson from '../../../../../package.json'
import { BasicVMProvider } from './vm-provider'
export class GoerliForkVMProvider extends BasicVMProvider {
nodeUrl: string
blockNumber: number | 'latest'
constructor (blockchain) {
super({
name: 'vm-goerli-fork',
displayName: 'Goerli fork - Remix VM (London)',
kind: 'provider',
description: 'Remix VM (London)',
methods: ['sendAsync', 'init'],
version: packageJson.version
}, blockchain)
this.blockchain = blockchain
this.fork = 'merge'
this.nodeUrl = 'https://remix-sepolia.ethdevops.io'
this.blockNumber = 'latest'
}
async init () {
return {
'fork': this.fork,
'nodeUrl': this.nodeUrl,
'blockNumber': this.blockNumber
}
}
}

@ -17,6 +17,7 @@ export class InjectedL2Provider extends InjectedProvider {
if (this.chainName && this.rpcUrls && this.rpcUrls.length > 0) await addL2Network(this.chainName, this.chainId, this.rpcUrls)
else
throw new Error('Cannot add the L2 network to main injected provider')
return {}
}
}

@ -1,12 +1,15 @@
/* global ethereum */
import React from 'react' // eslint-disable-line
import { Plugin } from '@remixproject/engine'
import { JsonDataRequest, RejectRequest, SuccessRequest } from '../providers/abstract-provider'
import Web3 from 'web3'
import { IProvider } from './abstract-provider'
const noInjectedProviderMsg = 'No injected provider found. Make sure your provider (e.g. MetaMask) is active and running (when recently activated you may have to reload the page).'
export class InjectedProvider extends Plugin {
export class InjectedProvider extends Plugin implements IProvider {
provider: any
options: { [id: string] : any } = {}
constructor (profile) {
super(profile)
@ -23,6 +26,12 @@ export class InjectedProvider extends Plugin {
}
}
body (): JSX.Element {
return (
<div></div>
)
}
async init () {
const injectedProvider = (window as any).ethereum
if (injectedProvider === undefined) {
@ -33,6 +42,7 @@ export class InjectedProvider extends Plugin {
}
this.askPermission(true)
}
return {}
}
sendAsync (data: JsonDataRequest): Promise<any> {

@ -0,0 +1,29 @@
import * as packageJson from '../../../../../package.json'
import { BasicVMProvider } from './vm-provider'
export class MainnetForkVMProvider extends BasicVMProvider {
nodeUrl: string
blockNumber: number | 'latest'
constructor (blockchain) {
super({
name: 'vm-mainnet-fork',
displayName: 'Mainet fork -Remix VM (London)',
kind: 'provider',
description: 'Remix VM (London)',
methods: ['sendAsync', 'init'],
version: packageJson.version
}, blockchain)
this.blockchain = blockchain
this.fork = 'london'
this.nodeUrl = 'https://rpc.archivenode.io/e50zmkroshle2e2e50zm0044i7ao04ym'
this.blockNumber = 'latest'
}
async init () {
return {
'fork': this.fork,
'nodeUrl': this.nodeUrl,
'blockNumber': this.blockNumber
}
}
}

@ -0,0 +1,29 @@
import * as packageJson from '../../../../../package.json'
import { BasicVMProvider } from './vm-provider'
export class SepoliaForkVMProvider extends BasicVMProvider {
nodeUrl: string
blockNumber: number | 'latest'
constructor (blockchain) {
super({
name: 'vm-sepolia-fork',
displayName: 'Sepolia fork - Remix VM (London)',
kind: 'provider',
description: 'Remix VM (London)',
methods: ['sendAsync', 'init'],
version: packageJson.version
}, blockchain)
this.blockchain = blockchain
this.fork = 'merge'
this.nodeUrl = 'https://remix-sepolia.ethdevops.io'
this.blockNumber = 'latest'
}
async init () {
return {
'fork': this.fork,
'nodeUrl': this.nodeUrl,
'blockNumber': this.blockNumber
}
}
}

@ -1,17 +1,26 @@
import React from 'react' // eslint-disable-line
import * as packageJson from '../../../../../package.json'
import { JsonDataRequest, RejectRequest, SuccessRequest } from '../providers/abstract-provider'
import { Plugin } from '@remixproject/engine'
import { IProvider } from './abstract-provider'
export class BasicVMProvider extends Plugin {
export class BasicVMProvider extends Plugin implements IProvider {
blockchain
fork: string
options: { [id: string] : any } = {}
constructor (profile, blockchain) {
super(profile)
this.blockchain = blockchain
this.fork = null
this.fork = ''
}
init () {}
async init (): Promise<{ [id: string] : any }> { return {} }
body (): JSX.Element {
return (
<div></div>
)
}
sendAsync (data: JsonDataRequest): Promise<any> {
return new Promise((resolve, reject) => {
@ -33,18 +42,18 @@ export class BasicVMProvider extends Plugin {
}
}
export class BerlinVMProvider extends BasicVMProvider {
export class MergeVMProvider extends BasicVMProvider {
constructor (blockchain) {
super({
name: 'vm-berlin',
displayName: 'Remix VM (Berlin)',
name: 'vm-merge',
displayName: 'Remix VM (Merge)',
kind: 'provider',
description: 'Remix VM (Berlin)',
description: 'Remix VM (Merge)',
methods: ['sendAsync', 'init'],
version: packageJson.version
}, blockchain)
this.blockchain = blockchain
this.fork = 'berlin'
this.fork = 'merge'
}
}
@ -61,4 +70,19 @@ export class LondonVMProvider extends BasicVMProvider {
this.blockchain = blockchain
this.fork = 'london'
}
}
export class BerlinVMProvider extends BasicVMProvider {
constructor (blockchain) {
super({
name: 'vm-berlin',
displayName: 'Remix VM (Berlin)',
kind: 'provider',
description: 'Remix VM (Berlin)',
methods: ['sendAsync', 'init'],
version: packageJson.version
}, blockchain)
this.blockchain = blockchain
this.fork = 'berlin'
}
}

@ -3,16 +3,18 @@
"filePanel.workspace": "WORKSPACES",
"filePanel.create": "Create",
"filePanel.clone": "Clone",
"filePanel.download": "Download",
"filePanel.download": "Backup",
"filePanel.restore": "Restore",
"filePanel.workspace.create": "Create Workspace",
"filePanel.workspace.rename": "Rename Workspace",
"filePanel.workspace.delete": "Delete Workspace",
"filePanel.workspace.deleteConfirm": "Are you sure to delete the current workspace?",
"filePanel.workspace.deleteAll": "Delete All Workspaces",
"filePanel.workspace.deleteAllConfirm": "Are you absolutely sure you want to delete all your workspaces? Deleted workspaces can not be restored in any manner.",
"filePanel.workspace.name": "Workspace name",
"filePanel.workspace.chooseTemplate": "Choose a template",
"filePanel.workspace.download": "Download Workspace",
"filePanel.workspace.restore": "Restore Workspace Backup",
"filePanel.workspace.download": "Backup Workspaces",
"filePanel.workspace.restore": "Restore Workspaces from the Backup",
"filePanel.workspace.clone": "Clone Git Repository",
"filePanel.workspace.cloneMessage": "Please provide a valid git repository url.",
"filePanel.workspace.enterGitUrl": "Enter git repository url",

@ -45,7 +45,7 @@
"home.remixTwitterProfile": "Remix Twitter Profile",
"home.remixLinkedinProfile": "Remix Linkedin Profile",
"home.remixMediumPosts": "Remix Medium Posts",
"home.remixGitterChannel": "Remix Gitter Channel",
"home.remixGitterChannel": "Join us on Discord",
"home.nativeIDE": "The Native IDE for Web3 Development.",
"home.website": "Website",
"home.documentation": "Documentation",

@ -9,6 +9,8 @@
"filePanel.workspace.rename": "重命名工作空间",
"filePanel.workspace.delete": "删除工作空间",
"filePanel.workspace.deleteConfirm": "确定要删除当前工作空间?",
"filePanel.workspace.deleteAll": "Delete All Workspaces",
"filePanel.workspace.deleteAllConfirm": "Are you absolutely sure you want to delete all your workspaces? Deleted workspaces can not be restored in any manner.",
"filePanel.workspace.name": "工作空间名称",
"filePanel.workspace.chooseTemplate": "选择一个工作空间模板",
"filePanel.workspace.download": "下载工作空间",

@ -43,7 +43,7 @@
"home.remixTwitterProfile": "Remix 推特档案",
"home.remixLinkedinProfile": "Remix 领英档案",
"home.remixMediumPosts": "Remix Medium 文章",
"home.remixGitterChannel": "Remix Gitter 频道",
"home.remixGitterChannel": "Join us on Discord",
"home.nativeIDE": "用于 Web3 开发的原生 IDE。",
"home.website": "网站",
"home.documentation": "文档",

@ -103,6 +103,7 @@ export class RunTab extends ViewPlugin {
const addProvider = async (name, displayName, isInjected, isVM, fork = '', dataId = '', title = '') => {
await this.call('blockchain', 'addProvider', {
options: {},
dataId,
name,
displayName,
@ -110,7 +111,13 @@ export class RunTab extends ViewPlugin {
isInjected,
isVM,
title,
init: () => { return this.call(name, 'init') },
init: async function () {
const options = await udapp.call(name, 'init')
if (options) {
this.options = options
if (options['fork']) this.fork = options['fork']
}
},
provider: {
async sendAsync (payload, callback) {
try {
@ -126,8 +133,13 @@ export class RunTab extends ViewPlugin {
// VM
const titleVM = 'Execution environment is local to Remix. Data is only saved to browser memory and will vanish upon reload.'
await addProvider('vm-merge', 'Remix VM (Merge)', false, true, 'merge', 'settingsVMMergeMode', titleVM)
await addProvider('vm-london', 'Remix VM (London)', false, true, 'london', 'settingsVMLondonMode', titleVM)
await addProvider('vm-berlin', 'Remix VM (Berlin)', false, true, 'berlin', 'settingsVMBerlinMode', titleVM)
await addProvider('vm-mainnet-fork', 'Remix VM - Mainnet fork', false, true, 'merge', 'settingsVMMainnetMode', titleVM)
await addProvider('vm-sepolia-fork', 'Remix VM - Sepolia fork', false, true, 'merge', 'settingsVMSepoliaMode', titleVM)
await addProvider('vm-goerli-fork', 'Remix VM - Goerli fork', false, true, 'merge', 'settingsVMGoerliMode', titleVM)
await addProvider('vm-custom-fork', 'Remix VM - Custom fork', false, true, '', 'settingsVMCustomMode', titleVM)
// external provider
await addProvider('hardhat-provider', 'Hardhat Provider', false, false)

@ -32,13 +32,6 @@ if (domains[window.location.hostname]) {
})()
}
createScriptTag = function (url, type) {
var script = document.createElement('script');
script.src = url;
script.type = type;
document.getElementsByTagName('head')[0].appendChild(script);
};
function isElectron() {
// Renderer process
if (typeof window !== 'undefined' && typeof window.process === 'object' && window.process.type === 'renderer') {
@ -63,11 +56,5 @@ fetch(versionUrl, { cache: "no-store" }).then(response => {
response.text().then(function (data) {
const version = JSON.parse(data);
console.log(`Loading Remix ${version.version}`);
createScriptTag(`polyfills.${version.version}.${version.timestamp}.js`, 'module');
if (version.mode === 'development') {
createScriptTag(`vendor.${version.version}.${version.timestamp}.js`, 'module');
createScriptTag(`runtime.${version.version}.${version.timestamp}.js`, 'module');
}
createScriptTag(`main.${version.version}.${version.timestamp}.js`, 'text/javascript');
});
});

@ -84,8 +84,6 @@ export class Blockchain extends Plugin {
setupProviders () {
const vmProvider = new VMProvider(this.executionContext)
this.providers = {}
this.providers['vm-berlin'] = vmProvider
this.providers['vm-london'] = vmProvider
this.providers['vm'] = vmProvider
this.providers.injected = new InjectedProvider(this.executionContext)
this.providers.web3 = new NodeProvider(this.executionContext, this.config)
@ -93,7 +91,7 @@ export class Blockchain extends Plugin {
getCurrentProvider () {
const provider = this.getProvider()
if (provider && provider.startsWith('vm')) return this.providers['vm']
if (this.providers[provider]) return this.providers[provider]
return this.providers.web3 // default to the common type of provider
}
@ -182,9 +180,8 @@ export class Blockchain extends Plugin {
_paq.push(['trackEvent', 'blockchain', 'Deploy With Proxy', 'Proxy deployment failed: ' + error])
return this.call('terminal', 'logHtml', log)
}
if (networkInfo.name === 'VM') this.config.set('vm/proxy', address)
else this.config.set(`${networkInfo.name}/${networkInfo.currentFork}/${networkInfo.id}/proxy`, address)
await this.saveDeployedContractStorageLayout(implementationContractObject, address, networkInfo)
this.events.emit('newProxyDeployment', address, new Date().toISOString(), implementationContractObject.contractName)
_paq.push(['trackEvent', 'blockchain', 'Deploy With Proxy', 'Proxy deployment successful'])
this.call('udapp', 'addInstance', addressToString(address), implementationContractObject.abi, implementationContractObject.name)
}
@ -238,23 +235,40 @@ export class Blockchain extends Plugin {
}
async saveDeployedContractStorageLayout (contractObject, proxyAddress, networkInfo) {
const { contractName, implementationAddress, contract } = contractObject
const hasPreviousDeploys = await this.call('fileManager', 'exists', `.deploys/upgradeable-contracts/${networkInfo.name}/UUPS.json`)
const { contractName, implementationAddress } = contractObject
const networkName = networkInfo.name === 'custom' ? networkInfo.name + '-' + networkInfo.id : networkInfo.name
const hasPreviousDeploys = await this.call('fileManager', 'exists', `.deploys/upgradeable-contracts/${networkName}/UUPS.json`)
// TODO: make deploys folder read only.
if (hasPreviousDeploys) {
const deployments = await this.call('fileManager', 'readFile', `.deploys/upgradeable-contracts/${networkInfo.name}/UUPS.json`)
const deployments = await this.call('fileManager', 'readFile', `.deploys/upgradeable-contracts/${networkName}/UUPS.json`)
const parsedDeployments = JSON.parse(deployments)
const proxyDeployment = parsedDeployments.deployments[proxyAddress]
if (proxyDeployment) {
const oldImplementationAddress = proxyDeployment.implementationAddress
const hasPreviousBuild = await this.call('fileManager', 'exists', `.deploys/upgradeable-contracts/${networkName}/solc-${oldImplementationAddress}.json`)
if (hasPreviousBuild) await this.call('fileManager', 'remove', `.deploys/upgradeable-contracts/${networkName}/solc-${oldImplementationAddress}.json`)
}
parsedDeployments.deployments[proxyAddress] = {
date: new Date().toISOString(),
contractName: contractName,
fork: networkInfo.currentFork,
implementationAddress: implementationAddress,
layout: contract.object.storageLayout
solcOutput: contractObject.compiler.data,
solcInput: contractObject.compiler.source
}
await this.call('fileManager', 'writeFile', `.deploys/upgradeable-contracts/${networkInfo.name}/UUPS.json`, JSON.stringify(parsedDeployments, null, 2))
await this.call('fileManager', 'writeFile', `.deploys/upgradeable-contracts/${networkName}/solc-${implementationAddress}.json`, JSON.stringify({
solcInput: contractObject.compiler.source,
solcOutput: contractObject.compiler.data
}, null, 2))
await this.call('fileManager', 'writeFile', `.deploys/upgradeable-contracts/${networkName}/UUPS.json`, JSON.stringify(parsedDeployments, null, 2))
} else {
await this.call('fileManager', 'writeFile', `.deploys/upgradeable-contracts/${networkInfo.name}/UUPS.json`, JSON.stringify({
await this.call('fileManager', 'writeFile', `.deploys/upgradeable-contracts/${networkName}/solc-${implementationAddress}.json`, JSON.stringify({
solcInput: contractObject.compiler.source,
solcOutput: contractObject.compiler.data
}, null, 2))
await this.call('fileManager', 'writeFile', `.deploys/upgradeable-contracts/${networkName}/UUPS.json`, JSON.stringify({
id: networkInfo.id,
network: networkInfo.name,
deployments: {
@ -262,8 +276,7 @@ export class Blockchain extends Plugin {
date: new Date().toISOString(),
contractName: contractName,
fork: networkInfo.currentFork,
implementationAddress: implementationAddress,
layout: contract.object.storageLayout
implementationAddress: implementationAddress
}
}
}, null, 2))

@ -20,7 +20,7 @@ if (typeof window !== 'undefined' && typeof window.ethereum !== 'undefined') {
export class ExecutionContext {
constructor () {
this.event = new EventManager()
this.executionContext = 'vm-london'
this.executionContext = 'vm-merge'
this.lastBlock = null
this.blockGasLimitDefault = 4300000
this.blockGasLimit = this.blockGasLimitDefault
@ -35,7 +35,7 @@ export class ExecutionContext {
init (config) {
if (config.get('settings/always-use-vm')) {
this.executionContext = 'vm-london'
this.executionContext = 'vm-merge'
}
}
@ -43,6 +43,10 @@ export class ExecutionContext {
return this.executionContext
}
getProviderObject () {
return this.customNetWorks[this.executionContext]
}
getSelectedAddress () {
return injectedProvider ? injectedProvider.selectedAddress : null
}
@ -98,7 +102,7 @@ export class ExecutionContext {
removeProvider (name) {
if (name && this.customNetWorks[name]) {
if (this.executionContext === name) this.setContext('vm-london', null, null, null)
if (this.executionContext === name) this.setContext('vm-merge', null, null, null)
delete this.customNetWorks[name]
this.event.trigger('removeProvider', [name])
}
@ -127,10 +131,10 @@ export class ExecutionContext {
if (!confirmCb) confirmCb = () => { /* Do nothing. */ }
if (!infoCb) infoCb = () => { /* Do nothing. */ }
if (this.customNetWorks[context]) {
var network = this.customNetWorks[context]
var network = this.customNetWorks[context]
await network.init()
this.currentFork = network.fork
this.executionContext = context
await network.init()
// injected
web3.setProvider(network.provider)
await this._updateChainContext()

@ -22,7 +22,9 @@ class VMProvider {
if (this.worker) this.worker.terminate()
this.accounts = {}
this.worker = new Worker(new URL('./worker-vm', import.meta.url))
this.worker.postMessage({ cmd: 'init', fork: this.executionContext.getCurrentFork() })
const provider = this.executionContext.getProviderObject()
this.worker.postMessage({ cmd: 'init', fork: this.executionContext.getCurrentFork(), nodeUrl: provider?.options['nodeUrl'], blockNumber: provider?.options['blockNumber']})
let incr = 0
const stamps = {}

@ -6,7 +6,7 @@ self.onmessage = (e: MessageEvent) => {
switch (data.cmd) {
case 'init':
{
provider = new Provider({ fork: data.fork })
provider = new Provider({ fork: data.fork, nodeUrl: data.nodeUrl, blockNumber: data.blockNumber })
if (provider) provider.init()
break
}

@ -19,6 +19,7 @@ export class RemixEngine extends Engine {
if (name === 'sourcify') return { queueTimeout: 60000 * 4 }
if (name === 'fetchAndCompile') return { queueTimeout: 60000 * 4 }
if (name === 'walletconnect') return { queueTimeout: 60000 * 4 }
if (name === 'udapp') return { queueTimeout: 60000 * 4 }
return { queueTimeout: 10000 }
}

@ -16,12 +16,50 @@ export type ResolvedImport = {
}
export class CompilerImports extends Plugin {
previouslyHandled: Record<string, ResolvedImport>
urlResolver: any
constructor () {
super(profile)
this.urlResolver = new RemixURLResolver()
this.previouslyHandled = {} // cache import so we don't make the request at each compilation.
this.urlResolver = new RemixURLResolver(async () => {
try {
let yarnLock
if (await this.call('fileManager', 'exists', './yarn.lock')) {
yarnLock = await this.call('fileManager', 'readFile', './yarn.lock')
}
let packageLock
if (await this.call('fileManager', 'exists', './package-lock.json')) {
packageLock = await this.call('fileManager', 'readFile', './package-lock.json')
packageLock = JSON.parse(packageLock)
}
if (await this.call('fileManager', 'exists', './package.json')) {
const content = await this.call('fileManager', 'readFile', './package.json')
const pkg = JSON.parse(content)
return { deps: { ...pkg['dependencies'], ...pkg['devDependencies'] }, yarnLock, packageLock }
} else {
return {}
}
} catch (e) {
console.error(e)
return {}
}
})
}
onActivation(): void {
const packageFiles = ['package.json', 'package-lock.json', 'yarn.lock']
this.on('filePanel', 'setWorkspace', () => this.urlResolver.clearCache())
this.on('fileManager', 'fileRemoved', (file: string) => {
if (packageFiles.includes(file)) {
this.urlResolver.clearCache()
}
})
this.on('fileManager', 'fileChanged', (file: string) => {
if (packageFiles.includes(file)) {
this.urlResolver.clearCache()
}
})
}
async setToken () {
@ -71,22 +109,12 @@ export class CompilerImports extends Plugin {
if (!cb) cb = () => {}
const self = this
if (force) delete this.previouslyHandled[url]
const imported = this.previouslyHandled[url]
if (imported) {
return cb(null, imported.content, imported.cleanUrl, imported.type, url)
}
let resolved
try {
await this.setToken()
resolved = await this.urlResolver.resolve(url)
resolved = await this.urlResolver.resolve(url, [], force)
const { content, cleanUrl, type } = resolved
self.previouslyHandled[url] = {
content,
cleanUrl,
type
}
cb(null, content, cleanUrl, type, url)
} catch (e) {
return cb(new Error('not found ' + url))

@ -1,3 +1,4 @@
import { SolcInput, SolcOutput } from "@openzeppelin/upgrades-core"
export interface FuncABI {
name: string,
type: string,
@ -19,7 +20,8 @@ export interface ContractData {
getConstructorInterface: () => any,
getConstructorInputs: () => any,
isOverSizeLimit: () => boolean,
metadata: any
metadata: any,
contractName?: string
}
export interface ContractAST {
@ -183,3 +185,21 @@ export interface ContractSources {
}
}
}
export interface NetworkDeploymentFile {
id: string,
network: string,
deployments: {
[proxyAddress: string]: {
date: Date,
contractName: string,
fork: string,
implementationAddress: string
}
}[]
}
export interface SolcBuildFile {
solcInput: SolcInput,
solcOutput: SolcOutput
}

@ -1,6 +1,6 @@
'use strict'
import BN from 'bn.js'
import { RunBlockResult, RunTxResult } from '@ethereumjs/vm'
import { ConsensusType } from '@ethereumjs/common'
import { Transaction, FeeMarketEIP1559Transaction } from '@ethereumjs/tx'
import { Block } from '@ethereumjs/block'
import { bufferToHex, Address } from '@ethereumjs/util'
@ -106,13 +106,14 @@ export class TxRunnerVM {
const coinbases = ['0x0e9281e9c6a0808672eaba6bd1220e144c9bb07a', '0x8945a1288dc78a6d8952a92c77aee6730b414778', '0x94d76e24f818426ae84aa404140e8d5f60e10e7e']
const difficulties = [69762765929000, 70762765929000, 71762765929000]
const difficulty = this.commonContext.consensusType() === ConsensusType.ProofOfStake ? 0 : difficulties[self.blockNumber % difficulties.length]
const block = Block.fromBlockData({
header: {
timestamp: new Date().getTime() / 1000 | 0,
number: self.blockNumber,
coinbase: coinbases[self.blockNumber % coinbases.length],
difficulty: difficulties[self.blockNumber % difficulties.length],
difficulty,
gasLimit,
baseFeePerGas: EIP1559 ? '0x1' : undefined
},

@ -1,13 +1,18 @@
import { Block } from '@ethereumjs/block'
import { ConsensusType } from '@ethereumjs/common'
export function generateBlock (vmContext) {
const common = vmContext.vmObject().common
const difficulty = common.consensusType() === ConsensusType.ProofOfStake ? 0 : 69762765929000
return new Promise((resolve, reject) => {
const block: Block = Block.fromBlockData({
header: {
timestamp: (new Date().getTime() / 1000 | 0),
number: 0,
coinbase: '0x0e9281e9c6a0808672eaba6bd1220e144c9bb07a',
difficulty: 69762765929000,
difficulty,
gasLimit: 8000000
}
}, { common: vmContext.vmObject().common })

@ -156,9 +156,9 @@ export class Transactions {
payload.params[0].gas = 10000000 * 10
this.vmContext.web3().flagNextAsDoNotRecordEvmSteps()
processTx(this.txRunnerInstance, payload, true, (error, value: VMexecutionResult) => {
const result: RunTxResult = value.result
processTx(this.txRunnerInstance, payload, true, (error, value: VMexecutionResult) => {
if (error) return cb(error)
const result: RunTxResult = value.result
if ((result as any).receipt?.status === '0x0' || (result as any).receipt?.status === 0) {
try {
const msg = `0x${result.execResult.returnValue.toString('hex') || '0'}`

@ -13,17 +13,17 @@ import { generateBlock } from './genesis'
import { VMContext } from './vm-context'
export class Provider {
options: Record<string, unknown>
options: Record<string, string | number>
vmContext
Accounts
Transactions
methods
connected: boolean;
constructor (options: Record<string, unknown> = {}) {
constructor (options: Record<string, string | number> = {}) {
this.options = options
this.connected = true
this.vmContext = new VMContext(options['fork'])
this.vmContext = new VMContext(options['fork'] as string, options['nodeUrl'] as string, options['blockNumber'] as number)
this.Accounts = new Web3Accounts(this.vmContext)
this.Transactions = new Transactions(this.vmContext)

@ -2,14 +2,16 @@
'use strict'
import { hash } from '@remix-project/remix-lib'
import { bufferToHex } from '@ethereumjs/util'
import type { Address } from '@ethereumjs/util'
import { decode } from 'rlp'
import { ethers } from 'ethers'
import { execution } from '@remix-project/remix-lib'
const { LogsManager } = execution
import { VmProxy } from './VmProxy'
import { VM } from '@ethereumjs/vm'
import { Common } from '@ethereumjs/common'
import { Trie } from '@ethereumjs/trie'
import { DefaultStateManager } from '@ethereumjs/statemanager'
import { DefaultStateManager, StateManager, EthersStateManager, EthersStateManagerOpts } from '@ethereumjs/statemanager'
import { StorageDump } from '@ethereumjs/statemanager/dist/interface'
import { EVM } from '@ethereumjs/evm'
import { EEI } from '@ethereumjs/vm'
@ -35,6 +37,44 @@ export interface DefaultStateManagerOpts {
prefixCodeHashes?: boolean
}
class CustomEthersStateManager extends EthersStateManager {
keyHashes: { [key: string]: string }
constructor (opts: EthersStateManagerOpts) {
super(opts)
this.keyHashes = {}
}
putContractStorage (address, key, value) {
this.keyHashes[bufferToHex(key).replace('0x', '')] = hash.keccak(key).toString('hex')
return super.putContractStorage(address, key, value)
}
copy(): CustomEthersStateManager {
const newState = new CustomEthersStateManager({
provider: (this as any).provider,
blockTag: BigInt((this as any).blockTag),
})
;(newState as any).contractCache = new Map((this as any).contractCache)
;(newState as any).storageCache = new Map((this as any).storageCache)
;(newState as any)._cache = this._cache
;(newState as any).keyHashes = this.keyHashes
return newState
}
async dumpStorage(address: Address): Promise<StorageDump> {
const storageDump = {}
const storage = await super.dumpStorage(address)
for (const key of Object.keys(storage)) {
const value = storage[key]
storageDump['0x' + this.keyHashes[key]] = {
key: '0x' + key,
value: value
}
}
return storageDump
}
}
/*
extend vm state manager and instanciate VM
*/
@ -86,7 +126,7 @@ class StateManagerCommonStorageDump extends DefaultStateManager {
export type CurrentVm = {
vm: VM,
web3vm: VmProxy,
stateManager: StateManagerCommonStorageDump,
stateManager: StateManager,
common: Common
}
@ -105,12 +145,15 @@ export class VMContext {
web3vm: VmProxy
logsManager: any // LogsManager
exeResults: Record<string, Transaction>
nodeUrl: string
blockNumber: number | 'latest'
constructor (fork?) {
constructor (fork?: string, nodeUrl?: string, blockNumber?: number | 'latest') {
this.blockGasLimitDefault = 4300000
this.blockGasLimit = this.blockGasLimitDefault
this.currentFork = fork || 'london'
this.nodeUrl = nodeUrl
this.blockNumber = blockNumber
this.blocks = {}
this.latestBlockNumber = "0x0"
this.blockByTxHash = {}
@ -124,7 +167,21 @@ export class VMContext {
}
async createVm (hardfork) {
const stateManager = new StateManagerCommonStorageDump()
let stateManager: StateManager
console.log('creating a new VM', hardfork, this.nodeUrl, this.blockNumber)
if (this.nodeUrl) {
let block = this.blockNumber
if (this.blockNumber === 'latest') {
const provider = new ethers.providers.StaticJsonRpcProvider(this.nodeUrl)
block = await provider.getBlockNumber()
}
stateManager = new CustomEthersStateManager({
provider: this.nodeUrl,
blockTag: BigInt(block)
})
} else
stateManager = new StateManagerCommonStorageDump()
const common = new Common({ chain: 'mainnet', hardfork })
const blockchain = new (Blockchain as any)({ common })
const eei = new EEI(stateManager, common, blockchain)

@ -30,13 +30,57 @@ export const CustomIconsToggle = React.forwardRef(({ onClick, icon, className =
className={`${className.replace('dropdown-toggle', '')} mb-0 pb-0 d-flex justify-content-end align-items-end remixuimenuicon_shadow fs-3`}
data-id="workspaceMenuDropdown"
>
{ icon && <i style={{ fontSize: 'large' }} className={`${icon}`} data-icon="workspaceDropdownMenuIcon"></i> }
{ icon && <i style={{ fontSize: 'large' }} className={`${icon}`} data-id="workspaceDropdownMenuIcon"></i> }
</span>
))
// forwardRef again here!
// Dropdown needs access to the DOM of the Menu to measure it
export const CustomMenu = React.forwardRef(
({ children, style, className, 'aria-labelledby': labeledBy }: { children: React.ReactNode, style?: React.CSSProperties, className: string, 'aria-labelledby'?: string }, ref: Ref<HTMLDivElement>) => {
const height = window.innerHeight * 0.6
return (
<div
ref={ref}
style={style}
className={className}
aria-labelledby={labeledBy}
>
<ul className="overflow-auto list-unstyled mb-0" style={{ maxHeight: height+'px' }}>
{
children
}
</ul>
</div>
)
},
)
export const ProxyAddressToggle = React.forwardRef(({ address, onClick, className = '', onChange }: { address: string, onClick: (e) => void, className: string, onChange: (e: React.ChangeEvent<HTMLInputElement>) => void }, ref: Ref<HTMLDivElement>) => (
<div
ref={ref}
onClick={(e) => {
e.preventDefault()
onClick(e)
}}
className={'d-flex '+ className.replace('dropdown-toggle', '')}
data-id="toggleProxyAddressDropdown"
>
<input
onChange={(e) => {
e.preventDefault()
onChange(e)
}}
className="udapp_input form-control"
value={address}
placeholder="Enter Proxy Address"
style={{ width: '100%' }}
data-id="ERC1967AddressInput"
/>
</div>
))
export const ProxyDropdownMenu = React.forwardRef(
({ children, style, className, 'aria-labelledby': labeledBy }: { children: React.ReactNode, style?: React.CSSProperties, className: string, 'aria-labelledby'?: string }, ref: Ref<HTMLDivElement>) => {
return (
<div

@ -1,3 +1,4 @@
import { LayoutCompatibilityReport } from '@openzeppelin/upgrades-core/dist/storage/report'
import React from 'react'
export const fileChangedToastMsg = (from: string, path: string) => (
@ -115,3 +116,27 @@ export const upgradeWithProxyMsg = () => (
</ol>
</div>
)
export const unavailableProxyLayoutMsg = () => (
<div>
<p>The previous contract implementation is NOT available for an upgrade comparison<br /> A new storage layout will be saved for future upgrades.</p>
</div>
)
export const upgradeReportMsg = (report: LayoutCompatibilityReport) => (
<div>
<div className="py-2 ml-2 mb-1 align-self-end mb-2 d-flex">
<span className="align-self-center pl-4 mt-1">
<i className="pr-2 text-warning far fa-exclamation-triangle" aria-hidden="true" style={{ fontSize: 'xxx-large', fontWeight: 'lighter' }}></i>
</span>
<div className="d-flex flex-column">
<span className="pl-4 mt-1">The storage layout of new implementation is NOT</span>
<span className="pl-4 mt-1">compatible with the previous implementation.</span>
<span className="pl-4 mt-1">Your contract's storage may be partially or fully erased!</span>
</div>
</div>
<div className='pl-4 text-danger'>
{ report.explain() }
</div>
</div>
)

@ -128,3 +128,15 @@ export const addSlash = (file: string) => {
if (!file.startsWith('/'))file = '/' + file
return file
}
export const shortenProxyAddress = (address: string) => {
const len = address.length
return address.slice(0, 5) + '...' + address.slice(len - 5, len)
}
export const shortenDate = (dateString: string) => {
const date = new Date(dateString)
return date.toLocaleDateString(undefined, { month: "short", day: "numeric" }) + ', ' + date.toLocaleTimeString(undefined, { hour: '2-digit', minute: '2-digit' })
}

@ -142,10 +142,10 @@ function HomeTabTitle() {
>
<button
onClick={() => {
openLink("https://gitter.im/ethereum/remix")
_paq.push(['trackEvent', 'hometab', 'socialmedia', 'gitter'])
openLink("https://discord.gg/mh9hFCKkEq")
_paq.push(['trackEvent', 'hometab', 'socialmedia', 'discord'])
}}
className="border-0 h-100 p-2 btn fab fa-gitter">
className="border-0 h-100 p-2 btn fab fa-discord">
</button>
</CustomTooltip>
</span>

@ -98,7 +98,7 @@ export const ModalDialog = (props: ModalDialogProps) => {
{/* todo add autofocus ^^ */}
{ props.okLabel && <button
data-id={`${props.id}-modal-footer-ok-react`}
className={'modal-ok btn btn-sm ' + (state.toggleBtn ? 'btn-dark' : 'btn-light')}
className={'modal-ok btn btn-sm ' + (props.okBtnClass ? props.okBtnClass : state.toggleBtn ? 'btn-dark' : 'btn-light')}
disabled={props.validation && !props.validation.valid}
onClick={() => {
if (props.validation && !props.validation.valid) return
@ -111,7 +111,7 @@ export const ModalDialog = (props: ModalDialogProps) => {
}
{ props.cancelLabel && <button
data-id={`${props.id}-modal-footer-cancel-react`}
className={'modal-cancel btn btn-sm ' + (state.toggleBtn ? 'btn-light' : 'btn-dark')}
className={'modal-cancel btn btn-sm ' + (props.cancelBtnClass ? props.cancelBtnClass : state.toggleBtn ? 'btn-light' : 'btn-dark')}
data-dismiss="modal"
onClick={() => {
if (props.cancelFn) props.cancelFn()

@ -22,5 +22,7 @@ export interface ModalDialogProps {
children?: React.ReactNode,
resolve?: (value?:any) => void,
next?: () => void,
data?: any
data?: any,
okBtnClass?: string,
cancelBtnClass?: string
}

@ -1,5 +1,5 @@
import { ContractData } from "@remix-project/core-plugin"
import { addNewInstance, addProvider, clearAllInstances, clearRecorderCount, hidePopUp, removeExistingInstance, removeProvider, setBaseFeePerGas, setConfirmSettings, setCurrentContract, setExecutionEnvironment, setExternalEndpoint, setGasLimit, setGasPrice, setGasPriceStatus, setMatchPassphrase, setMaxFee, setMaxPriorityFee, setNetworkName, setPassphrase, setPathToScenario, setSelectedAccount, setSendUnit, setSendValue } from "./payload"
import { addNewInstance, addProvider, clearAllInstances, clearRecorderCount, hidePopUp, newProxyDeployment, removeExistingInstance, removeProvider, setBaseFeePerGas, setConfirmSettings, setCurrentContract, setExecutionEnvironment, setExternalEndpoint, setGasLimit, setGasPrice, setGasPriceStatus, setMatchPassphrase, setMaxFee, setMaxPriorityFee, setNetworkName, setPassphrase, setPathToScenario, setSelectedAccount, setSendUnit, setSendValue } from "./payload"
export const setAccount = (dispatch: React.Dispatch<any>, account: string) => {
dispatch(setSelectedAccount(account))
@ -89,4 +89,8 @@ export const updateScenarioPath = (dispatch: React.Dispatch<any>, path: string)
export const setSendTransactionValue = (dispatch: React.Dispatch<any>, value: string) => {
dispatch(setSendValue(value))
}
export const addNewProxyDeployment = (dispatch: React.Dispatch<any>, address: string, date: string, contractName: string) => {
dispatch(newProxyDeployment({ address, date, contractName }))
}

@ -1,9 +1,12 @@
import { ContractData, FuncABI } from "@remix-project/core-plugin"
import { ContractData, FuncABI, NetworkDeploymentFile, SolcBuildFile } from "@remix-project/core-plugin"
import { RunTab } from "../types/run-tab"
import { CompilerAbstract as CompilerAbstractType } from '@remix-project/remix-solidity'
import * as remixLib from '@remix-project/remix-lib'
import { SolcInput, SolcOutput } from "@openzeppelin/upgrades-core"
// Used direct path to UpgradeableContract class to fix cyclic dependency error from @openzeppelin/upgrades-core library
import { UpgradeableContract } from '../../../../../../node_modules/@openzeppelin/upgrades-core/dist/standalone'
import { DeployMode, MainnetPrompt } from "../types"
import { displayNotification, displayPopUp, setDecodedResponse } from "./payload"
import { displayNotification, displayPopUp, fetchProxyDeploymentsSuccess, setDecodedResponse } from "./payload"
import { addInstance } from "./actions"
import { addressToString, logBuilder } from "@remix-ui/helper"
import Web3 from "web3"
@ -335,4 +338,57 @@ export const isValidContractAddress = async (plugin: RunTab, address: string) =>
return false
}
}
}
}
export const getNetworkProxyAddresses = async (plugin: RunTab, dispatch: React.Dispatch<any>) => {
const network = plugin.blockchain.networkStatus.network
const identifier = network.name === 'custom' ? network.name + '-' + network.id : network.name
const networkDeploymentsExists = await plugin.call('fileManager', 'exists', `.deploys/upgradeable-contracts/${identifier}/UUPS.json`)
if (networkDeploymentsExists) {
const networkFile: string = await plugin.call('fileManager', 'readFile', `.deploys/upgradeable-contracts/${identifier}/UUPS.json`)
const parsedNetworkFile: NetworkDeploymentFile = JSON.parse(networkFile)
const deployments = []
for (const proxyAddress in Object.keys(parsedNetworkFile.deployments)) {
const solcBuildExists = await plugin.call('fileManager', 'exists', `.deploys/upgradeable-contracts/${identifier}/solc-${parsedNetworkFile.deployments[proxyAddress].implementationAddress}.json`)
if (solcBuildExists) deployments.push({ address: proxyAddress, date: parsedNetworkFile.deployments[proxyAddress].date, contractName: parsedNetworkFile.deployments[proxyAddress].contractName })
}
dispatch(fetchProxyDeploymentsSuccess(deployments))
} else {
dispatch(fetchProxyDeploymentsSuccess([]))
}
}
export const isValidContractUpgrade = async (plugin: RunTab, proxyAddress: string, newContractName: string, solcInput: SolcInput, solcOutput: SolcOutput) => {
// build current contract first to get artefacts.
const network = plugin.blockchain.networkStatus.network
const identifier = network.name === 'custom' ? network.name + '-' + network.id : network.name
const networkDeploymentsExists = await plugin.call('fileManager', 'exists', `.deploys/upgradeable-contracts/${identifier}/UUPS.json`)
if (networkDeploymentsExists) {
const networkFile: string = await plugin.call('fileManager', 'readFile', `.deploys/upgradeable-contracts/${identifier}/UUPS.json`)
const parsedNetworkFile: NetworkDeploymentFile = JSON.parse(networkFile)
if (parsedNetworkFile.deployments[proxyAddress]) {
const solcBuildExists = await plugin.call('fileManager', 'exists', `.deploys/upgradeable-contracts/${identifier}/solc-${parsedNetworkFile.deployments[proxyAddress].implementationAddress}.json`)
if (solcBuildExists) {
const solcFile: string = await plugin.call('fileManager', 'readFile', `.deploys/upgradeable-contracts/${identifier}/solc-${parsedNetworkFile.deployments[proxyAddress].implementationAddress}.json`)
const parsedSolcFile: SolcBuildFile = JSON.parse(solcFile)
const oldImpl = new UpgradeableContract(parsedNetworkFile.deployments[proxyAddress].contractName, parsedSolcFile.solcInput, parsedSolcFile.solcOutput, { kind: 'uups' })
const newImpl = new UpgradeableContract(newContractName, solcInput, solcOutput, { kind: 'uups' })
const report = oldImpl.getStorageUpgradeReport(newImpl, { kind: 'uups' })
return report
} else {
return { ok: false, pass: false, warning: true }
}
} else {
return { ok: false, pass: false, warning: true }
}
} else {
return { ok: false, pass: false, warning: true }
}
}

@ -1,12 +1,14 @@
import { envChangeNotification } from "@remix-ui/helper"
import { RunTab } from "../types/run-tab"
import { setExecutionContext, setFinalContext, updateAccountBalances } from "./account"
import { addExternalProvider, addInstance, removeExternalProvider, setNetworkNameFromProvider } from "./actions"
import { addDeployOption, clearAllInstances, clearRecorderCount, fetchContractListSuccess, resetUdapp, setCurrentContract, setCurrentFile, setLoadType, setProxyEnvAddress, setRecorderCount, setRemixDActivated, setSendValue } from "./payload"
import { addExternalProvider, addInstance, addNewProxyDeployment, removeExternalProvider, setNetworkNameFromProvider } from "./actions"
import { addDeployOption, clearAllInstances, clearRecorderCount, fetchContractListSuccess, resetProxyDeployments, resetUdapp, setCurrentContract, setCurrentFile, setLoadType, setRecorderCount, setRemixDActivated, setSendValue } from "./payload"
import { CompilerAbstract } from '@remix-project/remix-solidity'
import BN from 'bn.js'
import Web3 from 'web3'
import { Plugin } from "@remixproject/engine"
import { getNetworkProxyAddresses } from "./deploy"
const _paq = window._paq = window._paq || []
export const setupEvents = (plugin: RunTab, dispatch: React.Dispatch<any>) => {
@ -21,6 +23,8 @@ export const setupEvents = (plugin: RunTab, dispatch: React.Dispatch<any>) => {
})
plugin.blockchain.event.register('contextChanged', (context, silent) => {
dispatch(resetProxyDeployments())
if (!context.startsWith('vm')) getNetworkProxyAddresses(plugin, dispatch)
setFinalContext(plugin, dispatch)
})
@ -35,14 +39,14 @@ export const setupEvents = (plugin: RunTab, dispatch: React.Dispatch<any>) => {
const netUI = !networkProvider().startsWith('vm') ? `${network.name} (${network.id || '-'}) network` : 'VM'
setNetworkNameFromProvider(dispatch, netUI)
if (network.name === 'VM') dispatch(setProxyEnvAddress(plugin.config.get('vm/proxy')))
else dispatch(setProxyEnvAddress(plugin.config.get(`${network.name}/${network.currentFork}/${network.id}/proxy`)))
})
plugin.blockchain.event.register('addProvider', provider => addExternalProvider(dispatch, provider))
plugin.blockchain.event.register('removeProvider', name => removeExternalProvider(dispatch, name))
plugin.blockchain.events.on('newProxyDeployment', (address, date, contractName) => addNewProxyDeployment(dispatch, address, date, contractName))
plugin.on('solidity', 'compilationFinished', (file, source, languageVersion, data, input, version) => broadcastCompilationResult('remix', plugin, dispatch, file, source, languageVersion, data, input))
plugin.on('vyper', 'compilationFinished', (file, source, languageVersion, data) => broadcastCompilationResult('vyper', plugin, dispatch, file, source, languageVersion, data))
@ -180,4 +184,4 @@ export const resetAndInit = (plugin: RunTab) => {
}
}
})
}
}

@ -6,11 +6,12 @@ import { createNewBlockchainAccount, fillAccountsList, setExecutionContext, sign
import { clearInstances, clearPopUp, removeInstance, setAccount, setGasFee, setMatchPassphrasePrompt,
setNetworkNameFromProvider, setPassphrasePrompt, setSelectedContract, setSendTransactionValue, setUnit,
updateBaseFeePerGas, updateConfirmSettings, updateGasPrice, updateGasPriceStatus, updateMaxFee, updateMaxPriorityFee, updateScenarioPath } from './actions'
import { createInstance, getContext, getFuncABIInputs, getSelectedContract, loadAddress, runTransactions, updateInstanceBalance, syncContractsInternal, isValidContractAddress } from './deploy'
import { createInstance, getContext, getFuncABIInputs, getSelectedContract, loadAddress, runTransactions, updateInstanceBalance, syncContractsInternal, isValidContractAddress, isValidContractUpgrade } from './deploy'
import { CompilerAbstract as CompilerAbstractType } from '@remix-project/remix-solidity'
import { ContractData, FuncABI } from "@remix-project/core-plugin"
import { DeployMode, MainnetPrompt } from '../types'
import { runCurrentScenario, storeScenario } from './recorder'
import { SolcInput, SolcOutput } from '@openzeppelin/upgrades-core'
declare global {
interface Window {
@ -62,4 +63,5 @@ export const getFuncABIValues = (funcABI: FuncABI) => getFuncABIInputs(plugin, f
export const setNetworkName = (networkName: string) => setNetworkNameFromProvider(dispatch, networkName)
export const updateSelectedContract = (contractName) => setSelectedContract(dispatch, contractName)
export const syncContracts = () => syncContractsInternal(plugin)
export const isValidProxyAddress = (address: string) => isValidContractAddress(plugin, address)
export const isValidProxyAddress = (address: string) => isValidContractAddress(plugin, address)
export const isValidProxyUpgrade = (proxyAddress: string, contractName: string, solcInput: SolcInput, solcOuput: SolcOutput) => isValidContractUpgrade(plugin, proxyAddress, contractName, solcInput, solcOuput)

@ -1,7 +1,6 @@
import { ContractList } from '../reducers/runTab'
import { ContractData } from '@remix-project/core-plugin'
import { ADD_DEPLOY_OPTION, ADD_INSTANCE, ADD_PROVIDER, CLEAR_INSTANCES, CLEAR_RECORDER_COUNT, DISPLAY_NOTIFICATION, DISPLAY_POPUP_MESSAGE, FETCH_ACCOUNTS_LIST_FAILED, FETCH_ACCOUNTS_LIST_REQUEST, FETCH_ACCOUNTS_LIST_SUCCESS, FETCH_CONTRACT_LIST_FAILED, FETCH_CONTRACT_LIST_REQUEST, FETCH_CONTRACT_LIST_SUCCESS, HIDE_NOTIFICATION, HIDE_POPUP_MESSAGE, REMOVE_DEPLOY_OPTION, REMOVE_INSTANCE, REMOVE_PROVIDER, RESET_STATE, SET_BASE_FEE_PER_GAS, SET_CONFIRM_SETTINGS, SET_CURRENT_CONTRACT, SET_CURRENT_FILE, SET_DECODED_RESPONSE, SET_DEPLOY_OPTIONS, SET_EXECUTION_ENVIRONMENT, SET_EXTERNAL_WEB3_ENDPOINT, SET_GAS_LIMIT, SET_GAS_PRICE, SET_GAS_PRICE_STATUS, SET_IPFS_CHECKED_STATE, SET_LOAD_TYPE, SET_MATCH_PASSPHRASE, SET_MAX_FEE, SET_MAX_PRIORITY_FEE, SET_NETWORK_NAME, SET_PASSPHRASE, SET_PATH_TO_SCENARIO, SET_PERSONAL_MODE, SET_PROXY_ENV_ADDRESS, SET_RECORDER_COUNT, SET_SELECTED_ACCOUNT, SET_SEND_UNIT, SET_SEND_VALUE, SET_REMIXD_ACTIVATED } from '../constants'
import { DeployMode, DeployOptions } from '../types'
import { ADD_DEPLOY_OPTION, ADD_INSTANCE, ADD_PROVIDER, CLEAR_INSTANCES, CLEAR_RECORDER_COUNT, DISPLAY_NOTIFICATION, DISPLAY_POPUP_MESSAGE, FETCH_ACCOUNTS_LIST_FAILED, FETCH_ACCOUNTS_LIST_REQUEST, FETCH_ACCOUNTS_LIST_SUCCESS, FETCH_CONTRACT_LIST_FAILED, FETCH_CONTRACT_LIST_REQUEST, FETCH_CONTRACT_LIST_SUCCESS, HIDE_NOTIFICATION, HIDE_POPUP_MESSAGE, REMOVE_DEPLOY_OPTION, REMOVE_INSTANCE, REMOVE_PROVIDER, RESET_STATE, SET_BASE_FEE_PER_GAS, SET_CONFIRM_SETTINGS, SET_CURRENT_CONTRACT, SET_CURRENT_FILE, SET_DECODED_RESPONSE, SET_DEPLOY_OPTIONS, SET_EXECUTION_ENVIRONMENT, SET_EXTERNAL_WEB3_ENDPOINT, SET_GAS_LIMIT, SET_GAS_PRICE, SET_GAS_PRICE_STATUS, SET_IPFS_CHECKED_STATE, SET_LOAD_TYPE, SET_MATCH_PASSPHRASE, SET_MAX_FEE, SET_MAX_PRIORITY_FEE, SET_NETWORK_NAME, SET_PASSPHRASE, SET_PATH_TO_SCENARIO, SET_PERSONAL_MODE, SET_RECORDER_COUNT, SET_SELECTED_ACCOUNT, SET_SEND_UNIT, SET_SEND_VALUE, SET_REMIXD_ACTIVATED, FETCH_PROXY_DEPLOYMENTS, NEW_PROXY_DEPLOYMENT, RESET_PROXY_DEPLOYMENTS } from '../constants'
import { ContractList, DeployOptions } from '../types'
export const fetchAccountsListRequest = () => {
return {
@ -302,16 +301,29 @@ export const setCurrentContract = (contractName: string) => {
}
}
export const setProxyEnvAddress = (key: string) => {
export const setRemixDActivated = (activated: boolean) => {
return {
payload: key,
type: SET_PROXY_ENV_ADDRESS
payload: activated,
type: SET_REMIXD_ACTIVATED
}
}
export const setRemixDActivated = (activated: boolean) => {
export const fetchProxyDeploymentsSuccess = (deployments: { address: string, date: string, contractName: string }[]) => {
return {
payload: activated,
type: SET_REMIXD_ACTIVATED
type: FETCH_PROXY_DEPLOYMENTS,
payload: deployments
}
}
export const newProxyDeployment = (deployment: { address: string, date: string, contractName: string }) => {
return {
type: NEW_PROXY_DEPLOYMENT,
payload: deployment
}
}
export const resetProxyDeployments = () => {
return {
type: RESET_PROXY_DEPLOYMENTS,
}
}

@ -30,14 +30,7 @@ export function AccountUI (props: AccountProps) {
})
break
case 'vm-london':
setPlusOpt({
classList: '',
title: 'Create a new account'
})
break
case 'vm-berlin':
case 'vm-merge':
setPlusOpt({
classList: '',
title: 'Create a new account'
@ -76,7 +69,7 @@ export function AccountUI (props: AccountProps) {
return props.tooltip('Account list is empty, please make sure the current provider is properly connected to remix')
}
if (props.selectExEnv !== 'vm-london' && props.selectExEnv !== 'vm-berlin' && props.selectExEnv !== 'injected') {
if (props.selectExEnv !== 'vm-merge' && props.selectExEnv !== 'injected') {
return props.modal('Passphrase to sign a message',
<PassphrasePrompt
message='Enter your passphrase for this account to sign the message'

@ -32,7 +32,7 @@ export function ContractDropdownUI (props: ContractDropdownProps) {
const [compilerName, setCompilerName] = useState<string>('')
const contractsRef = useRef<HTMLSelectElement>(null)
const atAddressValue = useRef<HTMLInputElement>(null)
const { contractList, loadType, currentFile, compilationSource, currentContract, compilationCount, deployOptions, proxyKey } = props.contracts
const { contractList, loadType, currentFile, compilationSource, currentContract, compilationCount, deployOptions } = props.contracts
useEffect(() => {
enableContractNames(Object.keys(props.contracts.contractList).length > 0)
@ -234,6 +234,10 @@ export function ContractDropdownUI (props: ContractDropdownProps) {
props.setSelectedContract(value)
}
const isValidProxyUpgrade = (proxyAddress: string) => {
return props.isValidProxyUpgrade(proxyAddress, loadedContractData.contractName || loadedContractData.name, loadedContractData.compiler.source, loadedContractData.compiler.data)
}
const checkSumWarning = () => {
return (
<span className="text-start">
@ -311,8 +315,10 @@ export function ContractDropdownUI (props: ContractDropdownProps) {
widthClass='w-50'
evmBC={loadedContractData.bytecodeObject}
lookupOnly={false}
savedProxyAddress={proxyKey}
proxy={props.proxy}
isValidProxyAddress={props.isValidProxyAddress}
isValidProxyUpgrade={isValidProxyUpgrade}
modal={props.modal}
/>
<div className="d-flex py-1 align-items-center custom-control custom-checkbox">
<input

@ -4,7 +4,8 @@ import { FormattedMessage, useIntl } from 'react-intl'
import * as remixLib from '@remix-project/remix-lib'
import { ContractGUIProps } from '../types'
import { CopyToClipboard } from '@remix-ui/clipboard'
import { CustomTooltip } from '@remix-ui/helper'
import { CustomTooltip, ProxyAddressToggle, ProxyDropdownMenu, shortenDate, shortenProxyAddress, unavailableProxyLayoutMsg, upgradeReportMsg } from '@remix-ui/helper'
import { Dropdown } from 'react-bootstrap'
const txFormat = remixLib.execution.txFormat
const txHelper = remixLib.execution.txHelper
@ -21,9 +22,9 @@ export function ContractGUI (props: ContractGUIProps) {
const [toggleDeployProxy, setToggleDeployProxy] = useState<boolean>(false)
const [toggleUpgradeImp, setToggleUpgradeImp] = useState<boolean>(false)
const [deployState, setDeployState] = useState<{ deploy: boolean, upgrade: boolean }>({ deploy: false, upgrade: false })
const [useLastProxy, setUseLastProxy] = useState<boolean>(false)
const [proxyAddress, setProxyAddress] = useState<string>('')
const [proxyAddressError, setProxyAddressError] = useState<string>('')
const [showDropdown, setShowDropdown] = useState<boolean>(false)
const multiFields = useRef<Array<HTMLInputElement | null>>([])
const initializeFields = useRef<Array<HTMLInputElement | null>>([])
const basicInputRef = useRef<HTMLInputElement>()
@ -171,13 +172,38 @@ export function ContractGUI (props: ContractGUIProps) {
}
}
const handleActionClick = () => {
const handleActionClick = async () => {
if (deployState.deploy) {
const proxyInitializeString = getMultiValsString(initializeFields.current)
props.clickCallBack(props.initializerOptions.inputs.inputs, proxyInitializeString, ['Deploy with Proxy'])
} else if (deployState.upgrade) {
!proxyAddressError && props.clickCallBack(props.funcABI.inputs, proxyAddress, ['Upgrade with Proxy'])
if (proxyAddress === '') {
setProxyAddressError('proxy address cannot be empty')
} else {
const isValidProxyAddress = await props.isValidProxyAddress(proxyAddress)
if (isValidProxyAddress) {
setProxyAddressError('')
const upgradeReport: any = await props.isValidProxyUpgrade(proxyAddress)
if (upgradeReport.ok) {
!proxyAddressError && props.clickCallBack(props.funcABI.inputs, proxyAddress, ['Upgrade with Proxy'])
} else {
if (upgradeReport.warning) {
props.modal('Proxy Upgrade Warning', unavailableProxyLayoutMsg(), 'Proceed', () => {
!proxyAddressError && props.clickCallBack(props.funcABI.inputs, proxyAddress, ['Upgrade with Proxy'])
}, 'Cancel', () => {}, 'btn-warning', 'btn-secondary')
} else {
props.modal('Proxy Upgrade Error', upgradeReportMsg(upgradeReport), 'Continue anyway ', () => {
!proxyAddressError && props.clickCallBack(props.funcABI.inputs, proxyAddress, ['Upgrade with Proxy'])
}, 'Cancel', () => {}, 'btn-warning', 'btn-secondary')
}
}
} else {
setProxyAddressError('not a valid contract address')
}
}
} else {
props.clickCallBack(props.funcABI.inputs, basicInput)
}
@ -217,43 +243,22 @@ export function ContractGUI (props: ContractGUIProps) {
setToggleUpgradeImp(value)
if (value) {
setToggleDeployProxy(false)
if (useLastProxy) setProxyAddress(props.savedProxyAddress)
}
setDeployState({ deploy: false, upgrade: value })
}
const handleUseLastProxySelect = (e) => {
const value = e.target.checked
const address = props.savedProxyAddress
if (value) {
if (address) {
setProxyAddress(address)
setProxyAddressError('')
} else {
setProxyAddressError('No proxy address available')
setProxyAddress('')
}
}
setUseLastProxy(value)
const switchProxyAddress = (address: string) => {
setProxyAddress(address)
}
const handleSetProxyAddress = (e) => {
const value = e.target.value
setProxyAddress(value)
const toggleDropdown = (isOpen: boolean) => {
setShowDropdown(isOpen)
}
const validateProxyAddress = async (address: string) => {
if (address === '') {
setProxyAddressError('proxy address cannot be empty')
} else {
if (await props.isValidProxyAddress(address)) {
setProxyAddressError('')
} else {
setProxyAddressError('not a valid contract address')
}
}
const handleAddressChange = (e: React.ChangeEvent<HTMLInputElement>) => {
const address = e.target.value
setProxyAddress(address)
}
return (
@ -270,21 +275,22 @@ export function ContractGUI (props: ContractGUIProps) {
className="udapp_contractActionsContainerSingle pt-2"
style={{ display: toggleContainer ? "none" : "flex" }}
>
<CustomTooltip
placement={"right-start"}
tooltipClasses="text-wrap"
tooltipId="remixUdappInstanceButtonTooltip"
tooltipText={buttonOptions.title}
>
<button
onClick={handleActionClick}
className={`udapp_instanceButton ${props.widthClass} btn btn-sm ${buttonOptions.classList}`}
data-id={buttonOptions.dataId}
data-title={buttonOptions.title}
disabled={toggleUpgradeImp && !proxyAddress}
>
{title}
<CustomTooltip
placement={"right-start"}
tooltipClasses="text-wrap"
tooltipId="remixUdappInstanceButtonTooltip"
tooltipText={toggleUpgradeImp && !proxyAddress ? 'Proxy address cannot be empty' : buttonOptions.title}
>
<div>{title}</div>
</CustomTooltip>
</button>
</CustomTooltip>
<CustomTooltip
placement={"right"}
tooltipClasses="text-nowrap"
@ -518,48 +524,44 @@ export function ContractGUI (props: ContractGUIProps) {
toggleUpgradeImp ? "d-flex" : "d-none"
}`}
>
<div className={`flex-column 'd-flex'}`}>
<div className="d-flex py-1 align-items-center custom-control custom-checkbox">
<input
id="proxyAddress"
data-id="contractGUIProxyAddress"
className="form-check-input custom-control-input"
type="checkbox"
onChange={handleUseLastProxySelect}
checked={useLastProxy}
/>
<CustomTooltip
tooltipText={<FormattedMessage id='udapp.proxyAddressTooltip' />}
tooltipId="proxyAddressTooltip"
placement="auto"
tooltipClasses="text-wrap"
>
<label
htmlFor="proxyAddress"
data-id="contractGUIProxyAddressLabel"
className="m-0 form-check-label custom-control-label udapp_checkboxAlign"
style={{ fontSize: 12 }}
>
<FormattedMessage id='udapp.useLastDeployedERC1967Contract' />
</label>
</CustomTooltip>
</div>
{
!useLastProxy ?
<div data-id="proxy-dropdown-items">
<Dropdown onToggle={toggleDropdown} show={showDropdown}>
<Dropdown.Toggle id="dropdown-custom-components" as={ProxyAddressToggle} address={proxyAddress} onChange={handleAddressChange} className="d-inline-block border border-dark bg-dark" />
{ props.proxy.deployments.length > 0 &&
<Dropdown.Menu as={ProxyDropdownMenu} className='w-100 custom-dropdown-items' style={{ overflow: 'hidden' }}>
{
props.proxy.deployments.map((deployment, index) => (
<CustomTooltip
placement={"right"}
tooltipClasses="text-nowrap"
tooltipId={`proxyAddressTooltip${index}`}
tooltipText={'Deployed ' + shortenDate(deployment.date)}
key={index}
>
<Dropdown.Item
key={index}
onClick={() => {
switchProxyAddress(deployment.address)
}}
data-id={`proxyAddress${index}`}
>
<span>{ proxyAddress === deployment.address ? <span>&#10003; { deployment.contractName + ' ' + shortenProxyAddress(deployment.address) } </span> : <span className="pl-3">{ deployment.contractName + ' ' + shortenProxyAddress(deployment.address) }</span> }</span>
</Dropdown.Item>
</CustomTooltip>
))
}
</Dropdown.Menu>
}
</Dropdown>
</div>
<div className='d-flex'>
<div className="mb-2">
<label className="mt-2 text-left d-block">
<FormattedMessage id='udapp.proxyAddressLabel' /> :
</label>
<CustomTooltip placement="right" tooltipText={<FormattedMessage id='udapp.proxyAddressInputTooltip' />}>
<input style={{ height: 32 }} className="form-control udapp_input" data-id="ERC1967AddressInput" placeholder={intl.formatMessage({ id: 'udapp.proxyAddressPlaceholder' })} onChange={handleSetProxyAddress} onBlur={() => validateProxyAddress(proxyAddress) } />
</CustomTooltip>
{ proxyAddressError && <span className='text-lowercase' data-id="errorMsgProxyAddress" style={{ fontSize: '.8em' }}>{ proxyAddressError }</span> }
</div> :
<span className='text-capitalize' data-id="lastDeployedERC1967Address" style={{ fontSize: '.8em' }}>{ proxyAddress || proxyAddressError }</span>
}
{ proxyAddressError && <span className='text-lowercase text-danger' data-id="errorMsgProxyAddress" style={{ fontSize: '.8em' }}>{ proxyAddressError }</span> }
</div>
</div>
</div>
</>
</>
) : null}
</div>
);

@ -44,7 +44,7 @@ export function InstanceContainerUI (props: InstanceContainerProps) {
{ instanceList.length > 0
? <div> { props.instances.instanceList.map((instance, index) => {
return <UniversalDappUI
key={instance.address}
key={index}
instance={instance}
context={props.getContext()}
removeInstance={props.removeInstance}

@ -44,5 +44,7 @@ export const ADD_DEPLOY_OPTION = 'ADD_DEPLOY_OPTION'
export const REMOVE_DEPLOY_OPTION = 'REMOVE_DEPLOY_OPTION'
export const SET_DEPLOY_OPTIONS = 'SET_DEPLOY_OPTIONS'
export const SET_CURRENT_CONTRACT = 'SET_CURRENT_CONTRACT'
export const SET_PROXY_ENV_ADDRESS = 'SET_PROXY_ENV_ADDRESS'
export const SET_REMIXD_ACTIVATED = 'SET_REMIXD_ACTIVATED'
export const FETCH_PROXY_DEPLOYMENTS = 'FETCH_PROXY_DEPLOYMENTS'
export const NEW_PROXY_DEPLOYMENT = 'NEW_PROXY_DEPLOYMENT'
export const RESET_PROXY_DEPLOYMENTS = 'RESET_PROXY_DEPLOYMENTS'

@ -362,8 +362,6 @@
}
.udapp_contractProperty button:disabled {
cursor: not-allowed;
background-color: white;
border-color: lightgray;
}
.udapp_contractProperty.udapp_constant button {
min-width: 100px;

@ -1,108 +1,12 @@
import { CompilerAbstract } from '@remix-project/remix-solidity'
import { ContractData } from '@remix-project/core-plugin'
import { DeployOptions } from '../types'
import { ADD_INSTANCE, ADD_PROVIDER, CLEAR_INSTANCES, CLEAR_RECORDER_COUNT, DISPLAY_NOTIFICATION, DISPLAY_POPUP_MESSAGE, FETCH_ACCOUNTS_LIST_FAILED, FETCH_ACCOUNTS_LIST_REQUEST, FETCH_ACCOUNTS_LIST_SUCCESS, FETCH_CONTRACT_LIST_FAILED, FETCH_CONTRACT_LIST_REQUEST, FETCH_CONTRACT_LIST_SUCCESS, FETCH_PROVIDER_LIST_FAILED, FETCH_PROVIDER_LIST_REQUEST, FETCH_PROVIDER_LIST_SUCCESS, HIDE_NOTIFICATION, HIDE_POPUP_MESSAGE, REMOVE_INSTANCE, REMOVE_PROVIDER, RESET_STATE, SET_BASE_FEE_PER_GAS, SET_CONFIRM_SETTINGS, SET_CURRENT_CONTRACT, SET_CURRENT_FILE, SET_DECODED_RESPONSE, SET_DEPLOY_OPTIONS, SET_EXECUTION_ENVIRONMENT, SET_EXTERNAL_WEB3_ENDPOINT, SET_GAS_LIMIT, SET_GAS_PRICE, SET_GAS_PRICE_STATUS, SET_IPFS_CHECKED_STATE, SET_LOAD_TYPE, SET_MATCH_PASSPHRASE, SET_MAX_FEE, SET_MAX_PRIORITY_FEE, SET_NETWORK_NAME, SET_PASSPHRASE, SET_PATH_TO_SCENARIO, SET_PERSONAL_MODE, SET_RECORDER_COUNT, SET_SELECTED_ACCOUNT, SET_SEND_UNIT, SET_SEND_VALUE, SET_PROXY_ENV_ADDRESS, ADD_DEPLOY_OPTION, REMOVE_DEPLOY_OPTION, SET_REMIXD_ACTIVATED } from '../constants'
import { ContractList, DeployOptions, RunTabState } from '../types'
import { ADD_INSTANCE, ADD_PROVIDER, CLEAR_INSTANCES, CLEAR_RECORDER_COUNT, DISPLAY_NOTIFICATION, DISPLAY_POPUP_MESSAGE, FETCH_ACCOUNTS_LIST_FAILED, FETCH_ACCOUNTS_LIST_REQUEST, FETCH_ACCOUNTS_LIST_SUCCESS, FETCH_CONTRACT_LIST_FAILED, FETCH_CONTRACT_LIST_REQUEST, FETCH_CONTRACT_LIST_SUCCESS, FETCH_PROVIDER_LIST_FAILED, FETCH_PROVIDER_LIST_REQUEST, FETCH_PROVIDER_LIST_SUCCESS, HIDE_NOTIFICATION, HIDE_POPUP_MESSAGE, REMOVE_INSTANCE, REMOVE_PROVIDER, RESET_STATE, SET_BASE_FEE_PER_GAS, SET_CONFIRM_SETTINGS, SET_CURRENT_CONTRACT, SET_CURRENT_FILE, SET_DECODED_RESPONSE, SET_DEPLOY_OPTIONS, SET_EXECUTION_ENVIRONMENT, SET_EXTERNAL_WEB3_ENDPOINT, SET_GAS_LIMIT, SET_GAS_PRICE, SET_GAS_PRICE_STATUS, SET_IPFS_CHECKED_STATE, SET_LOAD_TYPE, SET_MATCH_PASSPHRASE, SET_MAX_FEE, SET_MAX_PRIORITY_FEE, SET_NETWORK_NAME, SET_PASSPHRASE, SET_PATH_TO_SCENARIO, SET_PERSONAL_MODE, SET_RECORDER_COUNT, SET_SELECTED_ACCOUNT, SET_SEND_UNIT, SET_SEND_VALUE, ADD_DEPLOY_OPTION, REMOVE_DEPLOY_OPTION, SET_REMIXD_ACTIVATED, FETCH_PROXY_DEPLOYMENTS, NEW_PROXY_DEPLOYMENT, RESET_PROXY_DEPLOYMENTS } from '../constants'
declare const window: any
interface Action {
type: string
payload: any
}
export interface Contract {
name: string,
alias: string,
file: string,
compiler: CompilerAbstract,
compilerName: string
}
export interface ContractList {
[file: string]: Contract[]
}
export interface RunTabState {
accounts: {
loadedAccounts: Record<string, string>,
isRequesting: boolean,
isSuccessful: boolean,
error: string,
selectedAccount: string
},
sendValue: string,
sendUnit: 'ether' | 'finney' | 'gwei' | 'wei',
gasLimit: number,
selectExEnv: string,
personalMode: boolean,
networkName: string,
providers: {
providerList: {
id?: string,
dataId?: string,
title?: string,
value: string,
fork?: string
content: string
}[],
isRequesting: boolean,
isSuccessful: boolean,
error: string
},
notification: {
title: string,
message: string,
actionOk: () => void,
actionCancel: (() => void) | null,
labelOk: string,
labelCancel: string
},
externalEndpoint: string,
popup: string,
passphrase: string,
matchPassphrase: string,
contracts: {
contractList: {
[file: string]: {
name: string,
alias: string,
file: string,
compiler: CompilerAbstract
compilerName: string
}[]
},
deployOptions: { [file: string]: { [name: string]: DeployOptions } },
proxyKey: string,
loadType: 'abi' | 'sol' | 'other'
currentFile: string,
compilationSource: string,
currentContract: string,
compilationCount: number,
isRequesting: boolean,
isSuccessful: boolean,
error: string
},
ipfsChecked: boolean,
gasPriceStatus: boolean,
confirmSettings: boolean,
maxFee: string,
maxPriorityFee: string,
baseFeePerGas: string,
gasPrice: string,
instances: {
instanceList: {
contractData?: ContractData,
address: string,
balance?: number,
name: string,
decodedResponse?: Record<number, any>,
abi?: any
}[],
error: string
},
recorder: {
pathToScenario: string,
transactionCount: number
}
remixdActivated: boolean
}
export const runTabInitialState: RunTabState = {
accounts: {
@ -115,7 +19,7 @@ export const runTabInitialState: RunTabState = {
sendValue: '0',
sendUnit: 'wei',
gasLimit: 3000000,
selectExEnv: 'vm-london',
selectExEnv: 'vm-merge',
personalMode: false,
networkName: 'VM',
providers: {
@ -139,7 +43,6 @@ export const runTabInitialState: RunTabState = {
contracts: {
contractList: {},
deployOptions: {} as any,
proxyKey: '',
compilationSource: '',
loadType: 'other',
currentFile: '',
@ -164,7 +67,10 @@ export const runTabInitialState: RunTabState = {
pathToScenario: 'scenario.json',
transactionCount: 0
},
remixdActivated: false
remixdActivated: false,
proxy: {
deployments: []
}
}
type AddProvider = {
@ -263,7 +169,7 @@ export const runTabReducer = (state: RunTabState = runTabInitialState, action: A
return {
...state,
selectExEnv: payload,
networkName: state.selectExEnv === 'vm-london' || state.selectExEnv === 'vm-berlin' ? 'VM' : state.networkName,
networkName: state.selectExEnv === 'vm-merge' ? 'VM' : state.networkName,
accounts: {
...state.accounts,
selectedAccount: '',
@ -697,23 +603,45 @@ export const runTabReducer = (state: RunTabState = runTabInitialState, action: A
}
}
case SET_PROXY_ENV_ADDRESS: {
const payload: string = action.payload
case SET_REMIXD_ACTIVATED: {
const payload: boolean = action.payload
return {
...state,
remixdActivated: payload
}
}
case FETCH_PROXY_DEPLOYMENTS: {
const payload: { address: string, date: string, contractName: string }[] = action.payload
return {
...state,
contracts: {
...state.contracts,
proxyKey: payload
proxy: {
...state.proxy,
deployments: payload
}
}
}
case SET_REMIXD_ACTIVATED: {
const payload: boolean = action.payload
case NEW_PROXY_DEPLOYMENT: {
const payload: { address: string, date: string, contractName: string } = action.payload
return {
...state,
remixdActivated: payload
proxy: {
...state.proxy,
deployments: [...state.proxy.deployments, payload]
}
}
}
case RESET_PROXY_DEPLOYMENTS: {
return {
...state,
proxy: {
...state.proxy,
deployments: []
}
}
}

@ -27,7 +27,7 @@ import {
storeNewScenario, runScenario,
setScenarioPath, getFuncABIValues,
setNetworkName, updateSelectedContract,
syncContracts, isValidProxyAddress
syncContracts, isValidProxyAddress, isValidProxyUpgrade
} from './actions'
import './css/run-tab.css'
import { PublishToStorage } from '@remix-ui/publish-to-storage'
@ -81,7 +81,9 @@ export function RunTabUI (props: RunTabProps) {
okLabel: modals[0].okLabel,
okFn: modals[0].okFn,
cancelLabel: modals[0].cancelLabel,
cancelFn: modals[0].cancelFn
cancelFn: modals[0].cancelFn,
okBtnClass: modals[0].okBtnClass,
cancelBtnClass: modals[0].cancelBtnClass
}
return focusModal
})
@ -120,9 +122,9 @@ export function RunTabUI (props: RunTabProps) {
dispatch(setIpfsCheckedState(value))
}
const modal = (title: string, message: string | JSX.Element, okLabel: string, okFn: () => void, cancelLabel?: string, cancelFn?: () => void) => {
const modal = (title: string, message: string | JSX.Element, okLabel: string, okFn: () => void, cancelLabel?: string, cancelFn?: () => void, okBtnClass?: string, cancelBtnClass?: string) => {
setModals(modals => {
modals.push({ message, title, okLabel, okFn, cancelLabel, cancelFn })
modals.push({ message, title, okLabel, okFn, cancelLabel, cancelFn, okBtnClass, cancelBtnClass })
return [...modals]
})
}
@ -242,6 +244,8 @@ export function RunTabUI (props: RunTabProps) {
setSelectedContract={updateSelectedContract}
remixdActivated={runTab.remixdActivated}
isValidProxyAddress={isValidProxyAddress}
isValidProxyUpgrade={isValidProxyUpgrade}
proxy={runTab.proxy}
/>
<RecorderUI
gasEstimationPrompt={gasEstimationPrompt}

@ -10,8 +10,10 @@ export class Blockchain extends Plugin<any, any> {
txRunner: any;
networkcallid: number;
networkStatus: {
name: string;
id: string;
network: {
name: string;
id: string;
};
};
setupEvents(): void;
getCurrentNetworkStatus(): {

@ -1,12 +1,111 @@
import { Ref } from 'react'
import { CompilerAbstract } from '@remix-project/remix-solidity'
import { ContractData, FuncABI } from '@remix-project/core-plugin'
import { ContractList } from '../reducers/runTab'
import { RunTab } from './run-tab'
import { SolcInput, SolcOutput } from '@openzeppelin/upgrades-core'
import { LayoutCompatibilityReport } from '@openzeppelin/upgrades-core/dist/storage/report'
export interface RunTabProps {
plugin: RunTab
}
export interface Contract {
name: string,
alias: string,
file: string,
compiler: CompilerAbstract,
compilerName: string
}
export interface ContractList {
[file: string]: Contract[]
}
export interface RunTabState {
accounts: {
loadedAccounts: Record<string, string>,
isRequesting: boolean,
isSuccessful: boolean,
error: string,
selectedAccount: string
},
sendValue: string,
sendUnit: 'ether' | 'finney' | 'gwei' | 'wei',
gasLimit: number,
selectExEnv: string,
personalMode: boolean,
networkName: string,
providers: {
providerList: {
id?: string,
dataId?: string,
title?: string,
value: string,
fork?: string
content: string
}[],
isRequesting: boolean,
isSuccessful: boolean,
error: string
},
notification: {
title: string,
message: string,
actionOk: () => void,
actionCancel: (() => void) | null,
labelOk: string,
labelCancel: string
},
externalEndpoint: string,
popup: string,
passphrase: string,
matchPassphrase: string,
contracts: {
contractList: {
[file: string]: {
name: string,
alias: string,
file: string,
compiler: CompilerAbstract
compilerName: string
}[]
},
deployOptions: { [file: string]: { [name: string]: DeployOptions } },
loadType: 'abi' | 'sol' | 'other'
currentFile: string,
compilationSource: string,
currentContract: string,
compilationCount: number,
isRequesting: boolean,
isSuccessful: boolean,
error: string
},
ipfsChecked: boolean,
gasPriceStatus: boolean,
confirmSettings: boolean,
maxFee: string,
maxPriorityFee: string,
baseFeePerGas: string,
gasPrice: string,
instances: {
instanceList: {
contractData?: ContractData,
address: string,
balance?: number,
name: string,
decodedResponse?: Record<number, any>,
abi?: any
}[],
error: string
},
recorder: {
pathToScenario: string,
transactionCount: number
}
remixdActivated: boolean,
proxy: {
deployments: { address: string, date: string, contractName: string }[]
}
}
export interface SettingsProps {
selectExEnv: string,
accounts: {
@ -41,7 +140,7 @@ export interface SettingsProps {
createNewBlockchainAccount: (cbMessage: JSX.Element) => void,
setPassphrase: (passphrase: string) => void,
setMatchPassphrase: (passphrase: string) => void,
modal: (title: string, message: string | JSX.Element, okLabel: string, okFn: () => void, cancelLabel?: string, cancelFn?: () => void) => void,
modal: (title: string, message: string | JSX.Element, okLabel: string, okFn: () => void, cancelLabel?: string, cancelFn?: () => void, okBtnClass?: string, cancelBtnClass?: string) => void,
tooltip: (toasterMsg: string) => void,
signMessageWithAddress: (account: string, message: string, modalContent: (hash: string, data: string) => JSX.Element, passphrase?: string) => void,
passphrase: string,
@ -85,7 +184,7 @@ export interface AccountProps {
setPassphrase: (passphrase: string) => void,
setMatchPassphrase: (passphrase: string) => void,
tooltip: (toasterMsg: string) => void,
modal: (title: string, message: string | JSX.Element, okLabel: string, okFn: () => void, cancelLabel?: string, cancelFn?: () => void) => void,
modal: (title: string, message: string | JSX.Element, okLabel: string, okFn: () => void, cancelLabel?: string, cancelFn?: () => void, okBtnClass?: string, cancelBtnClass?: string) => void,
signMessageWithAddress: (account: string, message: string, modalContent: (hash: string, data: string) => JSX.Element, passphrase?: string) => void,
passphrase: string
}
@ -129,7 +228,6 @@ export interface ContractDropdownProps {
contracts: {
contractList: ContractList,
deployOptions: { [file: string]: { [name: string]: DeployOptions } },
proxyKey: string,
loadType: 'abi' | 'sol' | 'other',
currentFile: string,
compilationSource: string
@ -141,7 +239,7 @@ export interface ContractDropdownProps {
},
syncContracts: () => void,
getSelectedContract: (contractName: string, compiler: CompilerAbstract) => ContractData,
modal: (title: string, message: string | JSX.Element, okLabel: string, okFn: () => void, cancelLabel?: string, cancelFn?: () => void) => void,
modal: (title: string, message: string | JSX.Element, okLabel: string, okFn: () => void, cancelLabel?: string, cancelFn?: () => void, okBtnClass?: string, cancelBtnClass?: string) => void,
passphrase: string,
setPassphrase: (passphrase: string) => void,
createInstance: (
@ -166,7 +264,9 @@ export interface ContractDropdownProps {
setNetworkName: (name: string) => void,
setSelectedContract: (contractName: string) => void
remixdActivated: boolean,
isValidProxyAddress?: (address: string) => Promise<boolean>
isValidProxyAddress?: (address: string) => Promise<boolean>,
isValidProxyUpgrade?: (proxyAddress: string, contractName: string, solcInput: SolcInput, solcOuput: SolcOutput) => Promise<LayoutCompatibilityReport | { ok: boolean, pass: boolean, warning: boolean }>,
proxy: { deployments: { address: string, date: string, contractName: string }[] }
}
export interface RecorderProps {
@ -223,7 +323,9 @@ export interface Modal {
okLabel: string
okFn: () => void
cancelLabel: string
cancelFn: () => void
cancelFn: () => void,
okBtnClass?: string,
cancelBtnClass?: string
}
export type DeployMode = 'Deploy with Proxy' | 'Upgrade with Proxy'
@ -261,8 +363,10 @@ export interface ContractGUIProps {
isDeploy?: boolean,
deployOption?: { title: DeployMode, active: boolean }[],
initializerOptions?: DeployOption,
savedProxyAddress?: string,
isValidProxyAddress?: (address: string) => Promise<boolean>
proxy?: { deployments: { address: string, date: string, contractName: string }[] },
isValidProxyAddress?: (address: string) => Promise<boolean>,
isValidProxyUpgrade?: (proxyAddress: string) => Promise<LayoutCompatibilityReport | { ok: boolean, pass: boolean, warning: boolean }>,
modal?: (title: string, message: string | JSX.Element, okLabel: string, okFn: () => void, cancelLabel?: string, cancelFn?: () => void, okBtnClass?: string, cancelBtnClass?: string) => void
}
export interface MainnetProps {
network: Network,

@ -1,3 +1,9 @@
.remixui_default-message {
margin-top: 100px;
}
.remixui_no-shadow {
border-width: 1px;
border-style: solid;
border-color: var(--info);
}

@ -1,55 +1,29 @@
import { CustomTooltip } from '@remix-ui/helper'
import React, { Fragment, useEffect, useState } from 'react'
import { TransformComponent, TransformWrapper } from 'react-zoom-pan-pinch'
import { ISolidityUmlGen } from '../types'
import { ThemeSummary } from '../types'
import './css/solidity-uml-gen.css'
export interface RemixUiSolidityUmlGenProps {
plugin?: ISolidityUmlGen
updatedSvg?: string
loading?: boolean
themeSelected?: string
}
type ButtonAction = {
svgValid: () => boolean
action: () => void
buttonText: string
icon?: string
customcss?: string
themeName: string
themeCollection: ThemeSummary[]
}
interface ActionButtonsProps {
buttons: ButtonAction[]
actions: {
zoomIn: () => void,
zoomOut: () => void,
resetTransform: () => void
}
}
const ActionButtons = ({ buttons }: ActionButtonsProps) => (
<>
{buttons.map(btn => (
<CustomTooltip
key={btn.buttonText}
placement="top"
tooltipText={btn.buttonText}
tooltipId={btn.buttonText}
>
<button
key={btn.buttonText}
className={`btn btn-primary btn-sm rounded-circle ${btn.customcss}`}
disabled={!btn.svgValid}
onClick={btn.action}
>
<i className={btn.icon}></i>
</button>
</CustomTooltip>
))}
</>
)
export function RemixUiSolidityUmlGen ({ plugin, updatedSvg, loading, themeSelected }: RemixUiSolidityUmlGenProps) {
const [showViewer, setShowViewer] = useState(false)
const [svgPayload, setSVGPayload] = useState<string>('')
const [validSvg, setValidSvg] = useState(false)
export function RemixUiSolidityUmlGen ({ updatedSvg, loading }: RemixUiSolidityUmlGenProps) {
const [showViewer, setShowViewer] = useState(false)
const [validSvg, setValidSvg] = useState(false)
useEffect(() => {
setValidSvg (updatedSvg.startsWith('<?xml') && updatedSvg.includes('<svg'))
@ -57,20 +31,50 @@ export function RemixUiSolidityUmlGen ({ plugin, updatedSvg, loading, themeSelec
}
, [updatedSvg])
const buttons: ButtonAction[] = [
{
buttonText: 'Download as PDF',
svgValid: () => validSvg,
action: () => console.log('generated!!'),
icon: 'fa mr-1 pt-1 pb-1 fa-file'
},
{
buttonText: 'Download as PNG',
svgValid: () => validSvg,
action: () => console.log('generated!!'),
icon: 'fa fa-picture-o'
}
]
const encoder = new TextEncoder()
const data = encoder.encode(updatedSvg)
const final = btoa(String.fromCharCode.apply(null, data))
function ActionButtons({ actions: { zoomIn, zoomOut, resetTransform }}: ActionButtonsProps) {
return (
<>
<div
className="position-absolute bg-transparent mt-2"
id="buttons"
style={{ zIndex: 3, top: "10", right: "2em" }}
>
<div className="py-2 px-2 d-flex justify-content-center align-items-center">
<button
className="btn btn-outline-info d-none rounded-circle mr-2"
onClick={() => resetTransform()}
>
<i className="far fa-arrow-to-bottom align-item-center d-flex justify-content-center"></i>
</button>
<button
className="badge badge-info remixui_no-shadow p-2 rounded-circle mr-2"
onClick={() => zoomIn()}
>
<i className="far fa-plus "></i>
</button>
<button
className="badge badge-info remixui_no-shadow p-2 rounded-circle mr-2"
onClick={() => zoomOut()}
>
<i className="far fa-minus align-item-center d-flex justify-content-center"></i>
</button>
<button
className="badge badge-info remixui_no-shadow p-2 rounded-circle mr-2"
onClick={() => resetTransform()}
>
<i className="far fa-undo align-item-center d-flex justify-content-center"></i>
</button>
</div>
</div>
</>
)
}
const DefaultInfo = () => (
<div className="d-flex flex-column justify-content-center align-items-center mt-5">
@ -80,10 +84,8 @@ export function RemixUiSolidityUmlGen ({ plugin, updatedSvg, loading, themeSelec
</div>
)
const Display = () => {
const invert = themeSelected === 'dark' ? 'invert(0.8)' : 'invert(0)'
return (
<div className="d-flex flex-column justify-content-center align-items-center">
<div id="umlImageHolder" className="w-100 px-2 py-2">
<div id="umlImageHolder" className="w-100 px-2 py-2 d-flex">
{ validSvg && showViewer ? (
<TransformWrapper
initialScale={1}
@ -91,14 +93,16 @@ export function RemixUiSolidityUmlGen ({ plugin, updatedSvg, loading, themeSelec
{
({ zoomIn, zoomOut, resetTransform }) => (
<Fragment>
<TransformComponent>
<img
src={`data:image/svg+xml;base64,${btoa(plugin.updatedSvg ?? svgPayload)}`}
<ActionButtons actions={{ zoomIn, zoomOut, resetTransform }} />
<TransformComponent contentStyle={{ zIndex: 2 }}>
<img
id="umlImage"
src={`data:image/svg+xml;base64,${final}`}
width={'100%'}
height={'auto'}
style={{ filter: invert }}
className="position-relative"
/>
</TransformComponent>
</TransformComponent>
</Fragment>
)
}
@ -107,7 +111,6 @@ export function RemixUiSolidityUmlGen ({ plugin, updatedSvg, loading, themeSelec
<i className="fas fa-spinner fa-spin fa-4x"></i>
</div> : <DefaultInfo />}
</div>
</div>
)}
return (<>
{ <Display /> }

@ -1,4 +1,5 @@
import { ViewPlugin } from '@remixproject/engine-web'
import { customAction } from '@remixproject/plugin-api'
import React from 'react'
export interface ISolidityUmlGen extends ViewPlugin {
@ -6,9 +7,22 @@ export interface ISolidityUmlGen extends ViewPlugin {
currentFile: string
svgPayload: string
updatedSvg: string
currentlySelectedTheme: string
themeName: string
loading: boolean
themeCollection: ThemeSummary[]
showUmlDiagram(path: string, svgPayload: string): void
updateComponent(state: any): JSX.Element
setDispatch(dispatch: React.Dispatch<any>): void
mangleSvgPayload(svgPayload: string) : Promise<string>
generateCustomAction(action: customAction): Promise<void>
flattenContract (source: any, filePath: string, data: any): Promise<string>
hideSpinner(): void
renderComponent (): void
render(): JSX.Element
}
}
export type ThemeQualityType = { name: string, quality: 'light' | 'dark', url: string }
export type ThemeSummary = { themeName: string, backgroundColor: string, actualHex: string }

@ -112,16 +112,22 @@ export const listenOnProviderEvents = (provider) => (reducerDispatch: React.Disp
const config = plugin.registry.get('config').api
const editor = plugin.registry.get('editor').api
if (config.get('currentFile') === path && editor.currentContent() !== content) {
if (provider.isReadOnly(path)) return editor.setText(content)
if (editor.getText(path) === content) return
if (provider.isReadOnly(path)) return editor.setText(path, content)
if (config.get('currentFile') === path) {
// if it's the current file and the content is different:
dispatch(displayNotification(
path + ' changed',
'This file has been changed outside of Remix IDE.',
'Replace by the new content', 'Keep the content displayed in Remix',
() => {
editor.setText(content)
editor.setText(path, content)
}
))
} else {
// this isn't the current file, we can silently update the model
editor.setText(path, content)
}
})

@ -311,6 +311,14 @@ export const deleteWorkspace = async (workspaceName: string, cb?: (err: Error, r
cb && cb(null, workspaceName)
}
export const deleteAllWorkspaces = async () => {
await (await getWorkspaces()).map(async workspace => {
await deleteWorkspaceFromProvider(workspace.name)
await dispatch(setDeleteWorkspace(workspace.name))
plugin.workspaceDeleted(workspace.name)
})
}
const deleteWorkspaceFromProvider = async (workspaceName: string) => {
const workspacesPath = plugin.fileProviders.workspace.workspacesPath
@ -374,7 +382,7 @@ export const uploadFile = async (target, targetFolder: string, cb?: (err: Error,
const editor = plugin.registry.get('editor').api
if ((config.get('currentFile') === name) && (editor.currentContent() !== event.target.result)) {
editor.setText(event.target.result)
editor.setText(name, event.target.result)
}
}
fileReader.readAsText(file)
@ -450,7 +458,6 @@ export const cloneRepository = async (url: string) => {
if (!isActive) await plugin.call('manager', 'activatePlugin', 'dgit')
await fetchWorkspaceDirectory(ROOT_PATH)
const workspacesPath = plugin.fileProviders.workspace.workspacesPath
console.log('go in to promise')
const branches = await getGitRepoBranches(workspacesPath + '/' + repoName)
dispatch(setCurrentWorkspaceBranches(branches))
@ -481,7 +488,6 @@ export const cloneRepository = async (url: string) => {
}
}
export const checkGit = async () => {
const isGitRepo = await plugin.fileManager.isGitRepo()
dispatch(setCurrentWorkspaceIsGitRepo(isGitRepo))

@ -0,0 +1,51 @@
import React from 'react'
import { CustomTooltip } from '@remix-ui/helper'
import { Dropdown } from 'react-bootstrap'
import { FormattedMessage } from 'react-intl'
const _paq = window._paq = window._paq || []
export interface HamburgerMenuItemProps {
hideOption: boolean
kind: string
actionOnClick: () => void
fa: string
}
export function HamburgerMenuItem (props: HamburgerMenuItemProps) {
const { hideOption } = props
const uid = 'workspace' + props.kind
return (
<>
<Dropdown.Item>
<CustomTooltip
placement="right-start"
tooltipId={uid + "Tooltip"}
tooltipClasses="text-nowrap"
tooltipText={<FormattedMessage id={'filePanel.workspace.' + props.kind} />}
>
<div
data-id={uid}
key={uid + '-fe-ws'}
onClick={() => {
props.actionOnClick()
_paq.push(['trackEvent', 'fileExplorer', 'workspaceMenu', {uid}])
}}
>
<span
hidden={hideOption}
id={uid}
data-id={uid}
className={props.fa + ' pl-2'}
style={{width: '1.4rem'}}
>
</span>
<span className="px-2">
<FormattedMessage id={'filePanel.' + props.kind } />
</span>
</div>
</CustomTooltip>
</Dropdown.Item>
</>
)
}

@ -1,13 +1,11 @@
import React from 'react'
import { CustomTooltip } from '@remix-ui/helper'
import { Dropdown } from 'react-bootstrap'
import { FormattedMessage } from 'react-intl'
const _paq = window._paq = window._paq || []
import { HamburgerMenuItem } from './workspace-hamburger-item'
export interface HamburgerMenuProps {
createWorkspace: () => void,
deleteCurrentWorkspace: () => void,
deleteAllWorkspaces: () => void,
renameCurrentWorkspace: () => void,
cloneGitRepository: () => void,
downloadWorkspaces: () => void,
@ -23,294 +21,51 @@ export interface HamburgerMenuProps {
export function HamburgerMenu (props: HamburgerMenuProps) {
const { showIconsMenu, hideWorkspaceOptions, hideLocalhostOptions } = props
return (
<>
<Dropdown.Item>
<CustomTooltip
placement="right"
tooltipId="createWorkspaceTooltip"
tooltipClasses="text-nowrap"
tooltipText={<FormattedMessage id='filePanel.workspace.create' />}
>
<div
data-id='workspaceCreate'
onClick={() => {
props.createWorkspace()
_paq.push(['trackEvent', 'fileExplorer', 'workspaceMenu', 'workspaceCreate'])
props.hideIconsMenu(!showIconsMenu)
}}
key={`workspacesCreate-fe-ws`}
>
<span
hidden={hideWorkspaceOptions}
id='workspaceCreate'
data-id='workspaceCreate'
onClick={() => {
props.createWorkspace()
_paq.push(['trackEvent', 'fileExplorer', 'workspaceMenu', 'workspaceCreate'])
props.hideIconsMenu(!showIconsMenu)
}}
className='far fa-plus pl-2'
>
</span>
<span className="pl-3"><FormattedMessage id='filePanel.create' /></span>
</div>
</CustomTooltip>
</Dropdown.Item>
<Dropdown.Item>
<CustomTooltip
placement="right-start"
tooltipId="createWorkspaceTooltip"
tooltipClasses="text-nowrap"
tooltipText={<FormattedMessage id='filePanel.workspace.delete' />}
>
<div
data-id='workspaceDelete'
onClick={() => {
props.deleteCurrentWorkspace()
_paq.push(['trackEvent', 'fileExplorer', 'workspaceMenu', 'workspaceDelete'])
props.hideIconsMenu(!showIconsMenu)
}}
key={`workspacesDelete-fe-ws`}
>
<span
hidden={ hideWorkspaceOptions || hideLocalhostOptions }
id='workspaceDelete'
data-id='workspaceDelete'
onClick={() => {
props.deleteCurrentWorkspace()
_paq.push(['trackEvent', 'fileExplorer', 'workspaceMenu', 'workspaceDelete'])
props.hideIconsMenu(!showIconsMenu)
}}
className='far fa-trash pl-2'
>
</span>
<span className="pl-3"><FormattedMessage id='filePanel.delete' /></span>
</div>
</CustomTooltip>
</Dropdown.Item>
<Dropdown.Item>
<CustomTooltip
placement='right-start'
tooltipClasses="text-nowrap"
tooltipId="workspaceRenametooltip"
tooltipText={<FormattedMessage id='filePanel.workspace.rename' />}
>
<div onClick={() => {
props.renameCurrentWorkspace()
_paq.push(['trackEvent', 'fileExplorer', 'workspaceMenu', 'workspaceRename'])
props.hideIconsMenu(!showIconsMenu)
}}
data-id='workspaceRename'
key={`workspacesRename-fe-ws`}
>
<span
hidden={ hideWorkspaceOptions || hideLocalhostOptions }
id='workspaceRename'
data-id='workspaceRename'
onClick={() => {
props.renameCurrentWorkspace()
_paq.push(['trackEvent', 'fileExplorer', 'workspaceMenu', 'workspaceRename'])
props.hideIconsMenu(!showIconsMenu)
}}
className='far fa-edit pl-2'>
</span>
<span className="pl-3"><FormattedMessage id='filePanel.rename' /></span>
</div>
</CustomTooltip>
</Dropdown.Item>
<Dropdown.Item><Dropdown.Divider className="border mb-0 mt-0" /></Dropdown.Item>
<Dropdown.Item>
<CustomTooltip
placement="right-start"
tooltipId="cloneWorkspaceTooltip"
tooltipClasses="text-nowrap"
tooltipText={<FormattedMessage id='filePanel.workspace.clone' />}
>
<div
data-id='cloneGitRepository'
onClick={() => {
props.cloneGitRepository()
_paq.push(['trackEvent', 'fileExplorer', 'workspaceMenu', 'cloneGitRepository'])
props.hideIconsMenu(!showIconsMenu)
}}
key={`cloneGitRepository-fe-ws`}
>
<span
hidden={ hideWorkspaceOptions }
id='cloneGitRepository'
data-id='cloneGitRepository'
onClick={() => {
props.cloneGitRepository()
_paq.push(['trackEvent', 'fileExplorer', 'workspaceMenu', 'cloneGitRepository'])
props.hideIconsMenu(!showIconsMenu)
}}
className='fab fa-github pl-2'
>
</span>
<span className="pl-3"><FormattedMessage id='filePanel.clone' /></span>
</div>
</CustomTooltip>
</Dropdown.Item>
<Dropdown.Item><Dropdown.Divider className="border mt-0 mb-0 remixui_menuhr" style={{ pointerEvents: 'none' }}/></Dropdown.Item>
<Dropdown.Item>
<CustomTooltip
placement="right-start"
tooltipId="createWorkspaceTooltip"
tooltipClasses="text-nowrap"
tooltipText={<FormattedMessage id='filePanel.workspace.download' />}
>
<div
data-id='workspacesDownload'
onClick={() => {
props.downloadWorkspaces()
_paq.push(['trackEvent', 'fileExplorer', 'workspaceMenu', 'workspacesDownload'])
props.hideIconsMenu(!showIconsMenu)
}}
key={`workspacesDownload-fe-ws`}
>
<span
hidden={ hideWorkspaceOptions || hideLocalhostOptions }
id='workspacesDownload'
data-id='workspacesDownload'
onClick={() => {
props.downloadWorkspaces()
_paq.push(['trackEvent', 'fileExplorer', 'workspaceMenu', 'workspacesDownload'])
props.hideIconsMenu(!showIconsMenu)
}}
className='far fa-download pl-2 '
>
</span>
<span className="pl-3"><FormattedMessage id='filePanel.download' /></span>
</div>
</CustomTooltip>
</Dropdown.Item>
<Dropdown.Item>
<CustomTooltip
placement="right-start"
tooltipId="createWorkspaceTooltip"
tooltipClasses="text-nowrap"
tooltipText={<FormattedMessage id='filePanel.workspace.restore' />}
>
<div
data-id='workspacesRestore'
onClick={() => {
props.restoreBackup()
_paq.push(['trackEvent', 'fileExplorer', 'workspaceMenu', 'workspacesRestore'])
props.hideIconsMenu(!showIconsMenu)
}}
key={`workspacesRestore-fe-ws`}
>
<span
hidden={ hideWorkspaceOptions }
id='workspacesRestore'
data-id='workspacesRestore'
onClick={() => {
props.restoreBackup()
_paq.push(['trackEvent', 'fileExplorer', 'workspaceMenu', 'workspacesRestore'])
props.hideIconsMenu(!showIconsMenu)
}}
className='far fa-upload pl-2'
>
</span>
<span className="pl-3"><FormattedMessage id='filePanel.restore' /></span>
</div>
</CustomTooltip>
</Dropdown.Item>
<Dropdown.Item><Dropdown.Divider className="border mt-0 mb-0 remixui_menuhr" style={{ pointerEvents: 'none' }}/></Dropdown.Item>
<Dropdown.Item>
<CustomTooltip
placement="right-start"
tooltipId="createSolGHActionTooltip"
tooltipClasses="text-nowrap"
tooltipText={<FormattedMessage id='filePanel.workspace.solghaction' />}
>
<div
data-id='soliditygithubaction'
onClick={() => {
props.addGithubAction()
_paq.push(['trackEvent', 'fileExplorer', 'workspaceMenu', 'addSolidityTesting'])
props.hideIconsMenu(!showIconsMenu)
}}
>
<span
hidden={ hideWorkspaceOptions }
id='soliditygithubaction'
data-id='soliditygithubaction'
onClick={() => {
props.addGithubAction()
_paq.push(['trackEvent', 'fileExplorer', 'workspaceMenu', 'addSolidityTesting'])
props.hideIconsMenu(!showIconsMenu)
}}
className='fak fa-solidity-mono pl-2'
>
</span>
<span className="pl-3">{<FormattedMessage id='filePanel.solghaction' />}</span>
</div>
</CustomTooltip>
</Dropdown.Item>
<Dropdown.Item>
<CustomTooltip
placement="right-start"
tooltipId="createTsSolTestGHActionTooltip"
tooltipClasses="text-nowrap"
tooltipText={<FormattedMessage id='filePanel.workspace.tssoltestghaction' />}
>
<div
data-id='typescriptsoliditygithubtestaction'
onClick={() => {
props.addTsSolTestGithubAction()
_paq.push(['trackEvent', 'fileExplorer', 'workspaceMenu', 'addTsSolTestingAction'])
props.hideIconsMenu(!showIconsMenu)
}}
>
<span
hidden={ hideWorkspaceOptions }
id='tssoliditygithubaction'
data-id='tssoliditygithubaction'
onClick={() => {
props.addTsSolTestGithubAction()
_paq.push(['trackEvent', 'fileExplorer', 'workspaceMenu', 'addTsSolTestingAction'])
props.hideIconsMenu(!showIconsMenu)
}}
className='fab fa-js pl-2'
>
</span>
<span className="pl-3">{<FormattedMessage id='filePanel.tssoltestghaction' />}</span>
</div>
</CustomTooltip>
</Dropdown.Item>
<Dropdown.Item>
<CustomTooltip
placement="right-start"
tooltipId="createSlitherGHActionTooltip"
tooltipClasses="text-nowrap"
tooltipText={<FormattedMessage id='filePanel.workspace.slitherghaction' />}
>
<div
data-id='slithergithubtestaction'
onClick={() => {
props.addSlitherGithubAction()
_paq.push(['trackEvent', 'fileExplorer', 'workspaceMenu', 'addSlitherAction'])
props.hideIconsMenu(!showIconsMenu)
}}
>
<span
hidden={ hideWorkspaceOptions }
id='slithergithubaction'
data-id='slithergithubaction'
onClick={() => {
props.addSlitherGithubAction()
_paq.push(['trackEvent', 'fileExplorer', 'workspaceMenu', 'addSlitherAction'])
props.hideIconsMenu(!showIconsMenu)
}}
className='far fa-shield pl-2'
>
</span>
<span className="pl-3">{<FormattedMessage id='filePanel.slitherghaction' />}</span>
</div>
</CustomTooltip>
</Dropdown.Item>
</>
)
}
return (
<>
<HamburgerMenuItem kind='create' fa='far fa-plus' hideOption={hideWorkspaceOptions} actionOnClick={() => {
props.createWorkspace()
props.hideIconsMenu(!showIconsMenu)
}}></HamburgerMenuItem>
<HamburgerMenuItem kind='delete' fa='far fa-trash' hideOption={hideWorkspaceOptions || hideLocalhostOptions} actionOnClick={() => {
props.deleteCurrentWorkspace()
props.hideIconsMenu(!showIconsMenu)
}}></HamburgerMenuItem>
<HamburgerMenuItem kind='deleteAll' fa='far fa-trash-alt' hideOption={hideWorkspaceOptions || hideLocalhostOptions} actionOnClick={() => {
props.deleteAllWorkspaces()
props.hideIconsMenu(!showIconsMenu)
}}></HamburgerMenuItem>
<HamburgerMenuItem kind='rename' fa='far fa-edit' hideOption={hideWorkspaceOptions || hideLocalhostOptions} actionOnClick={() => {
props.renameCurrentWorkspace()
props.hideIconsMenu(!showIconsMenu)
}}></HamburgerMenuItem>
<Dropdown.Divider className="border mb-0 mt-0 remixui_menuhr" style={{ pointerEvents: 'none' }} />
<HamburgerMenuItem kind='clone' fa='fab fa-github' hideOption={hideWorkspaceOptions} actionOnClick={() => {
props.cloneGitRepository()
props.hideIconsMenu(!showIconsMenu)
}}></HamburgerMenuItem>
<Dropdown.Divider className="border mt-0 mb-0 remixui_menuhr" style={{ pointerEvents: 'none' }}/>
<HamburgerMenuItem kind='download' fa='far fa-download' hideOption={hideWorkspaceOptions || hideLocalhostOptions} actionOnClick={() => {
props.downloadWorkspaces()
props.hideIconsMenu(!showIconsMenu)
}}></HamburgerMenuItem>
<HamburgerMenuItem kind='restore' fa='far fa-upload' hideOption={hideWorkspaceOptions} actionOnClick={() => {
props.restoreBackup()
props.hideIconsMenu(!showIconsMenu)
}}></HamburgerMenuItem>
<Dropdown.Divider className="border mt-0 mb-0 remixui_menuhr" style={{ pointerEvents: 'none' }}/>
<HamburgerMenuItem kind='solghaction' fa='fak fa-solidity-mono' hideOption={hideWorkspaceOptions} actionOnClick={() => {
props.addGithubAction()
props.hideIconsMenu(!showIconsMenu)
}}></HamburgerMenuItem>
<HamburgerMenuItem kind='tssoltestghaction' fa='fab fa-js' hideOption={hideWorkspaceOptions} actionOnClick={() => {
props.addTsSolTestGithubAction()
props.hideIconsMenu(!showIconsMenu)
}}></HamburgerMenuItem>
<HamburgerMenuItem kind='slitherghaction' fa='far fa-shield' hideOption={hideWorkspaceOptions} actionOnClick={() => {
props.addSlitherGithubAction()
props.hideIconsMenu(!showIconsMenu)
}}></HamburgerMenuItem>
</>
)
}

@ -15,6 +15,7 @@ export const FileSystemContext = createContext<{
dispatchSwitchToWorkspace: (name: string) => Promise<void>,
dispatchRenameWorkspace: (oldName: string, workspaceName: string) => Promise<void>,
dispatchDeleteWorkspace: (workspaceName: string) => Promise<void>,
dispatchDeleteAllWorkspaces: () => Promise<void>,
dispatchPublishToGist: (path?: string, type?: string) => Promise<void>,
dispatchUploadFile: (target?: SyntheticEvent, targetFolder?: string) => Promise<void>,
dispatchCreateNewFile: (path: string, rootDir: string) => Promise<void>,

@ -115,21 +115,8 @@
min-width: 8rem;
}
.remixui_menuhr{
}
#workspacesMenuDropdown>div>ul>a:nth-child(6):hover {
background-color: rgba(0,0,0,0)
}
#workspacesMenuDropdown>div>ul>a:nth-child(4):hover {
background-color: rgba(0, 0, 0, 0)
}
#workspacesMenuDropdown > div > ul > a:hover {
background-color: var(--secondary);
/* border: 1px solid var(--secondary); */
border-radius: 2px;
color: var(--text)
}

@ -5,7 +5,7 @@ import { Toaster } from '@remix-ui/toaster' // eslint-disable-line
// eslint-disable-next-line @typescript-eslint/no-unused-vars
import { FileSystemContext } from '../contexts'
import { browserReducer, browserInitialState } from '../reducers/workspace'
import { initWorkspace, fetchDirectory, removeInputField, deleteWorkspace, clearPopUp, publishToGist, createNewFile, setFocusElement, createNewFolder,
import { initWorkspace, fetchDirectory, removeInputField, deleteWorkspace, deleteAllWorkspaces, clearPopUp, publishToGist, createNewFile, setFocusElement, createNewFolder,
deletePath, renamePath, downloadPath, copyFile, copyFolder, runScript, emitContextMenuEvent, handleClickFile, handleExpandPath, addInputField, createWorkspace,
fetchWorkspaceDirectory, renameWorkspace, switchToWorkspace, uploadFile, handleDownloadFiles, restoreBackupZip, cloneRepository, moveFile, moveFolder,
showAllBranches, switchBranch, createNewBranch, checkoutRemoteBranch, createSolidityGithubAction, createTsSolGithubAction, createSlitherGithubAction
@ -67,6 +67,10 @@ export const FileSystemProvider = (props: WorkspaceProps) => {
await deleteWorkspace(workspaceName)
}
const dispatchDeleteAllWorkspaces = async () => {
await deleteAllWorkspaces()
}
const dispatchPublishToGist = async (path?: string, type?: string) => {
await publishToGist(path, type)
}
@ -258,6 +262,7 @@ export const FileSystemProvider = (props: WorkspaceProps) => {
dispatchSwitchToWorkspace,
dispatchRenameWorkspace,
dispatchDeleteWorkspace,
dispatchDeleteAllWorkspaces,
dispatchPublishToGist,
dispatchUploadFile,
dispatchCreateNewFile,

@ -90,6 +90,16 @@ export function Workspace () {
)
}
const deleteAllWorkspaces = () => {
global.modal(
intl.formatMessage({ id: 'filePanel.workspace.deleteAll' }),
intl.formatMessage({ id: 'filePanel.workspace.deleteAllConfirm' }),
intl.formatMessage({ id: 'filePanel.ok' }),
onFinishDeleteAllWorkspaces,
''
)
}
const cloneGitRepository = () => {
global.modal(
intl.formatMessage({ id: 'filePanel.workspace.clone' }),
@ -175,7 +185,15 @@ export function Workspace () {
console.error(e)
}
}
/** ** ****/
const onFinishDeleteAllWorkspaces = async () => {
try {
await global.dispatchDeleteAllWorkspaces()
} catch (e) {
global.modal(intl.formatMessage({ id: 'filePanel.workspace.deleteAll' }), e.message, intl.formatMessage({ id: 'filePanel.ok' }), () => {}, '')
console.error(e)
}
}
const resetFocus = () => {
global.dispatchSetFocusElement([{ key: '', type: 'folder' }])
@ -433,6 +451,7 @@ export function Workspace () {
<HamburgerMenu
createWorkspace={createWorkspace}
deleteCurrentWorkspace={deleteCurrentWorkspace}
deleteAllWorkspaces={deleteAllWorkspaces}
renameCurrentWorkspace={renameCurrentWorkspace}
cloneGitRepository={cloneGitRepository}
downloadWorkspaces={downloadWorkspaces}
@ -451,7 +470,12 @@ export function Workspace () {
</div>
<Dropdown id="workspacesSelect" data-id="workspacesSelect" onToggle={toggleDropdown} show={showDropdown}>
<Dropdown.Toggle as={CustomToggle} id="dropdown-custom-components" className="btn btn-light btn-block w-100 d-inline-block border border-dark form-control mt-1" icon={selectedWorkspace && selectedWorkspace.isGitRepo && !(currentWorkspace === LOCALHOST) ? 'far fa-code-branch' : null}>
<Dropdown.Toggle
as={CustomToggle}
id="dropdown-custom-components"
className="btn btn-light btn-block w-100 d-inline-block border border-dark form-control mt-1"
icon={selectedWorkspace && selectedWorkspace.isGitRepo && !(currentWorkspace === LOCALHOST) ? 'far fa-code-branch' : null}
>
{ selectedWorkspace ? selectedWorkspace.name : currentWorkspace === LOCALHOST ? formatNameForReadonly("localhost") : NO_WORKSPACE }
</Dropdown.Toggle>

@ -27,7 +27,7 @@ export const contextMenuActions: MenuItems = [{
},{
id: 'download',
name: 'Download',
type: ['file'],
type: ['file', 'folder'],
multiselect: false,
label: ''
}, {

@ -23,13 +23,17 @@ interface HandlerResponse {
cleanUrl: string
}
export type getPackages = () => Promise<{ [name: string]: string }>
export class RemixURLResolver {
private previouslyHandled: PreviouslyHandledImports
gistAccessToken: string
protocol: string
getDependencies: getPackages
constructor (gistToken?: string, protocol = 'http:') {
constructor (getDependencies?: getPackages, gistToken?: string, protocol = 'http:') {
this.previouslyHandled = {}
this.getDependencies = getDependencies
this.setGistToken(gistToken, protocol)
}
@ -38,12 +42,16 @@ export class RemixURLResolver {
this.protocol = protocol
}
clearCache () {
this.previouslyHandled = {}
}
/**
* Handle an import statement based on github
* @param root The root of the github import statement
* @param filePath path of the file in github
*/
async handleGithubCall (root: string, filePath: string): Promise<HandlerResponse> {
async handleGithubCall(root: string, filePath: string): Promise<HandlerResponse> {
const regex = filePath.match(/blob\/([^/]+)\/(.*)/)
let reference = 'master'
if (regex) {
@ -67,7 +75,7 @@ export class RemixURLResolver {
* @param url The url of the import statement
* @param cleanUrl
*/
async handleHttp (url: string, cleanUrl: string): Promise<HandlerResponse> {
async handleHttp(url: string, cleanUrl: string): Promise<HandlerResponse> {
// eslint-disable-next-line no-useless-catch
try {
const response: AxiosResponse = await axios.get(url, { transformResponse: [] })
@ -82,7 +90,7 @@ export class RemixURLResolver {
* @param url The url of the import statement
* @param cleanUrl
*/
async handleHttps (url: string, cleanUrl: string): Promise<HandlerResponse> {
async handleHttps(url: string, cleanUrl: string): Promise<HandlerResponse> {
// eslint-disable-next-line no-useless-catch
try {
const response: AxiosResponse = await axios.get(url, { transformResponse: [] })
@ -92,7 +100,7 @@ export class RemixURLResolver {
}
}
async handleSwarm (url: string, cleanUrl: string): Promise<HandlerResponse> {
async handleSwarm(url: string, cleanUrl: string): Promise<HandlerResponse> {
// eslint-disable-next-line no-useless-catch
try {
const bzz = new Bzz({ url: this.protocol + '//swarm-gateways.net' })
@ -108,7 +116,7 @@ export class RemixURLResolver {
* Handle an import statement based on IPFS
* @param url The url of the IPFS import statement
*/
async handleIPFS (url: string): Promise<HandlerResponse> {
async handleIPFS(url: string): Promise<HandlerResponse> {
// replace ipfs:// with /ipfs/
url = url.replace(/^ipfs:\/\/?/, 'ipfs/')
// eslint-disable-next-line no-useless-catch
@ -127,16 +135,68 @@ export class RemixURLResolver {
* Handle an import statement based on NPM
* @param url The url of the NPM import statement
*/
async handleNpmImport (url: string): Promise<HandlerResponse> {
// eslint-disable-next-line no-useless-catch
try {
const req = 'https://unpkg.com/' + url
const response: AxiosResponse = await axios.get(req, { transformResponse: [] })
return { content: response.data, cleanUrl: url }
} catch (e) {
throw e
}
async handleNpmImport(url: string): Promise<HandlerResponse> {
if (this.getDependencies) {
try {
const { deps, yarnLock, packageLock } = await this.getDependencies()
let matchLength = 0
let pkg
if (deps) {
Object.keys(deps).map((dep) => {
const reg = new RegExp(dep, 'g')
const match = url.match(reg)
if (match && match.length > 0 && matchLength < match[0].length) {
matchLength = match[0].length
pkg = dep
}
})
if (pkg) {
let version
if (yarnLock) {
// yarn.lock
const regex = new RegExp(`"${pkg}@(.*)"`, 'g')
const yarnVersion = regex.exec(yarnLock)
if (yarnVersion && yarnVersion.length > 1) {
version = yarnVersion[1]
}
}
if (!version && packageLock && packageLock['dependencies'] && packageLock['dependencies'][pkg] && packageLock['dependencies'][pkg]['version']) {
// package-lock.json
version = packageLock['dependencies'][pkg]['version']
}
if (!version) {
// package.json
version = deps[pkg]
}
if (version) url = url.replace(pkg, `${pkg}@${version}`)
}
}
} catch (e) {
console.log(e)
}
}
const npm_urls = ["https://cdn.jsdelivr.net/npm/", "https://unpkg.com/"]
process && process.env && process.env['NPM_URL'] && npm_urls.unshift(process.env['NPM_URL'])
let content = null
// get response from all urls
for (let i = 0; i < npm_urls.length; i++) {
try {
const req = npm_urls[i] + url
const response: AxiosResponse = await axios.get(req, { transformResponse: [] })
content = response.data
break
} catch (e) {
// try next url
}
}
if (!content) throw new Error('Unable to load ' + url)
return { content, cleanUrl: url }
}
getHandlers (): Handler[] {
return [
@ -173,9 +233,9 @@ export class RemixURLResolver {
]
}
public async resolve (filePath: string, customHandlers?: Handler[]): Promise<Imported> {
public async resolve (filePath: string, customHandlers?: Handler[], force?: boolean): Promise<Imported> {
let imported: Imported = this.previouslyHandled[filePath]
if (imported) {
if (!force && imported) {
return imported
}
const builtinHandlers: Handler[] = this.getHandlers()

@ -1,17 +1,18 @@
import { PluginClient } from '@remixproject/plugin'
import { SharedFolderArgs, TrackDownStreamUpdate, Filelist, ResolveDirectory, FileContent } from '../types' // eslint-disable-line
import { SharedFolderArgs, Filelist, ResolveDirectory, FileContent } from '../types' // eslint-disable-line
import * as WS from 'ws' // eslint-disable-line
import * as utils from '../utils'
import * as chokidar from 'chokidar'
import * as fs from 'fs-extra'
import * as isbinaryfile from 'isbinaryfile'
import * as pathModule from 'path'
export class RemixdClient extends PluginClient {
methods: Array<string>
trackDownStreamUpdate: TrackDownStreamUpdate = {}
websocket: WS
currentSharedFolder: string
watcher: chokidar.FSWatcher
trackDownStreamUpdate: Record<string, string> = {}
constructor (private readOnly = false) {
super()
@ -101,12 +102,12 @@ export class RemixdClient extends PluginClient {
console.log('trying to write "undefined" ! stopping.')
return reject(new Error('trying to write "undefined" ! stopping.'))
}
this.trackDownStreamUpdate[path] = path
if (!exists && args.path.indexOf('/') !== -1) {
// the last element is the filename and we should remove it
this.createDir({ path: args.path.substr(0, args.path.lastIndexOf('/')) })
}
try {
this.trackDownStreamUpdate[path] = args.content
fs.writeFile(path, args.content, 'utf8', (error: Error) => {
if (error) {
console.log(error)
@ -250,7 +251,7 @@ export class RemixdClient extends PluginClient {
const absPath = utils.absolutePath('./', path)
if (!isRealPath(absPath)) return
this.watcher = chokidar.watch(path, { depth: 0, ignorePermissionErrors: true })
this.watcher = chokidar.watch(path, { depth: 2, ignorePermissionErrors: true })
console.log('setup notifications for ' + path)
/* we can't listen on created file / folder
watcher.on('add', (f, stat) => {
@ -265,11 +266,10 @@ export class RemixdClient extends PluginClient {
})
*/
this.watcher.on('change', async (f: string) => {
if (this.trackDownStreamUpdate[f]) {
delete this.trackDownStreamUpdate[f]
return
}
if (this.isLoaded) {
const path = pathModule.resolve(f)
const currentContent = this.trackDownStreamUpdate[path]
const newContent = fs.readFileSync(f, 'utf-8')
if (currentContent !== newContent && this.isLoaded) {
this.emit('changed', utils.relativePath(f, this.currentSharedFolder))
}
})

@ -42,8 +42,6 @@ export type FileContent = {
readonly: boolean
}
export type TrackDownStreamUpdate = KeyPairString
export type SharedFolderArgs = FolderArgs & KeyPairString
export type WS = typeof Websocket

@ -130,6 +130,7 @@
"@isomorphic-git/lightning-fs": "^4.4.1",
"@monaco-editor/react": "4.4.5",
"@openzeppelin/contracts": "^4.7.3",
"@openzeppelin/upgrades-core": "^1.22.0",
"@openzeppelin/wizard": "^0.1.1",
"@remixproject/engine": "0.3.33",
"@remixproject/engine-web": "0.3.33",
@ -233,6 +234,7 @@
"@nrwl/web": "15.6.3",
"@nrwl/webpack": "15.6.3",
"@nrwl/workspace": "^15.6.3",
"@openzeppelin/contracts-upgradeable": "^4.8.1",
"@svgr/webpack": "^6.5.1",
"@testing-library/react": "13.4.0",
"@types/axios": "^0.14.0",
@ -306,6 +308,7 @@
"ganache-cli": "^6.8.1",
"gists": "^1.0.1",
"gulp": "^4.0.2",
"hardhat": "^2.12.7",
"https-browserify": "^1.0.0",
"ipfs-http-client": "^47.0.1",
"ipfs-mini": "^1.1.5",

File diff suppressed because it is too large Load Diff
Loading…
Cancel
Save