refactor VM providers

pull/3356/head
yann300 2 years ago
parent f4b953932c
commit 3744c7dc00
  1. 2
      apps/remix-ide-e2e/src/tests/plugin_api.ts
  2. 5
      apps/remix-ide/src/app.js
  3. 2
      apps/remix-ide/src/app/providers/abstract-provider.tsx
  4. 64
      apps/remix-ide/src/app/providers/vm-provider.tsx
  5. 32
      apps/remix-ide/src/app/udapp/run-tab.js
  6. 16
      apps/remix-ide/src/blockchain/blockchain.js
  7. 62
      apps/remix-ide/src/blockchain/execution-context.js
  8. 9
      apps/remix-ide/src/blockchain/providers/vm.js
  9. 2
      libs/remix-lib/src/execution/txListener.ts
  10. 2
      libs/remix-ui/debugger-ui/src/lib/debugger-ui.tsx
  11. 5
      libs/remix-ui/run-tab/src/lib/actions/account.ts
  12. 2
      libs/remix-ui/run-tab/src/lib/actions/events.ts
  13. 5
      libs/remix-ui/run-tab/src/lib/components/environment.tsx
  14. 27
      libs/remix-ui/run-tab/src/lib/reducers/runTab.ts
  15. 1
      libs/remix-ui/run-tab/src/lib/run-tab.tsx
  16. 1
      libs/remix-ui/run-tab/src/lib/types/blockchain.d.ts
  17. 1
      libs/remix-ui/run-tab/src/lib/types/execution-context.d.ts
  18. 2
      libs/remix-ui/run-tab/src/lib/types/index.ts
  19. 4
      libs/remix-ui/terminal/src/lib/components/Context.tsx
  20. 2
      libs/remix-ui/terminal/src/lib/components/RenderCall.tsx
  21. 2
      libs/remix-ui/terminal/src/lib/components/RenderKnownTransactions.tsx
  22. 2
      libs/remix-ui/terminal/src/lib/components/RenderUnknownTransactions.tsx

@ -183,7 +183,7 @@ module.exports = {
},
'Should select another provider #group1': async function (browser: NightwatchBrowser) {
await clickAndCheckLog(browser, 'udapp:setEnvironmentMode', null, null, { context: 'vm', fork: 'berlin' })
await clickAndCheckLog(browser, 'udapp:setEnvironmentMode', null, null, { context: 'vm-berlin' })
await browser
.frameParent()
.useCss()

@ -27,6 +27,7 @@ import { StoragePlugin } from './app/plugins/storage'
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 { HardhatProvider } from './app/providers/hardhat-provider'
import { GanacheProvider } from './app/providers/ganache-provider'
import { FoundryProvider } from './app/providers/foundry-provider'
@ -202,6 +203,8 @@ class AppComponent {
const networkModule = new NetworkModule(blockchain)
// ----------------- represent the current selected web3 provider ----
const web3Provider = new Web3ProviderModule(blockchain)
const vmProviderBerlin = new BerlinVMProvider(blockchain)
const vmProviderLondon = new LondonVMProvider(blockchain)
const hardhatProvider = new HardhatProvider(blockchain)
const ganacheProvider = new GanacheProvider(blockchain)
const foundryProvider = new FoundryProvider(blockchain)
@ -268,6 +271,8 @@ class AppComponent {
fetchAndCompile,
dGitProvider,
storagePlugin,
vmProviderBerlin,
vmProviderLondon,
hardhatProvider,
ganacheProvider,
foundryProvider,

@ -129,7 +129,7 @@ export abstract class AbstractProvider extends Plugin {
}
this.call('notification', 'alert', modalContent)
}
await this.call('udapp', 'setEnvironmentMode', { context: 'vm', fork: 'london' })
await this.call('udapp', 'setEnvironmentMode', { context: 'vm-london'})
setTimeout(_ => { this.blocked = false }, 1000) // we wait 1 second for letting remix to switch to vm
return
}

@ -0,0 +1,64 @@
import * as packageJson from '../../../../../package.json'
import { JsonDataRequest, RejectRequest, SuccessRequest } from '../providers/abstract-provider'
import { Plugin } from '@remixproject/engine'
export class BasicVMProvider extends Plugin {
blockchain
fork: string
constructor (profile, blockchain) {
super(profile)
this.blockchain = blockchain
this.fork = null
}
init () {}
sendAsync (data: JsonDataRequest): Promise<any> {
return new Promise((resolve, reject) => {
this.sendAsyncInternal(data, resolve, reject)
})
}
private async sendAsyncInternal (data: JsonDataRequest, resolve: SuccessRequest, reject: RejectRequest): Promise<void> {
try {
await this.blockchain.providers.vm.provider.sendAsync(data, (error, result) => {
if (error) return reject(error)
else {
resolve({ jsonrpc: '2.0', result, id: data.id })
}
})
} catch (error) {
reject(error)
}
}
}
export class BerlinVMProvider extends BasicVMProvider {
constructor (blockchain) {
super({
name: 'vm-berlin',
displayName: 'Remix VM (Berlin)',
kind: 'provider',
description: 'Remix VM (Berlin)',
methods: ['sendAsync', 'init'],
version: packageJson.version
}, blockchain)
this.blockchain = blockchain
this.fork = 'berlin'
}
}
export class LondonVMProvider extends BasicVMProvider {
constructor (blockchain) {
super({
name: 'vm-london',
displayName: 'Remix VM (London)',
kind: 'provider',
description: 'Remix VM (London)',
methods: ['sendAsync', 'init'],
version: packageJson.version
}, blockchain)
this.blockchain = blockchain
this.fork = 'london'
}
}

@ -101,10 +101,15 @@ export class RunTab extends ViewPlugin {
async onInitDone () {
const udapp = this // eslint-disable-line
const addProvider = async (name, displayName, isInjected) => {
const addProvider = async (name, displayName, isInjected, isVM, fork = '', dataId = '', title = '') => {
await this.call('blockchain', 'addProvider', {
name: displayName,
dataId,
name,
displayName,
fork,
isInjected,
isVM,
title,
init: () => { return this.call(name, 'init') },
provider: {
async sendAsync (payload, callback) {
@ -119,19 +124,26 @@ export class RunTab extends ViewPlugin {
})
}
await addProvider('hardhat-provider', 'Hardhat Provider', false)
await addProvider('ganache-provider', 'Ganache Provider', false)
await addProvider('foundry-provider', 'Foundry Provider', false)
await addProvider('walletconnect', 'Wallet Connect', false)
await addProvider('basic-http-provider', 'External Http Provider', false)
// VM
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)
// external provider
await addProvider('hardhat-provider', 'Hardhat Provider', false, false)
await addProvider('ganache-provider', 'Ganache Provider', false, false)
await addProvider('foundry-provider', 'Foundry Provider', false, false)
await addProvider('walletconnect', 'Wallet Connect', false, false)
await addProvider('basic-http-provider', 'External Http Provider', false, false)
// injected provider
const displayNameInjected = `Injected Provider${(window && window.ethereum && !(window.ethereum.providers && !window.ethereum.selectedProvider)) ?
window.ethereum.isCoinbaseWallet || window.ethereum.selectedProvider?.isCoinbaseWallet ? ' - Coinbase' :
window.ethereum.isBraveWallet || window.ethereum.selectedProvider?.isBraveWallet ? ' - Brave' :
window.ethereum.isMetaMask || window.ethereum.selectedProvider?.isMetaMask ? ' - MetaMask' : '' : ''}`
await addProvider('injected', displayNameInjected, true)
await addProvider('injected-optimism-provider', 'Optimism Provider', true)
await addProvider('injected-arbitrum-one-provider', 'Arbitrum One Provider', true)
await addProvider('injected', displayNameInjected, true, false)
await addProvider('injected-optimism-provider', 'Optimism Provider', true, false)
await addProvider('injected-arbitrum-one-provider', 'Arbitrum One Provider', true, false)
}
writeFile (fileName, content) {

@ -82,14 +82,18 @@ export class Blockchain extends Plugin {
}
setupProviders () {
const vmProvider = new VMProvider(this.executionContext)
this.providers = {}
this.providers.vm = new VMProvider(this.executionContext)
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)
}
getCurrentProvider () {
const provider = this.getProvider()
if (this.providers[provider]) return this.providers[provider]
return this.providers.web3 // default to the common type of provider
}
@ -364,10 +368,6 @@ export class Blockchain extends Plugin {
return this.executionContext.executionContextChange(context, null, confirmCb, infoCb, cb)
}
setProviderFromEndpoint (target, context, cb) {
return this.executionContext.setProviderFromEndpoint(target, context, cb)
}
detectNetwork (cb) {
return this.executionContext.detectNetwork(cb)
}
@ -389,7 +389,7 @@ export class Blockchain extends Plugin {
}
isWeb3Provider () {
const isVM = this.getProvider() === 'vm'
const isVM = this.executionContext.isVM()
const isInjected = this.getProvider() === 'injected'
return (!isVM && !isInjected)
}
@ -408,7 +408,7 @@ export class Blockchain extends Plugin {
web3 () {
// @todo(https://github.com/ethereum/remix-project/issues/431)
const isVM = this.getProvider() === 'vm'
const isVM = this.executionContext.isVM()
if (isVM) {
return this.providers.vm.web3
}
@ -527,7 +527,7 @@ export class Blockchain extends Plugin {
* @param {{privateKey: string, balance: string}} newAccount The new account to create
*/
createVMAccount (newAccount) {
if (this.getProvider() !== 'vm') {
if (!this.executionContext.isVM()) {
throw new Error('plugin API does not allow creating a new account through web3 connection. Only vm mode is allowed')
}
return this.providers.vm.createVMAccount(newAccount)

@ -20,7 +20,7 @@ if (typeof window !== 'undefined' && typeof window.ethereum !== 'undefined') {
export class ExecutionContext {
constructor () {
this.event = new EventManager()
this.executionContext = 'vm'
this.executionContext = 'vm-london'
this.lastBlock = null
this.blockGasLimitDefault = 4300000
this.blockGasLimit = this.blockGasLimitDefault
@ -35,7 +35,7 @@ export class ExecutionContext {
init (config) {
if (config.get('settings/always-use-vm')) {
this.executionContext = 'vm'
this.executionContext = 'vm-london'
}
}
@ -52,7 +52,7 @@ export class ExecutionContext {
}
isVM () {
return this.executionContext === 'vm'
return this.executionContext.startsWith('vm')
}
setWeb3 (context, web3) {
@ -98,7 +98,7 @@ export class ExecutionContext {
removeProvider (name) {
if (name && this.customNetWorks[name]) {
if (this.executionContext === name) this.setContext('vm', null, null, null)
if (this.executionContext === name) this.setContext('vm-london', null, null, null)
delete this.customNetWorks[name]
this.event.trigger('removeProvider', [name])
}
@ -126,28 +126,16 @@ export class ExecutionContext {
if (!cb) cb = () => { /* Do nothing. */ }
if (!confirmCb) confirmCb = () => { /* Do nothing. */ }
if (!infoCb) infoCb = () => { /* Do nothing. */ }
if (context === 'vm') {
this.executionContext = context
this.currentFork = value.fork
this.event.trigger('contextChanged', ['vm'])
return cb()
}
if (this.customNetWorks[context]) {
var network = this.customNetWorks[context]
if (!this.customNetWorks[context].isInjected) {
this.setProviderFromEndpoint(network.provider, { context: network.name }, (error) => {
if (error) infoCb(error)
cb()
})
} else {
// injected
this.executionContext = context
web3.setProvider(network.provider)
await this._updateChainContext()
this.event.trigger('contextChanged', [context])
return cb()
}
this.currentFork = network.fork
this.executionContext = context
await network.init()
// injected
web3.setProvider(network.provider)
await this._updateChainContext()
this.event.trigger('contextChanged', [context])
cb()
}
}
@ -161,7 +149,7 @@ export class ExecutionContext {
}
async _updateChainContext () {
if (this.getProvider() !== 'vm') {
if (!this.isVM()) {
try {
const block = await web3.eth.getBlock('latest')
// we can't use the blockGasLimit cause the next blocks could have a lower limit : https://github.com/ethereum/remix/issues/506
@ -187,30 +175,6 @@ export class ExecutionContext {
}, 15000)
}
// TODO: remove this when this function is moved
setProviderFromEndpoint (endpoint, value, cb) {
const oldProvider = web3.currentProvider
const context = value.context
web3.setProvider(endpoint)
web3.eth.net.isListening((err, isConnected) => {
if (!err && isConnected === true) {
this.executionContext = context
this._updateChainContext()
this.event.trigger('contextChanged', [context])
this.event.trigger('web3EndpointChanged')
cb()
} else if (isConnected === 'canceled') {
web3.setProvider(oldProvider)
cb()
} else {
web3.setProvider(oldProvider)
cb(`Not possible to connect to ${context}. Make sure the provider is running, a connection is open (via IPC or RPC) or that the provider plugin is properly configured.`)
}
})
}
txDetailsLink (network, hash) {
const transactionDetailsLinks = {
Main: 'https://www.etherscan.io/tx/',

@ -6,6 +6,7 @@ class VMProvider {
constructor (executionContext) {
this.executionContext = executionContext
this.worker = null
this.provider = null
}
getAccounts (cb) {
@ -30,7 +31,7 @@ class VMProvider {
stamps[msg.data.stamp](msg.data.error, msg.data.result)
}
})
const provider = {
this.provider = {
sendAsync: (query, callback) => {
const stamp = Date.now() + incr
incr++
@ -38,10 +39,10 @@ class VMProvider {
this.worker.postMessage({ cmd: 'sendAsync', query, stamp })
}
}
this.web3 = new Web3(provider)
this.web3 = new Web3(this.provider)
extend(this.web3)
this.accounts = {}
this.executionContext.setWeb3('vm', this.web3)
this.executionContext.setWeb3(this.executionContext.getProvider(), this.web3)
}
// TODO: is still here because of the plugin API
@ -81,7 +82,7 @@ class VMProvider {
}
getProvider () {
return 'vm'
return this.executionContext.getProvider()
}
}

@ -139,7 +139,7 @@ export class TxListener {
startListening () {
this.init()
this._isListening = true
if (this._listenOnNetwork && this.executionContext.getProvider() !== 'vm') {
if (this._listenOnNetwork && !this.executionContext.isVM()) {
this._startListenOnNetwork()
}
}

@ -102,7 +102,7 @@ export const DebuggerUI = (props: DebuggerUIProps) => {
const providerChanged = () => {
debuggerModule.onEnvChanged((provider) => {
setState(prevState => {
const isLocalNodeUsed = provider !== 'vm' && provider !== 'injected'
const isLocalNodeUsed = !provider.startsWith('vm') && provider !== 'injected'
return { ...prevState, isLocalNodeUsed: isLocalNodeUsed }
})
})

@ -67,10 +67,7 @@ export const setFinalContext = (plugin: RunTab, dispatch: React.Dispatch<any>) =
}
const _getProviderDropdownValue = (plugin: RunTab): string => {
const provider = plugin.blockchain.getProvider()
const fork = plugin.blockchain.getCurrentFork()
return provider === 'vm' ? provider + '-' + fork : provider
return plugin.blockchain.getProvider()
}
export const setExecutionContext = (plugin: RunTab, dispatch: React.Dispatch<any>, executionContext: { context: string, fork: string }) => {

@ -32,7 +32,7 @@ export const setupEvents = (plugin: RunTab, dispatch: React.Dispatch<any>) => {
return
}
const networkProvider = plugin.networkModule.getNetworkProvider.bind(plugin.networkModule)
const netUI = (networkProvider() !== 'vm') ? `${network.name} (${network.id || '-'}) network` : 'VM'
const netUI = !networkProvider().startsWith('vm') ? `${network.name} (${network.id || '-'}) network` : 'VM'
setNetworkNameFromProvider(dispatch, netUI)
if (network.name === 'VM') dispatch(setProxyEnvAddress(plugin.config.get('vm/proxy')))

@ -9,12 +9,9 @@ export function EnvironmentUI (props: EnvironmentProps) {
const handleChangeExEnv = (env: string) => {
const provider = props.providers.providerList.find(exEnv => exEnv.value === env)
const fork = provider.fork // can be undefined if connected to an external source (External Http Provider / injected)
let context = provider.value
context = context.startsWith('vm') ? 'vm' : context
props.setExecutionContext({ context, fork })
props.setExecutionContext({ context })
}
const currentProvider = props.providers.providerList.find(exEnv => exEnv.value === props.selectedEnv)

@ -119,21 +119,7 @@ export const runTabInitialState: RunTabState = {
personalMode: false,
networkName: 'VM',
providers: {
providerList: [{
id: 'vm-mode-london',
dataId: 'settingsVMLondonMode',
title: 'Execution environment is local to Remix. Data is only saved to browser memory and will vanish upon reload.',
value: 'vm-london',
fork: 'london',
content: 'Remix VM (London)'
}, {
id: 'vm-mode-berlin',
dataId: 'settingsVMBerlinMode',
title: 'Execution environment is local to Remix. Data is only saved to browser memory and will vanish upon reload.',
value: 'vm-berlin',
fork: 'berlin',
content: 'Remix VM (Berlin)'
}],
providerList: [],
isRequesting: false,
isSuccessful: false,
error: null
@ -183,7 +169,10 @@ export const runTabInitialState: RunTabState = {
type AddProvider = {
name: string,
provider: any
displayName: string,
provider: any,
title?: string,
dataId?: string
}
export const runTabReducer = (state: RunTabState = runTabInitialState, action: Action) => {
@ -346,10 +335,10 @@ export const runTabReducer = (state: RunTabState = runTabInitialState, action: A
const payload: AddProvider = action.payload
const id = action.payload.name
state.providers.providerList.push({
content: payload.name,
dataId: id,
content: payload.displayName,
dataId: payload.dataId,
id,
title: payload.name,
title: payload.title,
value: id
})
return {

@ -58,7 +58,6 @@ export function RunTabUI (props: RunTabProps) {
contract: null
})
runTabInitialState.selectExEnv = plugin.blockchain.getProvider()
runTabInitialState.selectExEnv = runTabInitialState.selectExEnv === 'vm' ? 'vm-london' : runTabInitialState.selectExEnv
const [runTab, dispatch] = useReducer(runTabReducer, runTabInitialState)
const REACT_API = { runTab }
const currentfile = plugin.config.get('currentFile')

@ -37,7 +37,6 @@ export class Blockchain extends Plugin<any, any> {
calculateFee(gas: any, gasPrice: any, unit: any): import("bn.js");
determineGasFees(tx: any): (gasPrice: any, cb: any) => void;
changeExecutionContext(context: any, confirmCb: any, infoCb: any, cb: any): Promise<any>;
setProviderFromEndpoint(target: any, context: any, cb: any): void;
detectNetwork(cb: any): void;
getProvider(): any;
getInjectedWeb3Address(): any;

@ -32,6 +32,5 @@ export class ExecutionContext {
listenOnLastBlockId: NodeJS.Timer;
_updateChainContext(): Promise<void>;
listenOnLastBlock(): void;
setProviderFromEndpoint(endpoint: any, value: any, cb: any): void;
txDetailsLink(network: any, hash: any): any;
}

@ -63,7 +63,7 @@ export interface EnvironmentProps {
isSuccessful: boolean,
error: string
},
setExecutionContext: (executionContext: { context: string, fork: string }) => void
setExecutionContext: (executionContext: { context: string }) => void
}
export interface NetworkProps {

@ -16,7 +16,7 @@ const Context = ({ opts, provider }: { opts, provider: string }) => {
const i = data.receipt ? data.transactionIndex : data.transactionIndex
const value = val ? typeConversion.toInt(val) : 0
if (provider === 'vm') {
if (provider.startsWith('vm')) {
return (
<div>
<span>
@ -29,7 +29,7 @@ const Context = ({ opts, provider }: { opts, provider: string }) => {
<div className='remix_ui_terminal_txItem'><span className='remix_ui_terminal_txItemTitle'>hash:</span> {hash}</div>
</span>
</div>)
} else if (provider !== 'vm' && data.resolvedData) {
} else if (data.resolvedData) {
return (
<div>
<span>

@ -14,7 +14,7 @@ const RenderCall = ({ tx, resolvedData, logs, index, plugin, showTableHash, txDe
const debug = (event, tx) => {
event.stopPropagation()
if (tx.isCall && tx.envMode !== 'vm') {
if (tx.isCall && !tx.envMode.startsWith('vm')) {
modal('VM mode', 'Cannot debug this call. Debugging calls is only possible in Remix VM mode.', 'Ok', true, () => {}, 'Cancel', () => {})
} else {
plugin.event.trigger('debuggingRequested', [tx.hash])

@ -10,7 +10,7 @@ const typeConversion = execution.typeConversion
const RenderKnownTransactions = ({ tx, receipt, resolvedData, logs, index, plugin, showTableHash, txDetails, modal, provider }) => {
const debug = (event, tx) => {
event.stopPropagation()
if (tx.isCall && tx.envMode !== 'vm') {
if (tx.isCall && !tx.envMode.startsWith('vm')) {
modal('VM mode', 'Cannot debug this call. Debugging calls is only possible in Remix VM mode.', 'Ok', true, () => {}, 'Cancel', () => {})
} else {
plugin.event.trigger('debuggingRequested', [tx.hash])

@ -7,7 +7,7 @@ import showTable from './Table'
const RenderUnKnownTransactions = ({ tx, receipt, index, plugin, showTableHash, txDetails, modal, provider }) => {
const debug = (event, tx) => {
event.stopPropagation()
if (tx.isCall && tx.envMode !== 'vm') {
if (tx.isCall && !tx.envMode.startsWith('vm')) {
modal('VM mode', 'Cannot debug this call. Debugging calls is only possible in Remix VM mode.', 'Ok', true, () => {}, 'Cancel', () => {})
} else {
plugin.event.trigger('debuggingRequested', [tx.hash])

Loading…
Cancel
Save