diff --git a/apps/circuit-compiler/src/app/actions/constant.ts b/apps/circuit-compiler/src/app/actions/constant.ts index 6dc9ffcff0..4b3dc07e0e 100644 --- a/apps/circuit-compiler/src/app/actions/constant.ts +++ b/apps/circuit-compiler/src/app/actions/constant.ts @@ -318,7 +318,7 @@ contract PlonkVerifier { /////// // Computes the inverse of an array of values - // See https://vitalik.ca/general/2018/07/21/starks_part_3.html in section where explain fields operations + // See https://vitalik.eth.limo/general/2018/07/21/starks_part_3.html in section where explain fields operations ////// function inverseArray(pVals, n) { diff --git a/apps/remix-dapp/src/locales/en/udapp.json b/apps/remix-dapp/src/locales/en/udapp.json index d8ade051f8..26fd59aa95 100644 --- a/apps/remix-dapp/src/locales/en/udapp.json +++ b/apps/remix-dapp/src/locales/en/udapp.json @@ -49,6 +49,9 @@ "udapp.enterAMessageToSign": "Enter a message to sign", "udapp.hash": "hash", "udapp.signature": "signature", + "udapp.saveVmStateTitle": "Save VM state", + "udapp.saveVmStateLabel": "State Name", + "udapp.saveVmStateTip": "Saved VM states can be pinned as environment using Environment Explorer", "udapp.injectedTitle": "Unfortunately it's not possible to create an account using injected provider. Please create the account directly from your provider (i.e metamask or other of the same type).", "udapp.createNewAccount": "Create a new account", "udapp.web3Title": "Creating an account is possible only in Personal mode. Please go to Settings to enable it.", @@ -63,6 +66,7 @@ "udapp._comment_environment.tsx": "libs/remix-ui/run-tab/src/lib/components/environment.tsx", "udapp.environment": "Environment", "udapp.environmentDocs": "Click for docs about Environment", + "udapp.saveVmState": "Save VM state", "udapp.tooltipText2": "Open chainlist.org and get the connection specs of the chain you want to interact with.", "udapp.tooltipText3": "Click to open a bridge for converting L1 mainnet ETH to the selected network currency.", diff --git a/apps/remix-ide/src/app/providers/environment-explorer.tsx b/apps/remix-ide/src/app/providers/environment-explorer.tsx index 8f25793db9..facc93e4d8 100644 --- a/apps/remix-ide/src/app/providers/environment-explorer.tsx +++ b/apps/remix-ide/src/app/providers/environment-explorer.tsx @@ -1,6 +1,6 @@ import React from 'react' // eslint-disable-line import { ViewPlugin } from '@remixproject/engine-web' -import { PluginViewWrapper } from '@remix-ui/helper' +import { CustomTooltip, PluginViewWrapper } from '@remix-ui/helper' import { RemixUIGridView } from '@remix-ui/remix-ui-grid-view' import { RemixUIGridSection } from '@remix-ui/remix-ui-grid-section' import { RemixUIGridCell } from '@remix-ui/remix-ui-grid-cell' @@ -25,7 +25,7 @@ const profile = { methods: [] } -type ProvidersSection = `Injected` | 'Remix VMs' | 'Externals' | 'Remix forked VMs' +type ProvidersSection = `Injected` | 'Remix VMs' | 'Externals' | 'Remix forked VMs' | 'Saved VM States' export class EnvironmentExplorer extends ViewPlugin { providers: { [key in ProvidersSection]: Provider[] } @@ -39,6 +39,7 @@ export class EnvironmentExplorer extends ViewPlugin { this.providers = { 'Injected': [], 'Remix VMs': [], + 'Saved VM States': [], 'Remix forked VMs': [], 'Externals': [] } @@ -57,6 +58,8 @@ export class EnvironmentExplorer extends ViewPlugin { this.providers['Remix forked VMs'].push(provider) } else if (provider.isVM) { this.providers['Remix VMs'].push(provider) + } else if (provider.isSavedState) { + this.providers['Saved VM States'].push(provider) } else { this.providers['Externals'].push(provider) } @@ -84,6 +87,7 @@ export class EnvironmentExplorer extends ViewPlugin { this.providers = { 'Injected': [], 'Remix VMs': [], + 'Saved VM States': [], 'Externals': [], 'Remix forked VMs': [] } @@ -171,6 +175,48 @@ export class EnvironmentExplorer extends ViewPlugin {
{provider.description}
})} + {this.providers['Saved VM States'].map(provider => { + const { latestBlock, timestamp } = JSON.parse(provider.description) + return { + if (pinned) { + this.emit('providerPinned', provider.name, provider) + this.call('notification', 'toast', `"${provider.displayName}" has been added to the Environment list of the Deploy & Run Transactions plugin.`) + return true + } + const providerName = await this.call('blockchain', 'getProvider') + if (providerName !== provider.name) { + this.emit('providerUnpinned', provider.name, provider) + this.call('notification', 'toast', `"${provider.displayName}" has been removed from the Environment list of the Deploy & Run Transactions plugin.`) + return true + } else { + this.call('notification', 'toast', 'Cannot unpin the current selected provider') + return false + } + }} + > +
Latest Block: {parseInt(latestBlock)}
+ +
Saved at: {(new Date(timestamp)).toDateString()}
+
+
+ })}
{ + const addProvider = async (position, name, displayName, isInjected, isVM, isSavedState, fork = '', dataId = '', title = '', forkedVM = false) => { await this.call('blockchain', 'addProvider', { position, options: {}, @@ -191,6 +190,7 @@ export class RunTab extends ViewPlugin { isInjected, isForkedVM: forkedVM, isVM, + isSavedState, title, init: async function () { const options = await udapp.call(name, 'init') @@ -206,13 +206,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, true, false) + await addProvider(position, name, displayName + ' - ' + event.detail.info.name, true, false, false) } 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, true, false) + await addProvider(0, name, displayName, true, false, false) if (event.detail.info.name === 'MetaMask') { await addCustomInjectedProvider(7, event, 'injected-metamask-optimism', 'L2 - Optimism', '0xa', ['https://mainnet.optimism.io']) @@ -248,23 +248,63 @@ 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)', false, true, 'cancun', 'settingsVMCancunMode', titleVM) - await addProvider(50, 'vm-shanghai', 'Remix VM (Shanghai)', false, true, 'shanghai', 'settingsVMShanghaiMode', titleVM) - await addProvider(51, 'vm-paris', 'Remix VM (Paris)', false, true, 'paris', 'settingsVMParisMode', titleVM) - await addProvider(52, 'vm-london', 'Remix VM (London)', false, true, 'london', 'settingsVMLondonMode', titleVM) - await addProvider(53, 'vm-berlin', 'Remix VM (Berlin)', false, true, 'berlin', 'settingsVMBerlinMode', titleVM) - await addProvider(2, 'vm-mainnet-fork', 'Remix VM - Mainnet fork', false, true, 'cancun', 'settingsVMMainnetMode', titleVM, true) - await addProvider(3, 'vm-sepolia-fork', 'Remix VM - Sepolia fork', false, true, 'cancun', 'settingsVMSepoliaMode', titleVM, true) - await addProvider(4, 'vm-custom-fork', 'Remix VM - Custom fork', false, true, '', 'settingsVMCustomMode', titleVM, true) + await addProvider(1, 'vm-cancun', 'Remix VM (Cancun)', false, true, false, 'cancun', 'settingsVMCancunMode', titleVM) + await addProvider(50, 'vm-shanghai', 'Remix VM (Shanghai)', false, true, false, 'shanghai', 'settingsVMShanghaiMode', titleVM) + await addProvider(51, 'vm-paris', 'Remix VM (Paris)', false, true, false, 'paris', 'settingsVMParisMode', titleVM) + await addProvider(52, 'vm-london', 'Remix VM (London)', false, true, false, 'london', 'settingsVMLondonMode', titleVM) + await addProvider(53, 'vm-berlin', 'Remix VM (Berlin)', false, true, false, 'berlin', 'settingsVMBerlinMode', titleVM) + await addProvider(2, 'vm-mainnet-fork', 'Remix VM - Mainnet fork', false, true, false, 'cancun', 'settingsVMMainnetMode', titleVM, true) + await addProvider(3, 'vm-sepolia-fork', 'Remix VM - Sepolia fork', false, true, false, 'cancun', 'settingsVMSepoliaMode', titleVM, true) + await addProvider(4, 'vm-custom-fork', 'Remix VM - Custom fork', false, true, false, '', 'settingsVMCustomMode', titleVM, true) + + // Saved VM States + const addSVSProvider = async(stateFilePath, pos) => { + let stateDetail = await this.call('fileManager', 'readFile', stateFilePath) + stateDetail = JSON.parse(stateDetail) + const providerName = 'vm-svs-' + stateDetail.stateName + descriptions[providerName] = JSON.stringify({ + name: providerName, + latestBlock: stateDetail.latestBlockNumber, + timestamp: stateDetail.savingTimestamp + }) + // Create and register provider plugin for saved states + const svsProvider = new SavedVMStateProvider({ + name: providerName, + displayName: stateDetail.stateName, + kind: 'provider', + description: descriptions[providerName], + methods: ['sendAsync', 'init'], + version: packageJson.version + }, this.blockchain, stateDetail.forkName) + this.engine.register(svsProvider) + await addProvider(pos, providerName, stateDetail.stateName, false, false, true, stateDetail.forkName) + } + + this.on('filePanel', 'workspaceInitializationCompleted', async () => { + const ssExists = await this.call('fileManager', 'exists', '.states/saved_states') + if (ssExists) { + const savedStatesDetails = await this.call('fileManager', 'readdir', '.states/saved_states') + const savedStatesFiles = Object.keys(savedStatesDetails) + let pos = 10 + for (const filePath of savedStatesFiles) { + pos += 1 + await addSVSProvider(filePath, pos) + } + } + }) + + this.on('udapp', 'vmStateSaved', async (stateName) => { + await addSVSProvider(`.states/saved_states/${stateName}.json`, 20) + }) // wallet connect - await addProvider(6, 'walletconnect', 'WalletConnect', false, false) + await addProvider(6, 'walletconnect', 'WalletConnect', false, false, false) // external provider - await addProvider(10, 'basic-http-provider', 'Custom - External Http Provider', false, false) - await addProvider(20, 'hardhat-provider', 'Dev - Hardhat Provider', false, false) - await addProvider(21, 'ganache-provider', 'Dev - Ganache Provider', false, false) - await addProvider(22, 'foundry-provider', 'Dev - Foundry Provider', false, false) + await addProvider(10, 'basic-http-provider', 'Custom - External Http Provider', false, false, false) + await addProvider(20, 'hardhat-provider', 'Dev - Hardhat Provider', false, false, false) + await addProvider(21, 'ganache-provider', 'Dev - Ganache Provider', false, false, false) + await addProvider(22, 'foundry-provider', 'Dev - Foundry Provider', false, false, false) // register injected providers diff --git a/apps/remix-ide/src/blockchain/blockchain.tsx b/apps/remix-ide/src/blockchain/blockchain.tsx index 6ccbaed7ac..d5832e9ff4 100644 --- a/apps/remix-ide/src/blockchain/blockchain.tsx +++ b/apps/remix-ide/src/blockchain/blockchain.tsx @@ -55,6 +55,7 @@ export type Provider = { description?: string isInjected: boolean isVM: boolean + isSavedState: boolean isForkedVM: boolean title: string init: () => Promise @@ -78,7 +79,7 @@ export class Blockchain extends Plugin { } error?: string } - providers: {[key: string]: VMProvider | InjectedProvider | NodeProvider} + providers: {[key: string]: VMProvider | InjectedProvider | NodeProvider } transactionContextAPI: TransactionContextAPI registeredPluginEvents: string[] defaultPinnedProviders: string[] @@ -204,9 +205,8 @@ export class Blockchain extends Plugin { } setupProviders() { - const vmProvider = new VMProvider(this.executionContext) this.providers = {} - this.providers['vm'] = vmProvider + this.providers['vm'] = new VMProvider(this.executionContext) this.providers.injected = new InjectedProvider(this.executionContext) this.providers.web3 = new NodeProvider(this.executionContext, this.config) } @@ -692,13 +692,17 @@ export class Blockchain extends Plugin { if (saveEvmState) { const contextExists = await this.call('fileManager', 'exists', `.states/${context}/state.json`) - if (contextExists) { const stateDb = await this.call('fileManager', 'readFile', `.states/${context}/state.json`) - await this.getCurrentProvider().resetEnvironment(stateDb) } else { - await this.getCurrentProvider().resetEnvironment() + // check if saved VM state is used as provider + const stateName = context.replace('vm-svs-', '') + const contextExists = await this.call('fileManager', 'exists', `.states/saved_states/${stateName}.json`) + if (contextExists) { + const stateDb = await this.call('fileManager', 'readFile', `.states/saved_states/${stateName}.json`) + await this.getCurrentProvider().resetEnvironment(stateDb) + } else await this.getCurrentProvider().resetEnvironment() } } else { await this.getCurrentProvider().resetEnvironment() @@ -952,8 +956,23 @@ export class Blockchain extends Plugin { if (isVM) { if (!tx.useCall && this.config.get('settings/save-evm-state')) { try { - const state = await this.executionContext.getStateDetails() - this.call('fileManager', 'writeFile', `.states/${this.executionContext.getProvider()}/state.json`, state) + let state = await this.executionContext.getStateDetails() + const provider = this.executionContext.getProvider() + if (provider.startsWith('vm-svs-')) { + const stateName = provider.replace('vm-svs-', '') + const stateFileExists = this.call('fileManager', 'exists', `.states/saved_states/${stateName}.json`) + if (stateFileExists) { + let stateDetails = await this.call('fileManager', 'readFile', `.states/saved_states/${stateName}.json`) + stateDetails = JSON.parse(stateDetails) + state = JSON.parse(state) + state['stateName'] = stateDetails.stateName + state['forkName'] = stateDetails.forkName + state['savingTimestamp'] = stateDetails.savingTimestamp + state = JSON.stringify(state, null, 2) + } + this.call('fileManager', 'writeFile', `.states/saved_states/${stateName}.json`, state) + } + else this.call('fileManager', 'writeFile', `.states/${provider}/state.json`, state) } catch (e) { console.error(e) } @@ -985,7 +1004,6 @@ export class Blockchain extends Plugin { this.call('terminal', 'logHtml', finalLogs) } execResult = await this.web3().remix.getExecutionResultFromSimulator(txResult.transactionHash) - if (execResult) { // if it's not the VM, we don't have return value. We only have the transaction, and it does not contain the return value. returnValue = execResult 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 a0428c42a1..192bcaac6a 100644 --- a/libs/remix-ui/run-tab/src/lib/components/environment.tsx +++ b/libs/remix-ui/run-tab/src/lib/components/environment.tsx @@ -1,11 +1,12 @@ // eslint-disable-next-line no-use-before-define -import React, { useEffect } from 'react' -import { FormattedMessage } from 'react-intl' +import React, { useEffect, useRef } from 'react' +import { FormattedMessage, useIntl } from 'react-intl' import { EnvironmentProps, Provider } from '../types' import { Dropdown } from 'react-bootstrap' import { CustomMenu, CustomToggle, CustomTooltip } from '@remix-ui/helper' export function EnvironmentUI(props: EnvironmentProps) { + const vmStateName = useRef('') Object.entries(props.providers.providerList.filter((provider) => { return provider.isVM })) Object.entries(props.providers.providerList.filter((provider) => { return provider.isInjected })) @@ -23,6 +24,55 @@ export function EnvironmentUI(props: EnvironmentProps) { 'L2 - Arbitrum': 'https://bridge.arbitrum.io/' } + const intl = useIntl() + const isSaveEvmStateChecked = props.config.get('settings/save-evm-state') + + const saveVmStatePrompt = (defaultName: string) => { + return ( +
+ + vmStateName.current = e.target.value} + /> +
+
+ Tip: +
+
+ ) + } + + const saveVmState = () => { + const context = currentProvider.name + vmStateName.current = `${context}_${Date.now()}` + props.modal( + intl.formatMessage({ id: 'udapp.saveVmStateTitle' }), + saveVmStatePrompt(vmStateName.current), + intl.formatMessage({ id: 'udapp.save' }), + async () => { + const contextExists = await props.runTabPlugin.call('fileManager', 'exists', `.states/${context}/state.json`) + if (contextExists) { + let currentStateDb = await props.runTabPlugin.call('fileManager', 'readFile', `.states/${context}/state.json`) + currentStateDb = JSON.parse(currentStateDb) + currentStateDb.stateName = vmStateName.current + currentStateDb.forkName = currentProvider.fork + currentStateDb.savingTimestamp = Date.now() + await props.runTabPlugin.call('fileManager', 'writeFile', `.states/saved_states/${vmStateName.current}.json`, JSON.stringify(currentStateDb, null, 2)) + props.runTabPlugin.emit('vmStateSaved', vmStateName.current) + props.runTabPlugin.call('notification', 'toast', `VM state ${vmStateName.current} saved.`) + } + }, + intl.formatMessage({ id: 'udapp.cancel' }), + null + ) + } + const isL2 = (providerDisplayName: string) => providerDisplayName && (providerDisplayName.startsWith('L2 - Optimism') || providerDisplayName.startsWith('L2 - Arbitrum')) return (
@@ -38,6 +88,9 @@ export function EnvironmentUI(props: EnvironmentProps) { + { currentProvider && currentProvider.isVM && isSaveEvmStateChecked && }> + + }
diff --git a/libs/remix-ui/run-tab/src/lib/components/settingsUI.tsx b/libs/remix-ui/run-tab/src/lib/components/settingsUI.tsx index 12a7da58dd..733c867205 100644 --- a/libs/remix-ui/run-tab/src/lib/components/settingsUI.tsx +++ b/libs/remix-ui/run-tab/src/lib/components/settingsUI.tsx @@ -12,7 +12,15 @@ export function SettingsUI(props: SettingsProps) { return (
- + void + modal: (title: string, message: string | JSX.Element, okLabel: string, okFn: () => void, cancelLabel?: string, cancelFn?: () => void, okBtnClass?: string, cancelBtnClass?: string) => void, + config: any } export interface NetworkProps { diff --git a/libs/remix-ws-templates/src/templates/hashchecker/templates/plonk_verifier.sol.ejs b/libs/remix-ws-templates/src/templates/hashchecker/templates/plonk_verifier.sol.ejs index 8b3ed1d109..963bade7f1 100644 --- a/libs/remix-ws-templates/src/templates/hashchecker/templates/plonk_verifier.sol.ejs +++ b/libs/remix-ws-templates/src/templates/hashchecker/templates/plonk_verifier.sol.ejs @@ -152,7 +152,7 @@ contract PlonkVerifier { /////// // Computes the inverse of an array of values - // See https://vitalik.ca/general/2018/07/21/starks_part_3.html in section where explain fields operations + // See https://vitalik.eth.limo/general/2018/07/21/starks_part_3.html in section where explain fields operations ////// function inverseArray(pVals, n) { @@ -707,4 +707,4 @@ contract PlonkVerifier { } } -} \ No newline at end of file +} diff --git a/libs/remix-ws-templates/src/templates/rln/templates/plonk_verifier.sol.ejs b/libs/remix-ws-templates/src/templates/rln/templates/plonk_verifier.sol.ejs index 8b3ed1d109..963bade7f1 100644 --- a/libs/remix-ws-templates/src/templates/rln/templates/plonk_verifier.sol.ejs +++ b/libs/remix-ws-templates/src/templates/rln/templates/plonk_verifier.sol.ejs @@ -152,7 +152,7 @@ contract PlonkVerifier { /////// // Computes the inverse of an array of values - // See https://vitalik.ca/general/2018/07/21/starks_part_3.html in section where explain fields operations + // See https://vitalik.eth.limo/general/2018/07/21/starks_part_3.html in section where explain fields operations ////// function inverseArray(pVals, n) { @@ -707,4 +707,4 @@ contract PlonkVerifier { } } -} \ No newline at end of file +} diff --git a/libs/remix-ws-templates/src/templates/semaphore/templates/plonk_verifier.sol.ejs b/libs/remix-ws-templates/src/templates/semaphore/templates/plonk_verifier.sol.ejs index 8b3ed1d109..963bade7f1 100644 --- a/libs/remix-ws-templates/src/templates/semaphore/templates/plonk_verifier.sol.ejs +++ b/libs/remix-ws-templates/src/templates/semaphore/templates/plonk_verifier.sol.ejs @@ -152,7 +152,7 @@ contract PlonkVerifier { /////// // Computes the inverse of an array of values - // See https://vitalik.ca/general/2018/07/21/starks_part_3.html in section where explain fields operations + // See https://vitalik.eth.limo/general/2018/07/21/starks_part_3.html in section where explain fields operations ////// function inverseArray(pVals, n) { @@ -707,4 +707,4 @@ contract PlonkVerifier { } } -} \ No newline at end of file +}