Merge branch 'master' of https://github.com/ethereum/remix-project into signmacosdesktop

signmacosdesktop
bunsenstraat 11 months ago
commit 67fc0530ee
  1. 353
      apps/remix-ide-e2e/src/tests/proxy_oz_v5_non_shanghai_runtime.test.ts
  2. 89
      apps/remix-ide-e2e/src/tests/runAndDeploy.test.ts
  3. 14
      apps/remix-ide/src/app/panels/tab-proxy.js
  4. 3
      apps/remix-ide/src/app/tabs/locales/en/settings.json
  5. 3
      apps/remix-ide/src/app/tabs/locales/es/settings.json
  6. 3
      apps/remix-ide/src/app/tabs/locales/fr/settings.json
  7. 3
      apps/remix-ide/src/app/tabs/locales/it/settings.json
  8. 3
      apps/remix-ide/src/app/tabs/locales/zh/settings.json
  9. 7
      apps/remix-ide/src/app/tabs/web3-provider.js
  10. BIN
      apps/remix-ide/src/assets/img/vyperLogo2.webp
  11. 27
      apps/remix-ide/src/blockchain/blockchain.tsx
  12. 42
      apps/remix-ide/src/blockchain/execution-context.js
  13. 32
      apps/remix-ide/src/blockchain/providers/vm.ts
  14. 2
      apps/remix-ide/src/blockchain/providers/worker-vm.ts
  15. 4
      apps/vyper/src/app/app.css
  16. 20
      apps/vyper/src/app/app.tsx
  17. 14
      apps/vyper/src/app/components/CompileErrorCard.tsx
  18. 5
      apps/vyper/src/app/components/VyperResult.tsx
  19. 4
      apps/vyper/src/app/utils/compiler.tsx
  20. 11
      apps/vyper/src/app/utils/remix-client.tsx
  21. 6
      libs/remix-core-plugin/src/lib/compiler-fetch-and-compile.ts
  22. 6
      libs/remix-core-plugin/src/lib/constants/uups.ts
  23. 1
      libs/remix-lib/src/execution/logsManager.ts
  24. 25
      libs/remix-lib/src/execution/txRunnerVM.ts
  25. 23
      libs/remix-simulator/src/methods/transactions.ts
  26. 35
      libs/remix-simulator/src/provider.ts
  27. 54
      libs/remix-simulator/src/vm-context.ts
  28. 23
      libs/remix-ui/settings/src/lib/remix-ui-settings.tsx
  29. 5
      libs/remix-ui/settings/src/lib/settingsAction.ts
  30. 18
      libs/remix-ui/settings/src/lib/settingsReducer.ts
  31. 2
      libs/remix-ui/solidity-compiler/src/lib/api/compiler-api.ts

@ -0,0 +1,353 @@
'use strict'
import { NightwatchBrowser } from 'nightwatch'
import init from '../helpers/init'
let firstProxyAddress: string
let lastProxyAddress: string
let shortenedFirstAddress: string
let shortenedLastAddress: string
module.exports = {
'@disabled': true,
before: function (browser: NightwatchBrowser, done: VoidFunction) {
init(browser, done)
},
'@sources': function () {
return sources
},
'Should show deploy proxy option for UUPS upgradeable contract #group1': function (browser: NightwatchBrowser) {
browser
.clickLaunchIcon('udapp')
.switchEnvironment('vm-merge') // this runtime doesn't have the PUSH0 opcode.
.clickLaunchIcon('solidity')
.click('.remixui_compilerConfigSection')
.setValue('#evmVersionSelector', 'paris') // set an evm version which doesn't have PUSH0 opcode.
.clickLaunchIcon('filePanel')
.addFile('myTokenV1.sol', sources[0]['myTokenV1.sol'])
.clickLaunchIcon('solidity')
.pause(2000)
// because the compilatiom imports are slow and sometimes stop loading (not sure why, it's bug) we need to recompile and check to see if the files are really in de FS
.click('[data-id="compilerContainerCompileBtn"]')
.clickLaunchIcon('filePanel')
.isVisible({
selector: '*[data-id="treeViewDivtreeViewItem.deps/npm/@openzeppelin/contracts-upgradeable/utils/ContextUpgradeable.sol"]',
timeout: 120000,
suppressNotFoundErrors: true
})
.clickLaunchIcon('solidity')
.click('[data-id="compilerContainerCompileBtn"]')
.clickLaunchIcon('filePanel')
.isVisible({
selector: '*[data-id="treeViewDivtreeViewItem.deps/npm/@openzeppelin/contracts-upgradeable/utils/ContextUpgradeable.sol"]',
timeout: 120000,
suppressNotFoundErrors: true
})
.clickLaunchIcon('solidity')
.click('[data-id="compilerContainerCompileBtn"]')
.clickLaunchIcon('filePanel')
.waitForElementVisible({
selector: '*[data-id="treeViewDivtreeViewItem.deps/npm/@openzeppelin/contracts-upgradeable/utils/ContextUpgradeable.sol"]',
timeout: 120000,
})
.clickLaunchIcon('solidity')
.waitForElementPresent('select[id="compiledContracts"] option[value=MyToken]', 60000)
.clickLaunchIcon('udapp')
.click('select.udapp_contractNames')
.click('select.udapp_contractNames option[value=MyToken]')
.waitForElementPresent('[data-id="contractGUIDeployWithProxyLabel"]')
.waitForElementPresent('[data-id="contractGUIUpgradeImplementationLabel"]')
},
'Should show upgrade proxy option for child contract inheriting UUPS parent contract #group1': function (browser: NightwatchBrowser) {
browser
.addFile('myTokenV2.sol', sources[1]['myTokenV2.sol'])
.clickLaunchIcon('solidity')
.assert.visible('[data-id="compilerContainerCompileBtn"]')
.click('[data-id="compilerContainerCompileBtn"]')
.waitForElementPresent('select[id="compiledContracts"] option[value=MyTokenV2]', 60000)
.clickLaunchIcon('udapp')
.click('select.udapp_contractNames')
.click('select.udapp_contractNames option[value=MyTokenV2]')
.waitForElementPresent('[data-id="contractGUIDeployWithProxyLabel"]')
.waitForElementPresent('[data-id="contractGUIUpgradeImplementationLabel"]')
},
'Should deploy proxy without initialize parameters #group1': function (browser: NightwatchBrowser) {
browser
.openFile('myTokenV1.sol')
.clickLaunchIcon('solidity')
.assert.visible('[data-id="compilerContainerCompileBtn"]')
.click('[data-id="compilerContainerCompileBtn"]')
.waitForElementPresent('select[id="compiledContracts"] option[value=MyToken]', 60000)
.clickLaunchIcon('udapp')
.click('select.udapp_contractNames')
.click('select.udapp_contractNames option[value=MyToken]')
.verify.visible('[data-id="contractGUIDeployWithProxyLabel"]')
.waitForElementPresent('[data-id="contractGUIDeployWithProxyLabel"]')
.click('[data-id="contractGUIDeployWithProxyLabel"]')
.setValue('[data-id="initializeInputs-initialOwner"]', '0x5B38Da6a701c568545dCfcB03FcB875f56beddC4')
.createContract('')
.waitForElementContainsText('[data-id="udappNotifyModalDialogModalTitle-react"]', 'Deploy Implementation & Proxy (ERC1967)')
.waitForElementVisible('[data-id="udappNotify-modal-footer-ok-react"]')
.click('[data-id="udappNotify-modal-footer-ok-react"]')
.waitForElementContainsText('[data-id="confirmProxyDeploymentModalDialogModalTitle-react"]', 'Confirm Deploy Proxy (ERC1967)')
.waitForElementVisible('[data-id="confirmProxyDeployment-modal-footer-ok-react"]')
.click('[data-id="confirmProxyDeployment-modal-footer-ok-react"]')
.waitForElementPresent('[data-id="universalDappUiTitleExpander0"]')
.waitForElementPresent('[data-id="universalDappUiTitleExpander1"]')
.waitForElementContainsText('*[data-id="terminalJournal"]', 'Deploying ERC1967 >= 5.0.0 as proxy...')
},
'Should interact with deployed contract via ERC1967 (proxy) #group1': function (browser: NightwatchBrowser) {
browser
.getAddressAtPosition(1, (address) => {
firstProxyAddress = address
shortenedFirstAddress = address.slice(0, 5) + '...' + address.slice(address.length - 5, address.length)
})
.clickInstance(1)
.perform((done) => {
browser.testConstantFunction(firstProxyAddress, 'name - call', null, '0:\nstring: MyToken').perform(() => {
done()
})
})
.perform((done) => {
browser.testConstantFunction(firstProxyAddress, 'symbol - call', null, '0:\nstring: MTK').perform(() => {
done()
})
})
},
'Should deploy proxy with initialize parameters #group1': function (browser: NightwatchBrowser) {
browser
.waitForElementPresent('[data-id="deployAndRunClearInstances"]')
.click('[data-id="deployAndRunClearInstances"]')
.addFile('initializeProxy.sol', sources[2]['initializeProxy.sol'])
.clickLaunchIcon('solidity')
.assert.visible('[data-id="compilerContainerCompileBtn"]')
.click('[data-id="compilerContainerCompileBtn"]')
.waitForElementPresent('select[id="compiledContracts"] option[value=MyInitializedToken]', 60000)
.clickLaunchIcon('udapp')
.click('select.udapp_contractNames')
.click('select.udapp_contractNames option[value=MyInitializedToken]')
.waitForElementPresent('[data-id="contractGUIDeployWithProxyLabel"]')
.click('[data-id="contractGUIDeployWithProxyLabel"]')
.useXpath()
.waitForElementPresent('//*[@id="runTabView"]/div/div[2]/div[3]/div[1]/div/div[1]/div[4]/div/div[1]/input')
.waitForElementPresent('//*[@id="runTabView"]/div/div[2]/div[3]/div[1]/div/div[1]/div[4]/div/div[2]/input')
.setValue('//*[@id="runTabView"]/div/div[2]/div[3]/div[1]/div/div[1]/div[4]/div/div[1]/input', 'Remix')
.setValue('//*[@id="runTabView"]/div/div[2]/div[3]/div[1]/div/div[1]/div[4]/div/div[2]/input', "R")
.useCss()
.setValue('[data-id="initializeInputs-initialOwner"]', '0x5B38Da6a701c568545dCfcB03FcB875f56beddC4')
.createContract('')
.waitForElementContainsText('[data-id="udappNotifyModalDialogModalTitle-react"]', 'Deploy Implementation & Proxy (ERC1967)')
.waitForElementVisible('[data-id="udappNotify-modal-footer-ok-react"]')
.click('[data-id="udappNotify-modal-footer-ok-react"]')
.waitForElementContainsText('[data-id="confirmProxyDeploymentModalDialogModalTitle-react"]', 'Confirm Deploy Proxy (ERC1967)')
.waitForElementVisible('[data-id="confirmProxyDeployment-modal-footer-ok-react"]')
.click('[data-id="confirmProxyDeployment-modal-footer-ok-react"]')
.waitForElementPresent('[data-id="universalDappUiTitleExpander0"]')
.waitForElementPresent('[data-id="universalDappUiTitleExpander1"]')
.waitForElementContainsText('*[data-id="terminalJournal"]', 'Deploying ERC1967 >= 5.0.0 as proxy...')
},
'Should interact with initialized contract to verify parameters #group1': function (browser: NightwatchBrowser) {
browser
.getAddressAtPosition(1, (address) => {
lastProxyAddress = address
shortenedLastAddress = address.slice(0, 5) + '...' + address.slice(address.length - 5, address.length)
})
.clickInstance(1)
.perform((done) => {
browser.testConstantFunction(lastProxyAddress, 'name - call', null, '0:\nstring: Remix').perform(() => {
done()
})
})
.perform((done) => {
browser.testConstantFunction(lastProxyAddress, 'symbol - call', null, '0:\nstring: R').perform(() => {
done()
})
})
},
'Should upgrade contract by selecting a previously deployed proxy address from dropdown (MyTokenV1 to MyTokenV2) #group1': function (browser: NightwatchBrowser) {
browser
.click('*[data-id="terminalClearConsole"]')
.waitForElementPresent('[data-id="deployAndRunClearInstances"]')
.click('[data-id="deployAndRunClearInstances"]')
.openFile('myTokenV2.sol')
.clickLaunchIcon('solidity')
.assert.visible('[data-id="compilerContainerCompileBtn"]')
.click('[data-id="compilerContainerCompileBtn"]')
.waitForElementPresent('select[id="compiledContracts"] option[value=MyTokenV2]', 60000)
.clickLaunchIcon('udapp')
.click('select.udapp_contractNames')
.click('select.udapp_contractNames option[value=MyTokenV2]')
.waitForElementPresent('[data-id="contractGUIUpgradeImplementationLabel"]')
.click('[data-id="contractGUIUpgradeImplementationLabel"]')
.waitForElementPresent('[data-id="toggleProxyAddressDropdown"]')
.click('[data-id="toggleProxyAddressDropdown"]')
.waitForElementVisible('[data-id="proxy-dropdown-items"]')
.assert.textContains('[data-id="proxy-dropdown-items"]', shortenedFirstAddress)
.assert.textContains('[data-id="proxy-dropdown-items"]', shortenedLastAddress)
.click('[data-id="proxyAddress1"]')
.createContract('')
.waitForElementContainsText('[data-id="udappNotifyModalDialogModalTitle-react"]', 'Deploy Implementation & Update Proxy')
.waitForElementVisible('[data-id="udappNotify-modal-footer-ok-react"]')
.click('[data-id="udappNotify-modal-footer-ok-react"]')
.waitForElementContainsText('[data-id="confirmProxyDeploymentModalDialogModalTitle-react"]', 'Confirm Update Proxy (ERC1967)')
.waitForElementVisible('[data-id="confirmProxyDeployment-modal-footer-ok-react"]')
.click(
{
selector: '[data-id="confirmProxyDeployment-modal-footer-ok-react"]',
})
.waitForElementPresent('[data-id="universalDappUiTitleExpander0"]')
.waitForElementPresent('[data-id="universalDappUiTitleExpander1"]')
.waitForElementContainsText('*[data-id="terminalJournal"]', 'Using ERC1967 >= 5.0.0 for the proxy upgrade...')
},
'Should interact with upgraded function in contract MyTokenV2 #group1': function (browser: NightwatchBrowser) {
browser
.clickInstance(1)
.perform((done) => {
browser.testConstantFunction(lastProxyAddress, 'version - call', null, '0:\nstring: MyTokenV2!').perform(() => {
done()
})
})
},
'Should upgrade contract by providing proxy address in input field (MyTokenV1 to MyTokenV2) #group1': function (browser: NightwatchBrowser) {
browser
.click('*[data-id="terminalClearConsole"]')
.waitForElementPresent('[data-id="deployAndRunClearInstances"]')
.click('[data-id="deployAndRunClearInstances"]')
.openFile('myTokenV2.sol')
.clickLaunchIcon('solidity')
.assert.visible('[data-id="compilerContainerCompileBtn"]')
.click('[data-id="compilerContainerCompileBtn"]')
.waitForElementPresent('select[id="compiledContracts"] option[value=MyTokenV2]', 60000)
.clickLaunchIcon('udapp')
.click('select.udapp_contractNames')
.click('select.udapp_contractNames option[value=MyTokenV2]')
.waitForElementPresent('[data-id="contractGUIUpgradeImplementationLabel"]')
.waitForElementPresent('[data-id="toggleProxyAddressDropdown"]')
.clearValue('[data-id="ERC1967AddressInput"]')
.setValue('[data-id="ERC1967AddressInput"]', firstProxyAddress)
.createContract('')
.waitForElementContainsText('[data-id="udappNotifyModalDialogModalTitle-react"]', 'Deploy Implementation & Update Proxy')
.waitForElementVisible('[data-id="udappNotify-modal-footer-ok-react"]')
.click('[data-id="udappNotify-modal-footer-ok-react"]')
.waitForElementContainsText('[data-id="confirmProxyDeploymentModalDialogModalTitle-react"]', 'Confirm Update Proxy (ERC1967)')
.waitForElementVisible('[data-id="confirmProxyDeployment-modal-footer-ok-react"]')
.click('[data-id="confirmProxyDeployment-modal-footer-ok-react"]')
.waitForElementPresent('[data-id="universalDappUiTitleExpander0"]')
.waitForElementPresent('[data-id="universalDappUiTitleExpander1"]')
.waitForElementContainsText('*[data-id="terminalJournal"]', 'Using ERC1967 >= 5.0.0 for the proxy upgrade...')
},
'Should interact with upgraded contract through provided proxy address #group1': function (browser: NightwatchBrowser) {
browser
.clearConsole()
.clickInstance(1)
.perform((done) => {
browser.testConstantFunction(firstProxyAddress, 'version - call', null, '0:\nstring: MyTokenV2!').perform(() => {
done()
})
})
},
'Should debug the call': function(browser: NightwatchBrowser) {
browser
.debugTransaction(0)
.waitForElementVisible({
locateStrategy: 'xpath',
selector: '//*[@data-id="treeViewLivm trace step" and contains(.,"7")]',
timeout: 60000
})
.goToVMTraceStep(129)
.waitForElementContainsText('*[data-id="functionPanel"]', 'version()', 60000)
.end()
}
}
const sources = [
{
'myTokenV1.sol': {
content: `
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;
import "@openzeppelin/contracts-upgradeable/token/ERC721/ERC721Upgradeable.sol";
import "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol";
import "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol";
import "@openzeppelin/contracts-upgradeable/proxy/utils/UUPSUpgradeable.sol";
contract MyToken is Initializable, ERC721Upgradeable, OwnableUpgradeable, UUPSUpgradeable {
/// @custom:oz-upgrades-unsafe-allow constructor
constructor() {
_disableInitializers();
}
function initialize(address initialOwner) initializer public {
__ERC721_init("MyToken", "MTK");
__Ownable_init(initialOwner);
__UUPSUpgradeable_init();
}
function _authorizeUpgrade(address newImplementation)
internal
onlyOwner
override
{}
}
`
}
}, {
'myTokenV2.sol': {
content: `
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;
import "./myTokenV1.sol";
contract MyTokenV2 is MyToken {
function version () public view returns (string memory) {
return "MyTokenV2!";
}
}
`
}
}, {
'initializeProxy.sol': {
content: `
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;
import "@openzeppelin/contracts-upgradeable/token/ERC721/ERC721Upgradeable.sol";
import "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol";
import "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol";
import "@openzeppelin/contracts-upgradeable/proxy/utils/UUPSUpgradeable.sol";
contract MyInitializedToken is Initializable, ERC721Upgradeable, OwnableUpgradeable, UUPSUpgradeable {
/// @custom:oz-upgrades-unsafe-allow constructor
constructor() {
_disableInitializers();
}
function initialize(string memory tokenName, string memory tokenSymbol, address initialOwner) initializer public {
__ERC721_init(tokenName, tokenSymbol);
__Ownable_init(initialOwner);
__UUPSUpgradeable_init();
}
function _authorizeUpgrade(address newImplementation)
internal
onlyOwner
override
{}
}
`
}
}
]

@ -238,6 +238,95 @@ module.exports = {
.executeScriptInTerminal('web3.eth.getAccounts()') .executeScriptInTerminal('web3.eth.getAccounts()')
.journalLastChildIncludes('[ "0x76a3ABb5a12dcd603B52Ed22195dED17ee82708f" ]') .journalLastChildIncludes('[ "0x76a3ABb5a12dcd603B52Ed22195dED17ee82708f" ]')
.end() .end()
},
'Should ensure that save environment state is checked by default #group4 #group5': function (browser: NightwatchBrowser) {
browser.waitForElementPresent('*[data-id="remixIdeSidePanel"]')
.clickLaunchIcon('settings')
.waitForElementPresent('[data-id="settingsEnableSaveEnvStateLabel"]')
.scrollInto('[data-id="settingsEnableSaveEnvStateLabel"]')
.verify.elementPresent('[data-id="settingsEnableSaveEnvState"]:checked')
},
'Should deploy default storage contract; store value and ensure that state is saved. #group4 #group5': function (browser: NightwatchBrowser) {
browser
.clickLaunchIcon('filePanel')
.click('*[data-id="treeViewLitreeViewItemcontracts"]')
.openFile('contracts/1_Storage.sol')
.pause(5000)
.clickLaunchIcon('udapp')
.waitForElementPresent('*[data-id="Deploy - transact (not payable)"]')
.click('*[data-id="Deploy - transact (not payable)"]')
.waitForElementPresent('#instance0xd9145CCE52D386f254917e481eB44e9943F39138')
.clickInstance(0)
.clickFunction('store - transact (not payable)', { types: 'uint256 num', values: '10' })
.clickFunction('retrieve - call')
.waitForElementContainsText('[data-id="treeViewLi0"]', 'uint256: 10')
.clickLaunchIcon('filePanel')
.openFile('.states/vm-shanghai/state.json')
.getEditorValue((content) => {
browser
.assert.ok(content.includes('"latestBlockNumber": "0x02"'), 'State is saved')
})
},
'Should load state after page refresh #group4': function (browser: NightwatchBrowser) {
browser.refreshPage()
.waitForElementVisible('*[data-id="remixIdeSidePanel"]')
.click('*[data-id="treeViewLitreeViewItemcontracts"]')
.openFile('contracts/1_Storage.sol')
.addAtAddressInstance('0xd9145CCE52D386f254917e481eB44e9943F39138', true, true, false)
.clickInstance(0)
.clickFunction('retrieve - call')
.waitForElementContainsText('[data-id="treeViewLi0"]', 'uint256: 10')
},
'Should save state after running web3 script #group4': function (browser: NightwatchBrowser) {
browser
.clickLaunchIcon('settings')
.waitForElementPresent('[data-id="settingsTabGenerateContractMetadataLabel"]')
.click('[data-id="settingsTabGenerateContractMetadataLabel"]')
.verify.elementPresent('[data-id="settingsTabGenerateContractMetadata"]:checked')
.clickLaunchIcon('solidity')
.click('.remixui_compilerConfigSection')
.setValue('#evmVersionSelector', 'london')
.click('*[data-id="compilerContainerCompileBtn"]')
.pause(5000)
.clickLaunchIcon('udapp')
.switchEnvironment('vm-london')
.clickLaunchIcon('filePanel')
.click('*[data-id="treeViewLitreeViewItemscripts"]')
.openFile('scripts/deploy_with_web3.ts')
.click('[data-id="play-editor"]')
.waitForElementPresent('[data-id="treeViewDivDraggableItem.states/vm-london/state.json"]')
.click('[data-id="treeViewDivDraggableItem.states/vm-london/state.json"]')
.pause(100000)
.getEditorValue((content) => {
browser
.assert.ok(content.includes('"latestBlockNumber": "0x01"'), 'State is saved')
})
},
'Should ensure that .states is not updated when save env option is unchecked #group5': function (browser: NightwatchBrowser) {
browser
.clickLaunchIcon('settings')
.waitForElementPresent('[data-id="settingsEnableSaveEnvStateLabel"]')
.click('[data-id="settingsEnableSaveEnvStateLabel"]')
.verify.elementNotPresent('[data-id="settingsEnableSaveEnvState"]:checked')
.clickLaunchIcon('filePanel')
.openFile('contracts/1_Storage.sol')
.pause(5000)
.clickLaunchIcon('udapp')
.waitForElementPresent('*[data-id="Deploy - transact (not payable)"]')
.click('*[data-id="Deploy - transact (not payable)"]')
.pause(5000)
.clickLaunchIcon('filePanel')
.openFile('.states/vm-shanghai/state.json')
.getEditorValue((content) => {
browser
.assert.ok(content.includes('"latestBlockNumber": "0x02"'), 'State is unchanged')
})
.end()
} }
} }

@ -224,9 +224,23 @@ export class TabProxy extends Plugin {
this.removeTab(oldName) this.removeTab(oldName)
} }
/**
*
* @param {string} name
* @param {string} title
* @param {Function} switchTo
* @param {Function} close
* @param {string} icon
* @param {string} description
* @returns
*/
addTab (name, title, switchTo, close, icon, description = '') { addTab (name, title, switchTo, close, icon, description = '') {
if (this._handlers[name]) return this.renderComponent() if (this._handlers[name]) return this.renderComponent()
if ((name.endsWith('.vy') && icon === undefined) || title.includes('Vyper')) {
icon = 'assets/img/vyperLogo2.webp'
}
var slash = name.split('/') var slash = name.split('/')
const tabPath = slash.reverse() const tabPath = slash.reverse()
const tempTitle = [] const tempTitle = []

@ -43,5 +43,6 @@
"settings.copilot": "Solidity copilot - Alpha", "settings.copilot": "Solidity copilot - Alpha",
"settings.copilot.activate": "Load & Activate copilot", "settings.copilot.activate": "Load & Activate copilot",
"settings.copilot.max_new_tokens": "Maximum number of words to generate", "settings.copilot.max_new_tokens": "Maximum number of words to generate",
"settings.copilot.temperature": "Temperature" "settings.copilot.temperature": "Temperature",
"settings.enableSaveEnvState": "Save environment state"
} }

@ -36,5 +36,6 @@
"settings.port": "PUERTO", "settings.port": "PUERTO",
"settings.projectID": "ID DEL PROYECTO", "settings.projectID": "ID DEL PROYECTO",
"settings.projectSecret": "SECRETO DE PROYECTO", "settings.projectSecret": "SECRETO DE PROYECTO",
"settings.analyticsInRemix": "Analíticas en IDE Remix" "settings.analyticsInRemix": "Analíticas en IDE Remix",
"settings.enableSaveEnvState": "Save environment state"
} }

@ -36,5 +36,6 @@
"settings.port": "PORT", "settings.port": "PORT",
"settings.projectID": "ID du projet", "settings.projectID": "ID du projet",
"settings.projectSecret": "SECRET DU PROJET", "settings.projectSecret": "SECRET DU PROJET",
"settings.analyticsInRemix": "Analytics dans l'IDE de Remix" "settings.analyticsInRemix": "Analytics dans l'IDE de Remix",
"settings.enableSaveEnvState": "Save environment state"
} }

@ -36,5 +36,6 @@
"settings.port": "PORTA", "settings.port": "PORTA",
"settings.projectID": "ID PROGETTO", "settings.projectID": "ID PROGETTO",
"settings.projectSecret": "SEGRETO DEL PROGETTO", "settings.projectSecret": "SEGRETO DEL PROGETTO",
"settings.analyticsInRemix": "Analytics nella Remix IDE" "settings.analyticsInRemix": "Analytics nella Remix IDE",
"settings.enableSaveEnvState": "Save environment state"
} }

@ -36,5 +36,6 @@
"settings.port": "端口", "settings.port": "端口",
"settings.projectID": "项目 ID", "settings.projectID": "项目 ID",
"settings.projectSecret": "项目密钥", "settings.projectSecret": "项目密钥",
"settings.analyticsInRemix": "Remix IDE 中的分析功能" "settings.analyticsInRemix": "Remix IDE 中的分析功能",
"settings.enableSaveEnvState": "Save environment state"
} }

@ -63,6 +63,13 @@ export class Web3ProviderModule extends Plugin {
await this.call('compilerArtefacts', 'addResolvedContract', contractAddressStr, data) await this.call('compilerArtefacts', 'addResolvedContract', contractAddressStr, data)
} }
}, 50) }, 50)
const isVM = this.blockchain.executionContext.isVM()
if (isVM && this.blockchain.config.get('settings/save-evm-state')) {
await this.blockchain.executionContext.getStateDetails().then((state) => {
this.call('fileManager', 'writeFile', `.states/${this.blockchain.executionContext.getProvider()}/state.json`, state)
})
}
} }
} }
resolve(message) resolve(message)

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.5 KiB

@ -135,7 +135,8 @@ export class Blockchain extends Plugin {
setupEvents() { setupEvents() {
this.executionContext.event.register('contextChanged', async (context) => { this.executionContext.event.register('contextChanged', async (context) => {
await this.resetEnvironment() // reset environment to last known state of the context
await this.loadContext(context)
this._triggerEvent('contextChanged', [context]) this._triggerEvent('contextChanged', [context])
this.detectNetwork((error, network) => { this.detectNetwork((error, network) => {
this.networkStatus = {network, error} this.networkStatus = {network, error}
@ -643,8 +644,23 @@ export class Blockchain extends Plugin {
}) })
} }
async resetEnvironment() { async loadContext(context: string) {
const saveEvmState = this.config.get('settings/save-evm-state')
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()
}
} else {
await this.getCurrentProvider().resetEnvironment() await this.getCurrentProvider().resetEnvironment()
}
// TODO: most params here can be refactored away in txRunner // TODO: most params here can be refactored away in txRunner
const web3Runner = new TxRunnerWeb3( const web3Runner = new TxRunnerWeb3(
{ {
@ -889,8 +905,13 @@ export class Blockchain extends Plugin {
let execResult let execResult
let returnValue = null let returnValue = null
if (isVM) { if (isVM) {
const hhlogs = await this.web3().remix.getHHLogsForTx(txResult.transactionHash) if (!tx.useCall && this.config.get('settings/save-evm-state')) {
await this.executionContext.getStateDetails().then((state) => {
this.call('fileManager', 'writeFile', `.states/${this.executionContext.getProvider()}/state.json`, state)
})
}
const hhlogs = await this.web3().remix.getHHLogsForTx(txResult.transactionHash)
if (hhlogs && hhlogs.length) { if (hhlogs && hhlogs.length) {
const finalLogs = ( const finalLogs = (
<div> <div>

@ -3,6 +3,7 @@
import Web3 from 'web3' import Web3 from 'web3'
import { execution } from '@remix-project/remix-lib' import { execution } from '@remix-project/remix-lib'
import EventManager from '../lib/events' import EventManager from '../lib/events'
import {bufferToHex} from '@ethereumjs/util'
const _paq = window._paq = window._paq || [] const _paq = window._paq = window._paq || []
let web3 let web3
@ -71,11 +72,14 @@ export class ExecutionContext {
} }
detectNetwork (callback) { detectNetwork (callback) {
return new Promise((resolve, reject) => {
if (this.isVM()) { if (this.isVM()) {
callback(null, { id: '-', name: 'VM' }) callback && callback(null, { id: '-', name: 'VM' })
return resolve({ id: '-', name: 'VM' })
} else { } else {
if (!web3.currentProvider) { if (!web3.currentProvider) {
return callback('No provider set') callback && callback('No provider set')
return reject('No provider set')
} }
const cb = (err, id) => { const cb = (err, id) => {
let name = null let name = null
@ -92,14 +96,20 @@ export class ExecutionContext {
if (id === 1) { if (id === 1) {
web3.eth.getBlock(0).then((block) => { web3.eth.getBlock(0).then((block) => {
if (block && block.hash !== this.mainNetGenesisHash) name = 'Custom' if (block && block.hash !== this.mainNetGenesisHash) name = 'Custom'
callback(err, { id, name, lastBlock: this.lastBlock, currentFork: this.currentFork }) callback && callback(err, { id, name, lastBlock: this.lastBlock, currentFork: this.currentFork })
}).catch((error) => callback(error)) return resolve({ id, name, lastBlock: this.lastBlock, currentFork: this.currentFork })
}).catch((error) => {
callback && callback(error)
return reject(error)
})
} else { } else {
callback(err, { id, name, lastBlock: this.lastBlock, currentFork: this.currentFork }) callback && callback(err, { id, name, lastBlock: this.lastBlock, currentFork: this.currentFork })
return resolve({ id, name, lastBlock: this.lastBlock, currentFork: this.currentFork })
} }
} }
web3.eth.net.getId().then(id=>cb(null,parseInt(id))).catch(err=>cb(err)) web3.eth.net.getId().then(id=>cb(null,parseInt(id))).catch(err=>cb(err))
} }
})
} }
removeProvider (name) { removeProvider (name) {
@ -195,4 +205,26 @@ export class ExecutionContext {
return transactionDetailsLinks[network] + hash return transactionDetailsLinks[network] + hash
} }
} }
async getStateDetails() {
const db = await this.web3().remix.getStateDb()
const blocksData = await this.web3().remix.getBlocksData()
const state = {
db: Object.fromEntries(db._database),
blocks: blocksData.blocks,
latestBlockNumber: blocksData.latestBlockNumber
}
const stringifyed = JSON.stringify(state, (key, value) => {
if (key === 'db') {
return value
} else if (key === 'blocks') {
return value.map(block => bufferToHex(block))
}else if (key === '') {
return value
}
return bufferToHex(value)
}, '\t')
return stringifyed
}
} }

@ -2,6 +2,7 @@ import Web3, { FMT_BYTES, FMT_NUMBER, LegacySendAsyncProvider } from 'web3'
import { fromWei, toBigInt } from 'web3-utils' import { fromWei, toBigInt } from 'web3-utils'
import { privateToAddress, hashPersonalMessage, isHexString } from '@ethereumjs/util' import { privateToAddress, hashPersonalMessage, isHexString } from '@ethereumjs/util'
import { extend, JSONRPCRequestPayload, JSONRPCResponseCallback } from '@remix-project/remix-simulator' import { extend, JSONRPCRequestPayload, JSONRPCResponseCallback } from '@remix-project/remix-simulator'
import {toBuffer} from '@ethereumjs/util'
import { ExecutionContext } from '../execution-context' import { ExecutionContext } from '../execution-context'
export class VMProvider { export class VMProvider {
@ -12,9 +13,7 @@ export class VMProvider {
sendAsync: (query: JSONRPCRequestPayload, callback: JSONRPCResponseCallback) => void sendAsync: (query: JSONRPCRequestPayload, callback: JSONRPCResponseCallback) => void
} }
newAccountCallback: {[stamp: number]: (error: Error, address: string) => void} newAccountCallback: {[stamp: number]: (error: Error, address: string) => void}
constructor (executionContext: ExecutionContext) { constructor (executionContext: ExecutionContext) {
this.executionContext = executionContext this.executionContext = executionContext
this.worker = null this.worker = null
this.provider = null this.provider = null
@ -29,7 +28,7 @@ export class VMProvider {
}) })
} }
async resetEnvironment () { async resetEnvironment (stringifiedState?: string) {
if (this.worker) this.worker.terminate() if (this.worker) this.worker.terminate()
this.worker = new Worker(new URL('./worker-vm', import.meta.url)) this.worker = new Worker(new URL('./worker-vm', import.meta.url))
const provider = this.executionContext.getProviderObject() const provider = this.executionContext.getProviderObject()
@ -76,9 +75,34 @@ export class VMProvider {
} }
} }
}) })
this.worker.postMessage({ cmd: 'init', fork: this.executionContext.getCurrentFork(), nodeUrl: provider?.options['nodeUrl'], blockNumber: provider?.options['blockNumber']}) if (stringifiedState) {
try {
const blockchainState = JSON.parse(stringifiedState)
const blockNumber = parseInt(blockchainState.latestBlockNumber, 16)
const stateDb = blockchainState.db
this.worker.postMessage({
cmd: 'init',
fork: this.executionContext.getCurrentFork(),
nodeUrl: provider?.options['nodeUrl'],
blockNumber,
stateDb,
blocks: blockchainState.blocks
}) })
} catch (e) {
console.error(e)
} }
} else {
this.worker.postMessage({
cmd: 'init',
fork: this.executionContext.getCurrentFork(),
nodeUrl: provider?.options['nodeUrl'],
blockNumber: provider?.options['blockNumber']
})
}
})
}
// TODO: is still here because of the plugin API // TODO: is still here because of the plugin API
// can be removed later when we update the API // can be removed later when we update the API

@ -6,7 +6,7 @@ self.onmessage = (e: MessageEvent) => {
switch (data.cmd) { switch (data.cmd) {
case 'init': case 'init':
{ {
provider = new Provider({ fork: data.fork, nodeUrl: data.nodeUrl, blockNumber: data.blockNumber }) provider = new Provider({ fork: data.fork, nodeUrl: data.nodeUrl, blockNumber: data.blockNumber, stateDb: data.stateDb, blocks: data.blocks})
provider.init().then(() => { provider.init().then(() => {
self.postMessage({ self.postMessage({
cmd: 'initiateResult', cmd: 'initiateResult',

@ -262,3 +262,7 @@ html, body, #root, main {
padding: 0; padding: 0;
border-radius: 0; border-radius: 0;
} }
.vyper-panel-width {
width: 94%;
}

@ -131,19 +131,25 @@ const App = () => {
</CustomAccordionToggle> </CustomAccordionToggle>
</div> </div>
<Accordion.Collapse eventKey="0"> <Accordion.Collapse eventKey="0">
<div className="pl-3 pt-3 border-top-0"> <div className="pt-2">
<Form> <Form>
<div className="d-flex flex-row gap-5 mb-3 mt-2"> <div className="d-flex flex-row justify-content-around mb-1 mt-2">
<Form.Check inline id="remote-compiler" data-id="remote-compiler" type="radio" name="remote" value={state.environment} checked={state.environment === 'remote'} onChange={() => setEnvironment('remote')} label="Remote Compiler" className={`${state.environment === 'remote' ? 'd-flex mr-4' : 'd-flex mr-4 cursor-status'}`} /> <div className={`custom-control custom-radio ${state.environment === 'remote' ? 'd-flex' : 'd-flex cursor-status'}`}>
<Form.Check inline id="local-compiler" data-id="local-compiler" checked={state.environment === 'local'} type="radio" name="local" value={state.environment} onChange={() => setEnvironment('local')} label="Local Compiler" className={`${state.environment === 'local' ? '' : `cursor-status`}`} /> <input type="radio" id="remote-compiler" data-id="remote-compiler" name="remote" value={state.environment} checked={state.environment === 'remote'} onChange={() => setEnvironment('remote')} className={`custom-control-input ${state.environment === 'remote' ? 'd-flex mr-1' : 'd-flex mr-1 cursor-status'}`} />
<label htmlFor="remote-compiler" className="form-check-label custom-control-label">Remote Compiler</label>
</div>
<div className={`custom-control custom-radio ${state.environment === 'local' ? 'mr-2' : `cursor-status`}`}>
<input id="local-compiler" data-id="local-compiler" checked={state.environment === 'local'} type="radio" name="local" value={state.environment} onChange={() => setEnvironment('local')} className={`custom-control-input ${state.environment === 'local' ? '' : `cursor-status`}`} />
<label htmlFor="local-compiler" className="form-check-label custom-control-label">Local Compiler</label>
</div>
</div> </div>
</Form> </Form>
<LocalUrlInput url={state.localUrl} setUrl={setLocalUrl} environment={state.environment} />
</div> </div>
</Accordion.Collapse> </Accordion.Collapse>
</div> </div>
</Accordion> </Accordion>
<LocalUrlInput url={state.localUrl} setUrl={setLocalUrl} environment={state.environment} /> <span className="px-3 mt-3 mb-3 small text-warning">
<span className="px-3 mt-1 mb-1 small text-warning">
Specify the{' '} Specify the{' '}
<a className="text-warning" target="_blank" href="https://remix-ide.readthedocs.io/en/latest/vyper.html#specify-vyper-version"> <a className="text-warning" target="_blank" href="https://remix-ide.readthedocs.io/en/latest/vyper.html#specify-vyper-version">
compiler version compiler version
@ -164,7 +170,7 @@ const App = () => {
<VyperResult output={output} plugin={remixClient} /> <VyperResult output={output} plugin={remixClient} />
</> </>
) : output.status === 'failed' ? ( ) : output.status === 'failed' ? (
<CompileErrorCard output={output} askGpt={remixClient.askGpt} /> <CompileErrorCard output={output} plugin={remixClient} />
) : null} ) : null}
</article> </article>
</section> </section>

@ -1,15 +1,13 @@
import {CopyToClipboard} from '@remix-ui/clipboard' import {CopyToClipboard} from '@remix-ui/clipboard'
import Reaact from 'react' import Reaact from 'react'
import { RemixClient } from '../utils'
export function CompileErrorCard(props: any) { export function CompileErrorCard(props: { output: any, plugin: RemixClient }) {
return ( return (
<div <div
id="vyperErrorResult" id="vyperErrorResult"
className=" d-flex flex-column p-2 alert alert-danger error vyper-compile-error" className=" d-flex flex-column p-2 alert alert-danger error vyper-compile-error vyper-panel-width"
title={props.output?.title} title={props.output?.title}
style={{
width: '94%'
}}
> >
<span <span
data-id="error-message" data-id="error-message"
@ -21,16 +19,16 @@ export function CompileErrorCard(props: any) {
> >
{props.output.message.trim()} {props.output.message.trim()}
</span> </span>
{/* <div className="d-flex flex-column pt-3 align-items-end mb-2"> <div className="d-flex flex-column pt-3 align-items-end mb-2">
<div> <div>
<span className="border border-success text-success btn-sm" onClick={() => props.askGpt(props.output.message)}> <span className="border border-success text-success btn-sm" onClick={async () => await props.plugin.askGpt(props.output.message)}>
Ask GPT Ask GPT
</span> </span>
<span className="ml-3 pt-1 py-1"> <span className="ml-3 pt-1 py-1">
<CopyToClipboard content={props.output.message} className={`p-0 m-0 far fa-copy alert alert-danger`} direction={'top'} /> <CopyToClipboard content={props.output.message} className={`p-0 m-0 far fa-copy alert alert-danger`} direction={'top'} />
</span> </span>
</div> </div>
</div> */} </div>
</div> </div>
) )
} }

@ -58,9 +58,8 @@ function VyperResult({ output, plugin }: VyperResultProps) {
return ( return (
<> <>
<div className="border border-top"></div> <div className="d-flex justify-content-center mx-3 mb-3 mt-1 vyper-panel-width flex-column">
<div className="d-flex justify-content-center px-2 w-100 flex-column"> <button data-id="compilation-details" className="btn btn-secondary d-block btn-block" onClick={async () => {
<button data-id="compilation-details" className="btn btn-secondary w-100" onClick={async () => {
await plugin?.call('vyperCompilationDetails', 'showDetails', output) await plugin?.call('vyperCompilationDetails', 'showDetails', output)
}}> }}>
<span>Compilation Details</span> <span>Compilation Details</span>

@ -50,12 +50,10 @@ function parseErrorString(errorString) {
// Split the string into lines // Split the string into lines
let lines = errorString.trim().split('\n') let lines = errorString.trim().split('\n')
// Extract the line number and message // Extract the line number and message
console.log(lines)
let message = errorString.trim() let message = errorString.trim()
let targetLine = lines[2].split(',') let targetLine = lines[2].split(',')
let tline = lines[2].trim().split(' ')[1].split(':') let tline = lines[2].trim().split(' ')[1].split(':')
console.log('tline', tline)
const errorObject = { const errorObject = {
status: 'failed', status: 'failed',
message: message, message: message,
@ -192,7 +190,6 @@ export async function compile(url: string, contract: Contract): Promise<any> {
method: 'Get' method: 'Get'
})).data })).data
result = parseErrorString(intermediate[0]) result = parseErrorString(intermediate[0])
console.log('error payload', intermediate)
return result return result
} }
await new Promise((resolve) => setTimeout(() => resolve({}), 3000)) await new Promise((resolve) => setTimeout(() => resolve({}), 3000))
@ -273,7 +270,6 @@ export async function compileContract(contract: string, compilerUrl: string, set
// try { // try {
output = await compile(compilerUrl, _contract) output = await compile(compilerUrl, _contract)
if (output.status === 'failed') { if (output.status === 'failed') {
console.log('possible error', output)
remixClient.changeStatus({ remixClient.changeStatus({
key: 'failed', key: 'failed',
type: 'error', type: 'error',

@ -61,8 +61,16 @@ export class RemixClient extends PluginClient {
} }
async askGpt(message: string) { async askGpt(message: string) {
if (message.length === 0) {
this.client.call('terminal', 'log', { type: 'log', value: 'kindly send a proper message so I can respond please' })
return
}
try { try {
await this.client.call('openaigpt', 'message', message) const formattedMessage = `
${message}
can you explain why this error occurred and how to fix it?
`
await this.client.call('openaigpt' as any, 'message', formattedMessage)
} catch (err) { } catch (err) {
console.error('unable to askGpt') console.error('unable to askGpt')
console.error(err) console.error(err)
@ -153,4 +161,3 @@ export class RemixClient extends PluginClient {
} }
export const remixClient = new RemixClient() export const remixClient = new RemixClient()
// export const RemixClientContext = React.createContext(new RemixClient())

@ -4,7 +4,7 @@ import { util } from '@remix-project/remix-lib'
import { toChecksumAddress } from '@ethereumjs/util' import { toChecksumAddress } from '@ethereumjs/util'
import { fetchContractFromEtherscan } from './helpers/fetch-etherscan' import { fetchContractFromEtherscan } from './helpers/fetch-etherscan'
import { fetchContractFromSourcify } from './helpers/fetch-sourcify' import { fetchContractFromSourcify } from './helpers/fetch-sourcify'
import { UUPSDeployedByteCode, UUPSCompilerVersion, UUPSOptimize, UUPSRuns, UUPSEvmVersion, UUPSLanguage, UUPSDeployedByteCodeV5, UUPSCompilerVersionV5 } from './constants/uups' import { UUPSDeployedByteCode, UUPSCompilerVersion, UUPSOptimize, UUPSRuns, UUPSEvmVersion, UUPSLanguage, UUPSDeployedByteCodeV5, UUPSCompilerVersionV5, UUPSEvmVersionv5, UUPSOptimizev5 } from './constants/uups'
const profile = { const profile = {
name: 'fetchAndCompile', name: 'fetchAndCompile',
@ -88,8 +88,8 @@ export class FetchAndCompile extends Plugin {
const settings = { const settings = {
version: UUPSCompilerVersionV5, version: UUPSCompilerVersionV5,
language: UUPSLanguage, language: UUPSLanguage,
evmVersion: UUPSEvmVersion, evmVersion: UUPSEvmVersionv5,
optimize: UUPSOptimize, optimize: UUPSOptimizev5,
runs: UUPSRuns runs: UUPSRuns
} }
const proxyUrl = 'https://github.com/OpenZeppelin/openzeppelin-contracts/blob/v5.0.0/contracts/proxy/ERC1967/ERC1967Proxy.sol' const proxyUrl = 'https://github.com/OpenZeppelin/openzeppelin-contracts/blob/v5.0.0/contracts/proxy/ERC1967/ERC1967Proxy.sol'

File diff suppressed because one or more lines are too long

@ -21,6 +21,7 @@ export class LogsManager {
eachOf(block.transactions, (tx: any, i, next) => { eachOf(block.transactions, (tx: any, i, next) => {
const txHash = '0x' + tx.hash().toString('hex') const txHash = '0x' + tx.hash().toString('hex')
web3.eth.getTransactionReceipt(txHash, (_error, receipt) => { web3.eth.getTransactionReceipt(txHash, (_error, receipt) => {
if (!receipt) return next()
for (const log of receipt.logs) { for (const log of receipt.logs) {
this.oldLogs.push({ type: 'block', blockNumber, block, tx, log, txNumber: i, receipt }) this.oldLogs.push({ type: 'block', blockNumber, block, tx, log, txNumber: i, receipt })
const subscriptions = this.getSubscriptionsFor({ type: 'block', blockNumber, block, tx, log, receipt}) const subscriptions = this.getSubscriptionsFor({ type: 'block', blockNumber, block, tx, log, receipt})

@ -24,24 +24,23 @@ export class TxRunnerVM {
pendingTxs pendingTxs
vmaccounts vmaccounts
queusTxs queusTxs
blocks blocks: Buffer[]
logsManager logsManager
commonContext commonContext
blockParentHash blockParentHash
nextNonceForCall: number nextNonceForCall: number
getVMObject: () => any getVMObject: () => any
constructor (vmaccounts, api, getVMObject, blockNumber) { constructor (vmaccounts, api, getVMObject, blocks: Buffer[] = []) {
this.event = new EventManager() this.event = new EventManager()
this.logsManager = new LogsManager() this.logsManager = new LogsManager()
// has a default for now for backwards compatibility // has a default for now for backwards compatibility
this.getVMObject = getVMObject this.getVMObject = getVMObject
this.commonContext = this.getVMObject().common this.commonContext = this.getVMObject().common
this.blockNumber = blockNumber || 0 this.blockNumber = Array.isArray(blocks) ? blocks.length : 0 // TODO: this should be set to the fetched block number count
this.pendingTxs = {} this.pendingTxs = {}
this.vmaccounts = vmaccounts this.vmaccounts = vmaccounts
this.queusTxs = [] this.queusTxs = []
this.blocks = []
/* /*
txHash is generated using the nonce, txHash is generated using the nonce,
in order to have unique transaction hash, we need to keep using different nonce (in case of a call) in order to have unique transaction hash, we need to keep using different nonce (in case of a call)
@ -51,7 +50,15 @@ export class TxRunnerVM {
this.nextNonceForCall = 0 this.nextNonceForCall = 0
const vm = this.getVMObject().vm const vm = this.getVMObject().vm
if (Array.isArray(blocks) && (blocks || []).length > 0) {
const lastBlock = Block.fromRLPSerializedBlock(blocks[blocks.length - 1], { common: this.commonContext })
this.blockParentHash = lastBlock.hash()
this.blocks = blocks
} else {
this.blockParentHash = vm.blockchain.genesisBlock.hash() this.blockParentHash = vm.blockchain.genesisBlock.hash()
this.blocks = [vm.blockchain.genesisBlock.serialize()]
}
} }
execute (args: InternalTransaction, confirmationCb, gasEstimationForceSend, promptCb, callback: VMExecutionCallBack) { execute (args: InternalTransaction, confirmationCb, gasEstimationForceSend, promptCb, callback: VMExecutionCallBack) {
@ -106,8 +113,7 @@ export class TxRunnerVM {
const coinbases = ['0x0e9281e9c6a0808672eaba6bd1220e144c9bb07a', '0x8945a1288dc78a6d8952a92c77aee6730b414778', '0x94d76e24f818426ae84aa404140e8d5f60e10e7e'] const coinbases = ['0x0e9281e9c6a0808672eaba6bd1220e144c9bb07a', '0x8945a1288dc78a6d8952a92c77aee6730b414778', '0x94d76e24f818426ae84aa404140e8d5f60e10e7e']
const difficulties = [69762765929000, 70762765929000, 71762765929000] const difficulties = [69762765929000, 70762765929000, 71762765929000]
const difficulty = this.commonContext.consensusType() === ConsensusType.ProofOfStake ? 0 : difficulties[this.blockNumber % difficulties.length] const difficulty = this.commonContext.consensusType() === ConsensusType.ProofOfStake ? 0 : difficulties[this.blockNumber % difficulties.length]
const blocknumber = this.blocks.length
const blocknumber = this.blockNumber + 1
const block = Block.fromBlockData({ const block = Block.fromBlockData({
header: { header: {
timestamp: new Date().getTime() / 1000 | 0, timestamp: new Date().getTime() / 1000 | 0,
@ -122,10 +128,13 @@ export class TxRunnerVM {
}, { common: this.commonContext, hardforkByBlockNumber: false, hardforkByTTD: undefined }) }, { common: this.commonContext, hardforkByBlockNumber: false, hardforkByTTD: undefined })
if (!useCall) { if (!useCall) {
this.blockNumber = this.blockNumber + 1 this.blockNumber = blocknumber
this.blockParentHash = block.hash() this.blockParentHash = block.hash()
this.runBlockInVm(tx, block, (err, result) => { this.runBlockInVm(tx, block, (err, result) => {
if (!err) this.getVMObject().vm.blockchain.putBlock(block) if (!err) {
this.getVMObject().vm.blockchain.putBlock(block)
this.blocks.push(block.serialize())
}
callback(err, result) callback(err, result)
}) })
} else { } else {

@ -4,7 +4,7 @@ import { processTx } from './txProcess'
import { execution } from '@remix-project/remix-lib' import { execution } from '@remix-project/remix-lib'
import { ethers } from 'ethers' import { ethers } from 'ethers'
import { VMexecutionResult } from '@remix-project/remix-lib' import { VMexecutionResult } from '@remix-project/remix-lib'
import { RunTxResult } from '@ethereumjs/vm' import { VMContext } from '../vm-context'
import { Log, EvmError } from '@ethereumjs/evm' import { Log, EvmError } from '@ethereumjs/evm'
const TxRunnerVM = execution.TxRunnerVM const TxRunnerVM = execution.TxRunnerVM
const TxRunner = execution.TxRunner const TxRunner = execution.TxRunner
@ -19,7 +19,7 @@ export type VMExecResult = {
} }
export class Transactions { export class Transactions {
vmContext vmContext: VMContext
accounts accounts
tags tags
txRunnerVMInstance txRunnerVMInstance
@ -32,7 +32,7 @@ export class Transactions {
this.tags = {} this.tags = {}
} }
init (accounts, blockNumber) { init (accounts, blocksData: Buffer[]) {
this.accounts = accounts this.accounts = accounts
const api = { const api = {
logMessage: (msg) => { logMessage: (msg) => {
@ -55,7 +55,7 @@ export class Transactions {
} }
} }
this.txRunnerVMInstance = new TxRunnerVM(accounts, api, _ => this.vmContext.vmObject(), blockNumber) this.txRunnerVMInstance = new TxRunnerVM(accounts, api, _ => this.vmContext.vmObject(), blocksData)
this.txRunnerInstance = new TxRunner(this.txRunnerVMInstance, {}) this.txRunnerInstance = new TxRunner(this.txRunnerVMInstance, {})
this.txRunnerInstance.vmaccounts = accounts this.txRunnerInstance.vmaccounts = accounts
} }
@ -74,7 +74,9 @@ export class Transactions {
eth_getExecutionResultFromSimulator: this.eth_getExecutionResultFromSimulator.bind(this), eth_getExecutionResultFromSimulator: this.eth_getExecutionResultFromSimulator.bind(this),
eth_getHHLogsForTx: this.eth_getHHLogsForTx.bind(this), eth_getHHLogsForTx: this.eth_getHHLogsForTx.bind(this),
eth_getHashFromTagBySimulator: this.eth_getHashFromTagBySimulator.bind(this), eth_getHashFromTagBySimulator: this.eth_getHashFromTagBySimulator.bind(this),
eth_registerCallId: this.eth_registerCallId.bind(this) eth_registerCallId: this.eth_registerCallId.bind(this),
eth_getStateDb: this.eth_getStateDb.bind(this),
eth_getBlocksData: this.eth_getBlocksData.bind(this)
} }
} }
@ -198,6 +200,17 @@ export class Transactions {
cb() cb()
} }
eth_getStateDb (_, cb) {
cb(null, this.vmContext.currentVm.stateManager.getDb())
}
eth_getBlocksData (_, cb) {
cb(null, {
blocks: this.txRunnerVMInstance.blocks,
latestBlockNumber: this.txRunnerVMInstance.blockNumber
})
}
eth_call (payload, cb) { eth_call (payload, cb) {
// from might be lowercased address (web3) // from might be lowercased address (web3)
if (payload.params && payload.params.length > 0 && payload.params[0].from) { if (payload.params && payload.params.length > 0 && payload.params[0].from) {

@ -11,6 +11,7 @@ import { Transactions } from './methods/transactions'
import { Debug } from './methods/debug' import { Debug } from './methods/debug'
import { VMContext } from './vm-context' import { VMContext } from './vm-context'
import { Web3PluginBase } from 'web3' import { Web3PluginBase } from 'web3'
import { Block } from '@ethereumjs/block'
export interface JSONRPCRequestPayload { export interface JSONRPCRequestPayload {
params: any[]; params: any[];
@ -27,8 +28,20 @@ export interface JSONRPCResponsePayload {
export type JSONRPCResponseCallback = (err: Error, result?: JSONRPCResponsePayload) => void export type JSONRPCResponseCallback = (err: Error, result?: JSONRPCResponsePayload) => void
export type State = Record<string, string>
export type ProviderOptions = {
fork?: string,
nodeUrl?: string,
blockNumber?: number | 'latest',
stateDb?: State,
logDetails?: boolean
blocks?: string[],
coinbase?: string
}
export class Provider { export class Provider {
options: Record<string, string | number> options: ProviderOptions
vmContext vmContext
Accounts Accounts
Transactions Transactions
@ -37,10 +50,10 @@ export class Provider {
initialized: boolean initialized: boolean
pendingRequests: Array<any> pendingRequests: Array<any>
constructor (options: Record<string, string | number> = {}) { constructor (options: ProviderOptions = {} as ProviderOptions) {
this.options = options this.options = options
this.connected = true this.connected = true
this.vmContext = new VMContext(options['fork'] as string, options['nodeUrl'] as string, options['blockNumber'] as (number | 'latest')) this.vmContext = new VMContext(options['fork'], options['nodeUrl'], options['blockNumber'], options['stateDb'], options['blocks'])
this.Accounts = new Web3Accounts(this.vmContext) this.Accounts = new Web3Accounts(this.vmContext)
this.Transactions = new Transactions(this.vmContext) this.Transactions = new Transactions(this.vmContext)
@ -60,7 +73,7 @@ export class Provider {
this.pendingRequests = [] this.pendingRequests = []
await this.vmContext.init() await this.vmContext.init()
await this.Accounts.resetAccounts() await this.Accounts.resetAccounts()
this.Transactions.init(this.Accounts.accounts, this.vmContext.blockNumber) this.Transactions.init(this.Accounts.accounts, this.vmContext.serializedBlocks)
this.initialized = true this.initialized = true
if (this.pendingRequests.length > 0) { if (this.pendingRequests.length > 0) {
this.pendingRequests.map((req) => { this.pendingRequests.map((req) => {
@ -168,4 +181,18 @@ class Web3TestPlugin extends Web3PluginBase {
params: [id] params: [id]
}) })
} }
public getStateDb() {
return this.requestManager.send({
method: 'eth_getStateDb',
params: []
})
}
public getBlocksData() {
return this.requestManager.send({
method: 'eth_getBlocksData',
params: []
})
}
} }

@ -2,7 +2,7 @@
'use strict' 'use strict'
import { Cache } from '@ethereumjs/statemanager/dist/cache' import { Cache } from '@ethereumjs/statemanager/dist/cache'
import { hash } from '@remix-project/remix-lib' import { hash } from '@remix-project/remix-lib'
import { bufferToHex, Account, toBuffer, bufferToBigInt} from '@ethereumjs/util' import { bufferToHex, Account, toBuffer, bufferToBigInt, bigIntToHex } from '@ethereumjs/util'
import { keccak256 } from 'ethereum-cryptography/keccak' import { keccak256 } from 'ethereum-cryptography/keccak'
import type { Address } from '@ethereumjs/util' import type { Address } from '@ethereumjs/util'
import { decode } from 'rlp' import { decode } from 'rlp'
@ -13,7 +13,7 @@ import { VmProxy } from './VmProxy'
import { VM } from '@ethereumjs/vm' import { VM } from '@ethereumjs/vm'
import type { BigIntLike } from '@ethereumjs/util' import type { BigIntLike } from '@ethereumjs/util'
import { Common, ConsensusType } from '@ethereumjs/common' import { Common, ConsensusType } from '@ethereumjs/common'
import { Trie } from '@ethereumjs/trie' import { Trie, MapDB } from '@ethereumjs/trie'
import { DefaultStateManager, StateManager, EthersStateManager, EthersStateManagerOpts } from '@ethereumjs/statemanager' import { DefaultStateManager, StateManager, EthersStateManager, EthersStateManagerOpts } from '@ethereumjs/statemanager'
import { StorageDump } from '@ethereumjs/statemanager/dist/interface' import { StorageDump } from '@ethereumjs/statemanager/dist/interface'
import { EVM } from '@ethereumjs/evm' import { EVM } from '@ethereumjs/evm'
@ -21,7 +21,7 @@ import { EEI } from '@ethereumjs/vm'
import { Blockchain } from '@ethereumjs/blockchain' import { Blockchain } from '@ethereumjs/blockchain'
import { Block } from '@ethereumjs/block' import { Block } from '@ethereumjs/block'
import { Transaction } from '@ethereumjs/tx' import { Transaction } from '@ethereumjs/tx'
import { bigIntToHex } from '@ethereumjs/util' import { State } from './provider'
/** /**
* Options for constructing a {@link StateManager}. * Options for constructing a {@link StateManager}.
@ -50,6 +50,11 @@ class StateManagerCommonStorageDump extends DefaultStateManager {
this.keyHashes = {} this.keyHashes = {}
} }
getDb () {
// @ts-ignore
return this._trie.database().db
}
putContractStorage (address, key, value) { putContractStorage (address, key, value) {
this.keyHashes[hash.keccak(key).toString('hex')] = bufferToHex(key) this.keyHashes[hash.keccak(key).toString('hex')] = bufferToHex(key)
return super.putContractStorage(address, key, value) return super.putContractStorage(address, key, value)
@ -100,7 +105,6 @@ export interface CustomEthersStateManagerOpts {
class CustomEthersStateManager extends StateManagerCommonStorageDump { class CustomEthersStateManager extends StateManagerCommonStorageDump {
private provider: ethers.providers.StaticJsonRpcProvider | ethers.providers.JsonRpcProvider private provider: ethers.providers.StaticJsonRpcProvider | ethers.providers.JsonRpcProvider
private blockTag: string private blockTag: string
constructor(opts: CustomEthersStateManagerOpts) { constructor(opts: CustomEthersStateManagerOpts) {
super(opts) super(opts)
if (typeof opts.provider === 'string') { if (typeof opts.provider === 'string') {
@ -258,7 +262,7 @@ class CustomEthersStateManager extends StateManagerCommonStorageDump {
export type CurrentVm = { export type CurrentVm = {
vm: VM, vm: VM,
web3vm: VmProxy, web3vm: VmProxy,
stateManager: StateManager, stateManager: StateManagerCommonStorageDump,
common: Common common: Common
} }
@ -298,12 +302,16 @@ export class VMContext {
exeResults: Record<string, Transaction> exeResults: Record<string, Transaction>
nodeUrl: string nodeUrl: string
blockNumber: number | 'latest' blockNumber: number | 'latest'
stateDb: State
rawBlocks: string[]
serializedBlocks: Buffer[]
constructor (fork?: string, nodeUrl?: string, blockNumber?: number | 'latest') { constructor (fork?: string, nodeUrl?: string, blockNumber?: number | 'latest', stateDb?: State, blocksData?: string[]) {
this.blockGasLimitDefault = 4300000 this.blockGasLimitDefault = 4300000
this.blockGasLimit = this.blockGasLimitDefault this.blockGasLimit = this.blockGasLimitDefault
this.currentFork = fork || 'merge' this.currentFork = fork || 'merge'
this.nodeUrl = nodeUrl this.nodeUrl = nodeUrl
this.stateDb = stateDb
this.blockNumber = blockNumber this.blockNumber = blockNumber
this.blocks = {} this.blocks = {}
this.latestBlockNumber = "0x0" this.latestBlockNumber = "0x0"
@ -311,6 +319,8 @@ export class VMContext {
this.txByHash = {} this.txByHash = {}
this.exeResults = {} this.exeResults = {}
this.logsManager = new LogsManager() this.logsManager = new LogsManager()
this.rawBlocks = blocksData
this.serializedBlocks = []
} }
async init () { async init () {
@ -318,7 +328,7 @@ export class VMContext {
} }
async createVm (hardfork) { async createVm (hardfork) {
let stateManager: StateManager let stateManager: StateManagerCommonStorageDump
if (this.nodeUrl) { if (this.nodeUrl) {
let block = this.blockNumber let block = this.blockNumber
if (this.blockNumber === 'latest') { if (this.blockNumber === 'latest') {
@ -335,15 +345,25 @@ export class VMContext {
blockTag: '0x' + this.blockNumber.toString(16) blockTag: '0x' + this.blockNumber.toString(16)
}) })
} }
} else{
const db = this.stateDb ? new Map(Object.entries(this.stateDb).map(([k, v]) => [k, toBuffer(v)])) : new Map()
const mapDb = new MapDB(db)
const trie = await Trie.create({ useKeyHashing: true, db: mapDb, useRootPersistence: true })
} else stateManager = new StateManagerCommonStorageDump({ trie })
stateManager = new StateManagerCommonStorageDump() }
const consensusType = hardfork === 'berlin' || hardfork === 'london' ? ConsensusType.ProofOfWork : ConsensusType.ProofOfStake const consensusType = hardfork === 'berlin' || hardfork === 'london' ? ConsensusType.ProofOfWork : ConsensusType.ProofOfStake
const difficulty = consensusType === ConsensusType.ProofOfStake ? 0 : 69762765929000 const difficulty = consensusType === ConsensusType.ProofOfStake ? 0 : 69762765929000
const common = new VMCommon({ chain: 'mainnet', hardfork }) const common = new VMCommon({ chain: 'mainnet', hardfork })
const genesisBlock: Block = Block.fromBlockData({ const blocks = (this.rawBlocks || []).map(block => {
const serializedBlock = toBuffer(block)
this.serializedBlocks.push(serializedBlock)
return Block.fromRLPSerializedBlock(serializedBlock, { common })
})
const genesisBlock: Block = blocks.length > 0 && (blocks[0] || {}).isGenesis ? blocks[0] : Block.fromBlockData({
header: { header: {
timestamp: (new Date().getTime() / 1000 | 0), timestamp: (new Date().getTime() / 1000 | 0),
number: 0, number: 0,
@ -352,7 +372,6 @@ export class VMContext {
gasLimit: 8000000 gasLimit: 8000000
} }
}, { common, hardforkByBlockNumber: false, hardforkByTTD: undefined }) }, { common, hardforkByBlockNumber: false, hardforkByTTD: undefined })
const blockchain = await Blockchain.create({ common, validateBlocks: false, validateConsensus: false, genesisBlock }) const blockchain = await Blockchain.create({ common, validateBlocks: false, validateConsensus: false, genesisBlock })
const eei = new EEI(stateManager, common, blockchain) const eei = new EEI(stateManager, common, blockchain)
const evm = new EVM({ common, eei, allowUnlimitedContractSize: true }) const evm = new EVM({ common, eei, allowUnlimitedContractSize: true })
@ -365,13 +384,17 @@ export class VMContext {
blockchain, blockchain,
evm evm
}) })
// VmProxy and VMContext are very intricated. // VmProxy and VMContext are very intricated.
// VmProxy is used to track the EVM execution (to listen on opcode execution, in order for instance to generate the VM trace) // VmProxy is used to track the EVM execution (to listen on opcode execution, in order for instance to generate the VM trace)
const web3vm = new VmProxy(this) const web3vm = new VmProxy(this)
web3vm.setVM(vm) web3vm.setVM(vm)
this.addBlock(genesisBlock, true) this.addBlock(genesisBlock, true)
return { vm, web3vm, stateManager, common } if (blocks.length > 0) blocks.splice(0, 1)
blocks.forEach(block => {
blockchain.putBlock(block)
this.addBlock(block, false, false, web3vm)
})
return { vm, web3vm, stateManager, common, blocks }
} }
getCurrentFork () { getCurrentFork () {
@ -390,7 +413,7 @@ export class VMContext {
return this.currentVm return this.currentVm
} }
addBlock (block: Block, genesis?: boolean, isCall?: boolean) { addBlock (block: Block, genesis?: boolean, isCall?: boolean, web3vm?: VmProxy) {
let blockNumber = bigIntToHex(block.header.number) let blockNumber = bigIntToHex(block.header.number)
if (blockNumber === '0x') { if (blockNumber === '0x') {
blockNumber = '0x0' blockNumber = '0x0'
@ -400,7 +423,8 @@ export class VMContext {
this.blocks[blockNumber] = block this.blocks[blockNumber] = block
this.latestBlockNumber = blockNumber this.latestBlockNumber = blockNumber
if (!isCall && !genesis) this.logsManager.checkBlock(blockNumber, block, this.web3()) if (!isCall && !genesis && web3vm) this.logsManager.checkBlock(blockNumber, block, web3vm)
if (!isCall && !genesis && !web3vm) this.logsManager.checkBlock(blockNumber, block, this.web3())
} }
trackTx (txHash, block, tx) { trackTx (txHash, block, tx) {

@ -19,7 +19,8 @@ import {
saveIpfsSettingsToast, saveIpfsSettingsToast,
useAutoCompletion, useAutoCompletion,
useShowGasInEditor, useShowGasInEditor,
useDisplayErrors useDisplayErrors,
saveEnvState
} from './settingsAction' } from './settingsAction'
import {initialState, toastInitialState, toastReducer, settingReducer} from './settingsReducer' import {initialState, toastInitialState, toastReducer, settingReducer} from './settingsReducer'
import {Toaster} from '@remix-ui/toaster' // eslint-disable-line import {Toaster} from '@remix-ui/toaster' // eslint-disable-line
@ -69,6 +70,9 @@ export const RemixUiSettings = (props: RemixUiSettingsProps) => {
const useShowGas = props.config.get('settings/show-gas') const useShowGas = props.config.get('settings/show-gas')
if (useShowGas === null || useShowGas === undefined) useShowGasInEditor(props.config, true, dispatch) if (useShowGas === null || useShowGas === undefined) useShowGasInEditor(props.config, true, dispatch)
const enableSaveEnvState = props.config.get('settings/save-evm-state')
if (enableSaveEnvState === null || enableSaveEnvState === undefined) saveEnvState(props.config, true, dispatch)
} }
useEffect(() => initValue(), [resetState, props.config]) useEffect(() => initValue(), [resetState, props.config])
useEffect(() => initValue(), []) useEffect(() => initValue(), [])
@ -200,6 +204,10 @@ export const RemixUiSettings = (props: RemixUiSettingsProps) => {
useDisplayErrors(props.config, event.target.checked, dispatch) useDisplayErrors(props.config, event.target.checked, dispatch)
} }
const onchangeSaveEnvState= (event) => {
saveEnvState(props.config, event.target.checked, dispatch)
}
const getTextClass = (key) => { const getTextClass = (key) => {
if (props.config.get(key)) { if (props.config.get(key)) {
return textDark return textDark
@ -217,6 +225,7 @@ export const RemixUiSettings = (props: RemixUiSettingsProps) => {
const isAutoCompleteChecked = props.config.get('settings/auto-completion') || false const isAutoCompleteChecked = props.config.get('settings/auto-completion') || false
const isShowGasInEditorChecked = props.config.get('settings/show-gas') || false const isShowGasInEditorChecked = props.config.get('settings/show-gas') || false
const displayErrorsChecked = props.config.get('settings/display-errors') || false const displayErrorsChecked = props.config.get('settings/display-errors') || false
const isSaveEvmStateChecked = props.config.get('settings/save-evm-state') || false
return ( return (
<div className="$border-top"> <div className="$border-top">
<div className="d-flex justify-content-end pr-4"> <div className="d-flex justify-content-end pr-4">
@ -333,6 +342,18 @@ export const RemixUiSettings = (props: RemixUiSettingsProps) => {
</a> </a>
</label> </label>
</div> </div>
<div className="custom-control custom-checkbox mb-1">
<input onChange={onchangeSaveEnvState} id="settingsEnableSaveEnvState" data-id="settingsEnableSaveEnvState" type="checkbox" className="custom-control-input" checked={isSaveEvmStateChecked} />
<label
className={`form-check-label custom-control-label align-middle ${getTextClass('settings/save-evm-state')}`}
data-id="settingsEnableSaveEnvStateLabel"
htmlFor="settingsEnableSaveEnvState"
>
<span>
<FormattedMessage id="settings.enableSaveEnvState" />
</span>
</label>
</div>
</div> </div>
</div> </div>
) )

@ -90,3 +90,8 @@ export const saveIpfsSettingsToast = (config, dispatch, ipfsURL, ipfsProtocol, i
config.set('settings/ipfs-project-secret', ipfsProjectSecret) config.set('settings/ipfs-project-secret', ipfsProjectSecret)
dispatch({ type: 'save', payload: { message: 'IPFS settings have been saved' } }) dispatch({ type: 'save', payload: { message: 'IPFS settings have been saved' } })
} }
export const saveEnvState = (config, checked, dispatch) => {
config.set('settings/save-evm-state', checked)
dispatch({ type: 'save-evm-state', payload: { isChecked: checked, textClass: checked ? textDark : textSecondary } })
}

@ -56,7 +56,12 @@ export const initialState = {
name: 'copilot/suggest/temperature', name: 'copilot/suggest/temperature',
value: 0.5, value: 0.5,
textClass: textSecondary textClass: textSecondary
} },
{
name: 'save-evm-state',
isChecked: false,
textClass: textSecondary
},
] ]
} }
@ -173,6 +178,17 @@ export const settingReducer = (state, action) => {
return { return {
...state ...state
} }
case 'save-evm-state':
state.elementState.map(element => {
if (element.name === 'save-evm-state') {
element.isChecked = action.payload.isChecked
element.textClass = action.payload.textClass
}
})
return {
...state
}
default: default:
return initialState return initialState
} }

@ -360,6 +360,8 @@ export const CompilerApiMixin = (Base) => class extends Base {
else this.compileTabLogic.runCompiler(undefined) else this.compileTabLogic.runCompiler(undefined)
} else if (this.currentFile && this.currentFile.endsWith('.circom')) { } else if (this.currentFile && this.currentFile.endsWith('.circom')) {
await this.call('circuit-compiler', 'compile', this.currentFile) await this.call('circuit-compiler', 'compile', this.currentFile)
} else if (this.currentFile && this.currentFile.endsWith('.vy')) {
await this.call('vyper', 'vyperCompileCustomAction', this.currentFile)
} }
} }
} }

Loading…
Cancel
Save