Skip to content

Commit

Permalink
VM state fork with live net
Browse files Browse the repository at this point in the history
  • Loading branch information
yann300 committed Feb 6, 2025
1 parent ab225a6 commit f96a396
Show file tree
Hide file tree
Showing 7 changed files with 54 additions and 30 deletions.
6 changes: 5 additions & 1 deletion apps/remix-ide/src/app/providers/vm-provider.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
}
43 changes: 22 additions & 21 deletions apps/remix-ide/src/app/udapp/run-tab.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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: {},
Expand All @@ -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)
Expand All @@ -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'])
Expand Down Expand Up @@ -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) => {
Expand All @@ -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 () => {
Expand All @@ -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

Expand Down
2 changes: 1 addition & 1 deletion apps/remix-ide/src/blockchain/execution-context.js
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
1 change: 1 addition & 0 deletions libs/remix-simulator/src/provider.ts
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@ export class Provider {
pendingRequests: Array<any>

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'])
Expand Down
8 changes: 5 additions & 3 deletions libs/remix-ui/environment-explorer/src/lib/types/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 = {
Expand All @@ -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
Expand Down
23 changes: 19 additions & 4 deletions libs/remix-ui/run-tab/src/lib/components/environment.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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.`)
}
Expand Down
1 change: 1 addition & 0 deletions libs/remix-ui/run-tab/src/lib/types/execution-context.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,4 +32,5 @@ export class ExecutionContext {
_updateChainContext(): Promise<void>;
listenOnLastBlock(): void;
txDetailsLink(network: any, hash: any): any;
getStateDetails(): Promise<string>
}

0 comments on commit f96a396

Please sign in to comment.