diff --git a/apps/remix-ide/src/app/providers/vm-provider.tsx b/apps/remix-ide/src/app/providers/vm-provider.tsx index 37649480454..d7f5ea2298b 100644 --- a/apps/remix-ide/src/app/providers/vm-provider.tsx +++ b/apps/remix-ide/src/app/providers/vm-provider.tsx @@ -133,9 +133,13 @@ export class CancunVMProvider extends BasicVMProvider { } export class ForkedVMStateProvider extends BasicVMProvider { - constructor(profile, blockchain, fork) { + nodeUrl?: string + blockNumber?: string + constructor(profile, blockchain, fork: string, nodeUrl?: string, blockNumber?: string) { super(profile, blockchain) this.blockchain = blockchain this.fork = fork + this.nodeUrl = nodeUrl + this.blockNumber = blockNumber } } diff --git a/apps/remix-ide/src/app/udapp/run-tab.tsx b/apps/remix-ide/src/app/udapp/run-tab.tsx index 188ea9d7955..98199e209e2 100644 --- a/apps/remix-ide/src/app/udapp/run-tab.tsx +++ b/apps/remix-ide/src/app/udapp/run-tab.tsx @@ -180,7 +180,7 @@ export class RunTab extends ViewPlugin { 'foundry-provider': ['assets/img/foundry.png'] } - const addProvider = async (position: number, name: string, displayName: string, providerConfig: ProviderConfig, fork = '', dataId = '', title = '') => { + const addProvider = async (position: number, name: string, displayName: string, providerConfig: ProviderConfig, dataId = '', title = '') => { await this.call('blockchain', 'addProvider', { position, options: {}, @@ -189,14 +189,15 @@ export class RunTab extends ViewPlugin { displayName, description: descriptions[name] || displayName, logos: logos[name], - fork, config: providerConfig, title, init: async function () { - const options = await udapp.call(name, 'init') + const options = await udapp.call(name, 'init') if (options) { this.options = options - if (options['fork']) this.fork = options['fork'] + if (options['fork']) this.config.fork = options['fork'] + if (options['nodeUrl']) this.config.nodeUrl = options['nodeUrl'] + if (options['blockNumber']) this.config.blockNumber = options['blockNumber'] } }, provider: new Provider(udapp, name) @@ -206,13 +207,13 @@ export class RunTab extends ViewPlugin { const addCustomInjectedProvider = async (position, event, name, displayName, networkId, urls, nativeCurrency?) => { // name = `${name} through ${event.detail.info.name}` await this.engine.register([new InjectedCustomProvider(event.detail.provider, name, displayName, networkId, urls, nativeCurrency)]) - await addProvider(position, name, displayName + ' - ' + event.detail.info.name, { isInjected: true, isVM: false, isRpcForkedState: false }) + await addProvider(position, name, displayName + ' - ' + event.detail.info.name, { isInjected: true, isVM: false, isRpcForkedState: false, fork: ''}) } const registerInjectedProvider = async (event) => { const name = 'injected-' + event.detail.info.name const displayName = 'Injected Provider - ' + event.detail.info.name await this.engine.register([new InjectedProviderDefault(event.detail.provider, name)]) - await addProvider(0, name, displayName, { isInjected: true, isVM: false, isRpcForkedState: false }) + await addProvider(0, name, displayName, { isInjected: true, isVM: false, isRpcForkedState: false, fork: '' }) if (event.detail.info.name === 'MetaMask') { await addCustomInjectedProvider(7, event, 'injected-metamask-optimism', 'L2 - Optimism', '0xa', ['https://mainnet.optimism.io']) @@ -249,14 +250,14 @@ export class RunTab extends ViewPlugin { // VM const titleVM = 'Execution environment is local to Remix. Data is only saved to browser memory and will vanish upon reload.' - await addProvider(1, 'vm-cancun', 'Remix VM (Cancun)', { isInjected: false, isVM: true, isRpcForkedState: false, statePath: '.states/vm-cancun/state.json' }, 'cancun', 'settingsVMCancunMode', titleVM) - await addProvider(50, 'vm-shanghai', 'Remix VM (Shanghai)', { isInjected: false, isVM: true, isRpcForkedState: false, statePath: '.states/vm-shanghai/state.json' }, 'shanghai', 'settingsVMShanghaiMode', titleVM) - await addProvider(51, 'vm-paris', 'Remix VM (Paris)', { isInjected: false, isVM: true, isRpcForkedState: false, statePath: '.states/vm-paris/state.json' }, 'paris', 'settingsVMParisMode', titleVM) - await addProvider(52, 'vm-london', 'Remix VM (London)', { isInjected: false, isVM: true, isRpcForkedState: false, statePath: '.states/vm-london/state.json' }, 'london', 'settingsVMLondonMode', titleVM) - await addProvider(53, 'vm-berlin', 'Remix VM (Berlin)', { isInjected: false, isVM: true, isRpcForkedState: false, statePath: '.states/vm-berlin/state.json' }, 'berlin', 'settingsVMBerlinMode', titleVM) - await addProvider(2, 'vm-mainnet-fork', 'Remix VM - Mainnet fork', { isInjected: false, isVM: true, isRpcForkedState: true }, 'cancun', 'settingsVMMainnetMode', titleVM) - await addProvider(3, 'vm-sepolia-fork', 'Remix VM - Sepolia fork', { isInjected: false, isVM: true, isRpcForkedState: true }, 'cancun', 'settingsVMSepoliaMode', titleVM) - await addProvider(4, 'vm-custom-fork', 'Remix VM - Custom fork', { isInjected: false, isVM: true, isRpcForkedState: true }, '', 'settingsVMCustomMode', titleVM) + await addProvider(1, 'vm-cancun', 'Remix VM (Cancun)', { isInjected: false, isVM: true, isRpcForkedState: false, statePath: '.states/vm-cancun/state.json', fork: 'cancun' }, 'settingsVMCancunMode', titleVM) + await addProvider(50, 'vm-shanghai', 'Remix VM (Shanghai)', { isInjected: false, isVM: true, isRpcForkedState: false, statePath: '.states/vm-shanghai/state.json', fork: 'shanghai' }, 'settingsVMShanghaiMode', titleVM) + await addProvider(51, 'vm-paris', 'Remix VM (Paris)', { isInjected: false, isVM: true, isRpcForkedState: false, statePath: '.states/vm-paris/state.json', fork: 'paris' }, 'settingsVMParisMode', titleVM) + await addProvider(52, 'vm-london', 'Remix VM (London)', { isInjected: false, isVM: true, isRpcForkedState: false, statePath: '.states/vm-london/state.json', fork: 'london' }, 'settingsVMLondonMode', titleVM) + await addProvider(53, 'vm-berlin', 'Remix VM (Berlin)', { isInjected: false, isVM: true, isRpcForkedState: false, statePath: '.states/vm-berlin/state.json', fork: 'berlin' }, 'settingsVMBerlinMode', titleVM) + await addProvider(2, 'vm-mainnet-fork', 'Remix VM - Mainnet fork', { isInjected: false, isVM: true, isRpcForkedState: true, fork: 'cancun' }, 'settingsVMMainnetMode', titleVM) + await addProvider(3, 'vm-sepolia-fork', 'Remix VM - Sepolia fork', { isInjected: false, isVM: true, isRpcForkedState: true, fork: 'cancun' }, 'settingsVMSepoliaMode', titleVM) + await addProvider(4, 'vm-custom-fork', 'Remix VM - Custom fork', { isInjected: false, isVM: true, isRpcForkedState: true, fork: '' }, 'settingsVMCustomMode', titleVM) // Forked VM States const addFVSProvider = async(stateFilePath, pos) => { @@ -276,9 +277,9 @@ export class RunTab extends ViewPlugin { description: descriptions[providerName], methods: ['sendAsync', 'init'], version: packageJson.version - }, this.blockchain, stateDetail.forkName) + }, this.blockchain, stateDetail.forkName, stateDetail.nodeUrl, stateDetail.blockNumber) this.engine.register(fvsProvider) - await addProvider(pos, providerName, stateDetail.stateName, { isInjected: false, isVM: true, isRpcForkedState: false, isVMStateForked: true, statePath: `.states/forked_states/${stateDetail.stateName}.json` }, stateDetail.forkName) + await addProvider(pos, providerName, stateDetail.stateName, { isInjected: false, isVM: true, isRpcForkedState: false, isVMStateForked: true, statePath: `.states/forked_states/${stateDetail.stateName}.json`, fork: stateDetail.forkName }) } this.on('filePanel', 'workspaceInitializationCompleted', async () => { @@ -300,13 +301,13 @@ export class RunTab extends ViewPlugin { }) // wallet connect - await addProvider(6, 'walletconnect', 'WalletConnect', { isInjected: false, isVM: false, isRpcForkedState: false }) + await addProvider(6, 'walletconnect', 'WalletConnect', { isInjected: false, isVM: false, isRpcForkedState: false, fork: '' }) // external provider - await addProvider(10, 'basic-http-provider', 'Custom - External Http Provider', { isInjected: false, isVM: false, isRpcForkedState: false }) - await addProvider(20, 'hardhat-provider', 'Dev - Hardhat Provider', { isInjected: false, isVM: false, isRpcForkedState: false }) - await addProvider(21, 'ganache-provider', 'Dev - Ganache Provider', { isInjected: false, isVM: false, isRpcForkedState: false }) - await addProvider(22, 'foundry-provider', 'Dev - Foundry Provider', { isInjected: false, isVM: false, isRpcForkedState: false }) + await addProvider(10, 'basic-http-provider', 'Custom - External Http Provider', { isInjected: false, isVM: false, isRpcForkedState: false, fork: '' }) + await addProvider(20, 'hardhat-provider', 'Dev - Hardhat Provider', { isInjected: false, isVM: false, isRpcForkedState: false, fork: '' }) + await addProvider(21, 'ganache-provider', 'Dev - Ganache Provider', { isInjected: false, isVM: false, isRpcForkedState: false, fork: '' }) + await addProvider(22, 'foundry-provider', 'Dev - Foundry Provider', { isInjected: false, isVM: false, isRpcForkedState: false, fork: '' }) // register injected providers diff --git a/apps/remix-ide/src/blockchain/execution-context.js b/apps/remix-ide/src/blockchain/execution-context.js index cf82b96ad82..442e7184da7 100644 --- a/apps/remix-ide/src/blockchain/execution-context.js +++ b/apps/remix-ide/src/blockchain/execution-context.js @@ -148,7 +148,7 @@ export class ExecutionContext { if (this.customNetWorks[context]) { var network = this.customNetWorks[context] await network.init() - this.currentFork = network.fork + this.currentFork = network.config.fork this.executionContext = context // injected web3.setProvider(network.provider) diff --git a/libs/remix-simulator/src/provider.ts b/libs/remix-simulator/src/provider.ts index a9be59dacb5..8fae322c6b3 100644 --- a/libs/remix-simulator/src/provider.ts +++ b/libs/remix-simulator/src/provider.ts @@ -54,6 +54,7 @@ export class Provider { pendingRequests: Array constructor (options: ProviderOptions = {} as ProviderOptions) { + console.log(options) this.options = options this.connected = true this.vmContext = new VMContext(options['fork'], options['nodeUrl'], options['blockNumber'], options['stateDb'], options['blocks']) diff --git a/libs/remix-ui/environment-explorer/src/lib/types/index.ts b/libs/remix-ui/environment-explorer/src/lib/types/index.ts index 37c57362c46..fa502b54196 100644 --- a/libs/remix-ui/environment-explorer/src/lib/types/index.ts +++ b/libs/remix-ui/environment-explorer/src/lib/types/index.ts @@ -31,7 +31,10 @@ export type ProviderConfig = { isInjected: boolean isRpcForkedState?: boolean isVMStateForked?: boolean - statePath?: string + fork: string + statePath?: string, + blockNumber?: string + nodeUrl?: string } export type Provider = { @@ -41,8 +44,7 @@ export type Provider = { name: string displayName: string logo?: string, - logos?: string[], - fork: string + logos?: string[], description?: string config: ProviderConfig title: string 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 ecf0d53ba4b..44a10d7a6ef 100644 --- a/libs/remix-ui/run-tab/src/lib/components/environment.tsx +++ b/libs/remix-ui/run-tab/src/lib/components/environment.tsx @@ -64,26 +64,41 @@ export function EnvironmentUI(props: EnvironmentProps) { const forkState = async () => { _paq.push(['trackEvent', 'udapp', 'forkState', `forkState clicked`]) + + let stateTemp = `.states/forked_states/state_temp.json` + let statePath = currentProvider.config.statePath + if (!statePath) { + // if the current provider doesn't have a saved state, we dump the current state from memory. + // state_temp.json is removed after the operation completes + const state = await props.runTabPlugin.blockchain.executionContext.getStateDetails() + statePath = stateTemp + await props.runTabPlugin.call('fileManager', 'writeFile', statePath, state) + } + const context = currentProvider.name vmStateName.current = `${context}_${Date.now()}` - const contextExists = await props.runTabPlugin.call('fileManager', 'exists', currentProvider.config.statePath) + const contextExists = await props.runTabPlugin.call('fileManager', 'exists', statePath) if (contextExists) { props.modal( intl.formatMessage({ id: 'udapp.forkStateTitle' }), forkStatePrompt(vmStateName.current), intl.formatMessage({ id: 'udapp.fork' }), async () => { - let currentStateDb = await props.runTabPlugin.call('fileManager', 'readFile', currentProvider.config.statePath) + let currentStateDb = await props.runTabPlugin.call('fileManager', 'readFile', statePath) currentStateDb = JSON.parse(currentStateDb) currentStateDb.stateName = vmStateName.current - currentStateDb.forkName = currentProvider.fork + currentStateDb.forkName = currentProvider.config.fork + currentStateDb.nodeUrl = currentProvider.config.nodeUrl currentStateDb.savingTimestamp = Date.now() await props.runTabPlugin.call('fileManager', 'writeFile', `.states/forked_states/${vmStateName.current}.json`, JSON.stringify(currentStateDb, null, 2)) props.runTabPlugin.emit('vmStateForked', vmStateName.current) + await props.runTabPlugin.call('fileManager', 'remove', stateTemp) _paq.push(['trackEvent', 'udapp', 'forkState', `forked from ${context}`]) }, intl.formatMessage({ id: 'udapp.cancel' }), - null + () => { + props.runTabPlugin.call('fileManager', 'remove', stateTemp) + } ) } else props.runTabPlugin.call('notification', 'toast', `State not available to fork, as no transactions have been made for selected environment & selected workspace.`) } diff --git a/libs/remix-ui/run-tab/src/lib/types/execution-context.d.ts b/libs/remix-ui/run-tab/src/lib/types/execution-context.d.ts index 598c4f8f7d8..bf159b108fc 100644 --- a/libs/remix-ui/run-tab/src/lib/types/execution-context.d.ts +++ b/libs/remix-ui/run-tab/src/lib/types/execution-context.d.ts @@ -32,4 +32,5 @@ export class ExecutionContext { _updateChainContext(): Promise; listenOnLastBlock(): void; txDetailsLink(network: any, hash: any): any; + getStateDetails(): Promise }