diff --git a/.prettierignore b/.prettierignore deleted file mode 100644 index d0b804da2a..0000000000 --- a/.prettierignore +++ /dev/null @@ -1,4 +0,0 @@ -# Add files here to ignore them from prettier formatting - -/dist -/coverage diff --git a/.prettierrc b/.prettierrc deleted file mode 100644 index 544138be45..0000000000 --- a/.prettierrc +++ /dev/null @@ -1,3 +0,0 @@ -{ - "singleQuote": true -} diff --git a/apps/remix-ide-e2e/src/tests/ballot.test.ts b/apps/remix-ide-e2e/src/tests/ballot.test.ts index 5244241e89..9d2e9c5b07 100644 --- a/apps/remix-ide-e2e/src/tests/ballot.test.ts +++ b/apps/remix-ide-e2e/src/tests/ballot.test.ts @@ -83,10 +83,10 @@ module.exports = { browser .openFile('Untitled.sol') .clickLaunchIcon('udapp') - .click('*[data-id="settingsWeb3Mode"]') - .waitForElementPresent('[data-id="envNotification-modal-footer-ok-react"]') + .click('*[data-id="settingsSelectEnvOptions"] *[data-id="External Http Provider"]') + .waitForElementPresent('[data-id="basic-http-provider-modal-footer-ok-react"]') .execute(function () { - const modal = document.querySelector('[data-id="envNotification-modal-footer-ok-react"]') as any + const modal = document.querySelector('[data-id="basic-http-provider-modal-footer-ok-react"]') as any modal.click() }) @@ -96,7 +96,7 @@ module.exports = { return env.value }, [], function (result) { - browser.assert.ok(result.value === 'web3', 'Web3 Provider not selected') + browser.assert.ok(result.value === 'External Http Provider', 'Web3 Provider not selected') }) .clickLaunchIcon('solidity') .clickLaunchIcon('udapp') diff --git a/apps/remix-ide-e2e/src/tests/ballot_0_4_11.test.ts b/apps/remix-ide-e2e/src/tests/ballot_0_4_11.test.ts index 28b03219fc..95696e3882 100644 --- a/apps/remix-ide-e2e/src/tests/ballot_0_4_11.test.ts +++ b/apps/remix-ide-e2e/src/tests/ballot_0_4_11.test.ts @@ -78,10 +78,10 @@ module.exports = { browser .openFile('Untitled.sol') .clickLaunchIcon('udapp') - .click('*[data-id="settingsWeb3Mode"]') - .waitForElementPresent('[data-id="envNotification-modal-footer-ok-react"]') + .click('*[data-id="settingsSelectEnvOptions"] *[data-id="External Http Provider"]') + .waitForElementPresent('[data-id="basic-http-provider-modal-footer-ok-react"]') .execute(function () { - const modal = document.querySelector('[data-id="envNotification-modal-footer-ok-react"]') as any + const modal = document.querySelector('[data-id="basic-http-provider-modal-footer-ok-react"]') as any modal.click() }) diff --git a/apps/remix-ide-e2e/src/tests/debugger.test.ts b/apps/remix-ide-e2e/src/tests/debugger.test.ts index d46c1020ce..9b8da7e7df 100644 --- a/apps/remix-ide-e2e/src/tests/debugger.test.ts +++ b/apps/remix-ide-e2e/src/tests/debugger.test.ts @@ -214,10 +214,10 @@ module.exports = { .setSolidityCompilerVersion('soljson-v0.8.7+commit.e28d00a7.js') .addFile('useDebugNodes.sol', sources[5]['useDebugNodes.sol']) // compile contract .clickLaunchIcon('udapp') - .click('*[data-id="settingsWeb3Mode"]') // select web3 provider with debug nodes URL - .clearValue('*[data-id="modalDialogCustomPromptText"]') - .setValue('*[data-id="modalDialogCustomPromptText"]', 'https://remix-rinkeby.ethdevops.io') - .modalFooterOKClick() + .click('*[data-id="settingsSelectEnvOptions"] *[data-id="External Http Provider"]') // select web3 provider with debug nodes URL + .clearValue('*[data-id="modalDialogCustomPromp"]') + .setValue('*[data-id="modalDialogCustomPromp"]', 'https://remix-rinkeby.ethdevops.io') + .modalFooterOKClick('basic-http-provider') .waitForElementPresent('*[title="Deploy - transact (not payable)"]', 65000) // wait for the compilation to succeed .clickLaunchIcon('debugger') .clearValue('*[data-id="debuggerTransactionInput"]') diff --git a/apps/remix-ide-e2e/src/tests/plugin_api.ts b/apps/remix-ide-e2e/src/tests/plugin_api.ts index faabf03247..1c21c849ca 100644 --- a/apps/remix-ide-e2e/src/tests/plugin_api.ts +++ b/apps/remix-ide-e2e/src/tests/plugin_api.ts @@ -188,7 +188,7 @@ module.exports = { .frameParent() .useCss() .clickLaunchIcon('udapp') - .waitForElementContainsText('#selectExEnvOptions option:checked', 'JavaScript VM (Berlin)') + .waitForElementContainsText('#selectExEnvOptions option:checked', 'Remix VM (Berlin)') .clickLaunchIcon('localPlugin') .useXpath() // @ts-ignore diff --git a/apps/remix-ide-e2e/src/tests/providers.test.ts b/apps/remix-ide-e2e/src/tests/providers.test.ts index b2c883fd53..fe6dcde8bf 100644 --- a/apps/remix-ide-e2e/src/tests/providers.test.ts +++ b/apps/remix-ide-e2e/src/tests/providers.test.ts @@ -44,9 +44,6 @@ module.exports = { .waitForElementContainsText('*[data-id="foundry-providerModalDialogModalBody-react"]', 'Error while connecting to the provider') .modalFooterOKClick('foundry-provider') .waitForElementNotVisible('*[data-id="foundry-providerModalDialogModalBody-react"]') - .waitForElementVisible('*[data-id="PermissionHandler-modal-footer-ok-react"]') - .click('*[data-id="PermissionHandler-modal-footer-ok-react"]') - .waitForElementNotVisible('*[data-id="PermissionHandler-modal-footer-ok-react"]') .pause(1000) }, diff --git a/apps/remix-ide-e2e/src/tests/recorder.test.ts b/apps/remix-ide-e2e/src/tests/recorder.test.ts index 1b41d856da..7e8748b737 100644 --- a/apps/remix-ide-e2e/src/tests/recorder.test.ts +++ b/apps/remix-ide-e2e/src/tests/recorder.test.ts @@ -42,7 +42,7 @@ module.exports = { .createContract(['12']) .clickInstance(0) .clickFunction('set - transact (not payable)', { types: 'uint256 _p', values: '34' }) - .click('i.savetransaction') + .click('.savetransaction') .waitForElementVisible('[data-id="udappNotify-modal-footer-ok-react"]') .execute(function () { const modalOk = document.querySelector('[data-id="udappNotify-modal-footer-ok-react"]') as any @@ -77,7 +77,7 @@ module.exports = { .selectContract('t2est') .pause(1000) .createContract([]) - .click('i.savetransaction') + .click('.savetransaction') .waitForElementVisible('[data-id="udappNotify-modal-footer-ok-react"]') .execute(function () { const modalOk = document.querySelector('[data-id="udappNotify-modal-footer-ok-react"]') as any @@ -95,6 +95,50 @@ module.exports = { status: 'true Transaction mined and execution succeed', 'decoded input': { 'uint256 _po': '10' } }) + + }, + + 'Run with live "mode"': function (browser: NightwatchBrowser) { + let addressRef: string + browser.addFile('scenario_live_mode.json', { content: JSON.stringify(liveModeScenario, null, '\t') }) + .addFile('scenario_live_mode_storage.sol', { content: testStorageForLiveMode }) + .clickLaunchIcon('solidity') + .click('*[data-id="compilerContainerCompileBtn"]') + .openFile('scenario_live_mode.json') + .clickLaunchIcon('udapp') + .click('*[data-id="deployAndRunClearInstances"]') + .click('*[data-id="runtabLivemodeInput"]') + .click('.runtransaction') + .pause(1000) + .clickInstance(0) + .getAddressAtPosition(0, (address) => { + addressRef = address + }) + .clickFunction('retrieve - call') + .perform((done) => { + browser.verifyCallReturnValue(addressRef, ['', '0:uint256: 350']) + .perform(() => done()) + }) + // change the init state and recompile the same contract. + .openFile('scenario_live_mode_storage.sol') + .setEditorValue(testStorageForLiveMode.replace('number = 350', 'number = 300')) + .pause(5000) + .clickLaunchIcon('solidity') + .click('*[data-id="compilerContainerCompileBtn"]') + .openFile('scenario_live_mode.json') + .clickLaunchIcon('udapp') + .click('*[data-id="deployAndRunClearInstances"]') + .click('.runtransaction') + .pause(5000) + .clickInstance(0) + .getAddressAtPosition(0, (address) => { + addressRef = address + }) + .clickFunction('retrieve - call') + .perform((done) => { + browser.verifyCallReturnValue(addressRef, ['', '0:uint256: 300']) + .perform(() => done()) + }) .end() } } @@ -364,3 +408,91 @@ const scenario = { ] } } + +const liveModeScenario = { + "accounts": { + "account{0}": "0x5B38Da6a701c568545dCfcB03FcB875f56beddC4" + }, + "linkReferences": {}, + "transactions": [ + { + "timestamp": 1656329164297, + "record": { + "value": "0", + "parameters": [], + "abi": "0x8b8c9c14c8e1442e90dd6ff82bb9889ccfe5a54d88ef30776f11047ecce5fedb", + "contractName": "Storage", + "bytecode": "608060405234801561001057600080fd5b5060c88061001f6000396000f3fe6080604052348015600f57600080fd5b5060043610604e577c010000000000000000000000000000000000000000000000000000000060003504632e64cec1811460535780636057361d146068575b600080fd5b60005460405190815260200160405180910390f35b60786073366004607a565b600055565b005b600060208284031215608b57600080fd5b503591905056fea264697066735822122091f1bc250ccda7caf2b0d9f67b0314d92233fdb5952b72cece72bd2a5d43cfc264736f6c63430008070033", + "linkReferences": {}, + "name": "", + "inputs": "()", + "type": "constructor", + "from": "account{0}" + } + } + ], + "abis": { + "0x8b8c9c14c8e1442e90dd6ff82bb9889ccfe5a54d88ef30776f11047ecce5fedb": [ + { + "inputs": [ + { + "internalType": "uint256", + "name": "num", + "type": "uint256" + } + ], + "name": "store", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "retrieve", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + } + ] + } +} + +const testStorageForLiveMode = `// SPDX-License-Identifier: GPL-3.0 + +pragma solidity >=0.7.0 <0.9.0; + +/** + * @title Storage + * @dev Store & retrieve value in a variable + * @custom:dev-run-script ./scripts/deploy_with_ethers.ts + */ +contract Storage { + + uint256 number; + + constructor () { + number = 350; + } + + /** + * @dev Store value in variable + * @param num value to store + */ + function store(uint256 num) public { + number = num; + } + + /** + * @dev Return value + * @return value of 'number' + */ + function retrieve() public view returns (uint256){ + return number; + } +}` diff --git a/apps/remix-ide-e2e/src/tests/runAndDeploy.test.ts b/apps/remix-ide-e2e/src/tests/runAndDeploy.test.ts index 7b3315e614..dc00530bdd 100644 --- a/apps/remix-ide-e2e/src/tests/runAndDeploy.test.ts +++ b/apps/remix-ide-e2e/src/tests/runAndDeploy.test.ts @@ -198,7 +198,7 @@ module.exports = { .assert.containsText('*[data-id="solidityLocals"]', 'to: 0x6C3CCC7FBA111707D5A1AAF2758E9D4F4AC5E7B1') }, - 'Call web3.eth.getAccounts() using Injected web3 (Metamask)': '' + function (browser: NightwatchBrowser) { + 'Call web3.eth.getAccounts() using Injected Provider (Metamask)': '' + function (browser: NightwatchBrowser) { browser .executeScript('web3.eth.getAccounts()') .pause(2000) diff --git a/apps/remix-ide-e2e/src/tests/terminal.test.ts b/apps/remix-ide-e2e/src/tests/terminal.test.ts index 40c59875da..59af766031 100644 --- a/apps/remix-ide-e2e/src/tests/terminal.test.ts +++ b/apps/remix-ide-e2e/src/tests/terminal.test.ts @@ -40,7 +40,7 @@ module.exports = { .waitForElementContainsText('*[data-id="terminalJournal"]', 'contract Ballot {', 60000) }, - 'Call web3.eth.getAccounts() using JavaScript VM #group2': function (browser: NightwatchBrowser) { + 'Call web3.eth.getAccounts() using Remix VM #group2': function (browser: NightwatchBrowser) { browser .executeScript('web3.eth.getAccounts()') .waitForElementContainsText('*[data-id="terminalJournal"]', '["0x5B38Da6a701c568545dCfcB03FcB875f56beddC4","0xAb8483F64d9C6d1EcF9b849Ae677dD3315835cb2","0x4B20993Bc481177ec7E8f571ceCaE8A9e22C02db","0x78731D3Ca6b7E34aC0F824c42a7cC18A495cabaB","0x617F2E2fD72FD9D5503197092aC168c91465E7f2","0x17F6AD8Ef982297579C203069C1DbfFE4348c372","0x5c6B0f7Bf3E7ce046039Bd8FABdfD3f9F5021678","0x03C6FcED478cBbC9a4FAB34eF9f40767739D1Ff7","0x1aE0EA34a72D944a8C7603FfB3eC30a6669E454C","0x0A098Eda01Ce92ff4A4CCb7A4fFFb5A43EBC70DC","0xCA35b7d915458EF540aDe6068dFe2F44E8fa733c","0x14723A09ACff6D2A60DcdF7aA4AFf308FDDC160C","0x4B0897b0513fdC7C541B6d9D7E929C4e5364D2dB","0x583031D1113aD414F02576BD6afaBfb302140225","0xdD870fA1b7C4700F2BD7f44238821C26f7392148"]') @@ -50,8 +50,8 @@ module.exports = { browser .click('*[data-id="terminalClearConsole"]') // clear the terminal .clickLaunchIcon('udapp') - .click('*[data-id="settingsWeb3Mode"]') - .modalFooterOKClick('envNotification') + .click('*[data-id="settingsSelectEnvOptions"] *[data-id="External Http Provider"]') + .modalFooterOKClick('basic-http-provider') .executeScript('web3.eth.getAccounts()') .waitForElementContainsText('*[data-id="terminalJournal"]', '["', 60000) // we check if an array is present, don't need to check for the content .waitForElementContainsText('*[data-id="terminalJournal"]', '"]', 60000) diff --git a/apps/remix-ide-e2e/src/tests/workspace.test.ts b/apps/remix-ide-e2e/src/tests/workspace.test.ts index 7e7cfbd55e..95819b7598 100644 --- a/apps/remix-ide-e2e/src/tests/workspace.test.ts +++ b/apps/remix-ide-e2e/src/tests/workspace.test.ts @@ -38,7 +38,7 @@ module.exports = { .clickLaunchIcon('filePanel') .click('*[data-id="workspaceCreate"]') .waitForElementVisible('*[data-id="modalDialogCustomPromptTextCreate"]') - .waitForElementVisible('[data-id="fileSystemModalDialogModalFooter-react"] > span') + .waitForElementVisible('[data-id="fileSystemModalDialogModalFooter-react"] > button') // eslint-disable-next-line dot-notation .execute(function () { document.querySelector('*[data-id="modalDialogCustomPromptTextCreate"]')['value'] = 'workspace_remix_default' }) .waitForElementPresent('[data-id="fileSystemModalDialogModalFooter-react"] .modal-ok') @@ -94,7 +94,7 @@ module.exports = { browser .click('*[data-id="workspaceCreate"]') .waitForElementVisible('*[data-id="modalDialogCustomPromptTextCreate"]') - .waitForElementVisible('[data-id="fileSystemModalDialogModalFooter-react"] > span') + .waitForElementVisible('[data-id="fileSystemModalDialogModalFooter-react"] > button') // eslint-disable-next-line dot-notation .execute(function () { document.querySelector('*[data-id="modalDialogCustomPromptTextCreate"]')['value'] = 'workspace_blank' }) .click('select[id="wstemplate"]') @@ -115,7 +115,7 @@ module.exports = { browser .click('*[data-id="workspaceCreate"]') .waitForElementVisible('*[data-id="modalDialogCustomPromptTextCreate"]') - .waitForElementVisible('[data-id="fileSystemModalDialogModalFooter-react"] > span') + .waitForElementVisible('[data-id="fileSystemModalDialogModalFooter-react"] > button') // eslint-disable-next-line dot-notation .execute(function () { document.querySelector('*[data-id="modalDialogCustomPromptTextCreate"]')['value'] = 'workspace_erc20' }) .click('select[id="wstemplate"]') @@ -163,7 +163,7 @@ module.exports = { browser .click('*[data-id="workspaceCreate"]') .waitForElementVisible('*[data-id="modalDialogCustomPromptTextCreate"]') - .waitForElementVisible('[data-id="fileSystemModalDialogModalFooter-react"] > span') + .waitForElementVisible('[data-id="fileSystemModalDialogModalFooter-react"] > button') // eslint-disable-next-line dot-notation .execute(function () { document.querySelector('*[data-id="modalDialogCustomPromptTextCreate"]')['value'] = 'workspace_erc721' }) .click('select[id="wstemplate"]') @@ -213,7 +213,7 @@ module.exports = { browser .click('*[data-id="workspaceCreate"]') // create workspace_name .waitForElementVisible('*[data-id="modalDialogCustomPromptTextCreate"]') - .waitForElementVisible('[data-id="fileSystemModalDialogModalFooter-react"] > span') + .waitForElementVisible('[data-id="fileSystemModalDialogModalFooter-react"] > button') .click('*[data-id="modalDialogCustomPromptTextCreate"]') .clearValue('*[data-id="modalDialogCustomPromptTextCreate"]') .setValue('*[data-id="modalDialogCustomPromptTextCreate"]', 'workspace_name') @@ -225,7 +225,7 @@ module.exports = { .waitForElementVisible('*[data-id="treeViewLitreeViewItemtest.sol"]') .click('*[data-id="workspaceCreate"]') // create workspace_name_1 .waitForElementVisible('*[data-id="modalDialogCustomPromptTextCreate"]') - .waitForElementVisible('[data-id="fileSystemModalDialogModalFooter-react"] > span') + .waitForElementVisible('[data-id="fileSystemModalDialogModalFooter-react"] > button') .click('*[data-id="modalDialogCustomPromptTextCreate"]') .clearValue('*[data-id="modalDialogCustomPromptTextCreate"]') .setValue('*[data-id="modalDialogCustomPromptTextCreate"]', 'workspace_name_1') diff --git a/apps/remix-ide/bin/remix-ide b/apps/remix-ide/bin/remix-ide deleted file mode 100755 index 887bab51fa..0000000000 --- a/apps/remix-ide/bin/remix-ide +++ /dev/null @@ -1,21 +0,0 @@ -#!/usr/bin/env node -var path = require('path') -var httpServer = require('http-server') -var remixd = require('remixd') - -var server = httpServer.createServer({ - root: path.join(__dirname, '/../') -}) - -var folder = process.argv.length > 2 ? process.argv[2] : process.cwd() - -server.listen(8080, '127.0.0.1', function () {}) -var router = new remixd.Router(65520, remixd.services.sharedFolder, { remixIdeUrl: 'http://localhost:8080' }, (webSocket) => { - remixd.services.sharedFolder.setWebSocket(webSocket) - remixd.services.sharedFolder.setupNotifications(folder) - remixd.services.sharedFolder.sharedFolder(folder, false) -}) - -router.start() - -console.log('\x1b[33m%s\x1b[0m', 'Starting Remix IDE at http://localhost:8080 and sharing ' + folder) diff --git a/apps/remix-ide/src/app.js b/apps/remix-ide/src/app.js index 271324c107..789c1b51a9 100644 --- a/apps/remix-ide/src/app.js +++ b/apps/remix-ide/src/app.js @@ -30,6 +30,7 @@ import { Blockchain } from './blockchain/blockchain.js' import { HardhatProvider } from './app/tabs/hardhat-provider' import { GanacheProvider } from './app/tabs/ganache-provider' import { FoundryProvider } from './app/tabs/foundry-provider' +import { ExternalHttpProvider } from './app/tabs/external-http-provider' const isElectron = require('is-electron') @@ -181,6 +182,7 @@ class AppComponent { const hardhatProvider = new HardhatProvider(blockchain) const ganacheProvider = new GanacheProvider(blockchain) const foundryProvider = new FoundryProvider(blockchain) + const externalHttpProvider = new ExternalHttpProvider(blockchain) // ----------------- convert offset to line/column service ----------- const offsetToLineColumnConverter = new OffsetToLineColumnConverter() Registry.getInstance().put({ @@ -242,6 +244,7 @@ class AppComponent { hardhatProvider, ganacheProvider, foundryProvider, + externalHttpProvider, this.walkthroughService, search ]) @@ -327,7 +330,8 @@ class AppComponent { filePanel.slitherHandle, linkLibraries, deployLibraries, - openZeppelinProxy + openZeppelinProxy, + run.recorder ]) this.layout.panels = { @@ -361,7 +365,7 @@ class AppComponent { await this.appManager.activatePlugin(['settings', 'config']) await this.appManager.activatePlugin(['hiddenPanel', 'pluginManager', 'contextualListener', 'codeParser', 'fileDecorator', 'terminal', 'blockchain', 'fetchAndCompile', 'contentImport', 'gistHandler']) await this.appManager.activatePlugin(['settings']) - await this.appManager.activatePlugin(['walkthrough','storage', 'search','compileAndRun']) + await this.appManager.activatePlugin(['walkthrough','storage', 'search','compileAndRun', 'recorder']) this.appManager.on( 'filePanel', diff --git a/apps/remix-ide/src/app/plugins/storage.ts b/apps/remix-ide/src/app/plugins/storage.ts index 32d5c90387..cd7ec1e162 100644 --- a/apps/remix-ide/src/app/plugins/storage.ts +++ b/apps/remix-ide/src/app/plugins/storage.ts @@ -15,14 +15,14 @@ export class StoragePlugin extends Plugin { async getStorage() { let storage = null if ('storage' in navigator && 'estimate' in navigator.storage && (window as any).remixFileSystem.name !== 'localstorage') { - storage = navigator.storage.estimate() + storage = await navigator.storage.estimate() } else { storage ={ usage: parseFloat(this.calculateLocalStorage()) * 1000, quota: 5000000, } } - const _paq = window._paq = window._paq || [] + const _paq = (window as any)._paq = (window as any)._paq || [] _paq.push(['trackEvent', 'Storage', 'used', this.formatString(storage)]); return storage } @@ -32,8 +32,8 @@ export class StoragePlugin extends Plugin { } calculateLocalStorage() { - var _lsTotal = 0 - var _xLen; var _x + let _lsTotal = 0 + let _xLen; let _x for (_x in localStorage) { // eslint-disable-next-line no-prototype-builtins if (!localStorage.hasOwnProperty(_x)) { diff --git a/apps/remix-ide/src/app/tabs/abstract-provider.tsx b/apps/remix-ide/src/app/tabs/abstract-provider.tsx index b17f181166..ff924a618a 100644 --- a/apps/remix-ide/src/app/tabs/abstract-provider.tsx +++ b/apps/remix-ide/src/app/tabs/abstract-provider.tsx @@ -58,6 +58,20 @@ export abstract class AbstractProvider extends Plugin { modalType: ModalTypes.prompt, okLabel: 'OK', cancelLabel: 'Cancel', + validationFn: (value) => { + if (!value) return { valid: false, message: "value is empty" } + if (value.startsWith('https://') || value.startsWith('http://')) { + return { + valid: true, + message: '' + } + } else { + return { + valid: false, + message: 'the provided value should contain the protocol ( e.g starts with http:// or https:// )' + } + } + }, okFn: (value: string) => { setTimeout(() => resolve(value), 0) }, diff --git a/apps/remix-ide/src/app/tabs/external-http-provider.tsx b/apps/remix-ide/src/app/tabs/external-http-provider.tsx new file mode 100644 index 0000000000..5a22b48ba4 --- /dev/null +++ b/apps/remix-ide/src/app/tabs/external-http-provider.tsx @@ -0,0 +1,41 @@ +import * as packageJson from '../../../../../package.json' +import React from 'react' // eslint-disable-line +import { AbstractProvider } from './abstract-provider' + +const profile = { + name: 'basic-http-provider', + displayName: 'External Http Provider', + kind: 'provider', + description: '', + methods: ['sendAsync'], + version: packageJson.version +} + +export class ExternalHttpProvider extends AbstractProvider { + constructor (blockchain) { + super(profile, blockchain, 'http://127.0.0.1:8545') + } + + body (): JSX.Element { + const thePath = '' + return ( + <> +
+ Note: To use Geth & https://remix.ethereum.org, configure it to allow requests from Remix:(see Geth Docs on rpc server) +
geth --http --http.corsdomain https://remix.ethereum.org
+
+ To run Remix & a local Geth test node, use this command: (see Geth Docs on Dev mode) +
geth --http --http.corsdomain="{window.origin}" --http.api web3,eth,debug,personal,net --vmdebug --datadir {thePath} --dev console
+
+
+ WARNING: It is not safe to use the --http.corsdomain flag with a wildcard: --http.corsdomain * +
+
For more info: Remix Docs on Web3 Provider +
+
+ External HTTP Provider Endpoint +
+ + ) + } +} \ No newline at end of file diff --git a/apps/remix-ide/src/app/tabs/runTab/model/recorder.js b/apps/remix-ide/src/app/tabs/runTab/model/recorder.js index 6178063843..caa758b180 100644 --- a/apps/remix-ide/src/app/tabs/runTab/model/recorder.js +++ b/apps/remix-ide/src/app/tabs/runTab/model/recorder.js @@ -1,16 +1,28 @@ var async = require('async') var ethutil = require('ethereumjs-util') var remixLib = require('@remix-project/remix-lib') +import { Plugin } from '@remixproject/engine' +import * as packageJson from '../../../../.././../../package.json' var EventManager = remixLib.EventManager var format = remixLib.execution.txFormat var txHelper = remixLib.execution.txHelper const helper = require('../../../../lib/helper') +const _paq = window._paq = window._paq || [] //eslint-disable-line + +const profile = { + name: 'recorder', + displayName: 'Recorder', + description: '', + version: packageJson.version, + methods: [ ] +} /** * Record transaction as long as the user create them. */ -class Recorder { +class Recorder extends Plugin { constructor (blockchain) { + super(profile) this.event = new EventManager() this.blockchain = blockchain this.data = { _listen: true, _replay: false, journal: [], _createdContracts: {}, _createdContractsReverse: {}, _usedAccounts: {}, _abis: {}, _contractABIReferences: {}, _linkReferences: {} } @@ -21,7 +33,13 @@ class Recorder { // convert to and from to tokens if (this.data._listen) { - var record = { value, parameters: payLoad.funArgs } + var record = { + value, + inputs: txHelper.serializeInputs(payLoad.funAbi), + parameters: payLoad.funArgs, + name: payLoad.funAbi.name, + type: payLoad.funAbi.type + } if (!to) { var abi = payLoad.contractABI var keccak = ethutil.bufferToHex(ethutil.keccakFromString(JSON.stringify(abi))) @@ -43,10 +61,7 @@ class Recorder { var creationTimestamp = this.data._createdContracts[to] record.to = `created{${creationTimestamp}}` record.abi = this.data._contractABIReferences[creationTimestamp] - } - record.name = payLoad.funAbi.name - record.inputs = txHelper.serializeInputs(payLoad.funAbi) - record.type = payLoad.funAbi.type + } for (var p in record.parameters) { var thisarg = record.parameters[p] var thistimestamp = this.data._createdContracts[thisarg] @@ -169,16 +184,30 @@ class Recorder { /** * run the list of records * + * @param {Object} records * @param {Object} accounts * @param {Object} options * @param {Object} abis + * @param {Object} linkReferences + * @param {Function} confirmationCb + * @param {Function} continueCb + * @param {Function} promptCb + * @param {Function} alertCb + * @param {Function} logCallBack + * @param {Function} liveMode * @param {Function} newContractFn * */ - run (records, accounts, options, abis, linkReferences, confirmationCb, continueCb, promptCb, alertCb, logCallBack, newContractFn) { + run (records, accounts, options, abis, linkReferences, confirmationCb, continueCb, promptCb, alertCb, logCallBack, liveMode, newContractFn) { this.setListen(false) - logCallBack(`Running ${records.length} transaction(s) ...`) - async.eachOfSeries(records, (tx, index, cb) => { + const liveMsg = liveMode ? ' in live mode' : '' + logCallBack(`Running ${records.length} transaction(s)${liveMsg} ...`) + async.eachOfSeries(records, async (tx, index, cb) => { + if (liveMode && tx.record.type === 'constructor') { + // resolve the bytecode using the contract name, this ensure getting the last compiled one. + const data = await this.call('compilerArtefacts', 'getArtefactsByContractName', tx.record.contractName) + tx.record.bytecode = data.artefact.evm.bytecode.object + } var record = this.resolveAddress(tx.record, accounts, options) var abi = abis[tx.record.abi] if (!abi) { @@ -257,8 +286,10 @@ class Recorder { }, () => { this.setListen(true) }) } - runScenario (json, continueCb, promptCb, alertCb, confirmationCb, logCallBack, cb) { + runScenario (liveMode, json, continueCb, promptCb, alertCb, confirmationCb, logCallBack, cb) { + _paq.push(['trackEvent', 'run', 'recorder', 'start']) if (!json) { + _paq.push(['trackEvent', 'run', 'recorder', 'wrong-json']) return cb('a json content must be provided') } if (typeof json === 'string') { @@ -269,12 +300,17 @@ class Recorder { } } + let txArray + let accounts + let options + let abis + let linkReferences try { - var txArray = json.transactions || [] - var accounts = json.accounts || [] - var options = json.options || {} - var abis = json.abis || {} - var linkReferences = json.linkReferences || {} + txArray = json.transactions || [] + accounts = json.accounts || [] + options = json.options || {} + abis = json.abis || {} + linkReferences = json.linkReferences || {} } catch (e) { return cb('Invalid Scenario File. Please try again') } @@ -283,7 +319,7 @@ class Recorder { return } - this.run(txArray, accounts, options, abis, linkReferences, confirmationCb, continueCb, promptCb, alertCb, logCallBack, (abi, address, contractName) => { + this.run(txArray, accounts, options, abis, linkReferences, confirmationCb, continueCb, promptCb, alertCb, logCallBack, liveMode, (abi, address, contractName) => { cb(null, abi, address, contractName) }) } diff --git a/apps/remix-ide/src/app/tabs/theme-module.js b/apps/remix-ide/src/app/tabs/theme-module.js index 2fd9e92e1a..ceb7192317 100644 --- a/apps/remix-ide/src/app/tabs/theme-module.js +++ b/apps/remix-ide/src/app/tabs/theme-module.js @@ -37,7 +37,7 @@ export class ThemeModule extends Plugin { themes.map((theme) => { this.themes[theme.name.toLocaleLowerCase()] = { ...theme, - url: window.location.origin + window.location.pathname + theme.url + url: window.location.origin + ( window.location.pathname.startsWith('/address/') || window.location.pathname.endsWith('.sol') ? '/' : window.location.pathname ) + theme.url } }) this._paq = _paq diff --git a/apps/remix-ide/src/app/udapp/run-tab.js b/apps/remix-ide/src/app/udapp/run-tab.js index 8a1e30324e..cf71ad3780 100644 --- a/apps/remix-ide/src/app/udapp/run-tab.js +++ b/apps/remix-ide/src/app/udapp/run-tab.js @@ -155,6 +155,20 @@ export class RunTab extends ViewPlugin { } } }) + + await this.call('blockchain', 'addProvider', { + name: 'External Http Provider', + provider: { + async sendAsync (payload, callback) { + try { + const result = await udapp.call('basic-http-provider', 'sendAsync', payload) + callback(null, result) + } catch (e) { + callback(e) + } + } + } + }) } writeFile (fileName, content) { diff --git a/apps/remix-ide/src/blockchain/blockchain.js b/apps/remix-ide/src/blockchain/blockchain.js index 5f6d3ad6c6..3d6e885cec 100644 --- a/apps/remix-ide/src/blockchain/blockchain.js +++ b/apps/remix-ide/src/blockchain/blockchain.js @@ -464,7 +464,7 @@ export class Blockchain extends Plugin { } /** - * This function send a tx only to javascript VM or testnet, will return an error for the mainnet + * This function send a tx only to Remix VM or testnet, will return an error for the mainnet * SHOULD BE TAKEN CAREFULLY! * * @param {Object} tx - transaction. @@ -530,6 +530,7 @@ export class Blockchain extends Plugin { if (this.transactionContextAPI.getAddress) { return this.transactionContextAPI.getAddress(function (err, address) { if (err) return reject(err) + if (!address) return reject('"from" is not defined. Please make sure an account is selected. If you are using a public node, it is likely that no account will be provided. In that case, add the public node to your injected provider (type Metamask) and use injected provider in Remix.') return resolve(address) }) } @@ -548,9 +549,18 @@ export class Blockchain extends Plugin { const runTransaction = async () => { // eslint-disable-next-line no-async-promise-executor return new Promise(async (resolve, reject) => { - const fromAddress = await getAccount() - const value = await queryValue() - const gasLimit = await getGasLimit() + let fromAddress + let value + let gasLimit + try { + fromAddress = await getAccount() + value = await queryValue() + gasLimit = await getGasLimit() + } catch (e) { + reject(e) + return + } + const tx = { to: args.to, data: args.data.dataHex, useCall: args.useCall, from: fromAddress, value: value, gasLimit: gasLimit, timestamp: args.data.timestamp } const payLoad = { funAbi: args.data.funAbi, funArgs: args.data.funArgs, contractBytecode: args.data.contractBytecode, contractName: args.data.contractName, contractABI: args.data.contractABI, linkReferences: args.data.linkReferences } diff --git a/apps/remix-ide/src/blockchain/execution-context.js b/apps/remix-ide/src/blockchain/execution-context.js index e570b25056..95b4abe45d 100644 --- a/apps/remix-ide/src/blockchain/execution-context.js +++ b/apps/remix-ide/src/blockchain/execution-context.js @@ -164,9 +164,6 @@ export class ExecutionContext { } } - if (context === 'web3') { - confirmCb(cb) - } if (this.customNetWorks[context]) { var network = this.customNetWorks[context] this.setProviderFromEndpoint(network.provider, { context: network.name }, (error) => { diff --git a/apps/remix-ide/src/remixAppManager.js b/apps/remix-ide/src/remixAppManager.js index bf8c5dccd3..9c00f8ab46 100644 --- a/apps/remix-ide/src/remixAppManager.js +++ b/apps/remix-ide/src/remixAppManager.js @@ -19,7 +19,7 @@ const sensitiveCalls = { } export function isNative(name) { - const nativePlugins = ['vyper', 'workshops', 'debugger', 'remixd', 'menuicons', 'solidity', 'solidity-logic', 'solidityStaticAnalysis', 'solidityUnitTesting', 'layout', 'notification', 'hardhat-provider', 'ganache-provider'] + const nativePlugins = ['vyper', 'workshops', 'debugger', 'remixd', 'menuicons', 'solidity', 'solidity-logic', 'solidityStaticAnalysis', 'solidityUnitTesting', 'layout', 'notification', 'hardhat-provider', 'ganache-provider', 'foundry-provider', 'basic-http-provider'] return nativePlugins.includes(name) || requiredModules.includes(name) } diff --git a/libs/remix-core-plugin/src/index.ts b/libs/remix-core-plugin/src/index.ts index 6038733518..5018bca7e2 100644 --- a/libs/remix-core-plugin/src/index.ts +++ b/libs/remix-core-plugin/src/index.ts @@ -7,4 +7,5 @@ export { EditorContextListener } from './lib/editor-context-listener' export { GistHandler } from './lib/gist-handler' export * from './types/contract' export { LinkLibraries, DeployLibraries } from './lib/link-libraries' -export { OpenZeppelinProxy } from './lib/openzeppelin-proxy' \ No newline at end of file +export { OpenZeppelinProxy } from './lib/openzeppelin-proxy' +export { fetchContractFromEtherscan } from './lib/helpers/fetch-etherscan' diff --git a/libs/remix-core-plugin/src/lib/helpers/fetch-etherscan.ts b/libs/remix-core-plugin/src/lib/helpers/fetch-etherscan.ts index 32264c32fa..16f6b284b8 100644 --- a/libs/remix-core-plugin/src/lib/helpers/fetch-etherscan.ts +++ b/libs/remix-core-plugin/src/lib/helpers/fetch-etherscan.ts @@ -1,8 +1,11 @@ -export const fetchContractFromEtherscan = async (plugin, network, contractAddress, targetPath) => { +export const fetchContractFromEtherscan = async (plugin, network, contractAddress, targetPath, key?) => { let data const compilationTargets = {} + let etherscanKey - const etherscanKey = await plugin.call('config', 'getAppParameter', 'etherscan-access-token') + if (!key) etherscanKey = await plugin.call('config', 'getAppParameter', 'etherscan-access-token') + else etherscanKey = key + if (etherscanKey) { const endpoint = network.id == 1 ? 'api.etherscan.io' : 'api-' + network.name + '.etherscan.io' data = await fetch('https://' + endpoint + '/api?module=contract&action=getsourcecode&address=' + contractAddress + '&apikey=' + etherscanKey) @@ -10,7 +13,7 @@ export const fetchContractFromEtherscan = async (plugin, network, contractAddres // etherscan api doc https://docs.etherscan.io/api-endpoints/contracts if (data.message === 'OK' && data.status === "1") { if (data.result.length) { - if (data.result[0].SourceCode === '') throw new Error('contract not verified in Etherscan') + if (data.result[0].SourceCode === '') throw new Error(`contract not verified on Etherscan ${network.name} network`) if (data.result[0].SourceCode.startsWith('{')) { data.result[0].SourceCode = JSON.parse(data.result[0].SourceCode.replace(/(?:\r\n|\r|\n)/g, '').replace(/^{{/,'{').replace(/}}$/,'}')) } diff --git a/libs/remix-lib/src/execution/forkAt.ts b/libs/remix-lib/src/execution/forkAt.ts index c5f1a1e981..8e45ce9acf 100644 --- a/libs/remix-lib/src/execution/forkAt.ts +++ b/libs/remix-lib/src/execution/forkAt.ts @@ -50,6 +50,14 @@ const forks = { { number: 12965000, name: 'london' + }, + { + number: 13773000, + name: 'arrowGlacier' + }, + { + number: 15050000, + name: 'grayGlacier' } ], 3: [ diff --git a/libs/remix-lib/src/execution/txRunnerWeb3.ts b/libs/remix-lib/src/execution/txRunnerWeb3.ts index 4483a41fe0..2300973b3a 100644 --- a/libs/remix-lib/src/execution/txRunnerWeb3.ts +++ b/libs/remix-lib/src/execution/txRunnerWeb3.ts @@ -85,7 +85,7 @@ export class TxRunnerWeb3 { runInNode (from, to, data, value, gasLimit, useCall, timestamp, confirmCb, gasEstimationForceSend, promptCb, callback) { const tx = { from: from, to: to, data: data, value: value } - + if (!from) return callback('the value of "from" is not defined. Please make sure an account is selected.') if (useCall) { tx['gas'] = gasLimit if (this._api && this._api.isVM()) tx['timestamp'] = timestamp diff --git a/libs/remix-ui/app/src/lib/remix-app/components/modals/modal-wrapper.tsx b/libs/remix-ui/app/src/lib/remix-app/components/modals/modal-wrapper.tsx index 12644f5fa7..17c6f604d6 100644 --- a/libs/remix-ui/app/src/lib/remix-app/components/modals/modal-wrapper.tsx +++ b/libs/remix-ui/app/src/lib/remix-app/components/modals/modal-wrapper.tsx @@ -1,5 +1,5 @@ import React, { useEffect, useRef, useState } from 'react' -import { ModalDialog, ModalDialogProps } from '@remix-ui/modal-dialog' +import { ModalDialog, ModalDialogProps, ValidationResult } from '@remix-ui/modal-dialog' import { ModalTypes } from '../../types' interface ModalWrapperProps extends ModalDialogProps { @@ -29,12 +29,23 @@ const ModalWrapper = (props: ModalWrapperProps) => { (props.cancelFn) ? props.cancelFn() : props.resolve(false) } - const createModalMessage = (defaultValue: string) => { + const onInputChanged = (event) => { + if (props.validationFn) { + const validation = props.validationFn(event.target.value) + setState(prevState => { + return { ...prevState, message: createModalMessage(props.defaultValue, validation), validation } + }) + } + } + + const createModalMessage = (defaultValue: string, validation: ValidationResult) => { return ( <> {props.message} - - ) + + {!validation.valid && {validation.message}} + + ) } useEffect(() => { @@ -47,13 +58,13 @@ const ModalWrapper = (props: ModalWrapperProps) => { ...props, okFn: onFinishPrompt, cancelFn: onCancelFn, - message: createModalMessage(props.defaultValue) + message: createModalMessage(props.defaultValue, { valid: true }) }) break default: setState({ ...props, - okFn: (onOkFn), + okFn: onOkFn, cancelFn: onCancelFn }) break @@ -67,8 +78,16 @@ const ModalWrapper = (props: ModalWrapperProps) => { } }, [props]) + // reset the message and input if any, so when the modal is shown again it doesn't show the previous value. + const handleHide = () => { + setState(prevState => { + return { ...prevState, message: '' } + }) + props.handleHide() + } + return ( - + ) } export default ModalWrapper diff --git a/libs/remix-ui/app/src/lib/remix-app/context/provider.tsx b/libs/remix-ui/app/src/lib/remix-app/context/provider.tsx index 2aca27c1be..701375265d 100644 --- a/libs/remix-ui/app/src/lib/remix-app/context/provider.tsx +++ b/libs/remix-ui/app/src/lib/remix-app/context/provider.tsx @@ -16,11 +16,11 @@ export const ModalProvider = ({ children = [], reducer = modalReducer, initialSt } const modal = (modalData: AppModal) => { - const { id, title, message, okLabel, okFn, cancelLabel, cancelFn, modalType, defaultValue, hideFn, data } = modalData + const { id, title, message, validationFn, okLabel, okFn, cancelLabel, cancelFn, modalType, defaultValue, hideFn, data } = modalData return new Promise((resolve, reject) => { dispatch({ type: modalActionTypes.setModal, - payload: { id, title, message, okLabel, okFn, cancelLabel, cancelFn, modalType: modalType || ModalTypes.default, defaultValue: defaultValue, hideFn, resolve, next: onNextFn, data } + payload: { id, title, message, okLabel, validationFn, okFn, cancelLabel, cancelFn, modalType: modalType || ModalTypes.default, defaultValue: defaultValue, hideFn, resolve, next: onNextFn, data } }) }) } diff --git a/libs/remix-ui/app/src/lib/remix-app/interface/index.ts b/libs/remix-ui/app/src/lib/remix-app/interface/index.ts index f31536678e..cc0521dd51 100644 --- a/libs/remix-ui/app/src/lib/remix-app/interface/index.ts +++ b/libs/remix-ui/app/src/lib/remix-app/interface/index.ts @@ -1,10 +1,16 @@ import { ModalTypes } from '../types' +export type ValidationResult = { + valid: boolean, + message?: string +} + export interface AppModal { id: string timestamp?: number hide?: boolean title: string + validationFn?: (value: string) => ValidationResult // eslint-disable-next-line no-undef message: string | JSX.Element okLabel: string diff --git a/libs/remix-ui/app/src/lib/remix-app/reducer/modals.ts b/libs/remix-ui/app/src/lib/remix-app/reducer/modals.ts index a50a3dbd66..51641c654c 100644 --- a/libs/remix-ui/app/src/lib/remix-app/reducer/modals.ts +++ b/libs/remix-ui/app/src/lib/remix-app/reducer/modals.ts @@ -11,6 +11,7 @@ export const modalReducer = (state: ModalState = ModalInitialState, action: Moda id: action.payload.id || timestamp.toString(), hide: false, title: action.payload.title, + validationFn: action.payload.validationFn, message: action.payload.message, okLabel: action.payload.okLabel, okFn: action.payload.okFn, diff --git a/libs/remix-ui/app/src/lib/remix-app/state/modals.ts b/libs/remix-ui/app/src/lib/remix-app/state/modals.ts index 8332d60120..2b10dccbea 100644 --- a/libs/remix-ui/app/src/lib/remix-app/state/modals.ts +++ b/libs/remix-ui/app/src/lib/remix-app/state/modals.ts @@ -8,6 +8,7 @@ export const ModalInitialState: ModalState = { hide: true, title: '', message: '', + validationFn: () => { return {valid: true, message: ''} }, okLabel: '', okFn: () => { }, cancelLabel: '', diff --git a/libs/remix-ui/debugger-ui/src/lib/debugger-ui.tsx b/libs/remix-ui/debugger-ui/src/lib/debugger-ui.tsx index 25dd08705b..bd8008d0e3 100644 --- a/libs/remix-ui/debugger-ui/src/lib/debugger-ui.tsx +++ b/libs/remix-ui/debugger-ui/src/lib/debugger-ui.tsx @@ -331,7 +331,7 @@ export const DebuggerUI = (props: DebuggerUIProps) => { return { ...prevState, opt: { ...prevState.opt, debugWithGeneratedSources: checked } } }) }} type="checkbox" title="Debug with generated sources" /> - + { state.isLocalNodeUsed &&
{ diff --git a/libs/remix-ui/editor/src/lib/remix-ui-editor.tsx b/libs/remix-ui/editor/src/lib/remix-ui-editor.tsx index 72adeda0ce..23ddbb7aac 100644 --- a/libs/remix-ui/editor/src/lib/remix-ui-editor.tsx +++ b/libs/remix-ui/editor/src/lib/remix-ui-editor.tsx @@ -519,6 +519,20 @@ export const EditorUI = (props: EditorUIProps) => { editor.addCommand(monacoRef.current.KeyMod.CtrlCmd | (monacoRef.current.KeyCode as any).US_MINUS, () => { editor.updateOptions({ fontSize: editor.getOption(43).fontSize - 1 }) }) + + const editorService = editor._codeEditorService; + const openEditorBase = editorService.openCodeEditor.bind(editorService); + editorService.openCodeEditor = async (input, source) => { + const result = await openEditorBase(input, source) + if (input && input.resource && input.resource.path) { + try { + await props.plugin.call('fileManager', 'open', input.resource.path) + } catch (e) { + console.log(e) + } + } + return result + } } function handleEditorWillMount(monaco: Monaco) { diff --git a/libs/remix-ui/helper/src/lib/components/web3Dialog.tsx b/libs/remix-ui/helper/src/lib/components/web3Dialog.tsx index 30055f322a..3412148086 100644 --- a/libs/remix-ui/helper/src/lib/components/web3Dialog.tsx +++ b/libs/remix-ui/helper/src/lib/components/web3Dialog.tsx @@ -24,10 +24,10 @@ export function Web3ProviderDialog (props: web3ProviderDialogProps) {
WARNING: It is not safe to use the --http.corsdomain flag with a wildcard: --http.corsdomain *
-
For more info: Remix Docs on Web3 Provider +
For more info: Remix Docs on Remix Provider

- Web3 Provider Endpoint + External HTTP Provider Endpoint
(
@@ -54,10 +53,6 @@ export const sourceVerificationNotAvailableToastMsg = () => (
) -export const web3Dialog = (externalEndpoint: string, setWeb3Endpoint: (value: string) => void) => ( - -) - export const envChangeNotification = (env: { context: string, fork: string }, from: string) => (
diff --git a/libs/remix-ui/modal-dialog/src/lib/remix-ui-modal-dialog.tsx b/libs/remix-ui/modal-dialog/src/lib/remix-ui-modal-dialog.tsx index 38df2fd8b8..936956c62a 100644 --- a/libs/remix-ui/modal-dialog/src/lib/remix-ui-modal-dialog.tsx +++ b/libs/remix-ui/modal-dialog/src/lib/remix-ui-modal-dialog.tsx @@ -96,18 +96,20 @@ export const ModalDialog = (props: ModalDialogProps) => {
{/* todo add autofocus ^^ */} - { props.okLabel && { + if (props.validation && !props.validation.valid) return if (props.okFn) props.okFn() handleHide() }} > {props.okLabel ? props.okLabel : 'OK'} - + } - { props.cancelLabel && { }} > {props.cancelLabel ? props.cancelLabel : 'Cancel'} - + }
diff --git a/libs/remix-ui/modal-dialog/src/lib/types/index.ts b/libs/remix-ui/modal-dialog/src/lib/types/index.ts index a8793971d7..f1ec8c88d9 100644 --- a/libs/remix-ui/modal-dialog/src/lib/types/index.ts +++ b/libs/remix-ui/modal-dialog/src/lib/types/index.ts @@ -1,8 +1,15 @@ +export type ValidationResult = { + valid: boolean, + message?: string +} + /* eslint-disable no-undef */ export interface ModalDialogProps { id: string timestamp?: number, title?: string, + validation?: ValidationResult + validationFn?: (value: string) => ValidationResult message?: string | JSX.Element, okLabel?: string, okFn?: (value?:any) => void, diff --git a/libs/remix-ui/run-tab/src/lib/actions/account.ts b/libs/remix-ui/run-tab/src/lib/actions/account.ts index 9fba9a0723..12b05bec2b 100644 --- a/libs/remix-ui/run-tab/src/lib/actions/account.ts +++ b/libs/remix-ui/run-tab/src/lib/actions/account.ts @@ -1,4 +1,4 @@ -import { shortenAddress, web3Dialog } from "@remix-ui/helper" +import { shortenAddress } from "@remix-ui/helper" import { RunTab } from "../types/run-tab" import { clearInstances, setAccount, setExecEnv } from "./actions" import { displayNotification, displayPopUp, fetchAccountsListFailed, fetchAccountsListRequest, fetchAccountsListSuccess, setExternalEndpoint, setMatchPassphrase, setPassphrase } from "./payload" @@ -74,28 +74,7 @@ const _getProviderDropdownValue = (plugin: RunTab): string => { } export const setExecutionContext = (plugin: RunTab, dispatch: React.Dispatch, executionContext: { context: string, fork: string }) => { - const displayContent = web3Dialog(plugin.REACT_API.externalEndpoint, (endpoint: string) => { - dispatch(setExternalEndpoint(endpoint)) - }) - - plugin.blockchain.changeExecutionContext(executionContext, () => { - plugin.call('notification', 'modal', { - id: 'envNotification', - title: 'External node request', - message: displayContent, - okLabel: 'OK', - cancelLabel: 'Cancel', - okFn: () => { - plugin.blockchain.setProviderFromEndpoint(plugin.REACT_API.externalEndpoint, executionContext, (alertMsg) => { - if (alertMsg) plugin.call('notification', 'toast', alertMsg) - setFinalContext(plugin, dispatch) - }) - }, - cancelFn: () => { - setFinalContext(plugin, dispatch) - } - }) - }, (alertMsg) => { + plugin.blockchain.changeExecutionContext(executionContext, null, (alertMsg) => { plugin.call('notification', 'toast', alertMsg) }, () => { setFinalContext(plugin, dispatch) }) } diff --git a/libs/remix-ui/run-tab/src/lib/actions/index.ts b/libs/remix-ui/run-tab/src/lib/actions/index.ts index 60cfe8513f..5dc0eb2de7 100644 --- a/libs/remix-ui/run-tab/src/lib/actions/index.ts +++ b/libs/remix-ui/run-tab/src/lib/actions/index.ts @@ -54,7 +54,7 @@ export const getExecutionContext = () => getContext(plugin) export const executeTransactions = (instanceIndex: number, lookupOnly: boolean, funcABI: FuncABI, inputsValues: string, contractName: string, contractABI, contract, address, logMsg:string, mainnetPrompt: MainnetPrompt, gasEstimationPrompt: (msg: string) => JSX.Element, passphrasePrompt: (msg: string) => JSX.Element, funcIndex?: number) => runTransactions(plugin, dispatch, instanceIndex, lookupOnly, funcABI, inputsValues, contractName, contractABI, contract, address, logMsg, mainnetPrompt, gasEstimationPrompt, passphrasePrompt, funcIndex) export const loadFromAddress = (contract: ContractData, address: string) => loadAddress(plugin, dispatch, contract, address) export const storeNewScenario = async (prompt: (msg: string, defaultValue: string) => JSX.Element) => storeScenario(plugin, dispatch, prompt) -export const runScenario = (gasEstimationPrompt: (msg: string) => JSX.Element, passphrasePrompt: (msg: string) => JSX.Element, confirmDialogContent: MainnetPrompt) => runCurrentScenario(plugin, dispatch, gasEstimationPrompt, passphrasePrompt, confirmDialogContent) +export const runScenario = (liveMode: boolean, gasEstimationPrompt: (msg: string) => JSX.Element, passphrasePrompt: (msg: string) => JSX.Element, confirmDialogContent: MainnetPrompt) => runCurrentScenario(liveMode, plugin, dispatch, gasEstimationPrompt, passphrasePrompt, confirmDialogContent) export const setScenarioPath = (path: string) => updateScenarioPath(dispatch, path) export const getFuncABIValues = (funcABI: FuncABI) => getFuncABIInputs(plugin, funcABI) export const setNetworkName = (networkName: string) => setNetworkNameFromProvider(dispatch, networkName) diff --git a/libs/remix-ui/run-tab/src/lib/actions/recorder.ts b/libs/remix-ui/run-tab/src/lib/actions/recorder.ts index 7f30ee1759..2bf910650b 100644 --- a/libs/remix-ui/run-tab/src/lib/actions/recorder.ts +++ b/libs/remix-ui/run-tab/src/lib/actions/recorder.ts @@ -36,12 +36,13 @@ export const storeScenario = async (plugin: RunTab, dispatch: React.Dispatch, file: string, gasEstimationPrompt: (msg: string) => JSX.Element, passphrasePrompt: (msg: string) => JSX.Element, confirmDialogContent: MainnetPrompt) => { +const runScenario = (liveMode: boolean, plugin: RunTab, dispatch: React.Dispatch, file: string, gasEstimationPrompt: (msg: string) => JSX.Element, passphrasePrompt: (msg: string) => JSX.Element, confirmDialogContent: MainnetPrompt) => { if (!file) return dispatch(displayNotification('Alert', 'Unable to run scenerio, no specified scenario file', 'OK', null)) plugin.fileManager.readFile(file).then((json) => { // TODO: there is still a UI dependency to remove here, it's still too coupled at this point to remove easily plugin.recorder.runScenario( + liveMode, json, (error, continueTxExecution, cancelCb) => { continueHandler(dispatch, gasEstimationPrompt, error, continueTxExecution, cancelCb) @@ -64,9 +65,9 @@ const runScenario = (plugin: RunTab, dispatch: React.Dispatch, file: string }).catch((error) => dispatch(displayNotification('Alert', error, 'OK', null))) } -export const runCurrentScenario = (plugin: RunTab, dispatch: React.Dispatch, gasEstimationPrompt: (msg: string) => JSX.Element, passphrasePrompt: (msg: string) => JSX.Element, confirmDialogContent: MainnetPrompt) => { +export const runCurrentScenario = (liveMode: boolean, plugin: RunTab, dispatch: React.Dispatch, gasEstimationPrompt: (msg: string) => JSX.Element, passphrasePrompt: (msg: string) => JSX.Element, confirmDialogContent: MainnetPrompt) => { const file = plugin.config.get('currentFile') if (!file) return dispatch(displayNotification('Alert', 'A scenario file has to be selected', 'Ok', null)) - runScenario(plugin, dispatch, file, gasEstimationPrompt, passphrasePrompt, confirmDialogContent) + runScenario(liveMode, plugin, dispatch, file, gasEstimationPrompt, passphrasePrompt, confirmDialogContent) } \ No newline at end of file diff --git a/libs/remix-ui/run-tab/src/lib/components/account.tsx b/libs/remix-ui/run-tab/src/lib/components/account.tsx index f8a74a1a34..1cb7aebc4c 100644 --- a/libs/remix-ui/run-tab/src/lib/components/account.tsx +++ b/libs/remix-ui/run-tab/src/lib/components/account.tsx @@ -160,8 +160,8 @@ export function AccountUI (props: AccountProps) { accounts.map((value, index) => ) } -
- +
+ ) diff --git a/libs/remix-ui/run-tab/src/lib/components/contractDropdownUI.tsx b/libs/remix-ui/run-tab/src/lib/components/contractDropdownUI.tsx index df1831deaa..900b7dc2c4 100644 --- a/libs/remix-ui/run-tab/src/lib/components/contractDropdownUI.tsx +++ b/libs/remix-ui/run-tab/src/lib/components/contractDropdownUI.tsx @@ -119,12 +119,12 @@ export function ContractDropdownUI (props: ContractDropdownProps) { if (enable) { setAtAddressOptions({ disabled: false, - title: 'Interact with the given contract.' + title: 'Interact with the deployed contract - requires the .abi file or compiled .sol file to be selected in the editor (with the same compiler configuration)' }) } else { setAtAddressOptions({ disabled: true, - title: loadedAddress ? '⚠ Compile *.sol file or select *.abi file.' : '⚠ Compile *.sol file or select *.abi file & then enter the address of deployed contract.' + title: loadedAddress ? 'Compile a *.sol file or select a *.abi file.' : 'To interact with a deployed contract, enter its address and compile its source *.sol file (with the same compiler settings) or select its .abi file in the editor. ' }) } } @@ -133,12 +133,12 @@ export function ContractDropdownUI (props: ContractDropdownProps) { if (enable) { setContractOptions({ disabled: false, - title: 'Select contract for Deploy or At Address.' + title: 'Select a compiled contract to deploy or to use with At Address.' }) } else { setContractOptions({ disabled: true, - title: loadType === 'sol' ? '⚠ Select and compile *.sol file to deploy or access a contract.' : '⚠ Selected *.abi file allows accessing contracts, select and compile *.sol file to deploy and access one.' + title: loadType === 'sol' ? 'Select and compile *.sol file to deploy or access a contract.' : 'When there is a compiled .sol file, the choice of contracts to deploy or to use with AtAddress is made here.' }) } } diff --git a/libs/remix-ui/run-tab/src/lib/components/environment.tsx b/libs/remix-ui/run-tab/src/lib/components/environment.tsx index 4853f6f12b..be6b4a3f94 100644 --- a/libs/remix-ui/run-tab/src/lib/components/environment.tsx +++ b/libs/remix-ui/run-tab/src/lib/components/environment.tsx @@ -8,7 +8,7 @@ export function EnvironmentUI (props: EnvironmentProps) { const fork = provider.fork // can be undefined if connected to an external source (web3 provider / injected) let context = provider.value - context = context.startsWith('vm') ? 'vm' : context // context has to be 'vm', 'web3' or 'injected' + context = context.startsWith('vm') ? 'vm' : context props.setExecutionContext({ context, fork }) } @@ -29,7 +29,7 @@ export function EnvironmentUI (props: EnvironmentProps) { ) } - + ) diff --git a/libs/remix-ui/run-tab/src/lib/components/gasPrice.tsx b/libs/remix-ui/run-tab/src/lib/components/gasPrice.tsx index 19e7876a75..a7c5335abd 100644 --- a/libs/remix-ui/run-tab/src/lib/components/gasPrice.tsx +++ b/libs/remix-ui/run-tab/src/lib/components/gasPrice.tsx @@ -10,7 +10,7 @@ export function GasPriceUI (props: GasPriceProps) { return (
- +
) } diff --git a/libs/remix-ui/run-tab/src/lib/components/instanceContainerUI.tsx b/libs/remix-ui/run-tab/src/lib/components/instanceContainerUI.tsx index 5c2800ef4d..6890c53f6d 100644 --- a/libs/remix-ui/run-tab/src/lib/components/instanceContainerUI.tsx +++ b/libs/remix-ui/run-tab/src/lib/components/instanceContainerUI.tsx @@ -12,7 +12,7 @@ export function InstanceContainerUI (props: InstanceContainerProps) { return (
-
-
-
- All transactions (deployed contracts and function executions) can be saved and replayed in - another environment. e.g Transactions created in Javascript VM can be replayed in the Injected Web3. -
-
- - -
+
+
+ + +
+
+ + Save {props.count} transaction(s) as scenario file. + + + }> + + + + Run transaction(s) from the current scenario file. + + + }> + + +
) diff --git a/libs/remix-ui/run-tab/src/lib/components/universalDappUI.tsx b/libs/remix-ui/run-tab/src/lib/components/universalDappUI.tsx index ad3c48cabc..679ab61b15 100644 --- a/libs/remix-ui/run-tab/src/lib/components/universalDappUI.tsx +++ b/libs/remix-ui/run-tab/src/lib/components/universalDappUI.tsx @@ -204,7 +204,7 @@ export function UniversalDappUI (props: UdappProps) { return (
-
+
diff --git a/libs/remix-ui/run-tab/src/lib/components/value.tsx b/libs/remix-ui/run-tab/src/lib/components/value.tsx index 3b2008a5f1..6728639a45 100644 --- a/libs/remix-ui/run-tab/src/lib/components/value.tsx +++ b/libs/remix-ui/run-tab/src/lib/components/value.tsx @@ -58,7 +58,7 @@ export function ValueUI (props: ValueProps) { className="form-control udapp_gasNval udapp_col2" id="value" data-id="dandrValue" - title="Enter the value and choose the unit" + title="Enter an amount and choose its unit" onKeyPress={validateInputKey} onChange={validateValue} value={props.sendValue} diff --git a/libs/remix-ui/run-tab/src/lib/css/run-tab.css b/libs/remix-ui/run-tab/src/lib/css/run-tab.css index 60cf8ad00f..c4c65e4e01 100644 --- a/libs/remix-ui/run-tab/src/lib/css/run-tab.css +++ b/libs/remix-ui/run-tab/src/lib/css/run-tab.css @@ -286,7 +286,6 @@ .udapp_instance { display: block; flex-direction: column; - margin-bottom: 12px; background: none; border-radius: 2px; } diff --git a/libs/remix-ui/run-tab/src/lib/reducers/runTab.ts b/libs/remix-ui/run-tab/src/lib/reducers/runTab.ts index 41950fc4e1..2a0ff1c855 100644 --- a/libs/remix-ui/run-tab/src/lib/reducers/runTab.ts +++ b/libs/remix-ui/run-tab/src/lib/reducers/runTab.ts @@ -2,6 +2,9 @@ import { CompilerAbstract } from '@remix-project/remix-solidity-ts' import { ContractData } from '@remix-project/core-plugin' import { DeployMode, DeployOption, 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, FETCH_PROVIDER_LIST_FAILED, FETCH_PROVIDER_LIST_REQUEST, FETCH_PROVIDER_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_TX_FEE_CONTENT } from '../constants' +import Web3 from 'web3' + +declare const window: any interface Action { type: string payload: any @@ -115,30 +118,23 @@ export const runTabInitialState: RunTabState = { providerList: [{ id: 'vm-mode-london', dataId: 'settingsVMLondonMode', - title: 'Execution environment does not connect to any node, everything is local and in memory only.', + title: 'Execution environment is local to Remix. Data is only saved to browser memory and will vanish upon reload.', value: 'vm-london', fork: 'london', - content: 'JavaScript VM (London)' + content: 'Remix VM (London)' }, { id: 'vm-mode-berlin', dataId: 'settingsVMBerlinMode', - title: 'Execution environment does not connect to any node, everything is local and in memory only.', + title: 'Execution environment is local to Remix. Data is only saved to browser memory and will vanish upon reload.', value: 'vm-berlin', fork: 'berlin', - content: 'JavaScript VM (Berlin)' + content: 'Remix VM (Berlin)' }, { id: 'injected-mode', dataId: 'settingsInjectedMode', title: 'Execution environment has been provided by Metamask or similar provider.', value: 'injected', - content: 'Injected Web3' - }, { - id: 'web3-mode', - dataId: 'settingsWeb3Mode', - title: `Execution environment connects to node at localhost (or via IPC if available), transactions will be sent to the network and can cause loss of money or worse! - If this page is served via https and you access your node via http, it might not work. In this case, try cloning the repository and serving it via http.`, - value: 'web3', - content: 'Web3 Provider' + content: `Injected Provider${(window && window.ethereum && window.ethereum.isMetaMask) ? ' - Metamask' : ''}` }], isRequesting: false, isSuccessful: false, diff --git a/libs/remix-ui/run-tab/src/lib/types/blockchain.d.ts b/libs/remix-ui/run-tab/src/lib/types/blockchain.d.ts index 1b2f7d442d..1d6e7a5edc 100644 --- a/libs/remix-ui/run-tab/src/lib/types/blockchain.d.ts +++ b/libs/remix-ui/run-tab/src/lib/types/blockchain.d.ts @@ -1,3 +1,6 @@ +import { Plugin } from "@remixproject/engine/lib/abstract"; +import { ExecutionContext } from "./execution-context"; +import { EventEmitter } from "events"; export class Blockchain extends Plugin { constructor(config: any); event: any; @@ -70,7 +73,7 @@ export class Blockchain extends Plugin { getBalanceInEther(address: any, cb: any): void; pendingTransactionsCount(): number; /** - * This function send a tx only to javascript VM or testnet, will return an error for the mainnet + * This function send a tx only to Remix VM or testnet, will return an error for the mainnet * SHOULD BE TAKEN CAREFULLY! * * @param {Object} tx - transaction. @@ -78,6 +81,4 @@ export class Blockchain extends Plugin { sendTransaction(tx: any): any; runTx(args: any, confirmationCb: any, continueCb: any, promptCb: any, cb: any): void; } -import { Plugin } from "@remixproject/engine/lib/abstract"; -import { ExecutionContext } from "./execution-context"; -import { EventEmitter } from "events"; + diff --git a/libs/remix-ui/run-tab/src/lib/types/index.ts b/libs/remix-ui/run-tab/src/lib/types/index.ts index 3e162ea9d6..c304d8815f 100644 --- a/libs/remix-ui/run-tab/src/lib/types/index.ts +++ b/libs/remix-ui/run-tab/src/lib/types/index.ts @@ -166,7 +166,7 @@ export interface ContractDropdownProps { export interface RecorderProps { storeScenario: (prompt: (msg: string, defaultValue: string) => JSX.Element) => void, - runCurrentScenario: (gasEstimationPrompt: (msg: string) => JSX.Element, passphrasePrompt: (msg: string) => JSX.Element, confirmDialogContent: MainnetPrompt) => void, + runCurrentScenario: (liveMode: boolean, gasEstimationPrompt: (msg: string) => JSX.Element, passphrasePrompt: (msg: string) => JSX.Element, confirmDialogContent: MainnetPrompt) => void, mainnetPrompt: MainnetPrompt, gasEstimationPrompt: (msg: string) => JSX.Element, passphrasePrompt: (msg: string) => JSX.Element, diff --git a/libs/remix-ui/run-tab/src/lib/types/recorder.d.ts b/libs/remix-ui/run-tab/src/lib/types/recorder.d.ts index 44f79bda4f..3c4ec18c20 100644 --- a/libs/remix-ui/run-tab/src/lib/types/recorder.d.ts +++ b/libs/remix-ui/run-tab/src/lib/types/recorder.d.ts @@ -9,7 +9,7 @@ export class Recorder { getAll: () => void; clearAll: () => void; run: (records, accounts, options, abis, linkReferences, confirmationCb, continueCb, promptCb, alertCb, logCallBack, newContractFn) => void - runScenario: (json, continueCb, promptCb, alertCb, confirmationCb, logCallBack, cb) => void + runScenario: (liveMode, json, continueCb, promptCb, alertCb, confirmationCb, logCallBack, cb) => void } import { Blockchain } from "./blockchain"; diff --git a/libs/remix-ui/search/src/lib/reducers/Reducer.ts b/libs/remix-ui/search/src/lib/reducers/Reducer.ts index 3e83b75aea..8ff867d1c8 100644 --- a/libs/remix-ui/search/src/lib/reducers/Reducer.ts +++ b/libs/remix-ui/search/src/lib/reducers/Reducer.ts @@ -46,7 +46,7 @@ export const SearchReducer = (state: SearchState = SearchingInitialState, action run: true } case 'SET_UNDO_ENABLED': - if(state.undoBuffer[`${action.payload.workspace}/${action.payload.path}`]){ + if(action.payload.workspace && state.undoBuffer[`${action.payload.workspace}/${action.payload.path}`]){ state.undoBuffer[`${action.payload.workspace}/${action.payload.path}`].enabled = (action.payload.content === state.undoBuffer[`${action.payload.workspace}/${action.payload.path}`].newContent) state.undoBuffer[`${action.payload.workspace}/${action.payload.path}`].visible = (action.payload.content !== state.undoBuffer[`${action.payload.workspace}/${action.payload.path}`].oldContent) } diff --git a/libs/remix-ui/settings/src/lib/constants.ts b/libs/remix-ui/settings/src/lib/constants.ts index 6d1b87b8f4..1e7c02d1d7 100644 --- a/libs/remix-ui/settings/src/lib/constants.ts +++ b/libs/remix-ui/settings/src/lib/constants.ts @@ -11,9 +11,9 @@ export const etherscanTokenTitle = 'EtherScan Access Token' export const etherscanTokenLink = 'https://etherscan.io/myapikey' export const etherscanAccessTokenText = 'Manage the api key used to interact with Etherscan.' export const etherscanAccessTokenText2 = 'Go to Etherscan api key page (link below) to create a new api key and save it in Remix.' -export const ethereunVMText = 'Always use Javascript VM at load' +export const ethereunVMText = 'Always use Remix VM at load' export const wordWrapText = 'Word wrap in editor' -export const enablePersonalModeText = ' Enable Personal Mode for web3 provider. Transaction sent over Web3 will use the web3.personal API.\n' +export const enablePersonalModeText = ' Enable Personal Mode for Remix Provider. Transaction sent over Web3 will use the web3.personal API.\n' export const useAutoCompleteText = 'Enable code completion in editor.' export const matomoAnalytics = 'Enable Matomo Analytics. We do not collect personally identifiable information (PII). The info is used to improve the site’s UX & UI. See more about ' export const swarmSettingsTitle = 'Swarm Settings' diff --git a/libs/remix-ui/solidity-compiler/src/lib/compiler-container.tsx b/libs/remix-ui/solidity-compiler/src/lib/compiler-container.tsx index bc06aae822..cb27b4d122 100644 --- a/libs/remix-ui/solidity-compiler/src/lib/compiler-container.tsx +++ b/libs/remix-ui/solidity-compiler/src/lib/compiler-container.tsx @@ -33,6 +33,7 @@ export const CompilerContainer = (props: CompilerContainerProps) => { configurationSettings, isHardhatProject, isTruffleProject, + isFoundryProject, workspaceName, configFilePath, setConfigFilePath, @@ -71,7 +72,10 @@ export const CompilerContainer = (props: CompilerContainerProps) => { api.setAppParameter('configFilePath', defaultPath) if (state.useFileConfiguration) { api.fileExists(defaultPath).then((exists) => { - if (!exists && state.useFileConfiguration) createNewConfigFile() + if (!exists && state.useFileConfiguration) { + configFilePathInput.current.value = defaultPath + createNewConfigFile() + } }) } setShowFilePathInput(false) @@ -90,8 +94,9 @@ export const CompilerContainer = (props: CompilerContainerProps) => { useEffect(() => { const listener = (event) => { - if (configFilePathInput.current !== event.target) { + if (configFilePathInput.current !== event.target && event.target.innerText !== "Create") { setShowFilePathInput(false) + configFilePathInput.current.value = "" return; } }; @@ -241,7 +246,16 @@ export const CompilerContainer = (props: CompilerContainerProps) => { if (filePath === '') filePath = defaultPath if (!filePath.endsWith('.json')) filePath = filePath + '.json' - await api.writeFile(filePath, configFileContent) + let compilerConfig = configFileContent + if (isFoundryProject && !compilerConfig.includes('remappings')) { + const config = JSON.parse(compilerConfig) + config.settings.remappings = [ + 'ds-test/=lib/forge-std/lib/ds-test/src/', + 'forge-std/=lib/forge-std/src/' + ] + compilerConfig = JSON.stringify(config, null, '\t') + } + await api.writeFile(filePath, compilerConfig) api.setAppParameter('configFilePath', filePath) setConfigFilePath(filePath) compileTabLogic.setConfigFilePath(filePath) @@ -754,7 +768,7 @@ export const CompilerContainer = (props: CompilerContainerProps) => {
- handleLanguageChange(e.target.value)} disabled={state.useFileConfiguration} value={state.language} className="custom-select" id="compilierLanguageSelector" title="Language specification available from Compiler >= v0.5.7"> @@ -789,7 +803,7 @@ export const CompilerContainer = (props: CompilerContainerProps) => {
{ (!showFilePathInput && state.useFileConfiguration) && {} : openFile} className="py-2 remixui_compilerConfigPath" >{configFilePath === '' ? 'No file selected.' : configFilePath} } @@ -861,7 +875,7 @@ export const CompilerContainer = (props: CompilerContainerProps) => { }> - '@custom:dev-run-script file_path'} direction='top'> + '@custom:dev-run-script file_path'} direction='top'> diff --git a/libs/remix-ui/solidity-compiler/src/lib/logic/compileTabLogic.ts b/libs/remix-ui/solidity-compiler/src/lib/logic/compileTabLogic.ts index 7497a529ae..10e08f86ed 100644 --- a/libs/remix-ui/solidity-compiler/src/lib/logic/compileTabLogic.ts +++ b/libs/remix-ui/solidity-compiler/src/lib/logic/compileTabLogic.ts @@ -131,6 +131,12 @@ export class CompileTabLogic { } else return false } + async isFoundryProject () { + if (this.api.getFileManagerMode() === 'localhost') { + return await this.api.fileExists('foundry.toml') + } else return false + } + runCompiler (externalCompType) { try { if (this.api.getFileManagerMode() === 'localhost') { diff --git a/libs/remix-ui/solidity-compiler/src/lib/solidity-compiler.tsx b/libs/remix-ui/solidity-compiler/src/lib/solidity-compiler.tsx index 827dea9f3f..3a9ce290e8 100644 --- a/libs/remix-ui/solidity-compiler/src/lib/solidity-compiler.tsx +++ b/libs/remix-ui/solidity-compiler/src/lib/solidity-compiler.tsx @@ -13,6 +13,7 @@ export const SolidityCompiler = (props: SolidityCompilerProps) => { const [state, setState] = useState({ isHardhatProject: false, isTruffleProject: false, + isFoundryProject: false, workspaceName: '', currentFile, configFilePath: 'compiler_config.json', @@ -67,9 +68,10 @@ export const SolidityCompiler = (props: SolidityCompilerProps) => { api.onSetWorkspace = async (isLocalhost: boolean, workspaceName: string) => { const isHardhat = isLocalhost && await compileTabLogic.isHardhatProject() - const isTruffle = await compileTabLogic.isTruffleProject() + const isTruffle = isLocalhost && await compileTabLogic.isTruffleProject() + const isFoundry = isLocalhost && await compileTabLogic.isFoundryProject() setState(prevState => { - return { ...prevState, currentFile, isHardhatProject: isHardhat, workspaceName: workspaceName, isTruffleProject: isTruffle } + return { ...prevState, currentFile, isHardhatProject: isHardhat, workspaceName: workspaceName, isTruffleProject: isTruffle, isFoundryProject: isFoundry } }) } @@ -171,6 +173,7 @@ export const SolidityCompiler = (props: SolidityCompilerProps) => { isHardhatProject={state.isHardhatProject} workspaceName={state.workspaceName} isTruffleProject={state.isTruffleProject} + isFoundryProject={state.isFoundryProject} compileTabLogic={compileTabLogic} tooltip={toast} modal={modal} diff --git a/libs/remix-ui/solidity-compiler/src/lib/types/index.ts b/libs/remix-ui/solidity-compiler/src/lib/types/index.ts index 38f4edb61e..4fd80bcf7d 100644 --- a/libs/remix-ui/solidity-compiler/src/lib/types/index.ts +++ b/libs/remix-ui/solidity-compiler/src/lib/types/index.ts @@ -11,6 +11,7 @@ export interface CompilerContainerProps { compileTabLogic: CompileTabLogic, isHardhatProject: boolean, isTruffleProject: boolean, + isFoundryProject: boolean, workspaceName: string, tooltip: (message: string | JSX.Element) => void, modal: (title: string, message: string | JSX.Element, okLabel: string, okFn: () => void, cancelLabel?: string, cancelFn?: () => void) => void, diff --git a/libs/remix-ui/solidity-unit-testing/src/lib/solidity-unit-testing.tsx b/libs/remix-ui/solidity-unit-testing/src/lib/solidity-unit-testing.tsx index 30f1b2e0df..8f55fe2ea7 100644 --- a/libs/remix-ui/solidity-unit-testing/src/lib/solidity-unit-testing.tsx +++ b/libs/remix-ui/solidity-unit-testing/src/lib/solidity-unit-testing.tsx @@ -677,7 +677,7 @@ export const SolidityUnitTesting = (props: Record) => { // eslint-d