Merge branch 'master' into script-runner-custom

pull/5324/head
bunsenstraat 3 weeks ago committed by GitHub
commit 30a6f12ce3
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
  1. 4
      .github/workflows/pr-reminder.yml
  2. 26
      apps/contract-verification/src/app/components/SearchableChainDropdown.tsx
  3. 6
      apps/contract-verification/src/app/views/VerifyView.tsx
  4. 2
      apps/remix-dapp/src/utils/txRunner.ts
  5. 2
      apps/remix-ide-e2e/src/commands/hideMetaMaskPopup.ts
  6. 33
      apps/remix-ide-e2e/src/tests/editorHoverContext.test.ts
  7. 188
      apps/remix-ide-e2e/src/tests/metamask.test.ts
  8. 123
      apps/remix-ide-e2e/src/tests/providers.test.ts
  9. 38
      apps/remix-ide/src/app/providers/abstract-provider.tsx
  10. 2
      apps/remix-ide/src/app/tabs/web3-provider.js
  11. 57
      apps/remix-ide/src/app/udapp/run-tab.tsx
  12. 10
      apps/remix-ide/src/blockchain/blockchain.tsx
  13. 6
      libs/remix-lib/src/execution/txRunnerWeb3.ts
  14. 4
      libs/remix-ui/editor/src/lib/remix-ui-editor.tsx
  15. 11
      libs/remix-ui/run-tab/src/lib/actions/deploy.ts

@ -3,7 +3,7 @@ name: PRs reviews reminder
on: on:
schedule: schedule:
- cron: "0 8 * * 1-5" - cron: "0 8 * * 1-5"
- cron: '0 9 * * 1-5' - cron: '58 9 * * 1-5'
workflow_dispatch: workflow_dispatch:
jobs: jobs:
@ -19,7 +19,7 @@ jobs:
webhook-url: ${{ secrets.DISCORD_WEBHOOK_URL }} webhook-url: ${{ secrets.DISCORD_WEBHOOK_URL }}
freeze-date: '2024-11-04T18:00:00Z' freeze-date: '2024-11-04T18:00:00Z'
- name: Reminder for standup - name: Reminder for standup
if: github.event.schedule == '0 9 * * 1-5' if: github.event.schedule == '58 9 * * 1-5'
uses: Aniket-Engg/pr-reviews-reminder-action@master uses: Aniket-Engg/pr-reviews-reminder-action@master
env: env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

@ -37,7 +37,6 @@ export const SearchableChainDropdown: React.FC<DropdownProps> = ({ label, id, se
const [searchTerm, setSearchTerm] = useState(selectedChain ? getChainDescriptor(selectedChain) : '') const [searchTerm, setSearchTerm] = useState(selectedChain ? getChainDescriptor(selectedChain) : '')
const [isOpen, setIsOpen] = useState(false) const [isOpen, setIsOpen] = useState(false)
const [filteredOptions, setFilteredOptions] = useState<Chain[]>(dropdownChains)
const dropdownRef = useRef<HTMLDivElement>(null) const dropdownRef = useRef<HTMLDivElement>(null)
const fuse = new Fuse(dropdownChains, { const fuse = new Fuse(dropdownChains, {
@ -45,14 +44,7 @@ export const SearchableChainDropdown: React.FC<DropdownProps> = ({ label, id, se
threshold: 0.3, threshold: 0.3,
}) })
useEffect(() => { const filteredOptions = searchTerm ? fuse.search(searchTerm).map(({ item }) => item) : dropdownChains
if (searchTerm === '') {
setFilteredOptions(dropdownChains)
} else {
const result = fuse.search(searchTerm)
setFilteredOptions(result.map(({ item }) => item))
}
}, [searchTerm, dropdownChains])
// Close dropdown when user clicks outside // Close dropdown when user clicks outside
useEffect(() => { useEffect(() => {
@ -99,15 +91,13 @@ export const SearchableChainDropdown: React.FC<DropdownProps> = ({ label, id, se
{/* Add ref here */} {/* Add ref here */}
<label htmlFor={id}>{label}</label> <label htmlFor={id}>{label}</label>
<input type="text" value={searchTerm} onChange={handleInputChange} onClick={openDropdown} placeholder="Select a chain" className="form-control" /> <input type="text" value={searchTerm} onChange={handleInputChange} onClick={openDropdown} placeholder="Select a chain" className="form-control" />
{isOpen && ( <ul className="dropdown-menu show w-100 bg-light" style={{ maxHeight: '400px', overflowY: 'auto', display: isOpen ? 'initial' : 'none' }}>
<ul className="dropdown-menu show w-100 bg-light" style={{ maxHeight: '400px', overflowY: 'auto' }}> {filteredOptions.map((chain) => (
{filteredOptions.map((chain) => ( <li key={chain.chainId} onClick={() => handleOptionClick(chain)} className={`dropdown-item text-dark ${selectedChain?.chainId === chain.chainId ? 'active' : ''}`} style={{ cursor: 'pointer', whiteSpace: 'normal' }}>
<li key={chain.chainId} onClick={() => handleOptionClick(chain)} className={`dropdown-item text-dark ${selectedChain?.chainId === chain.chainId ? 'active' : ''}`} style={{ cursor: 'pointer', whiteSpace: 'normal' }}> {getChainDescriptor(chain)}
{getChainDescriptor(chain)} </li>
</li> ))}
))} </ul>
</ul>
)}
</div> </div>
) )
} }

@ -68,9 +68,9 @@ export const VerifyView = () => {
name: verifierId as VerifierIdentifier, name: verifierId as VerifierIdentifier,
} }
receipts.push({ verifierInfo, status: 'pending', contractId, isProxyReceipt: false, failedChecks: 0 }) receipts.push({ verifierInfo, status: 'pending', contractId, isProxyReceipt: false, failedChecks: 0 })
if (enabledVerifiers.Blockscout) await sendToMatomo('verify', "verifyWith: Blockscout On: " + selectedChain + " IsProxy: " + (hasProxy && !proxyAddress)) if (enabledVerifiers.Blockscout) await sendToMatomo('verify', "verifyWith: Blockscout On: " + selectedChain?.chainId + " IsProxy: " + (hasProxy && !proxyAddress))
if (enabledVerifiers.Etherscan) await sendToMatomo('verify', "verifyWithEtherscan On: " + selectedChain + " IsProxy: " + (hasProxy && !proxyAddress)) if (enabledVerifiers.Etherscan) await sendToMatomo('verify', "verifyWithEtherscan On: " + selectedChain?.chainId + " IsProxy: " + (hasProxy && !proxyAddress))
if (enabledVerifiers.Sourcify) await sendToMatomo('verify', "verifyWithSourcify On: " + selectedChain + " IsProxy: " + (hasProxy && !proxyAddress)) if (enabledVerifiers.Sourcify) await sendToMatomo('verify', "verifyWithSourcify On: " + selectedChain?.chainId + " IsProxy: " + (hasProxy && !proxyAddress))
} }
const newSubmittedContract: SubmittedContract = { const newSubmittedContract: SubmittedContract = {

@ -267,7 +267,7 @@ export class TxRunner {
}; };
} catch (error: any) { } catch (error: any) {
console.log( console.log(
`Send transaction failed: ${error.message} . if you use an injected provider, please check it is properly unlocked. ` `Send transaction failed: ${error.message || error.error} . if you use an injected provider, please check it is properly unlocked. `
); );
return { error }; return { error };
} }

@ -5,7 +5,7 @@ class HideMetaMaskPopup extends EventEmitter {
command(this: NightwatchBrowser) { command(this: NightwatchBrowser) {
browser browser
.pause(5000) .pause(5000)
.isVisible({ .isPresent({
selector: 'button[data-testid="popover-close"]', selector: 'button[data-testid="popover-close"]',
locateStrategy: 'css selector', locateStrategy: 'css selector',
suppressNotFoundErrors: true, suppressNotFoundErrors: true,

@ -19,16 +19,17 @@ module.exports = {
}, },
'Should load the test file #group1': function (browser: NightwatchBrowser) { 'Should load the test file #group1': function (browser: NightwatchBrowser) {
browser.openFile('contracts') browser.openFile('contracts')
.openFile('contracts/3_Ballot.sol') .addFile('contracts/3_BallotHover.sol', {
.waitForElementVisible('#editorView') content: BallotWithARefToOwner
.setEditorValue(BallotWithARefToOwner) })
.pause(4000) // wait for the compiler to finish
.scrollToLine(37) .scrollToLine(37)
.useXpath().waitForElementVisible("//*[@class='view-line' and contains(.,'gas')]")
}, },
'Should show hover over contract in editor #group1': function (browser: NightwatchBrowser) { 'Should show hover over contract in editor #group1': function (browser: NightwatchBrowser) {
const path = "//*[contains(text(),'BallotHoverTest')]" const path = "//*[contains(text(),'BallotHoverTest')]"
checkEditorHoverContent(browser, path, 'contract BallotHoverTest is BallotHoverTest') checkEditorHoverContent(browser, path, 'contract BallotHoverTest is BallotHoverTest')
checkEditorHoverContent(browser, path, 'contracts/3_Ballot.sol 10:0') checkEditorHoverContent(browser, path, 'contracts/3_BallotHover.sol 10:0')
checkEditorHoverContent(browser, path, '@title Ballot') checkEditorHoverContent(browser, path, '@title Ballot')
}, },
'Should show hover over var definition in editor #group1': function (browser: NightwatchBrowser) { 'Should show hover over var definition in editor #group1': function (browser: NightwatchBrowser) {
@ -88,13 +89,13 @@ module.exports = {
}, },
'Add token file #group1': function (browser: NightwatchBrowser) { 'Add token file #group1': function (browser: NightwatchBrowser) {
browser browser
.clickLaunchIcon('solidity') .clickLaunchIcon('solidity')
.setSolidityCompilerVersion('soljson-v0.8.20+commit.a1b79de6.js') .setSolidityCompilerVersion('soljson-v0.8.20+commit.a1b79de6.js')
.click('*[data-id="scConfigExpander"]') .click('*[data-id="scConfigExpander"]')
.setValue('#evmVersionSelector', 'berlin') // set target EVM for parser to berlin .setValue('#evmVersionSelector', 'berlin') // set target EVM for parser to berlin
.addFile('contracts/mytoken.sol', { .addFile('contracts/mytoken.sol', {
content: myToken content: myToken
}).useXpath().waitForElementVisible("//*[@class='view-line' and contains(.,'gas')]") }).useXpath().waitForElementVisible("//*[@class='view-line' and contains(.,'gas')]")
}, },
// here we change quickly between files to test the files being parsed correctly when switching between them // here we change quickly between files to test the files being parsed correctly when switching between them
'Should show ERC20 hover over contract in editor #group1': function (browser: NightwatchBrowser) { 'Should show ERC20 hover over contract in editor #group1': function (browser: NightwatchBrowser) {
@ -103,8 +104,12 @@ module.exports = {
const expectedContent = 'contract ERC20Burnable is ERC20Burnable, ERC20, IERC20Errors, IERC20Metadata, IERC20, Context' const expectedContent = 'contract ERC20Burnable is ERC20Burnable, ERC20, IERC20Errors, IERC20Metadata, IERC20, Context'
checkEditorHoverContent(browser, path, expectedContent, 25) checkEditorHoverContent(browser, path, expectedContent, 25)
}, },
'Go back to ballot file': function (browser: NightwatchBrowser) { 'Go back to ballot file #group1': function (browser: NightwatchBrowser) {
browser.openFile('contracts/3_Ballot.sol') browser
.waitForElementVisible('*[data-id="treeViewDivDraggableItem.deps"]')
.click('*[data-id="treeViewDivDraggableItem.deps"]')
.openFile('contracts/3_BallotHover.sol')
.scrollToLine(37)
.useXpath().waitForElementVisible("//*[@class='view-line' and contains(.,'gas')]") .useXpath().waitForElementVisible("//*[@class='view-line' and contains(.,'gas')]")
}, },
'Should show hover over function in editor again #group1': function (browser: NightwatchBrowser) { 'Should show hover over function in editor again #group1': function (browser: NightwatchBrowser) {

@ -0,0 +1,188 @@
'use strict'
import { NightwatchBrowser } from 'nightwatch'
import init from '../helpers/init'
const passphrase = process.env.account_passphrase
const password = process.env.account_password
const extension_id = 'nkbihfbeogaeaoehlefnkodbefgpgknn'
const extension_url = `chrome-extension://${extension_id}/home.html`
const checkBrowserIsChrome = function (browser: NightwatchBrowser) {
return browser.browserName.indexOf('chrome') > -1
}
const checkAlerts = function (browser: NightwatchBrowser) {
browser.isVisible({
selector: '//*[contains(.,"not have enough")]',
locateStrategy: 'xpath',
suppressNotFoundErrors: true,
timeout: 3000
}, (okVisible) => {
if (okVisible.value) {
browser.assert.fail('Not enough ETH in test account!!')
browser.end()
}
})
}
const tests = {
'@disabled': true,
before: function (browser: NightwatchBrowser, done: VoidFunction) {
init(browser, done)
},
'@sources': function () {
return sources
},
'Should connect to Sepolia Test Network using MetaMask #group1': function (browser: NightwatchBrowser) {
if (!checkBrowserIsChrome(browser)) return
browser.waitForElementPresent('*[data-id="remixIdeSidePanel"]')
.setupMetamask(passphrase, password)
.useCss().switchBrowserTab(0)
.refreshPage()
.waitForElementVisible('*[data-id="remixIdeIconPanel"]', 10000)
.click('*[data-id="landingPageStartSolidity"]')
.clickLaunchIcon('udapp')
.switchEnvironment('injected-MetaMask')
.waitForElementPresent('*[data-id="settingsNetworkEnv"]')
.assert.containsText('*[data-id="settingsNetworkEnv"]', 'Sepolia (11155111) network')
.pause(5000)
.switchBrowserWindow(extension_url, 'MetaMask', (browser) => {
browser
.hideMetaMaskPopup()
.waitForElementVisible('*[data-testid="page-container-footer-next"]', 60000)
.click('*[data-testid="page-container-footer-next"]') // this connects the metamask account to remix
.pause(2000)
.waitForElementVisible('*[data-testid="page-container-footer-next"]', 60000)
.click('*[data-testid="page-container-footer-next"]')
// .waitForElementVisible('*[data-testid="popover-close"]')
// .click('*[data-testid="popover-close"]')
})
.switchBrowserTab(0) // back to remix
},
'Should add a contract file #group1': function (browser: NightwatchBrowser) {
if (!checkBrowserIsChrome(browser)) return
browser.waitForElementVisible('*[data-id="remixIdeSidePanel"]')
.clickLaunchIcon('filePanel')
.addFile('Greet.sol', sources[0]['Greet.sol'])
.clickLaunchIcon('udapp')
.waitForElementVisible('*[data-id="Deploy - transact (not payable)"]', 45000) // wait for the contract to compile
},
'Should deploy contract on Sepolia Test Network using MetaMask #group1': function (browser: NightwatchBrowser) {
if (!checkBrowserIsChrome(browser)) return
browser.clearConsole().waitForElementPresent('*[data-id="runTabSelectAccount"] option', 45000)
.clickLaunchIcon('filePanel')
.openFile('Greet.sol')
.clickLaunchIcon('udapp')
.waitForElementPresent('*[data-id="Deploy - transact (not payable)"]')
.click('*[data-id="Deploy - transact (not payable)"]')
.pause(5000)
.clearConsole()
.perform((done) => {
browser.switchBrowserWindow(extension_url, 'MetaMask', (browser) => {
checkAlerts(browser)
browser
.maximizeWindow()
.hideMetaMaskPopup()
.waitForElementPresent('[data-testid="page-container-footer-next"]')
.click('[data-testid="page-container-footer-next"]') // approve the tx
.switchBrowserTab(0) // back to remix
.waitForElementContainsText('*[data-id="terminalJournal"]', 'view on etherscan', 60000)
.waitForElementContainsText('*[data-id="terminalJournal"]', 'from: 0x76a...2708f', 60000)
.perform(() => done())
})
})
},
'Should deploy faulty contract on Sepolia Test Network using MetaMask and show error in terminal #group1': function (browser: NightwatchBrowser) {
browser
.clearConsole()
.clickLaunchIcon('filePanel')
.addFile('faulty.sol', sources[0]['faulty.sol'])
.clickLaunchIcon('udapp')
.waitForElementPresent('*[data-id="Deploy - transact (not payable)"]')
.click('*[data-id="Deploy - transact (not payable)"]')
.pause(5000)
.saveScreenshot('./reports/screenshots/metamask_7.png')
.waitForElementVisible('*[data-id="udappNotifyModalDialogModalBody-react"]', 60000)
.click('[data-id="udappNotify-modal-footer-cancel-react"]')
.saveScreenshot('./reports/screenshots/metamask_8.png')
.waitForElementVisible({
locateStrategy: 'xpath',
selector: "//span[@class='text-log' and contains(., 'errored')]"
})
},
'Should deploy contract on Sepolia Test Network using MetaMask again #group1': function (browser: NightwatchBrowser) {
if (!checkBrowserIsChrome(browser)) return
browser.clearConsole().waitForElementPresent('*[data-id="runTabSelectAccount"] option', 45000)
.clickLaunchIcon('filePanel')
.openFile('Greet.sol')
.clickLaunchIcon('udapp')
.waitForElementPresent('*[data-id="Deploy - transact (not payable)"]')
.click('*[data-id="Deploy - transact (not payable)"]')
.pause(5000)
.clearConsole()
.perform((done) => {
browser.switchBrowserWindow(extension_url, 'MetaMask', (browser) => {
checkAlerts(browser)
browser
.maximizeWindow()
.hideMetaMaskPopup()
.waitForElementPresent('[data-testid="page-container-footer-next"]')
.click('[data-testid="page-container-footer-next"]') // approve the tx
.switchBrowserTab(0) // back to remix
.waitForElementContainsText('*[data-id="terminalJournal"]', 'view on etherscan', 60000)
.waitForElementContainsText('*[data-id="terminalJournal"]', 'from: 0x76a...2708f', 60000)
.perform(() => done())
})
})
}
}
const branch = process.env.CIRCLE_BRANCH;
const isMasterBranch = (branch === 'master' || branch === 'remix_beta');
if (!checkBrowserIsChrome(browser)) {
module.exports = {}
} else {
module.exports = {
...(branch ? (isMasterBranch ? tests : {}) : tests),
};
}
const sources = [
{
'Greet.sol': {
content:
`
pragma solidity ^0.8.0;
contract HelloWorld {
string public message;
fallback () external {
message = 'Hello World!';
}
function greet(string memory _message) public {
message = _message;
}
}`
},
'faulty.sol': {
content: `// SPDX-License-Identifier: GPL-3.0
pragma solidity >=0.8.2 <0.9.0;
contract Test {
error O_o(uint256);
constructor() {
revert O_o(block.timestamp);
}
}`
}
}
]

@ -3,54 +3,119 @@ import { NightwatchBrowser } from 'nightwatch'
import init from '../helpers/init' import init from '../helpers/init'
module.exports = { module.exports = {
'@disabled': true,
before: function (browser: NightwatchBrowser, done: VoidFunction) { before: function (browser: NightwatchBrowser, done: VoidFunction) {
init(browser, done, 'http://127.0.0.1:8080', false) init(browser, done, 'http://127.0.0.1:8080', false)
}, },
'Should switch to ganache provider, set a custom URL and fail to connect': function (browser: NightwatchBrowser) { 'Should switch to ganache provider, set a custom URL and fail to connect #group1': function (browser: NightwatchBrowser) {
browser.waitForElementVisible('div[data-id="remixIdeIconPanel"]', 10000) browser.waitForElementVisible('div[data-id="remixIdeIconPanel"]', 10000)
.clickLaunchIcon('udapp') .clickLaunchIcon('udapp')
.switchEnvironment('ganache-provider') .switchEnvironment('ganache-provider')
.waitForElementVisible('*[data-id="ganache-providerModalDialogModalBody-react"]') .waitForElementVisible('*[data-id="ganache-providerModalDialogModalBody-react"]')
.execute(() => { .execute(() => {
(document.querySelector('*[data-id="ganache-providerModalDialogModalBody-react"] input') as any).focus() (document.querySelector('*[data-id="ganache-providerModalDialogModalBody-react"] input') as any).focus()
}, [], () => {}) }, [], () => { })
.clearValue('*[data-id="ganache-providerModalDialogModalBody-react"] input') .clearValue('*[data-id="ganache-providerModalDialogModalBody-react"] input')
.setValue('*[data-id="ganache-providerModalDialogModalBody-react"] input', 'http://127.0.0.1:8084') .setValue('*[data-id="ganache-providerModalDialogModalBody-react"] input', 'http://127.0.0.1:8084')
.modalFooterOKClick('ganache-provider') .modalFooterOKClick('ganache-provider')
.waitForElementContainsText('*[data-id="ganache-providerModalDialogModalBody-react"]', 'Error while connecting to the provider') .waitForElementVisible({
.modalFooterOKClick('ganache-provider') locateStrategy: 'xpath',
.waitForElementNotVisible('*[data-id="ganache-providerModalDialogModalBody-react"]') selector: "//span[@class='text-danger' and contains(., 'missing response')]"
})
.waitForElementPresent({ selector: `[data-id="selected-provider-ganache-provider"]`, timeout: 5000 })
.pause(1000) .pause(1000)
}, },
'Should switch to ganache provider, use the default ganache URL and succeed to connect': function (browser: NightwatchBrowser) { 'Should switch to ganache provider, use the default ganache URL and succeed to connect #group1': function (browser: NightwatchBrowser) {
browser.switchEnvironment('ganache-provider') browser
.waitForElementVisible('*[data-id="ganache-providerModalDialogModalBody-react"]') .switchEnvironment('vm-cancun')
.modalFooterOKClick('ganache-provider') .pause(2000)
.waitForElementContainsText('*[data-id="settingsNetworkEnv"]', 'Custom (') .switchEnvironment('ganache-provider')
.waitForElementVisible('*[data-id="ganache-providerModalDialogModalBody-react"]')
.modalFooterOKClick('ganache-provider')
.waitForElementContainsText('*[data-id="settingsNetworkEnv"]', 'Custom (')
.waitForElementVisible({ selector: `[data-id="selected-provider-ganache-provider"]`, timeout: 5000 })
}, },
'Should switch to foundry provider, set a custom URL and fail to connect': function (browser: NightwatchBrowser) { 'Should switch to foundry provider, set a custom URL and fail to connect #group1': function (browser: NightwatchBrowser) {
browser.waitForElementVisible('div[data-id="remixIdeIconPanel"]', 10000) browser.waitForElementVisible('div[data-id="remixIdeIconPanel"]', 10000)
.switchEnvironment('foundry-provider') .switchEnvironment('foundry-provider')
.waitForElementVisible('*[data-id="foundry-providerModalDialogModalBody-react"]') .waitForElementVisible('*[data-id="foundry-providerModalDialogModalBody-react"]')
.execute(() => { .execute(() => {
(document.querySelector('*[data-id="foundry-providerModalDialogModalBody-react"] input') as any).focus() (document.querySelector('*[data-id="foundry-providerModalDialogModalBody-react"] input') as any).focus()
}, [], () => {}) }, [], () => { })
.clearValue('*[data-id="foundry-providerModalDialogModalBody-react"] input') .clearValue('*[data-id="foundry-providerModalDialogModalBody-react"] input')
.setValue('*[data-id="foundry-providerModalDialogModalBody-react"] input', 'http://127.0.0.1:8084') .setValue('*[data-id="foundry-providerModalDialogModalBody-react"] input', 'http://127.0.0.1:8084')
.modalFooterOKClick('foundry-provider') .modalFooterOKClick('foundry-provider')
.waitForElementContainsText('*[data-id="foundry-providerModalDialogModalBody-react"]', 'Error while connecting to the provider') .pause(1000)
.modalFooterOKClick('foundry-provider')
.waitForElementNotVisible('*[data-id="foundry-providerModalDialogModalBody-react"]') },
.pause(1000) 'Should switch to foundry provider, use the default foundry URL and succeed to connect #group1': !function (browser: NightwatchBrowser) {
},
'Should switch to foundry provider, use the default foundry URL and succeed to connect': function (browser: NightwatchBrowser) {
browser.switchEnvironment('foundry-provider') browser.switchEnvironment('foundry-provider')
.waitForElementVisible('*[data-id="foundry-providerModalDialogModalBody-react"]') .waitForElementVisible('*[data-id="foundry-providerModalDialogModalBody-react"]')
.modalFooterOKClick('foundry-provider') .modalFooterOKClick('foundry-provider')
.waitForElementContainsText('*[data-id="settingsNetworkEnv"]', 'Custom (') .waitForElementContainsText('*[data-id="settingsNetworkEnv"]', 'Custom (')
},
'Should switch to custom provider #group2': function (browser: NightwatchBrowser) {
browser.waitForElementVisible('div[data-id="remixIdeIconPanel"]', 10000)
.clickLaunchIcon('udapp')
.switchEnvironment('ganache-provider')
.waitForElementVisible('*[data-id="ganache-providerModalDialogModalBody-react"]')
.execute(() => {
(document.querySelector('*[data-id="ganache-providerModalDialogModalBody-react"] input') as any).focus()
}, [], () => { })
.clearValue('*[data-id="ganache-providerModalDialogModalBody-react"] input')
.setValue('*[data-id="ganache-providerModalDialogModalBody-react"] input', 'https://scroll-rpc.publicnode.com')
.modalFooterOKClick('ganache-provider')
.pause(100)
.waitForElementPresent({ selector: `[data-id="selected-provider-ganache-provider"]`, timeout: 5000 })
.pause(1000)
},
'execute script #group2': function (browser: NightwatchBrowser) {
browser.clickLaunchIcon('filePanel')
.addFile('testScript.ts', { content: testScript })
.clearConsole()
.pause(10000)
.waitForElementVisible('*[data-id="play-editor"]')
.click('*[data-id="play-editor"]')
.waitForElementVisible({
locateStrategy: 'xpath',
selector: "//span[@class='text-danger' and contains(., 'exceed maximum block range')]"
})
.waitForElementPresent({ selector: `[data-id="selected-provider-ganache-provider"]`, timeout: 5000 })
} }
} }
const testScript = `
// Importing necessary libraries from Ethers.js for interaction with Ethereum blockchain.
import { ethers } from "hardhat";
// https://scroll-rpc.publicnode.com
async function main() {
// Setting up provider (RPC URL) to interact with your chosen Ethereum chain,
const [deployer] = await ethers.getSigners();
try{
let provider;
if(!provider){
provider=ethers.provider;
}
const contractAddress = "0x2bC16Bf30435fd9B3A3E73Eb759176C77c28308D"; // Replace with your smart contract's address.
// Retrieving all events of a specific kind from the blockchain
let logs = await provider.getLogs({address:contractAddress, fromBlock: '0x332f23',toBlock: '0x384410', topics: ['0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef']});
console.log("Got Logs ",logs)
}catch(error){
}
}
main()`

@ -26,8 +26,8 @@ export type RejectRequest = (error: JsonDataResult) => void
export type SuccessRequest = (data: JsonDataResult) => void export type SuccessRequest = (data: JsonDataResult) => void
export interface IProvider { export interface IProvider {
options: {[id: string]: any} options: { [id: string]: any }
init(): Promise<{[id: string]: any}> init(): Promise<{ [id: string]: any }>
body(): JSX.Element body(): JSX.Element
sendAsync(data: JsonDataRequest): Promise<JsonDataResult> sendAsync(data: JsonDataRequest): Promise<JsonDataResult>
} }
@ -38,7 +38,7 @@ export abstract class AbstractProvider extends Plugin implements IProvider {
defaultUrl: string defaultUrl: string
connected: boolean connected: boolean
nodeUrl: string nodeUrl: string
options: {[id: string]: any} = {} options: { [id: string]: any } = {}
constructor(profile, blockchain, defaultUrl) { constructor(profile, blockchain, defaultUrl) {
super(profile) super(profile)
@ -102,24 +102,16 @@ export abstract class AbstractProvider extends Plugin implements IProvider {
sendAsync(data: JsonDataRequest): Promise<JsonDataResult> { sendAsync(data: JsonDataRequest): Promise<JsonDataResult> {
// eslint-disable-next-line no-async-promise-executor // eslint-disable-next-line no-async-promise-executor
return new Promise(async (resolve, reject) => { return new Promise(async (resolve, reject) => {
if (!this.provider) return reject({ jsonrpc: '2.0', id: data.id, error: { message: 'provider node set', code: -32603 } } as JsonDataResult) if (!this.provider) return reject({ jsonrpc: '2.0', id: data.id, error: { message: 'provider not set', code: -32603 } } as JsonDataResult)
this.sendAsyncInternal(data, resolve, reject) this.sendAsyncInternal(data, resolve, reject)
}) })
} }
private async switchAway(showError) { private async switchAway(showError: boolean, msg: string) {
if (!this.provider) return if (!this.provider) return
this.provider = null
this.connected = false
if (showError) { if (showError) {
const modalContent: AlertModal = { this.call('terminal', 'log', { type: 'error', value: 'Error while querying the provider: ' + msg })
id: this.profile.name,
title: this.profile.displayName,
message: `Error while connecting to the provider, provider not connected`
}
this.call('notification', 'alert', modalContent)
} }
await this.call('udapp', 'setEnvironmentMode', { context: 'vm-cancun' })
return return
} }
@ -130,10 +122,22 @@ export abstract class AbstractProvider extends Plugin implements IProvider {
resolve({ jsonrpc: '2.0', result, id: data.id }) resolve({ jsonrpc: '2.0', result, id: data.id })
} catch (error) { } catch (error) {
if (error && error.message && error.message.includes('SERVER_ERROR')) { if (error && error.message && error.message.includes('SERVER_ERROR')) {
this.switchAway(true) try {
// replace escaped quotes with normal quotes
const errorString = String(error.message).replace(/\\"/g, '"');
const messageMatches = Array.from(errorString.matchAll(/"message":"(.*?)"/g));
// Extract the message values
const messages = messageMatches.map(match => match[1]);
if (messages && messages.length > 0) {
this.switchAway(true, messages[0])
} else {
this.switchAway(true, error.message ? error.message : error.error ? error.error : error)
}
} catch (error) {
this.switchAway(true, error.message ? error.message : error.error ? error.error : error)
}
} }
error.code = -32603 reject({ jsonrpc: '2.0', error: { message: error.message, code: -32603 }, id: data.id })
reject({ jsonrpc: '2.0', error, id: data.id })
} }
} else { } else {
const result = data.method === 'net_listening' ? 'canceled' : [] const result = data.method === 'net_listening' ? 'canceled' : []

@ -86,7 +86,7 @@ export class Web3ProviderModule extends Plugin {
try { try {
resultFn(null, await provider.sendAsync(payload)) resultFn(null, await provider.sendAsync(payload))
} catch (e) { } catch (e) {
resultFn(e.error ? new Error(e.error) : new Error(e)) resultFn(e.error ? e.error : e)
} }
} else { } else {
reject(new Error('User denied permission')) reject(new Error('User denied permission'))

@ -194,22 +194,7 @@ export class RunTab extends ViewPlugin {
if (options['fork']) this.fork = options['fork'] if (options['fork']) this.fork = options['fork']
} }
}, },
provider: { provider: new Provider(udapp, name)
sendAsync (payload) {
return udapp.call(name, 'sendAsync', payload)
},
async request (payload) {
try {
const requestResult = await udapp.call(name, 'sendAsync', payload)
if (requestResult.error) {
throw new Error(requestResult.error.message)
}
return requestResult.result
} catch (err) {
throw new Error(err.message)
}
}
}
}) })
} }
@ -302,3 +287,43 @@ export class RunTab extends ViewPlugin {
this.addInstance(address, contractObject.abi, contractObject.name) this.addInstance(address, contractObject.abi, contractObject.name)
} }
} }
class Provider {
udapp: RunTab
name: string
constructor(udapp, name) {
this.udapp = udapp
this.name = name
}
sendAsync (payload) {
return this.udapp.call(this.name, 'sendAsync', payload)
}
request (payload): Promise<any> {
return new Promise((resolve, reject) => {
this.udapp.call(this.name, 'sendAsync', payload).then((response) => {
if (response.error) {
reject(response.error.message)
} else {
resolve(response)
}
}).catch((err) => {
if (typeof err === 'string') {
reject(err)
} else if (err.error && err.error.message) {
reject(err.error.message)
} else if (err.error && typeof err.error === 'string') {
reject(err.error)
} else {
let e
try {
e = JSON.stringify(err)
} catch (e) {
reject('unknown error')
return
}
reject(e)
}
})
})
}
}

@ -247,7 +247,7 @@ export class Blockchain extends Plugin {
args, args,
(error, data) => { (error, data) => {
if (error) { if (error) {
return statusCb(`creation of ${selectedContract.name} errored: ${error.message ? error.message : error}`) return statusCb(`creation of ${selectedContract.name} errored: ${error.message ? error.message : error.error ? error.error : error}`)
} }
statusCb(`creation of ${selectedContract.name} pending...`) statusCb(`creation of ${selectedContract.name} pending...`)
@ -272,7 +272,7 @@ export class Blockchain extends Plugin {
selectedContract.bytecodeLinkReferences, selectedContract.bytecodeLinkReferences,
(error, data) => { (error, data) => {
if (error) { if (error) {
return statusCb(`creation of ${selectedContract.name} errored: ${error.message ? error.message : error}`) return statusCb(`creation of ${selectedContract.name} errored: ${error.message ? error.message : error.error ? error.error : error}`)
} }
statusCb(`creation of ${selectedContract.name} pending...`) statusCb(`creation of ${selectedContract.name} pending...`)
@ -485,7 +485,7 @@ export class Blockchain extends Plugin {
this.runTx({ data: data, useCall: false }, confirmationCb, continueCb, promptCb, (error, txResult, address) => { this.runTx({ data: data, useCall: false }, confirmationCb, continueCb, promptCb, (error, txResult, address) => {
if (error) { if (error) {
return finalCb(`creation of ${selectedContract.name} errored: ${error.message ? error.message : error}`) return finalCb(`creation of ${selectedContract.name} errored: ${error.message ? error.message : error.error ? error.error : error}`)
} }
if (txResult.receipt.status === false || txResult.receipt.status === '0x0' || txResult.receipt.status === 0) { if (txResult.receipt.status === false || txResult.receipt.status === '0x0' || txResult.receipt.status === 0) {
return finalCb(`creation of ${selectedContract.name} errored: transaction execution failed`) return finalCb(`creation of ${selectedContract.name} errored: transaction execution failed`)
@ -614,7 +614,7 @@ export class Blockchain extends Plugin {
callType, callType,
(error, data) => { (error, data) => {
if (error) { if (error) {
return logCallback(`${logMsg} errored: ${error.message ? error.message : error}`) return logCallback(`${logMsg} errored: ${error.message ? error.message : error.error ? error.error : error}`)
} }
if (!lookupOnly) { if (!lookupOnly) {
logCallback(`${logMsg} pending ... `) logCallback(`${logMsg} pending ... `)
@ -631,7 +631,7 @@ export class Blockchain extends Plugin {
const useCall = funABI.stateMutability === 'view' || funABI.stateMutability === 'pure' const useCall = funABI.stateMutability === 'view' || funABI.stateMutability === 'pure'
this.runTx({ to: address, data, useCall }, confirmationCb, continueCb, promptCb, (error, txResult, _address, returnValue) => { this.runTx({ to: address, data, useCall }, confirmationCb, continueCb, promptCb, (error, txResult, _address, returnValue) => {
if (error) { if (error) {
return logCallback(`${logMsg} errored: ${error.message ? error.message : error}`) return logCallback(`${logMsg} errored: ${error.message ? error.message : error.error ? error.error : error}`)
} }
if (lookupOnly) { if (lookupOnly) {
outputCb(returnValue) outputCb(returnValue)

@ -66,7 +66,7 @@ export class TxRunnerWeb3 {
const res = await (this.getWeb3() as any).eth.personal.sendTransaction({ ...tx, value }, { checkRevertBeforeSending: false, ignoreGasPricing: true }) const res = await (this.getWeb3() as any).eth.personal.sendTransaction({ ...tx, value }, { checkRevertBeforeSending: false, ignoreGasPricing: true })
cb(null, res.transactionHash) cb(null, res.transactionHash)
} catch (e) { } catch (e) {
console.log(`Send transaction failed: ${e.message} . if you use an injected provider, please check it is properly unlocked. `) console.log(`Send transaction failed: ${e.message || e.error} . if you use an injected provider, please check it is properly unlocked. `)
// in case the receipt is available, we consider that only the execution failed but the transaction went through. // in case the receipt is available, we consider that only the execution failed but the transaction went through.
// So we don't consider this to be an error. // So we don't consider this to be an error.
if (e.receipt) cb(null, e.receipt.transactionHash) if (e.receipt) cb(null, e.receipt.transactionHash)
@ -82,6 +82,10 @@ export class TxRunnerWeb3 {
const res = await this.getWeb3().eth.sendTransaction(tx, null, { checkRevertBeforeSending: false, ignoreGasPricing: true }) const res = await this.getWeb3().eth.sendTransaction(tx, null, { checkRevertBeforeSending: false, ignoreGasPricing: true })
cb(null, res.transactionHash) cb(null, res.transactionHash)
} catch (e) { } catch (e) {
if (!e.message) e.message = ''
if (e.error) {
e.message = e.message + ' ' + e.error
}
console.log(`Send transaction failed: ${e.message} . if you use an injected provider, please check it is properly unlocked. `) console.log(`Send transaction failed: ${e.message} . if you use an injected provider, please check it is properly unlocked. `)
// in case the receipt is available, we consider that only the execution failed but the transaction went through. // in case the receipt is available, we consider that only the execution failed but the transaction went through.
// So we don't consider this to be an error. // So we don't consider this to be an error.

@ -825,10 +825,6 @@ export const EditorUI = (props: EditorUIProps) => {
label: intl.formatMessage({ id: 'editor.explainFunction' }), label: intl.formatMessage({ id: 'editor.explainFunction' }),
contextMenuOrder: 1, // choose the order contextMenuOrder: 1, // choose the order
contextMenuGroupId: 'gtp', // create a new grouping contextMenuGroupId: 'gtp', // create a new grouping
keybindings: [
// Keybinding for Ctrl + Shift + E
monacoRef.current.KeyMod.CtrlCmd | monacoRef.current.KeyMod.Shift | monacoRef.current.KeyCode.KeyE
],
run: async () => { run: async () => {
const file = await props.plugin.call('fileManager', 'getCurrentFile') const file = await props.plugin.call('fileManager', 'getCurrentFile')
const context = await props.plugin.call('fileManager', 'readFile', file) const context = await props.plugin.call('fileManager', 'readFile', file)

@ -119,10 +119,19 @@ const getConfirmationCb = (plugin: RunTab, dispatch: React.Dispatch<any>, confir
export const continueHandler = (dispatch: React.Dispatch<any>, gasEstimationPrompt: (msg: string) => JSX.Element, error, continueTxExecution, cancelCb) => { export const continueHandler = (dispatch: React.Dispatch<any>, gasEstimationPrompt: (msg: string) => JSX.Element, error, continueTxExecution, cancelCb) => {
if (error) { if (error) {
let msg = typeof error !== 'string' ? error.message : error let msg = ''
if (typeof error === 'string') {
msg = error
}
if (error && error.innerError) { if (error && error.innerError) {
msg += '\n' + error.innerError msg += '\n' + error.innerError
} }
if (error && error.message) {
msg += '\n' + error.message
}
if (error && error.error) {
msg += '\n' + error.error
}
if (msg.includes('invalid opcode')) msg += '\nThe EVM version used by the selected environment is not compatible with the compiler EVM version.' if (msg.includes('invalid opcode')) msg += '\nThe EVM version used by the selected environment is not compatible with the compiler EVM version.'

Loading…
Cancel
Save