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

pull/5370/head
bunsenstraat 9 months ago
commit f5a6e23878
  1. 353
      apps/remix-ide-e2e/src/tests/proxy_oz_v5_non_shanghai_runtime.test.ts
  2. 115
      apps/remix-ide-e2e/src/tests/runAndDeploy.test.ts
  3. 16
      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. 31
      apps/remix-ide/src/blockchain/blockchain.tsx
  12. 86
      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. 27
      libs/remix-lib/src/execution/txRunnerVM.ts
  25. 25
      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. 10
      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
{}
}
`
}
}
]

@ -82,21 +82,21 @@ module.exports = {
instanceAddress = address
console.log('instanceAddress', instanceAddress)
browser
.waitForElementVisible(`#instance${instanceAddress} [data-id="instanceContractBal"]`)
.waitForElementVisible(`#instance${instanceAddress} [data-id="instanceContractBal"]`)
//*[@id="instance0xbBF289D846208c16EDc8474705C748aff07732dB" and contains(.,"Balance") and contains(.,'0.000000000000000111')]
.waitForElementVisible({
locateStrategy: 'xpath',
selector: `//*[@id="instance${instanceAddress}" and contains(.,"Balance") and contains(.,'0.000000000000000111')]`,
timeout: 60000
})
.waitForElementVisible({
locateStrategy: 'xpath',
selector: `//*[@id="instance${instanceAddress}" and contains(.,"Balance") and contains(.,'0.000000000000000111')]`,
timeout: 60000
})
//.waitForElementContainsText(`#instance${instanceAddress} [data-id="instanceContractBal"]`, 'Balance: 0.000000000000000111 ETH', 60000)
.clickFunction('sendSomeEther - transact (not payable)', { types: 'uint256 num', values: '2' })
.pause(1000)
.waitForElementVisible({
locateStrategy: 'xpath',
selector: `//*[@id="instance${instanceAddress}" and contains(.,"Balance") and contains(.,'0.000000000000000109')]`,
timeout: 60000
})
.clickFunction('sendSomeEther - transact (not payable)', { types: 'uint256 num', values: '2' })
.pause(1000)
.waitForElementVisible({
locateStrategy: 'xpath',
selector: `//*[@id="instance${instanceAddress}" and contains(.,"Balance") and contains(.,'0.000000000000000109')]`,
timeout: 60000
})
})
},
@ -238,6 +238,95 @@ module.exports = {
.executeScriptInTerminal('web3.eth.getAccounts()')
.journalLastChildIncludes('[ "0x76a3ABb5a12dcd603B52Ed22195dED17ee82708f" ]')
.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)
}
/**
*
* @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 = '') {
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('/')
const tabPath = slash.reverse()
const tempTitle = []
@ -292,7 +306,7 @@ export class TabProxy extends Plugin {
if (!previous && tab.name === name) {
if(index - 1 >= 0 && this.loadedTabs[index - 1])
previous = this.loadedTabs[index - 1]
else if (index + 1 && this.loadedTabs[index + 1])
else if (index + 1 && this.loadedTabs[index + 1])
previous = this.loadedTabs[index + 1]
}
return tab.name !== name

@ -43,5 +43,6 @@
"settings.copilot": "Solidity copilot - Alpha",
"settings.copilot.activate": "Load & Activate copilot",
"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.projectID": "ID DEL 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.projectID": "ID 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.projectID": "ID 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.projectID": "项目 ID",
"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)
}
}, 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)

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.5 KiB

@ -135,7 +135,8 @@ export class Blockchain extends Plugin {
setupEvents() {
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.detectNetwork((error, network) => {
this.networkStatus = {network, error}
@ -643,8 +644,23 @@ export class Blockchain extends Plugin {
})
}
async resetEnvironment() {
await this.getCurrentProvider().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()
}
// TODO: most params here can be refactored away in txRunner
const web3Runner = new TxRunnerWeb3(
{
@ -677,7 +693,7 @@ export class Blockchain extends Plugin {
view on etherscan
</a>
)
}
}
})
})
this.txRunner = new TxRunner(web3Runner, {})
@ -889,8 +905,13 @@ export class Blockchain extends Plugin {
let execResult
let returnValue = null
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) {
const finalLogs = (
<div>

@ -3,6 +3,7 @@
import Web3 from 'web3'
import { execution } from '@remix-project/remix-lib'
import EventManager from '../lib/events'
import {bufferToHex} from '@ethereumjs/util'
const _paq = window._paq = window._paq || []
let web3
@ -71,35 +72,44 @@ export class ExecutionContext {
}
detectNetwork (callback) {
if (this.isVM()) {
callback(null, { id: '-', name: 'VM' })
} else {
if (!web3.currentProvider) {
return callback('No provider set')
}
const cb = (err, id) => {
let name = null
if (err) name = 'Unknown'
// https://github.com/ethereum/EIPs/blob/master/EIPS/eip-155.md
else if (id === 1) name = 'Main'
else if (id === 3) name = 'Ropsten'
else if (id === 4) name = 'Rinkeby'
else if (id === 5) name = 'Goerli'
else if (id === 42) name = 'Kovan'
else if (id === 11155111) name = 'Sepolia'
else name = 'Custom'
if (id === 1) {
web3.eth.getBlock(0).then((block) => {
if (block && block.hash !== this.mainNetGenesisHash) name = 'Custom'
callback(err, { id, name, lastBlock: this.lastBlock, currentFork: this.currentFork })
}).catch((error) => callback(error))
} else {
callback(err, { id, name, lastBlock: this.lastBlock, currentFork: this.currentFork })
return new Promise((resolve, reject) => {
if (this.isVM()) {
callback && callback(null, { id: '-', name: 'VM' })
return resolve({ id: '-', name: 'VM' })
} else {
if (!web3.currentProvider) {
callback && callback('No provider set')
return reject('No provider set')
}
const cb = (err, id) => {
let name = null
if (err) name = 'Unknown'
// https://github.com/ethereum/EIPs/blob/master/EIPS/eip-155.md
else if (id === 1) name = 'Main'
else if (id === 3) name = 'Ropsten'
else if (id === 4) name = 'Rinkeby'
else if (id === 5) name = 'Goerli'
else if (id === 42) name = 'Kovan'
else if (id === 11155111) name = 'Sepolia'
else name = 'Custom'
if (id === 1) {
web3.eth.getBlock(0).then((block) => {
if (block && block.hash !== this.mainNetGenesisHash) name = 'Custom'
callback && callback(err, { id, name, lastBlock: this.lastBlock, currentFork: this.currentFork })
return resolve({ id, name, lastBlock: this.lastBlock, currentFork: this.currentFork })
}).catch((error) => {
callback && callback(error)
return reject(error)
})
} else {
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) {
@ -195,4 +205,26 @@ export class ExecutionContext {
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 { privateToAddress, hashPersonalMessage, isHexString } from '@ethereumjs/util'
import { extend, JSONRPCRequestPayload, JSONRPCResponseCallback } from '@remix-project/remix-simulator'
import {toBuffer} from '@ethereumjs/util'
import { ExecutionContext } from '../execution-context'
export class VMProvider {
@ -12,9 +13,7 @@ export class VMProvider {
sendAsync: (query: JSONRPCRequestPayload, callback: JSONRPCResponseCallback) => void
}
newAccountCallback: {[stamp: number]: (error: Error, address: string) => void}
constructor (executionContext: ExecutionContext) {
this.executionContext = executionContext
this.worker = null
this.provider = null
@ -29,7 +28,7 @@ export class VMProvider {
})
}
async resetEnvironment () {
async resetEnvironment (stringifiedState?: string) {
if (this.worker) this.worker.terminate()
this.worker = new Worker(new URL('./worker-vm', import.meta.url))
const provider = this.executionContext.getProviderObject()
@ -76,10 +75,35 @@ 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
// can be removed later when we update the API
createVMAccount (newAccount) {

@ -6,7 +6,7 @@ self.onmessage = (e: MessageEvent) => {
switch (data.cmd) {
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(() => {
self.postMessage({
cmd: 'initiateResult',

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

@ -131,19 +131,25 @@ const App = () => {
</CustomAccordionToggle>
</div>
<Accordion.Collapse eventKey="0">
<div className="pl-3 pt-3 border-top-0">
<div className="pt-2">
<Form>
<div className="d-flex flex-row gap-5 mb-3 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'}`} />
<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`}`} />
<div className="d-flex flex-row justify-content-around mb-1 mt-2">
<div className={`custom-control custom-radio ${state.environment === 'remote' ? 'd-flex' : 'd-flex 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>
</Form>
<LocalUrlInput url={state.localUrl} setUrl={setLocalUrl} environment={state.environment} />
</div>
</Accordion.Collapse>
</div>
</Accordion>
<LocalUrlInput url={state.localUrl} setUrl={setLocalUrl} environment={state.environment} />
<span className="px-3 mt-1 mb-1 small text-warning">
<span className="px-3 mt-3 mb-3 small text-warning">
Specify the{' '}
<a className="text-warning" target="_blank" href="https://remix-ide.readthedocs.io/en/latest/vyper.html#specify-vyper-version">
compiler version
@ -164,7 +170,7 @@ const App = () => {
<VyperResult output={output} plugin={remixClient} />
</>
) : output.status === 'failed' ? (
<CompileErrorCard output={output} askGpt={remixClient.askGpt} />
<CompileErrorCard output={output} plugin={remixClient} />
) : null}
</article>
</section>

@ -1,15 +1,13 @@
import {CopyToClipboard} from '@remix-ui/clipboard'
import Reaact from 'react'
import { RemixClient } from '../utils'
export function CompileErrorCard(props: any) {
export function CompileErrorCard(props: { output: any, plugin: RemixClient }) {
return (
<div
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}
style={{
width: '94%'
}}
>
<span
data-id="error-message"
@ -21,16 +19,16 @@ export function CompileErrorCard(props: any) {
>
{props.output.message.trim()}
</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>
<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
</span>
<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'} />
</span>
</div>
</div> */}
</div>
</div>
)
}

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

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

@ -61,8 +61,16 @@ export class RemixClient extends PluginClient {
}
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 {
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) {
console.error('unable to askGpt')
console.error(err)
@ -153,4 +161,3 @@ export class RemixClient extends PluginClient {
}
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 { fetchContractFromEtherscan } from './helpers/fetch-etherscan'
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 = {
name: 'fetchAndCompile',
@ -88,8 +88,8 @@ export class FetchAndCompile extends Plugin {
const settings = {
version: UUPSCompilerVersionV5,
language: UUPSLanguage,
evmVersion: UUPSEvmVersion,
optimize: UUPSOptimize,
evmVersion: UUPSEvmVersionv5,
optimize: UUPSOptimizev5,
runs: UUPSRuns
}
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) => {
const txHash = '0x' + tx.hash().toString('hex')
web3.eth.getTransactionReceipt(txHash, (_error, receipt) => {
if (!receipt) return next()
for (const log of receipt.logs) {
this.oldLogs.push({ type: 'block', blockNumber, block, tx, log, txNumber: i, receipt })
const subscriptions = this.getSubscriptionsFor({ type: 'block', blockNumber, block, tx, log, receipt})

@ -24,24 +24,23 @@ export class TxRunnerVM {
pendingTxs
vmaccounts
queusTxs
blocks
blocks: Buffer[]
logsManager
commonContext
blockParentHash
nextNonceForCall: number
getVMObject: () => any
constructor (vmaccounts, api, getVMObject, blockNumber) {
constructor (vmaccounts, api, getVMObject, blocks: Buffer[] = []) {
this.event = new EventManager()
this.logsManager = new LogsManager()
// has a default for now for backwards compatibility
this.getVMObject = getVMObject
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.vmaccounts = vmaccounts
this.queusTxs = []
this.blocks = []
/*
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)
@ -51,7 +50,15 @@ export class TxRunnerVM {
this.nextNonceForCall = 0
const vm = this.getVMObject().vm
this.blockParentHash = vm.blockchain.genesisBlock.hash()
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.blocks = [vm.blockchain.genesisBlock.serialize()]
}
}
execute (args: InternalTransaction, confirmationCb, gasEstimationForceSend, promptCb, callback: VMExecutionCallBack) {
@ -106,8 +113,7 @@ export class TxRunnerVM {
const coinbases = ['0x0e9281e9c6a0808672eaba6bd1220e144c9bb07a', '0x8945a1288dc78a6d8952a92c77aee6730b414778', '0x94d76e24f818426ae84aa404140e8d5f60e10e7e']
const difficulties = [69762765929000, 70762765929000, 71762765929000]
const difficulty = this.commonContext.consensusType() === ConsensusType.ProofOfStake ? 0 : difficulties[this.blockNumber % difficulties.length]
const blocknumber = this.blockNumber + 1
const blocknumber = this.blocks.length
const block = Block.fromBlockData({
header: {
timestamp: new Date().getTime() / 1000 | 0,
@ -122,10 +128,13 @@ export class TxRunnerVM {
}, { common: this.commonContext, hardforkByBlockNumber: false, hardforkByTTD: undefined })
if (!useCall) {
this.blockNumber = this.blockNumber + 1
this.blockNumber = blocknumber
this.blockParentHash = block.hash()
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)
})
} else {

@ -4,7 +4,7 @@ import { processTx } from './txProcess'
import { execution } from '@remix-project/remix-lib'
import { ethers } from 'ethers'
import { VMexecutionResult } from '@remix-project/remix-lib'
import { RunTxResult } from '@ethereumjs/vm'
import { VMContext } from '../vm-context'
import { Log, EvmError } from '@ethereumjs/evm'
const TxRunnerVM = execution.TxRunnerVM
const TxRunner = execution.TxRunner
@ -19,7 +19,7 @@ export type VMExecResult = {
}
export class Transactions {
vmContext
vmContext: VMContext
accounts
tags
txRunnerVMInstance
@ -32,7 +32,7 @@ export class Transactions {
this.tags = {}
}
init (accounts, blockNumber) {
init (accounts, blocksData: Buffer[]) {
this.accounts = accounts
const api = {
logMessage: (msg) => {
@ -55,11 +55,11 @@ 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.vmaccounts = accounts
}
methods () {
return {
eth_sendTransaction: this.eth_sendTransaction.bind(this),
@ -74,7 +74,9 @@ export class Transactions {
eth_getExecutionResultFromSimulator: this.eth_getExecutionResultFromSimulator.bind(this),
eth_getHHLogsForTx: this.eth_getHHLogsForTx.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()
}
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) {
// from might be lowercased address (web3)
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 { VMContext } from './vm-context'
import { Web3PluginBase } from 'web3'
import { Block } from '@ethereumjs/block'
export interface JSONRPCRequestPayload {
params: any[];
@ -27,8 +28,20 @@ export interface JSONRPCResponsePayload {
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 {
options: Record<string, string | number>
options: ProviderOptions
vmContext
Accounts
Transactions
@ -37,10 +50,10 @@ export class Provider {
initialized: boolean
pendingRequests: Array<any>
constructor (options: Record<string, string | number> = {}) {
constructor (options: ProviderOptions = {} as ProviderOptions) {
this.options = options
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.Transactions = new Transactions(this.vmContext)
@ -60,7 +73,7 @@ export class Provider {
this.pendingRequests = []
await this.vmContext.init()
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
if (this.pendingRequests.length > 0) {
this.pendingRequests.map((req) => {
@ -168,4 +181,18 @@ class Web3TestPlugin extends Web3PluginBase {
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'
import { Cache } from '@ethereumjs/statemanager/dist/cache'
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 type { Address } from '@ethereumjs/util'
import { decode } from 'rlp'
@ -13,7 +13,7 @@ import { VmProxy } from './VmProxy'
import { VM } from '@ethereumjs/vm'
import type { BigIntLike } from '@ethereumjs/util'
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 { StorageDump } from '@ethereumjs/statemanager/dist/interface'
import { EVM } from '@ethereumjs/evm'
@ -21,7 +21,7 @@ import { EEI } from '@ethereumjs/vm'
import { Blockchain } from '@ethereumjs/blockchain'
import { Block } from '@ethereumjs/block'
import { Transaction } from '@ethereumjs/tx'
import { bigIntToHex } from '@ethereumjs/util'
import { State } from './provider'
/**
* Options for constructing a {@link StateManager}.
@ -50,6 +50,11 @@ class StateManagerCommonStorageDump extends DefaultStateManager {
this.keyHashes = {}
}
getDb () {
// @ts-ignore
return this._trie.database().db
}
putContractStorage (address, key, value) {
this.keyHashes[hash.keccak(key).toString('hex')] = bufferToHex(key)
return super.putContractStorage(address, key, value)
@ -100,7 +105,6 @@ export interface CustomEthersStateManagerOpts {
class CustomEthersStateManager extends StateManagerCommonStorageDump {
private provider: ethers.providers.StaticJsonRpcProvider | ethers.providers.JsonRpcProvider
private blockTag: string
constructor(opts: CustomEthersStateManagerOpts) {
super(opts)
if (typeof opts.provider === 'string') {
@ -258,7 +262,7 @@ class CustomEthersStateManager extends StateManagerCommonStorageDump {
export type CurrentVm = {
vm: VM,
web3vm: VmProxy,
stateManager: StateManager,
stateManager: StateManagerCommonStorageDump,
common: Common
}
@ -298,12 +302,16 @@ export class VMContext {
exeResults: Record<string, Transaction>
nodeUrl: string
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.blockGasLimit = this.blockGasLimitDefault
this.currentFork = fork || 'merge'
this.nodeUrl = nodeUrl
this.stateDb = stateDb
this.blockNumber = blockNumber
this.blocks = {}
this.latestBlockNumber = "0x0"
@ -311,6 +319,8 @@ export class VMContext {
this.txByHash = {}
this.exeResults = {}
this.logsManager = new LogsManager()
this.rawBlocks = blocksData
this.serializedBlocks = []
}
async init () {
@ -318,7 +328,7 @@ export class VMContext {
}
async createVm (hardfork) {
let stateManager: StateManager
let stateManager: StateManagerCommonStorageDump
if (this.nodeUrl) {
let block = this.blockNumber
if (this.blockNumber === 'latest') {
@ -335,15 +345,25 @@ export class VMContext {
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()
stateManager = new StateManagerCommonStorageDump({ trie })
}
const consensusType = hardfork === 'berlin' || hardfork === 'london' ? ConsensusType.ProofOfWork : ConsensusType.ProofOfStake
const difficulty = consensusType === ConsensusType.ProofOfStake ? 0 : 69762765929000
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: {
timestamp: (new Date().getTime() / 1000 | 0),
number: 0,
@ -352,7 +372,6 @@ export class VMContext {
gasLimit: 8000000
}
}, { common, hardforkByBlockNumber: false, hardforkByTTD: undefined })
const blockchain = await Blockchain.create({ common, validateBlocks: false, validateConsensus: false, genesisBlock })
const eei = new EEI(stateManager, common, blockchain)
const evm = new EVM({ common, eei, allowUnlimitedContractSize: true })
@ -365,13 +384,17 @@ export class VMContext {
blockchain,
evm
})
// 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)
const web3vm = new VmProxy(this)
web3vm.setVM(vm)
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 () {
@ -390,7 +413,7 @@ export class VMContext {
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)
if (blockNumber === '0x') {
blockNumber = '0x0'
@ -400,7 +423,8 @@ export class VMContext {
this.blocks[blockNumber] = block
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) {

@ -19,7 +19,8 @@ import {
saveIpfsSettingsToast,
useAutoCompletion,
useShowGasInEditor,
useDisplayErrors
useDisplayErrors,
saveEnvState
} from './settingsAction'
import {initialState, toastInitialState, toastReducer, settingReducer} from './settingsReducer'
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')
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(), [])
@ -200,6 +204,10 @@ export const RemixUiSettings = (props: RemixUiSettingsProps) => {
useDisplayErrors(props.config, event.target.checked, dispatch)
}
const onchangeSaveEnvState= (event) => {
saveEnvState(props.config, event.target.checked, dispatch)
}
const getTextClass = (key) => {
if (props.config.get(key)) {
return textDark
@ -217,6 +225,7 @@ export const RemixUiSettings = (props: RemixUiSettingsProps) => {
const isAutoCompleteChecked = props.config.get('settings/auto-completion') || false
const isShowGasInEditorChecked = props.config.get('settings/show-gas') || false
const displayErrorsChecked = props.config.get('settings/display-errors') || false
const isSaveEvmStateChecked = props.config.get('settings/save-evm-state') || false
return (
<div className="$border-top">
<div className="d-flex justify-content-end pr-4">
@ -333,6 +342,18 @@ export const RemixUiSettings = (props: RemixUiSettingsProps) => {
</a>
</label>
</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>
)

@ -90,3 +90,8 @@ export const saveIpfsSettingsToast = (config, dispatch, ipfsURL, ipfsProtocol, i
config.set('settings/ipfs-project-secret', ipfsProjectSecret)
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',
value: 0.5,
textClass: textSecondary
}
},
{
name: 'save-evm-state',
isChecked: false,
textClass: textSecondary
},
]
}
@ -173,6 +178,17 @@ export const settingReducer = (state, action) => {
return {
...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:
return initialState
}

@ -28,7 +28,7 @@ export const CompilerApiMixin = (Base) => class extends Base {
onContentChanged: () => void
onFileClosed: (name: string) => void
statusChanged: (data: { key: string, title?: string, type?: string }) => void
setSolJsonBinData: (urls: iSolJsonBinData) => void
initCompilerApi () {
@ -336,7 +336,7 @@ export const CompilerApiMixin = (Base) => class extends Base {
await this.call('editor', 'addAnnotation', pos, file)
}
}
}
}
}
this.compiler.event.register('compilationFinished', this.data.eventHandlers.onCompilationFinished)
@ -360,6 +360,8 @@ export const CompilerApiMixin = (Base) => class extends Base {
else this.compileTabLogic.runCompiler(undefined)
} else if (this.currentFile && this.currentFile.endsWith('.circom')) {
await this.call('circuit-compiler', 'compile', this.currentFile)
} else if (this.currentFile && this.currentFile.endsWith('.vy')) {
await this.call('vyper', 'vyperCompileCustomAction', this.currentFile)
}
}
}
@ -370,7 +372,7 @@ export const CompilerApiMixin = (Base) => class extends Base {
return new Promise((resolve) => {
if (!data.contracts || (data.contracts && Object.keys(data.contracts).length === 0)) {
return resolve({
contractMap: {},
contractMap: {},
contractsDetails: {},
target: source.target
})
@ -386,7 +388,7 @@ export const CompilerApiMixin = (Base) => class extends Base {
)
})
return resolve({
contractMap,
contractMap,
contractsDetails,
target: source.target
})

Loading…
Cancel
Save