diff --git a/apps/remix-ide-e2e/src/tests/terminal.test.ts b/apps/remix-ide-e2e/src/tests/terminal.test.ts index 5b827ecfbb..9f84d684bb 100644 --- a/apps/remix-ide-e2e/src/tests/terminal.test.ts +++ b/apps/remix-ide-e2e/src/tests/terminal.test.ts @@ -286,7 +286,15 @@ module.exports = { .end() } }) - } + }, + + 'Should connect to the mainnet fork and run web3.eth.getCode in the terminal #group9': function (browser: NightwatchBrowser) { + browser + .clickLaunchIcon('udapp') + .switchEnvironment('vm-mainnet-fork') + .executeScriptInTerminal(`web3.eth.getCode('0x180587b00c8642e2c7ac3a758712d97e6f7bdcc7')`) // mainnet contract + .waitForElementContainsText('*[data-id="terminalJournal"]', '0x608060405260043610601f5760003560e01c80635c60da1b14603157602b565b36602b576029605f565b005b6029605f565b348015603c57600080fd5b5060436097565b6040516001600160a01b03909116815260200160405180910390f35b609560917f360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc546001600160a01b031690565b60d1565b565b600060c97f360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc546001600160a01b031690565b905090565b90565b3660008037600080366000845af43d6000803e80801560ef573d6000f35b3d6000fdfea2646970667358221220969dbb4b1d8aec2bb348e26488dc1a33b6bcf0190f567d161312ab7ca9193d8d64736f6c63430008110033', 120000) + }, } diff --git a/apps/remix-ide/src/app.js b/apps/remix-ide/src/app.js index 836235d208..403de27733 100644 --- a/apps/remix-ide/src/app.js +++ b/apps/remix-ide/src/app.js @@ -28,6 +28,8 @@ import { Layout } from './app/panels/layout' import { NotificationPlugin } from './app/plugins/notification' import { Blockchain } from './blockchain/blockchain.js' import { BerlinVMProvider, LondonVMProvider } from './app/providers/vm-provider' +import { MainnetForkVMProvider } from './app/providers/mainnet-vm-fork-provider' +import { CustomForkVMProvider } from './app/providers/custom-vm-fork-provider' import { HardhatProvider } from './app/providers/hardhat-provider' import { GanacheProvider } from './app/providers/ganache-provider' import { FoundryProvider } from './app/providers/foundry-provider' @@ -203,6 +205,8 @@ class AppComponent { const networkModule = new NetworkModule(blockchain) // ----------------- represent the current selected web3 provider ---- const web3Provider = new Web3ProviderModule(blockchain) + const vmProviderCustomFork = new CustomForkVMProvider(blockchain) + const vmProviderMainnetFork = new MainnetForkVMProvider(blockchain) const vmProviderBerlin = new BerlinVMProvider(blockchain) const vmProviderLondon = new LondonVMProvider(blockchain) const hardhatProvider = new HardhatProvider(blockchain) @@ -273,6 +277,8 @@ class AppComponent { storagePlugin, vmProviderBerlin, vmProviderLondon, + vmProviderMainnetFork, + vmProviderCustomFork, hardhatProvider, ganacheProvider, foundryProvider, diff --git a/apps/remix-ide/src/app/files/fileManager.ts b/apps/remix-ide/src/app/files/fileManager.ts index 19cb956d34..95860a0ee4 100644 --- a/apps/remix-ide/src/app/files/fileManager.ts +++ b/apps/remix-ide/src/app/files/fileManager.ts @@ -1,5 +1,6 @@ 'use strict' import { saveAs } from 'file-saver' +import JSZip from 'jszip' import { Plugin } from '@remixproject/engine' import * as packageJson from '../../../../../package.json' import Registry from '../state/registry' @@ -344,12 +345,31 @@ class FileManager extends Plugin { } } + async zipDir(dirPath, zip) { + const filesAndFolders = await this.readdir(dirPath) + for(let path in filesAndFolders) { + if (filesAndFolders[path].isDirectory) await this.zipDir(path, zip) + else { + path = this.normalize(path) + const content: any = await this.readFile(path) + zip.file(path, content) + } + } + } + async download(path) { try { - const fileName = helper.extractNameFromKey(path) - path = this.normalize(path) - const content: any = await this.readFile(path) - saveAs(new Blob([content]), fileName) + const downloadFileName = helper.extractNameFromKey(path) + if (await this.isDirectory(path)) { + const zip = new JSZip() + await this.zipDir(path, zip) + const content = await zip.generateAsync({type: 'blob'}) + saveAs(content, `${downloadFileName}.zip`) + } else { + path = this.normalize(path) + const content: any = await this.readFile(path) + saveAs(new Blob([content]), downloadFileName) + } } catch (e) { throw new Error(e) } @@ -377,9 +397,9 @@ class FileManager extends Plugin { /** * Get the list of files in the directory * @param {string} path path of the directory - * @returns {string[]} list of the file/directory name in this directory + * @returns {Object} list of the file/directory name in this directory e.g; {contracts/1_Storage.sol:{isDirectory: false}} */ - async readdir(path) { + async readdir(path): Promise>> { try { path = this.normalize(path) path = this.limitPluginScope(path) diff --git a/apps/remix-ide/src/app/plugins/contractFlattener.tsx b/apps/remix-ide/src/app/plugins/contractFlattener.tsx index 4433ffcf23..71c3a4a1c1 100644 --- a/apps/remix-ide/src/app/plugins/contractFlattener.tsx +++ b/apps/remix-ide/src/app/plugins/contractFlattener.tsx @@ -26,7 +26,7 @@ export class ContractFlattener extends Plugin { async flattenAContract(action: customAction) { this.fileName = action.path[0] - this.call('solidity', 'compile', this.fileName) + await this.call('solidity', 'compile', this.fileName) } /** @@ -39,8 +39,8 @@ export class ContractFlattener extends Plugin { const ast = data.sources const dependencyGraph = getDependencyGraph(ast, filePath) const sorted = dependencyGraph.isEmpty() - ? [filePath] - : dependencyGraph.sort().reverse() + ? [filePath] + : dependencyGraph.sort().reverse() const sources = source.sources const result = concatSourceFiles(sorted, sources) await this.call('fileManager', 'writeFile', `${filePath}_flattened.sol`, result) diff --git a/apps/remix-ide/src/app/plugins/remixd-handle.tsx b/apps/remix-ide/src/app/plugins/remixd-handle.tsx index 7f5b1e8f5e..a39c52c3d3 100644 --- a/apps/remix-ide/src/app/plugins/remixd-handle.tsx +++ b/apps/remix-ide/src/app/plugins/remixd-handle.tsx @@ -115,7 +115,7 @@ export class RemixdHandle extends WebsocketPlugin { // warn the user only if he/she is in the browser context const mod: AppModal = { id: 'remixdConnect', - title: 'Connect to localhost', + title: 'Access file system using remixd', message: remixdDialog(), okLabel: 'Connect', cancelLabel: 'Cancel', diff --git a/apps/remix-ide/src/app/providers/abstract-provider.tsx b/apps/remix-ide/src/app/providers/abstract-provider.tsx index d9d73c32e9..c4a5d65077 100644 --- a/apps/remix-ide/src/app/providers/abstract-provider.tsx +++ b/apps/remix-ide/src/app/providers/abstract-provider.tsx @@ -19,12 +19,20 @@ export type JsonDataResult = { export type RejectRequest = (error: Error) => void export type SuccessRequest = (data: JsonDataResult) => void -export abstract class AbstractProvider extends Plugin { +export interface IProvider { + options: { [id: string] : any } + init(): Promise<{ [id: string] : any }> + body(): JSX.Element + sendAsync (data: JsonDataRequest): Promise +} + +export abstract class AbstractProvider extends Plugin implements IProvider { provider: ethers.providers.JsonRpcProvider blockchain: Blockchain defaultUrl: string connected: boolean nodeUrl: string + options: { [id: string] : any } = {} constructor (profile, blockchain, defaultUrl) { super(profile) @@ -41,7 +49,7 @@ export abstract class AbstractProvider extends Plugin { this.provider = null } - async init () { + async init () { this.nodeUrl = await ((): Promise => { return new Promise((resolve, reject) => { const modalContent: AppModal = { @@ -80,9 +88,12 @@ export abstract class AbstractProvider extends Plugin { }) })() this.provider = new ethers.providers.JsonRpcProvider(this.nodeUrl) + return { + nodeUrl: this.nodeUrl + } } - sendAsync (data: JsonDataRequest): Promise { + sendAsync (data: JsonDataRequest): Promise { // eslint-disable-next-line no-async-promise-executor return new Promise(async (resolve, reject) => { if (!this.provider) return reject(new Error('provider node set')) diff --git a/apps/remix-ide/src/app/providers/custom-vm-fork-provider.tsx b/apps/remix-ide/src/app/providers/custom-vm-fork-provider.tsx new file mode 100644 index 0000000000..26218a4656 --- /dev/null +++ b/apps/remix-ide/src/app/providers/custom-vm-fork-provider.tsx @@ -0,0 +1,80 @@ +import React, { useRef } from 'react' // eslint-disable-line +import * as packageJson from '../../../../../package.json' +import { AppModal, ModalTypes } from '@remix-ui/app' +import { BasicVMProvider } from './vm-provider' + +export class CustomForkVMProvider extends BasicVMProvider { + nodeUrl: string + blockNumber: number | 'latest' + inputs: any + + constructor (blockchain) { + super({ + name: 'vm-custom-fork', + displayName: 'Custom fork - Remix VM (London)', + kind: 'provider', + description: 'Remix VM (London)', + methods: ['sendAsync', 'init'], + version: packageJson.version + }, blockchain) + this.blockchain = blockchain + this.fork = '' + this.nodeUrl = '' + this.blockNumber = 'latest' + this.inputs = {} + } + + async init () { + this.inputs = {nodeUrl: '', evm: '', blockNumber: '' } + const body = () => { + return
+
+ + +
+
+ + +
+
+ + +
+
+ } + await ((): Promise => { + return new Promise((resolve, reject) => { + const modalContent: AppModal = { + id: this.profile.name, + title: this.profile.displayName, + message: body(), + modalType: ModalTypes.default, + okLabel: 'Connect', + cancelLabel: 'Cancel', + okFn: (value: string) => { + setTimeout(() => resolve(value), 0) + }, + cancelFn: () => { + setTimeout(() => reject(new Error('Canceled')), 0) + }, + hideFn: () => { + setTimeout(() => reject(new Error('Hide')), 0) + } + } + this.call('notification', 'modal', modalContent) + }) + })() + this.fork = this.inputs.evm + this.nodeUrl = this.inputs.nodeUrl + const block = this.inputs.blockNumber + this.blockNumber = block === 'latest' ? 'latest' : parseInt(block) + return { + 'fork': this.fork, + 'nodeUrl': this.nodeUrl, + 'blockNumber': this.blockNumber + } + } +} diff --git a/apps/remix-ide/src/app/providers/injected-L2-provider.tsx b/apps/remix-ide/src/app/providers/injected-L2-provider.tsx index 18c761b47b..3aa3ab770f 100644 --- a/apps/remix-ide/src/app/providers/injected-L2-provider.tsx +++ b/apps/remix-ide/src/app/providers/injected-L2-provider.tsx @@ -17,6 +17,7 @@ export class InjectedL2Provider extends InjectedProvider { if (this.chainName && this.rpcUrls && this.rpcUrls.length > 0) await addL2Network(this.chainName, this.chainId, this.rpcUrls) else throw new Error('Cannot add the L2 network to main injected provider') + return {} } } diff --git a/apps/remix-ide/src/app/providers/injected-provider.tsx b/apps/remix-ide/src/app/providers/injected-provider.tsx index 928c3133cd..55f50850d1 100644 --- a/apps/remix-ide/src/app/providers/injected-provider.tsx +++ b/apps/remix-ide/src/app/providers/injected-provider.tsx @@ -1,12 +1,15 @@ /* global ethereum */ +import React from 'react' // eslint-disable-line import { Plugin } from '@remixproject/engine' import { JsonDataRequest, RejectRequest, SuccessRequest } from '../providers/abstract-provider' import Web3 from 'web3' +import { IProvider } from './abstract-provider' const noInjectedProviderMsg = 'No injected provider found. Make sure your provider (e.g. MetaMask) is active and running (when recently activated you may have to reload the page).' -export class InjectedProvider extends Plugin { +export class InjectedProvider extends Plugin implements IProvider { provider: any + options: { [id: string] : any } = {} constructor (profile) { super(profile) @@ -23,6 +26,12 @@ export class InjectedProvider extends Plugin { } } + body (): JSX.Element { + return ( +
+ ) + } + async init () { const injectedProvider = (window as any).ethereum if (injectedProvider === undefined) { @@ -33,6 +42,7 @@ export class InjectedProvider extends Plugin { } this.askPermission(true) } + return {} } sendAsync (data: JsonDataRequest): Promise { diff --git a/apps/remix-ide/src/app/providers/mainnet-vm-fork-provider.tsx b/apps/remix-ide/src/app/providers/mainnet-vm-fork-provider.tsx new file mode 100644 index 0000000000..582ded0745 --- /dev/null +++ b/apps/remix-ide/src/app/providers/mainnet-vm-fork-provider.tsx @@ -0,0 +1,29 @@ +import * as packageJson from '../../../../../package.json' +import { BasicVMProvider } from './vm-provider' + +export class MainnetForkVMProvider extends BasicVMProvider { + nodeUrl: string + blockNumber: number | 'latest' + constructor (blockchain) { + super({ + name: 'vm-mainnet-fork', + displayName: 'Mainet fork -Remix VM (London)', + kind: 'provider', + description: 'Remix VM (London)', + methods: ['sendAsync', 'init'], + version: packageJson.version + }, blockchain) + this.blockchain = blockchain + this.fork = 'london' + this.nodeUrl = 'https://rpc.archivenode.io/e50zmkroshle2e2e50zm0044i7ao04ym' + this.blockNumber = 'latest' + } + + async init () { + return { + 'fork': this.fork, + 'nodeUrl': this.nodeUrl, + 'blockNumber': this.blockNumber + } + } +} diff --git a/apps/remix-ide/src/app/providers/vm-provider.tsx b/apps/remix-ide/src/app/providers/vm-provider.tsx index 6ea15bb2fc..40e3214f0f 100644 --- a/apps/remix-ide/src/app/providers/vm-provider.tsx +++ b/apps/remix-ide/src/app/providers/vm-provider.tsx @@ -1,17 +1,26 @@ +import React from 'react' // eslint-disable-line import * as packageJson from '../../../../../package.json' import { JsonDataRequest, RejectRequest, SuccessRequest } from '../providers/abstract-provider' import { Plugin } from '@remixproject/engine' +import { IProvider } from './abstract-provider' -export class BasicVMProvider extends Plugin { +export class BasicVMProvider extends Plugin implements IProvider { blockchain fork: string + options: { [id: string] : any } = {} constructor (profile, blockchain) { super(profile) this.blockchain = blockchain - this.fork = null + this.fork = '' } - init () {} + async init (): Promise<{ [id: string] : any }> { return {} } + + body (): JSX.Element { + return ( +
+ ) + } sendAsync (data: JsonDataRequest): Promise { return new Promise((resolve, reject) => { diff --git a/apps/remix-ide/src/app/tabs/locales/en/home.json b/apps/remix-ide/src/app/tabs/locales/en/home.json index 3f32d49043..c5fd63d9ab 100644 --- a/apps/remix-ide/src/app/tabs/locales/en/home.json +++ b/apps/remix-ide/src/app/tabs/locales/en/home.json @@ -55,7 +55,7 @@ "home.files": "Files", "home.newFile": "New File", "home.openFile": "Open File", - "home.connectToLocalhost": "Connect to Localhost", - "home.loadFrom": "LOAD FROM", + "home.connectToLocalhost": "Access File System", + "home.loadFrom": "Load from", "home.resources": "Resources" } diff --git a/apps/remix-ide/src/app/udapp/run-tab.js b/apps/remix-ide/src/app/udapp/run-tab.js index 4216fa6840..a96db8a3a8 100644 --- a/apps/remix-ide/src/app/udapp/run-tab.js +++ b/apps/remix-ide/src/app/udapp/run-tab.js @@ -103,6 +103,7 @@ export class RunTab extends ViewPlugin { const addProvider = async (name, displayName, isInjected, isVM, fork = '', dataId = '', title = '') => { await this.call('blockchain', 'addProvider', { + options: {}, dataId, name, displayName, @@ -110,7 +111,13 @@ export class RunTab extends ViewPlugin { isInjected, isVM, title, - init: () => { return this.call(name, 'init') }, + init: async function () { + const options = await udapp.call(name, 'init') + if (options) { + this.options = options + if (options['fork']) this.fork = options['fork'] + } + }, provider: { async sendAsync (payload, callback) { try { @@ -128,6 +135,8 @@ export class RunTab extends ViewPlugin { const titleVM = 'Execution environment is local to Remix. Data is only saved to browser memory and will vanish upon reload.' await addProvider('vm-london', 'Remix VM (London)', false, true, 'london', 'settingsVMLondonMode', titleVM) await addProvider('vm-berlin', 'Remix VM (Berlin)', false, true, 'berlin', 'settingsVMBerlinMode', titleVM) + await addProvider('vm-mainnet-fork', 'Remix VM - Mainnet fork', false, true, 'london', 'settingsVMMainnetMode', titleVM) + // await addProvider('vm-custom-fork', 'Remix VM - Custom fork', false, true, '', 'settingsVMCustomMode', titleVM) // external provider await addProvider('hardhat-provider', 'Hardhat Provider', false, false) diff --git a/apps/remix-ide/src/blockchain/blockchain.js b/apps/remix-ide/src/blockchain/blockchain.js index 4ea2d8a09f..785d5f05d1 100644 --- a/apps/remix-ide/src/blockchain/blockchain.js +++ b/apps/remix-ide/src/blockchain/blockchain.js @@ -84,8 +84,6 @@ export class Blockchain extends Plugin { setupProviders () { const vmProvider = new VMProvider(this.executionContext) this.providers = {} - this.providers['vm-berlin'] = vmProvider - this.providers['vm-london'] = vmProvider this.providers['vm'] = vmProvider this.providers.injected = new InjectedProvider(this.executionContext) this.providers.web3 = new NodeProvider(this.executionContext, this.config) @@ -93,7 +91,7 @@ export class Blockchain extends Plugin { getCurrentProvider () { const provider = this.getProvider() - + if (provider && provider.startsWith('vm')) return this.providers['vm'] if (this.providers[provider]) return this.providers[provider] return this.providers.web3 // default to the common type of provider } diff --git a/apps/remix-ide/src/blockchain/execution-context.js b/apps/remix-ide/src/blockchain/execution-context.js index 67d7f20d36..692396265d 100644 --- a/apps/remix-ide/src/blockchain/execution-context.js +++ b/apps/remix-ide/src/blockchain/execution-context.js @@ -43,6 +43,10 @@ export class ExecutionContext { return this.executionContext } + getProviderObject () { + return this.customNetWorks[this.executionContext] + } + getSelectedAddress () { return injectedProvider ? injectedProvider.selectedAddress : null } @@ -127,10 +131,10 @@ export class ExecutionContext { if (!confirmCb) confirmCb = () => { /* Do nothing. */ } if (!infoCb) infoCb = () => { /* Do nothing. */ } if (this.customNetWorks[context]) { - var network = this.customNetWorks[context] + var network = this.customNetWorks[context] + await network.init() this.currentFork = network.fork this.executionContext = context - await network.init() // injected web3.setProvider(network.provider) await this._updateChainContext() diff --git a/apps/remix-ide/src/blockchain/providers/vm.js b/apps/remix-ide/src/blockchain/providers/vm.js index 90434ae319..96466065ca 100644 --- a/apps/remix-ide/src/blockchain/providers/vm.js +++ b/apps/remix-ide/src/blockchain/providers/vm.js @@ -22,7 +22,9 @@ class VMProvider { if (this.worker) this.worker.terminate() this.accounts = {} this.worker = new Worker(new URL('./worker-vm', import.meta.url)) - this.worker.postMessage({ cmd: 'init', fork: this.executionContext.getCurrentFork() }) + const provider = this.executionContext.getProviderObject() + + this.worker.postMessage({ cmd: 'init', fork: this.executionContext.getCurrentFork(), nodeUrl: provider?.options['nodeUrl'], blockNumber: provider?.options['blockNumber']}) let incr = 0 const stamps = {} diff --git a/apps/remix-ide/src/blockchain/providers/worker-vm.ts b/apps/remix-ide/src/blockchain/providers/worker-vm.ts index 56d479041d..55ebb013a1 100644 --- a/apps/remix-ide/src/blockchain/providers/worker-vm.ts +++ b/apps/remix-ide/src/blockchain/providers/worker-vm.ts @@ -6,7 +6,7 @@ self.onmessage = (e: MessageEvent) => { switch (data.cmd) { case 'init': { - provider = new Provider({ fork: data.fork }) + provider = new Provider({ fork: data.fork, nodeUrl: data.nodeUrl, blockNumber: data.blockNumber }) if (provider) provider.init() break } diff --git a/apps/remix-ide/src/remixEngine.js b/apps/remix-ide/src/remixEngine.js index 9d34b58bf5..64fd918227 100644 --- a/apps/remix-ide/src/remixEngine.js +++ b/apps/remix-ide/src/remixEngine.js @@ -15,7 +15,7 @@ export class RemixEngine extends Engine { if (name === 'hardhat') return { queueTimeout: 60000 * 4 } if (name === 'truffle') return { queueTimeout: 60000 * 4 } if (name === 'localPlugin') return { queueTimeout: 60000 * 4 } - if (name === 'notification') return { queueTimeout: 60000 * 4 } + if (name === 'notification') return { queueTimeout: 60000 * 10 } if (name === 'sourcify') return { queueTimeout: 60000 * 4 } if (name === 'fetchAndCompile') return { queueTimeout: 60000 * 4 } if (name === 'walletconnect') return { queueTimeout: 60000 * 4 } diff --git a/libs/remix-simulator/src/provider.ts b/libs/remix-simulator/src/provider.ts index ab5e235014..1c87e28105 100644 --- a/libs/remix-simulator/src/provider.ts +++ b/libs/remix-simulator/src/provider.ts @@ -13,17 +13,17 @@ import { generateBlock } from './genesis' import { VMContext } from './vm-context' export class Provider { - options: Record + options: Record vmContext Accounts Transactions methods connected: boolean; - constructor (options: Record = {}) { + constructor (options: Record = {}) { this.options = options this.connected = true - this.vmContext = new VMContext(options['fork']) + this.vmContext = new VMContext(options['fork'] as string, options['nodeUrl'] as string, options['blockNumber'] as number) this.Accounts = new Web3Accounts(this.vmContext) this.Transactions = new Transactions(this.vmContext) diff --git a/libs/remix-simulator/src/vm-context.ts b/libs/remix-simulator/src/vm-context.ts index 4bd9bb2481..c2bf935afa 100644 --- a/libs/remix-simulator/src/vm-context.ts +++ b/libs/remix-simulator/src/vm-context.ts @@ -2,14 +2,16 @@ 'use strict' import { hash } from '@remix-project/remix-lib' import { bufferToHex } from '@ethereumjs/util' +import type { Address } from '@ethereumjs/util' import { decode } from 'rlp' +import { ethers } from 'ethers' import { execution } from '@remix-project/remix-lib' const { LogsManager } = execution import { VmProxy } from './VmProxy' import { VM } from '@ethereumjs/vm' import { Common } from '@ethereumjs/common' import { Trie } from '@ethereumjs/trie' -import { DefaultStateManager } from '@ethereumjs/statemanager' +import { DefaultStateManager, StateManager, EthersStateManager, EthersStateManagerOpts } from '@ethereumjs/statemanager' import { StorageDump } from '@ethereumjs/statemanager/dist/interface' import { EVM } from '@ethereumjs/evm' import { EEI } from '@ethereumjs/vm' @@ -35,6 +37,44 @@ export interface DefaultStateManagerOpts { prefixCodeHashes?: boolean } +class CustomEthersStateManager extends EthersStateManager { + keyHashes: { [key: string]: string } + constructor (opts: EthersStateManagerOpts) { + super(opts) + this.keyHashes = {} + } + + putContractStorage (address, key, value) { + this.keyHashes[bufferToHex(key).replace('0x', '')] = hash.keccak(key).toString('hex') + return super.putContractStorage(address, key, value) + } + + copy(): CustomEthersStateManager { + const newState = new CustomEthersStateManager({ + provider: (this as any).provider, + blockTag: BigInt((this as any).blockTag), + }) + ;(newState as any).contractCache = new Map((this as any).contractCache) + ;(newState as any).storageCache = new Map((this as any).storageCache) + ;(newState as any)._cache = this._cache + ;(newState as any).keyHashes = this.keyHashes + return newState + } + + async dumpStorage(address: Address): Promise { + const storageDump = {} + const storage = await super.dumpStorage(address) + for (const key of Object.keys(storage)) { + const value = storage[key] + storageDump['0x' + this.keyHashes[key]] = { + key: '0x' + key, + value: value + } + } + return storageDump + } +} + /* extend vm state manager and instanciate VM */ @@ -86,7 +126,7 @@ class StateManagerCommonStorageDump extends DefaultStateManager { export type CurrentVm = { vm: VM, web3vm: VmProxy, - stateManager: StateManagerCommonStorageDump, + stateManager: StateManager, common: Common } @@ -105,12 +145,15 @@ export class VMContext { web3vm: VmProxy logsManager: any // LogsManager exeResults: Record + nodeUrl: string + blockNumber: number | 'latest' - constructor (fork?) { + constructor (fork?: string, nodeUrl?: string, blockNumber?: number | 'latest') { this.blockGasLimitDefault = 4300000 this.blockGasLimit = this.blockGasLimitDefault this.currentFork = fork || 'london' - + this.nodeUrl = nodeUrl + this.blockNumber = blockNumber this.blocks = {} this.latestBlockNumber = "0x0" this.blockByTxHash = {} @@ -124,7 +167,21 @@ export class VMContext { } async createVm (hardfork) { - const stateManager = new StateManagerCommonStorageDump() + let stateManager: StateManager + + if (this.nodeUrl) { + let block = this.blockNumber + if (this.blockNumber === 'latest') { + const provider = new ethers.providers.StaticJsonRpcProvider(this.nodeUrl) + block = await provider.getBlockNumber() + } + stateManager = new CustomEthersStateManager({ + provider: this.nodeUrl, + blockTag: BigInt(block) + }) + } else + stateManager = new StateManagerCommonStorageDump() + const common = new Common({ chain: 'mainnet', hardfork }) const blockchain = new (Blockchain as any)({ common }) const eei = new EEI(stateManager, common, blockchain) 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 17c6f604d6..450d59f1de 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 @@ -3,19 +3,33 @@ import { ModalDialog, ModalDialogProps, ValidationResult } from '@remix-ui/modal import { ModalTypes } from '../../types' interface ModalWrapperProps extends ModalDialogProps { - modalType?: ModalTypes - defaultValue?: string + modalType?: ModalTypes + defaultValue?: string } const ModalWrapper = (props: ModalWrapperProps) => { const [state, setState] = useState() const ref = useRef() + const formRef = useRef() const data = useRef() + const getFormData = () => { + if (formRef.current) { + const formData = new FormData(formRef.current) + const data = {} + for (const pair of formData.entries()) { + data[pair[0]] = pair[1] + } + return data + } + } + const onFinishPrompt = async () => { - if (ref.current === undefined) { + if (ref.current === undefined && formRef.current === undefined) { onOkFn() - } else { + } else if (formRef.current) { + (props.okFn) ? props.okFn(getFormData()) : props.resolve(getFormData()) + } else if(ref.current) { // @ts-ignore: Object is possibly 'null'. (props.okFn) ? props.okFn(ref.current.value) : props.resolve(ref.current.value) } @@ -43,9 +57,29 @@ const ModalWrapper = (props: ModalWrapperProps) => { <> {props.message} - {!validation.valid && {validation.message}} - - ) + {validation && !validation.valid && {validation.message}} + + ) + } + + const onFormChanged = () => { + if (props.validationFn) { + const validation = props.validationFn(getFormData()) + setState(prevState => { + return { ...prevState, message: createForm(validation), validation } + }) + } + } + + const createForm = (validation: ValidationResult) => { + return ( + <> +
+ {props.message} +
+ {validation && !validation.valid && {validation.message}} + + ) } useEffect(() => { @@ -61,6 +95,14 @@ const ModalWrapper = (props: ModalWrapperProps) => { message: createModalMessage(props.defaultValue, { valid: true }) }) break + case ModalTypes.form: + setState({ + ...props, + okFn: onFinishPrompt, + cancelFn: onCancelFn, + message: createForm({ valid: true }) + }) + break default: setState({ ...props, diff --git a/libs/remix-ui/app/src/lib/remix-app/types/index.ts b/libs/remix-ui/app/src/lib/remix-app/types/index.ts index edca9e147c..6822dbe73e 100644 --- a/libs/remix-ui/app/src/lib/remix-app/types/index.ts +++ b/libs/remix-ui/app/src/lib/remix-app/types/index.ts @@ -4,4 +4,5 @@ export const enum ModalTypes { prompt = 'prompt', password = 'password', default = 'default', + form = 'form', } diff --git a/libs/remix-ui/home-tab/src/lib/components/homeTabFeatured.tsx b/libs/remix-ui/home-tab/src/lib/components/homeTabFeatured.tsx index f2ea97fe57..c8de93004e 100644 --- a/libs/remix-ui/home-tab/src/lib/components/homeTabFeatured.tsx +++ b/libs/remix-ui/home-tab/src/lib/components/homeTabFeatured.tsx @@ -37,50 +37,56 @@ function HomeTabFeatured() { dotListClass="position-relative mt-2" >
- -
- + -
- - diff --git a/libs/remix-ui/home-tab/src/lib/components/homeTabFile.tsx b/libs/remix-ui/home-tab/src/lib/components/homeTabFile.tsx index 50ec5bf8fa..df6b89dd79 100644 --- a/libs/remix-ui/home-tab/src/lib/components/homeTabFile.tsx +++ b/libs/remix-ui/home-tab/src/lib/components/homeTabFile.tsx @@ -4,6 +4,7 @@ import { FormattedMessage } from 'react-intl' import { ModalDialog } from '@remix-ui/modal-dialog' // eslint-disable-line import { Toaster } from '@remix-ui/toaster' // eslint-disable-line const _paq = window._paq = window._paq || [] // eslint-disable-line +import { CustomTooltip } from '@remix-ui/helper'; interface HomeTabFileProps { plugin: any @@ -153,17 +154,27 @@ function HomeTabFile ({plugin}: HomeTabFileProps) {
-
- - - - { - event.stopPropagation() - plugin.verticalIcons.select('filePanel') - uploadFile(event.target) - }} multiple /> - - +
+ +
+ + + { + event.stopPropagation() + plugin.verticalIcons.select('filePanel') + uploadFile(event.target) + }} multiple /> + + + +
+
+ +
{(state.visibleTutorial === VisibleTutorial.Basics) &&
- + + +
} {(state.visibleTutorial === VisibleTutorial.Advanced) &&
- - + + + + +
}
diff --git a/libs/remix-ui/home-tab/src/lib/components/homeTabScamAlert.tsx b/libs/remix-ui/home-tab/src/lib/components/homeTabScamAlert.tsx index 790a3bc843..82e16305bd 100644 --- a/libs/remix-ui/home-tab/src/lib/components/homeTabScamAlert.tsx +++ b/libs/remix-ui/home-tab/src/lib/components/homeTabScamAlert.tsx @@ -8,27 +8,28 @@ function HomeTabScamAlert () { return (
- ) diff --git a/libs/remix-ui/home-tab/src/lib/components/homeTabTitle.tsx b/libs/remix-ui/home-tab/src/lib/components/homeTabTitle.tsx index b09242c7e5..ef3fe4b4df 100644 --- a/libs/remix-ui/home-tab/src/lib/components/homeTabTitle.tsx +++ b/libs/remix-ui/home-tab/src/lib/components/homeTabTitle.tsx @@ -52,19 +52,22 @@ function HomeTabTitle() { return (
-
-
playRemi()} style={{ filter: themeFilter.filter}} > - -
- -
-
+ +
+
Remix +
+
playRemi()} > + +
+ +
+
+ className="border-0 p-2 h-100 pl-2 btn fab fa-twitter"> @@ -110,7 +113,7 @@ function HomeTabTitle() { openLink("https://www.linkedin.com/company/ethereum-remix/") _paq.push(['trackEvent', 'hometab', 'socialmedia', 'linkedin']) }} - className="border-0 h-100 pl-2 btn fa fa-linkedin"> + className="border-0 p-2 h-100 pl-2 btn fa fa-linkedin"> @@ -126,7 +129,7 @@ function HomeTabTitle() { openLink("https://medium.com/remix-ide") _paq.push(['trackEvent', 'hometab', 'socialmedia', 'medium']) }} - className="border-0 h-100 pl-2 btn fab fa-medium"> + className="border-0 p-2 h-100 pl-2 btn fab fa-medium"> @@ -142,12 +145,12 @@ function HomeTabTitle() { openLink("https://gitter.im/ethereum/remix") _paq.push(['trackEvent', 'hometab', 'socialmedia', 'gitter']) }} - className="border-0 h-100 pl-2 btn fab fa-gitter"> + className="border-0 h-100 p-2 btn fab fa-gitter">
- +