diff --git a/apps/remix-ide-e2e/nightwatch.ts b/apps/remix-ide-e2e/nightwatch.ts index 7ca74fd2a7..3f59c1549e 100644 --- a/apps/remix-ide-e2e/nightwatch.ts +++ b/apps/remix-ide-e2e/nightwatch.ts @@ -29,7 +29,13 @@ module.exports = { javascriptEnabled: true, acceptSslCerts: true, 'goog:chromeOptions': { - args: ['window-size=2560,1440', 'start-fullscreen', '--no-sandbox', '--headless', '--verbose'] + args: ['window-size=2560,1440', + 'start-fullscreen', + '--no-sandbox', + '--headless', + '--verbose', + "--user-agent=Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/104.0.0.0 Safari/537.36", + ] } } }, diff --git a/apps/remix-ide-e2e/src/tests/url.test.ts b/apps/remix-ide-e2e/src/tests/url.test.ts index b85b2caa1e..760d3eb6ae 100644 --- a/apps/remix-ide-e2e/src/tests/url.test.ts +++ b/apps/remix-ide-e2e/src/tests/url.test.ts @@ -41,6 +41,7 @@ const sources = [ ] module.exports = { + '@disabled': true, before: function (browser: NightwatchBrowser, done: VoidFunction) { init(browser, done, 'http://127.0.0.1:8080/#optimize=true&runs=300&evmVersion=istanbul&version=soljson-v0.7.4+commit.3f05b770.js', true) }, @@ -49,7 +50,7 @@ module.exports = { return sources }, - 'Should load the code from URL params (code param)': function (browser: NightwatchBrowser) { + 'Should load the code from URL params (code param) #group1': function (browser: NightwatchBrowser) { browser .waitForElementVisible('[for="autoCompile"]') .click('[for="autoCompile"]') // we set it too false in the local storage @@ -66,11 +67,11 @@ module.exports = { .getEditorValue((content) => { browser.assert.ok(content && content.indexOf( 'https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/access/Ownable.sol') !== -1, - 'code has not been loaded') + 'code has not been loaded') }) }, - 'Should load the code from URL params (url param)': function (browser: NightwatchBrowser) { + 'Should load the code from URL params (url param) #group1': function (browser: NightwatchBrowser) { browser .pause(5000) .url('http://127.0.0.1:8080/#optimize=true&runs=300&evmVersion=istanbul&version=soljson-v0.7.4+commit.3f05b770.js&url=https://github.com/ethereum/remix-project/blob/master/apps/remix-ide/contracts/app/solidity/mode.sol') @@ -80,11 +81,40 @@ module.exports = { .getEditorValue((content) => { browser.assert.ok(content && content.indexOf( 'proposals.length = _numProposals;') !== -1, - 'url has not been loaded') + 'url has not been loaded') }) }, - 'Should load the code from URL & code params': function (browser: NightwatchBrowser) { + 'Should load Etherscan verified contractss from URL "address" param) #group2': function (browser: NightwatchBrowser) { + browser + .pause(5000) + .url('http://127.0.0.1:8080/#address=0x56db08fb78bc6689a1ef66efd079083fed0e4915') + .refresh() + .pause(7000) + .currentWorkspaceIs('etherscan-code-sample') + .assert.elementPresent('*[data-id=treeViewLitreeViewItemropsten]') + .assert.elementPresent('*[data-id=treeViewLitreeViewItemrinkeby]') + .assert.elementPresent('*[data-id="treeViewLitreeViewItemrinkeby/0x56db08fb78bc6689a1ef66efd079083fed0e4915"]') + .assert.elementPresent('*[data-id="treeViewLitreeViewItemrinkeby/0x56db08fb78bc6689a1ef66efd079083fed0e4915/Sample.sol"]') + .getEditorValue((content) => { + browser.assert.ok(content && content.indexOf( + 'contract Sample {') !== -1) + }) + .url('http://127.0.0.1:8080/#address=0xdac17f958d2ee523a2206206994597c13d831ec7') + .refresh() + .pause(7000) + .currentWorkspaceIs('etherscan-code-sample') + .assert.elementPresent('*[data-id=treeViewLitreeViewItemmainnet]') + .assert.elementPresent('*[data-id="treeViewLitreeViewItemmainnet/0xdac17f958d2ee523a2206206994597c13d831ec7"]') + .assert.elementPresent('*[data-id="treeViewLitreeViewItemmainnet/0xdac17f958d2ee523a2206206994597c13d831ec7/TetherToken.sol"]') + .getEditorValue((content) => { + browser.assert.ok(content && content.indexOf( + 'contract TetherToken is Pausable, StandardToken, BlackList {') !== -1) + + }) + }, + + 'Should load the code from URL & code params #group1': function (browser: NightwatchBrowser) { browser .pause(5000) .url('http://127.0.0.1:8080/#optimize=true&runs=300&evmVersion=istanbul&version=soljson-v0.7.4+commit.3f05b770.js&url=https://github.com/ethereum/remix-project/blob/master/apps/remix-ide/contracts/app/solidity/mode.sol&code=cHJhZ21hIHNvbGlkaXR5ID49MC42LjAgPDAuNy4wOwoKaW1wb3J0ICJodHRwczovL2dpdGh1Yi5jb20vT3BlblplcHBlbGluL29wZW56ZXBwZWxpbi1jb250cmFjdHMvYmxvYi9tYXN0ZXIvY29udHJhY3RzL2FjY2Vzcy9Pd25hYmxlLnNvbCI7Cgpjb250cmFjdCBHZXRQYWlkIGlzIE93bmFibGUgewogIGZ1bmN0aW9uIHdpdGhkcmF3KCkgZXh0ZXJuYWwgb25seU93bmVyIHsKICB9Cn0') @@ -95,7 +125,7 @@ module.exports = { .getEditorValue((content) => { browser.assert.ok(content && content.indexOf( 'proposals.length = _numProposals;') !== -1, - 'code has not been loaded') + 'code has not been loaded') }) .openFile('contract-76747f6e19.sol') .openFile('ethereum') @@ -108,7 +138,7 @@ module.exports = { .openFile('ethereum/remix-project/apps/remix-ide/contracts/app/solidity/mode.sol') }, - 'Should load the code from language & code params': function (browser: NightwatchBrowser) { + 'Should load the code from language & code params #group1': function (browser: NightwatchBrowser) { browser .pause(5000) .url('http://127.0.0.1:8080/#language=yul&version=soljson-v0.8.7+commit.e28d00a7.js&code=Ly8gQSBjb250cmFjdCBjb25zaXN0cyBvZiBhIHNpbmdsZSBvYmplY3Qgd2l0aCBzdWItb2JqZWN0cyByZXByZXNlbnRpbmcKLy8gdGhlIGNvZGUgdG8gYmUgZGVwbG95ZWQgb3Igb3RoZXIgY29udHJhY3RzIGl0IGNhbiBjcmVhdGUuCi8vIFRoZSBzaW5nbGUgImNvZGUiIG5vZGUgaXMgdGhlIGV4ZWN1dGFibGUgY29kZSBvZiB0aGUgb2JqZWN0LgovLyBFdmVyeSAob3RoZXIpIG5hbWVkIG9iamVjdCBvciBkYXRhIHNlY3Rpb24gaXMgc2VyaWFsaXplZCBhbmQKLy8gbWFkZSBhY2Nlc3NpYmxlIHRvIHRoZSBzcGVjaWFsIGJ1aWx0LWluIGZ1bmN0aW9ucyBkYXRhY29weSAvIGRhdGFvZmZzZXQgLyBkYXRhc2l6ZQovLyBUaGUgY3VycmVudCBvYmplY3QsIHN1Yi1vYmplY3RzIGFuZCBkYXRhIGl0ZW1zIGluc2lkZSB0aGUgY3VycmVudCBvYmplY3QKLy8gYXJlIGluIHNjb3BlLgpvYmplY3QgIkNvbnRyYWN0MSIgewogICAgLy8gVGhpcyBpcyB0aGUgY29uc3RydWN0b3IgY29kZSBvZiB0aGUgY29udHJhY3QuCiAgICBjb2RlIHsKICAgICAgICBmdW5jdGlvbiBhbGxvY2F0ZShzaXplKSAtPiBwdHIgewogICAgICAgICAgICBwdHIgOj0gbWxvYWQoMHg0MCkKICAgICAgICAgICAgaWYgaXN6ZXJvKHB0cikgeyBwdHIgOj0gMHg2MCB9CiAgICAgICAgICAgIG1zdG9yZSgweDQwLCBhZGQocHRyLCBzaXplKSkKICAgICAgICB9CgogICAgICAgIC8vIGZpcnN0IGNyZWF0ZSAiQ29udHJhY3QyIgogICAgICAgIGxldCBzaXplIDo9IGRhdGFzaXplKCJDb250cmFjdDIiKQogICAgICAgIGxldCBvZmZzZXQgOj0gYWxsb2NhdGUoc2l6ZSkKICAgICAgICAvLyBUaGlzIHdpbGwgdHVybiBpbnRvIGNvZGVjb3B5IGZvciBFVk0KICAgICAgICBkYXRhY29weShvZmZzZXQsIGRhdGFvZmZzZXQoIkNvbnRyYWN0MiIpLCBzaXplKQogICAgICAgIC8vIGNvbnN0cnVjdG9yIHBhcmFtZXRlciBpcyBhIHNpbmdsZSBudW1iZXIgMHgxMjM0CiAgICAgICAgbXN0b3JlKGFkZChvZmZzZXQsIHNpemUpLCAweDEyMzQpCiAgICAgICAgcG9wKGNyZWF0ZShvZmZzZXQsIGFkZChzaXplLCAzMiksIDApKQoKICAgICAgICAvLyBub3cgcmV0dXJuIHRoZSBydW50aW1lIG9iamVjdCAodGhlIGN1cnJlbnRseQogICAgICAgIC8vIGV4ZWN1dGluZyBjb2RlIGlzIHRoZSBjb25zdHJ1Y3RvciBjb2RlKQogICAgICAgIHNpemUgOj0gZGF0YXNpemUoIkNvbnRyYWN0MV9kZXBsb3llZCIpCiAgICAgICAgb2Zmc2V0IDo9IGFsbG9jYXRlKHNpemUpCiAgICAgICAgLy8gVGhpcyB3aWxsIHR1cm4gaW50byBhIG1lbW9yeS0+bWVtb3J5IGNvcHkgZm9yIEV3YXNtIGFuZAogICAgICAgIC8vIGEgY29kZWNvcHkgZm9yIEVWTQogICAgICAgIGRhdGFjb3B5KG9mZnNldCwgZGF0YW9mZnNldCgiQ29udHJhY3QxX2RlcGxveWVkIiksIHNpemUpCiAgICAgICAgcmV0dXJuKG9mZnNldCwgc2l6ZSkKICAgIH0KCiAgICBkYXRhICJUYWJsZTIiIGhleCI0MTIzIgoKICAgIG9iamVjdCAiQ29udHJhY3QxX2RlcGxveWVkIiB7CiAgICAgICAgY29kZSB7CiAgICAgICAgICAgIGZ1bmN0aW9uIGFsbG9jYXRlKHNpemUpIC0+IHB0ciB7CiAgICAgICAgICAgICAgICBwdHIgOj0gbWxvYWQoMHg0MCkKICAgICAgICAgICAgICAgIGlmIGlzemVybyhwdHIpIHsgcHRyIDo9IDB4NjAgfQogICAgICAgICAgICAgICAgbXN0b3JlKDB4NDAsIGFkZChwdHIsIHNpemUpKQogICAgICAgICAgICB9CgogICAgICAgICAgICAvLyBydW50aW1lIGNvZGUKCiAgICAgICAgICAgIG1zdG9yZSgwLCAiSGVsbG8sIFdvcmxkISIpCiAgICAgICAgICAgIHJldHVybigwLCAweDIwKQogICAgICAgIH0KICAgIH0KCiAgICAvLyBFbWJlZGRlZCBvYmplY3QuIFVzZSBjYXNlIGlzIHRoYXQgdGhlIG91dHNpZGUgaXMgYSBmYWN0b3J5IGNvbnRyYWN0LAogICAgLy8gYW5kIENvbnRyYWN0MiBpcyB0aGUgY29kZSB0byBiZSBjcmVhdGVkIGJ5IHRoZSBmYWN0b3J5CiAgICBvYmplY3QgIkNvbnRyYWN0MiIgewogICAgICAgIGNvZGUgewogICAgICAgICAgICAvLyBjb2RlIGhlcmUgLi4uCiAgICAgICAgfQoKICAgICAgICBvYmplY3QgIkNvbnRyYWN0Ml9kZXBsb3llZCIgewogICAgICAgICAgICBjb2RlIHsKICAgICAgICAgICAgICAgIC8vIGNvZGUgaGVyZSAuLi4KICAgICAgICAgICAgfQogICAgICAgIH0KCiAgICAgICAgZGF0YSAiVGFibGUxIiBoZXgiNDEyMyIKICAgIH0KfQ&optimize=false&runs=200&evmVersion=null') @@ -124,7 +154,7 @@ module.exports = { }) }, - 'Should select deploy with proxy option from URL params': function (browser: NightwatchBrowser) { + 'Should select deploy with proxy option from URL params #group1': function (browser: NightwatchBrowser) { browser .url('http://127.0.0.1:8080/#optimize=false&runs=200&deployProxy=true') .refresh() @@ -142,7 +172,7 @@ module.exports = { .expect.element('[data-id="contractGUIDeployWithProxy"]').to.be.selected }, - 'Should select upgrade with proxy option from URL params': function (browser: NightwatchBrowser) { + 'Should select upgrade with proxy option from URL params #group1': function (browser: NightwatchBrowser) { browser .url('http://127.0.0.1:8080/#optimize=false&runs=200&upgradeProxy=true') .refresh() @@ -159,7 +189,7 @@ module.exports = { .expect.element('[data-id="contractGUIUpgradeImplementation"]').to.be.selected }, - 'Should load using various URL compiler params': function (browser: NightwatchBrowser) { + 'Should load using various URL compiler params #group1': function (browser: NightwatchBrowser) { browser .pause(5000) .url('http://127.0.0.1:8080/#optimize=true&runs=300&autoCompile=true&evmVersion=istanbul&version=soljson-v0.7.4+commit.3f05b770.js&language=Yul') @@ -185,7 +215,7 @@ module.exports = { .assert.containsText('#versionSelector option[data-id="selected"]', '0.8.15+commit.e14f2714') }, - 'Should load using compiler from link passed in remix URL': function (browser: NightwatchBrowser) { + 'Should load using compiler from link passed in remix URL #group1': function (browser: NightwatchBrowser) { browser .url('http://127.0.0.1:8080/#version=https://solidity-blog.s3.eu-central-1.amazonaws.com/data/08preview/soljson.js&optimize=false') .refresh() @@ -194,7 +224,7 @@ module.exports = { .pause(5000) .click('*[data-id="scConfigExpander"]') .assert.containsText('#versionSelector option[data-id="selected"]', 'custom') - // default values + // default values .assert.containsText('#evmVersionSelector option[data-id="selected"]', 'default') .verify.elementPresent('#optimize') .assert.elementNotPresent('#optimize:checked') @@ -203,7 +233,7 @@ module.exports = { .verify.attributeEquals('#runs', 'value', '200') }, - 'Should load json files from link passed in remix URL': function (browser: NightwatchBrowser) { + 'Should load json files from link passed in remix URL #group1': function (browser: NightwatchBrowser) { browser .url('http://127.0.0.1:8080/#optimize=false&runs=200&evmVersion=null&version=soljson-v0.6.12+commit.27d51765.js&url=https://raw.githubusercontent.com/EthVM/evm-source-verification/main/contracts/1/0x011e5846975c6463a8c6337eecf3cbf64e328884/input.json') .refresh() 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 16f6b284b8..1592663c38 100644 --- a/libs/remix-core-plugin/src/lib/helpers/fetch-etherscan.ts +++ b/libs/remix-core-plugin/src/lib/helpers/fetch-etherscan.ts @@ -1,24 +1,28 @@ -export const fetchContractFromEtherscan = async (plugin, network, contractAddress, targetPath, key?) => { +export const fetchContractFromEtherscan = async (plugin, network, contractAddress, targetPath, shouldSetFile = true, key?) => { let data const compilationTargets = {} let etherscanKey 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) - data = await data.json() - // 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 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(/}}$/,'}')) + try { + data = await fetch('https://' + endpoint + '/api?module=contract&action=getsourcecode&address=' + contractAddress + '&apikey=' + etherscanKey) + data = await data.json() + // 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 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(/}}$/, '}')) + } } - } - } else throw new Error('unable to retrieve contract data ' + data.message) + } else throw new Error('unable to retrieve contract data ' + data.message) + } catch (e) { + throw new Error('unable to retrieve contract data: ' + e.message) + } } else throw new Error('unable to try fetching the source code from etherscan: etherscan access token not found. please go to the Remix settings page and provide an access token.') if (!data || !data.result) { @@ -27,7 +31,7 @@ export const fetchContractFromEtherscan = async (plugin, network, contractAddres if (typeof data.result[0].SourceCode === 'string') { const fileName = `${targetPath}/${data.result[0].ContractName}.sol` - await plugin.call('fileManager', 'setFile', fileName , data.result[0].SourceCode) + if (shouldSetFile) await plugin.call('fileManager', 'setFile', fileName, data.result[0].SourceCode) compilationTargets[fileName] = { content: data.result[0].SourceCode } } else if (data.result[0].SourceCode && typeof data.result[0].SourceCode == 'object') { const sources = data.result[0].SourceCode.sources @@ -36,10 +40,10 @@ export const fetchContractFromEtherscan = async (plugin, network, contractAddres file = file.replace(/^\//g, '') // remove first slash. if (await plugin.call('contentImport', 'isExternalUrl', file)) { // nothing to do, the compiler callback will handle those - } else { + } else { const path = `${targetPath}/${file}` const content = (source as any).content - await plugin.call('fileManager', 'setFile', path, content) + if (shouldSetFile) await plugin.call('fileManager', 'setFile', path, content) compilationTargets[path] = { content } } } @@ -47,7 +51,7 @@ export const fetchContractFromEtherscan = async (plugin, network, contractAddres let runs = 0 try { runs = parseInt(data.result[0].Runs) - } catch (e) {} + } catch (e) { } const settings = { version: data.result[0].CompilerVersion.replace(/^v/, ''), language: 'Solidity', diff --git a/libs/remix-ui/workspace/src/lib/actions/index.ts b/libs/remix-ui/workspace/src/lib/actions/index.ts index 953a75ba88..31fe71a453 100644 --- a/libs/remix-ui/workspace/src/lib/actions/index.ts +++ b/libs/remix-ui/workspace/src/lib/actions/index.ts @@ -4,7 +4,7 @@ import Gists from 'gists' import { customAction } from '@remixproject/plugin-api/lib/file-system/file-panel/type' import { displayNotification, displayPopUp, fetchDirectoryError, fetchDirectoryRequest, fetchDirectorySuccess, focusElement, fsInitializationCompleted, hidePopUp, removeInputFieldSuccess, setCurrentWorkspace, setExpandPath, setMode, setWorkspaces } from './payload' import { listenOnPluginEvents, listenOnProviderEvents } from './events' -import { createWorkspaceTemplate, getWorkspaces, loadWorkspacePreset, setPlugin } from './workspace' +import { createWorkspaceTemplate, getWorkspaces, loadWorkspacePreset, setPlugin, workspaceExists } from './workspace' import { QueryParams } from '@remix-project/remix-lib' import { fetchContractFromEtherscan } from '@remix-project/core-plugin' // eslint-disable-line import JSZip from 'jszip' @@ -80,11 +80,13 @@ export const initWorkspace = (filePanelPlugin) => async (reducerDispatch: React. {id: 5, name: 'goerli'} ] let found = false + const workspaceName = 'etherscan-code-sample' + let filePath const foundOnNetworks = [] for (const network of networks) { const target = `/${network.name}/${contractAddress}` try { - data = await fetchContractFromEtherscan(plugin, network, contractAddress, target, etherscanKey) + data = await fetchContractFromEtherscan(plugin, network, contractAddress, target, false, etherscanKey) } catch (error) { if ((error.message.startsWith('contract not verified on Etherscan') || error.message.startsWith('unable to retrieve contract data')) && network.id !== 5) continue @@ -95,15 +97,15 @@ export const initWorkspace = (filePanelPlugin) => async (reducerDispatch: React. } found = true foundOnNetworks.push(network.name) - await createWorkspaceTemplate('etherscan-code-sample', 'code-template') - plugin.setWorkspace({ name: 'etherscan-code-sample', isLocalhost: false }) - dispatch(setCurrentWorkspace({ name: 'etherscan-code-sample', isGitRepo: false })) - let filePath + if (await workspaceExists(workspaceName)) workspaceProvider.setWorkspace(workspaceName) + else await createWorkspaceTemplate(workspaceName, 'code-template') + plugin.setWorkspace({ name: workspaceName, isLocalhost: false }) + dispatch(setCurrentWorkspace({ name: workspaceName, isGitRepo: false })) count = count + (Object.keys(data.compilationTargets)).length for (filePath in data.compilationTargets) await workspaceProvider.set(filePath, data.compilationTargets[filePath]['content']) - plugin.on('editor', 'editorMounted', async () => await plugin.fileManager.openFile(filePath)) } + plugin.on('editor', 'editorMounted', async () => await plugin.fileManager.openFile(filePath)) plugin.call('notification', 'toast', `Added ${count} verified contract${count === 1 ? '': 's'} from ${foundOnNetworks.join(',')} network${foundOnNetworks.length === 1 ? '': 's'} of Etherscan for contract address ${contractAddress} !!`) } catch (error) { await basicWorkspaceInit(workspaces, workspaceProvider)