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
+}