Merge branch 'master' into fixrefresh

pull/1756/head
bunsenstraat 3 years ago committed by GitHub
commit bdfb9a1aa6
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 10
      apps/remix-ide-e2e/src/commands/journalChildIncludes.ts
  2. 23
      apps/remix-ide-e2e/src/tests/solidityUnittests.spec.ts
  3. 83
      apps/remix-ide-e2e/src/tests/terminal.test.ts
  4. 2
      apps/remix-ide-e2e/src/types/index.d.ts
  5. 13
      apps/remix-ide/src/app/editor/editor.js
  6. 5
      apps/remix-ide/src/app/files/fileProvider.js
  7. 50
      apps/remix-ide/src/app/panels/tab-proxy.js
  8. 22
      apps/remix-ide/src/app/panels/terminal.js
  9. 7
      apps/remix-ide/src/app/tabs/runTab/contractDropdown.js
  10. 30
      apps/remix-ide/src/app/tabs/test-tab.js
  11. 9
      apps/remix-ide/src/app/tabs/theme-module.js
  12. 2
      apps/remix-ide/src/assets/js/react-tabs.production.min.js
  13. 2
      apps/remix-ide/webpack.config.js
  14. 10
      apps/solidity-compiler/src/app/compiler-api.ts
  15. 2
      libs/remix-core-plugin/src/lib/compiler-content-imports.ts
  16. 15
      libs/remix-lib/src/execution/txRunnerVM.ts
  17. 4
      libs/remix-lib/src/web3Provider/web3VmProvider.ts
  18. 2
      libs/remix-tests/sol/tests_accounts.sol.ts
  19. 11
      libs/remix-tests/src/compiler.ts
  20. 10
      libs/remix-tests/src/deployer.ts
  21. 1
      libs/remix-tests/src/index.ts
  22. 4
      libs/remix-tests/src/runTestFiles.ts
  23. 40
      libs/remix-tests/src/runTestSources.ts
  24. 2
      libs/remix-tests/tests/testRunner.spec.ts
  25. 70
      libs/remix-ui/editor/src/lib/remix-ui-editor.tsx
  26. 1384
      libs/remix-ui/editor/src/lib/syntax.ts
  27. 12
      libs/remix-ui/tabs/src/lib/remix-ui-tabs.css
  28. 37
      libs/remix-ui/tabs/src/lib/remix-ui-tabs.tsx
  29. 2
      libs/remix-ui/terminal/src/lib/components/Context.tsx
  30. 2
      libs/remix-ui/terminal/src/lib/components/RenderKnownTransactions.tsx
  31. 286
      libs/remix-ui/terminal/src/lib/components/Table.tsx
  32. 19
      libs/remix-ui/terminal/src/lib/remix-ui-terminal.tsx
  33. 3
      libs/remix-ui/terminal/src/lib/types/terminalTypes.ts

@ -5,10 +5,10 @@ import EventEmitter from 'events'
Checks if any child elements of journal (console) contains a matching value.
*/
class JournalChildIncludes extends EventEmitter {
command (this: NightwatchBrowser, val: string): NightwatchBrowser {
command (this: NightwatchBrowser, val: string, opts = { shouldHaveOnlyOneOccurence: false }): NightwatchBrowser {
let isTextFound = false
const browser = this.api
let occurence = 0
this.api.elements('css selector', '*[data-id="terminalJournal"]', (res) => {
Array.isArray(res.value) && res.value.forEach(function (jsonWebElement) {
const jsonWebElementId = jsonWebElement.ELEMENT || jsonWebElement[Object.keys(jsonWebElement)[0]]
@ -16,12 +16,16 @@ class JournalChildIncludes extends EventEmitter {
browser.elementIdText(jsonWebElementId, (jsonElement) => {
const text = jsonElement.value
if (typeof text === 'string' && text.indexOf(val) !== -1) isTextFound = true
if (typeof text === 'string' && text.indexOf(val) !== -1) {
isTextFound = true
occurence++
}
})
})
})
browser.perform(() => {
browser.assert.ok(isTextFound, isTextFound ? `<*[data-id="terminalJournal"]> contains ${val}.` : `${val} not found in <*[data-id="terminalJournal"]> div:last-child>`)
if (opts.shouldHaveOnlyOneOccurence) browser.assert.ok(occurence === 1, `${occurence} occurence found of "${val}"`)
this.emit('complete')
})
return this

@ -13,7 +13,7 @@ module.exports = {
return sources
},
'Should launch solidity unit test plugin': function (browser: NightwatchBrowser) {
'Should launch solidity unit test plugin and create test files in FE': function (browser: NightwatchBrowser) {
browser.waitForElementPresent('*[data-id="verticalIconsKindfilePanel"]')
.clickLaunchIcon('filePanel')
.addFile('simple_storage.sol', sources[0]['simple_storage.sol'])
@ -23,6 +23,15 @@ module.exports = {
.click('*[data-id="verticalIconsKindsolidityUnitTesting"]')
.waitForElementPresent('*[data-id="sidePanelSwapitTitle"]')
.assert.containsText('*[data-id="sidePanelSwapitTitle"]', 'SOLIDITY UNIT TESTING')
.clickLaunchIcon('filePanel')
.waitForElementVisible('li[data-id="treeViewLitreeViewItem.deps/remix-tests/remix_tests.sol"]')
.waitForElementVisible('li[data-id="treeViewLitreeViewItem.deps/remix-tests/remix_accounts.sol"]')
.openFile('.deps/remix-tests/remix_tests.sol')
// remix_test.sol should be opened in editor
.getEditorValue((content) => browser.assert.ok(content.indexOf('library Assert {') !== -1))
.openFile('.deps/remix-tests/remix_accounts.sol')
// remix_accounts.sol should be opened in editor
.getEditorValue((content) => browser.assert.ok(content.indexOf('library TestsAccounts {') !== -1))
},
'Should generate test file': function (browser: NightwatchBrowser) {
@ -150,7 +159,7 @@ module.exports = {
.click('*[data-id="testTabGenerateTestFolder"]')
},
'Changing current path when workspace changed': function (browser: NightwatchBrowser) {
'Changing current path when workspace changed and checking test files creation': function (browser: NightwatchBrowser) {
browser
.waitForElementPresent('*[data-id="verticalIconsKindfilePanel"]')
.clickLaunchIcon('settings')
@ -168,6 +177,14 @@ module.exports = {
.waitForElementVisible('*[data-id="fileSystem-modal-footer-ok-react"]')
.execute(function () { (document.querySelector('[data-id="fileSystem-modal-footer-ok-react"]') as HTMLElement).click() })
.waitForElementPresent('*[data-id="workspacesSelect"] option[value="workspace_new"]')
.waitForElementVisible('li[data-id="treeViewLitreeViewItem.deps/remix-tests/remix_tests.sol"]')
.waitForElementVisible('li[data-id="treeViewLitreeViewItem.deps/remix-tests/remix_accounts.sol"]')
.openFile('.deps/remix-tests/remix_tests.sol')
// remix_test.sol should be opened in editor
.getEditorValue((content) => browser.assert.ok(content.indexOf('library Assert {') !== -1))
.openFile('.deps/remix-tests/remix_accounts.sol')
// remix_accounts.sol should be opened in editor
.getEditorValue((content) => browser.assert.ok(content.indexOf('library TestsAccounts {') !== -1))
// end of creating
.clickLaunchIcon('solidityUnitTesting')
.pause(2000)
@ -276,6 +293,8 @@ module.exports = {
.setValue('*[data-id="slider"]', new Array(1).fill(browser.Keys.RIGHT_ARROW))
.waitForElementContainsText('*[data-id="functionPanel"]', 'equal(a, b, message)', 60000)
.waitForElementContainsText('*[data-id="functionPanel"]', 'checkWinningProposalPassed()', 60000)
// remix_test.sol should be opened in editor
.getEditorValue((content) => browser.assert.ok(content.indexOf('library Assert {') !== -1))
.pause(1000)
.clickLaunchIcon('solidityUnitTesting')
.scrollAndClick('#Check_winning_proposal_again')

@ -128,6 +128,31 @@ module.exports = {
.waitForElementContainsText('*[data-id="terminalJournal"]', '0x5B38Da6a701c568545dCfcB03FcB875f56beddC4', 60000) // check that the script is logging the event
.waitForElementContainsText('*[data-id="terminalJournal"]', 'newOwner', 60000)
.waitForElementContainsText('*[data-id="terminalJournal"]', '0xd9145CCE52D386f254917e481eB44e9943F39138', 60000)
},
'Should print hardhat logs': function (browser: NightwatchBrowser) {
browser
.click('*[data-id="terminalClearConsole"]') // clear the terminal
.addFile('printHardhatlog.sol', { content: hardhatLog })
.clickLaunchIcon('solidity')
.waitForElementVisible('[for="autoCompile"]')
.click('[for="autoCompile"]')
.testContracts('printHardhatlog.sol', { content: hardhatLog }, ['OwnerTest'])
.clickLaunchIcon('udapp')
.click('*[data-id="deployAndRunClearInstances"]')
.selectContract('OwnerTest')
.createContract('')
.pause(1000)
.journalChildIncludes('constructor', { shouldHaveOnlyOneOccurence: true })
.pause(5000)
.click('*[data-id="terminalClearConsole"]') // clear the terminal
.clickInstance(0)
.clickFunction('changeOwner - transact (not payable)', { types: 'address newOwner', values: '0xd9145CCE52D386f254917e481eB44e9943F39138' })
.pause(1000)
.journalChildIncludes('inside changeOwner', { shouldHaveOnlyOneOccurence: true })
.clickFunction('getOwner - call')
.pause(1000)
.journalChildIncludes('inside getOwner', { shouldHaveOnlyOneOccurence: true })
.end()
}
}
@ -238,3 +263,61 @@ const deployWithEthersJs = `
console.log(e.message)
}
})()`
const hardhatLog = `
// SPDX-License-Identifier: GPL-3.0
pragma solidity >=0.7.0 <0.9.0;
import "hardhat/console.sol";
/**
* @title Owner
* @dev Set & change owner
*/
contract OwnerTest {
address private owner;
// event for EVM logging
event OwnerSet(address indexed oldOwner, address indexed newOwner);
// modifier to check if caller is owner
modifier isOwner() {
// If the first argument of 'require' evaluates to 'false', execution terminates and all
// changes to the state and to Ether balances are reverted.
// This used to consume all gas in old EVM versions, but not anymore.
// It is often a good idea to use 'require' to check if functions are called correctly.
// As a second argument, you can also provide an explanation about what went wrong.
require(msg.sender == owner, "Caller is not owner");
_;
}
/**
* @dev Set contract deployer as owner
*/
constructor() {
console.log("constructor");
owner = msg.sender; // 'msg.sender' is sender of current call, contract deployer for a constructor
emit OwnerSet(address(0), owner);
}
/**
* @dev Change owner
* @param newOwner address of new owner
*/
function changeOwner(address newOwner) public isOwner {
console.log("inside changeOwner");
emit OwnerSet(owner, newOwner);
owner = newOwner;
}
/**
* @dev Return owner address
* @return address of owner
*/
function getOwner() external view returns (address) {
console.log("inside getOwner");
return owner;
}
}`

@ -23,7 +23,7 @@ declare module 'nightwatch' {
journalLastChildIncludes(val: string): NightwatchBrowser,
executeScript(script: string): NightwatchBrowser,
clearEditableContent(cssSelector: string): NightwatchBrowser,
journalChildIncludes(val: string): NightwatchBrowser,
journalChildIncludes(val: string, opts = { shouldHaveOnlyOneOccurence: boolean }): NightwatchBrowser,
debugTransaction(index: number): NightwatchBrowser,
checkElementStyle(cssSelector: string, styleProperty: string, expectedResult: string): NightwatchBrowser,
openFile(name: string): NightwatchBrowser,

@ -97,7 +97,7 @@ class Editor extends Plugin {
this.emit(name, ...params) // plugin stack
}
onActivation () {
async onActivation () {
this.activated = true
this.on('sidePanel', 'focusChanged', (name) => {
this.keepDecorationsFor(name, 'sourceAnnotationsPerFile')
@ -108,14 +108,15 @@ class Editor extends Plugin {
})
const translateTheme = (theme) => this._themes[theme.name === 'Dark' ? 'remixDark' : theme.quality]
this.on('theme', 'themeChanged', (theme) => {
this.currentTheme = translateTheme(theme)
this.renderComponent()
})
this.call('theme', 'currentTheme', (theme) => {
this.on('theme', 'themeLoaded', (theme) => {
this.currentTheme = translateTheme(theme)
this.renderComponent()
})
try {
this.currentTheme = translateTheme(await this.call('theme', 'currentTheme'))
} catch (e) {
console.log('unable to select the theme ' + e.message)
}
this.renderComponent()
}

@ -17,8 +17,9 @@ class FileProvider {
}
addNormalizedName (path, url) {
this.providerExternalsStorage.set(this.type + '/' + path, url)
this.providerExternalsStorage.set(this.reverseKey + url, this.type + '/' + path)
if (this.type) path = this.type + '/' + path
this.providerExternalsStorage.set(path, url)
this.providerExternalsStorage.set(this.reverseKey + url, path)
}
removeNormalizedName (path) {

@ -191,13 +191,6 @@ export class TabProxy extends Plugin {
}
}
switchToActiveTab () {
const active = this.tabsApi.active()
if (active && this._handlers[active]) {
this.switchTab(active)
}
}
renameTab (oldName, newName) {
this.addTab(newName, '', () => {
this.fileManager.open(newName)
@ -236,19 +229,21 @@ export class TabProxy extends Plugin {
})
formatPath.shift()
if (formatPath.length > 0) {
const duplicateTabName = this.loadedTabs.find(({ title }) => title === formatPath.join('/')).name
const duplicateTabPath = duplicateTabName.split('/')
const duplicateTabFormatPath = [...duplicateTabPath].reverse()
const duplicateTabTitle = duplicateTabFormatPath.slice(0, titleLength).reverse().join('/')
this.loadedTabs.push({
id: duplicateTabName,
name: duplicateTabName,
title: duplicateTabTitle,
icon,
tooltip: duplicateTabName,
iconClass: helper.getPathIcon(duplicateTabName)
})
const index = this.loadedTabs.findIndex(({ title }) => title === formatPath.join('/'))
if (index > -1) {
const duplicateTabName = this.loadedTabs[index].name
const duplicateTabPath = duplicateTabName.split('/')
const duplicateTabFormatPath = [...duplicateTabPath].reverse()
const duplicateTabTitle = duplicateTabFormatPath.slice(0, titleLength).reverse().join('/')
this.loadedTabs[index] = {
id: duplicateTabName,
name: duplicateTabName,
title: duplicateTabTitle,
icon,
tooltip: duplicateTabName,
iconClass: helper.getPathIcon(duplicateTabName)
}
}
}
break
}
@ -271,10 +266,14 @@ export class TabProxy extends Plugin {
removeTab (name) {
delete this._handlers[name]
this.switchToActiveTab()
this.loadedTabs = this.loadedTabs.filter(tab => tab.name !== name)
let previous = null
this.loadedTabs = this.loadedTabs.filter((tab, index) => {
if (tab.name === name) previous = this.loadedTabs[index - 1]
return tab.name !== name
})
this.renderComponent()
this.updateImgStyles()
if (previous) this.switchTab(previous.name)
}
addHandler (type, fn) {
@ -309,8 +308,13 @@ export class TabProxy extends Plugin {
}
renderTabsbar () {
window.React = React
const script = document.createElement('script')
script.type = 'text/javascript'
script.src = 'assets/js/react-tabs.production.min.js'
document.head.appendChild(script)
script.addEventListener('load', () => this.renderComponent())
this.el = document.createElement('div')
this.renderComponent()
return this.el
}
}

@ -86,8 +86,6 @@ class Terminal extends Plugin {
this.call('menuicons', 'select', 'debugger')
this.call('debugger', 'debug', hash)
})
this.logHtmlResponse = []
this.logResponse = []
}
onActivation () {
@ -102,23 +100,11 @@ class Terminal extends Plugin {
}
logHtml (html) {
this.logHtmlResponse.push(html.innerText)
this.renderComponent()
this.resetLogHtml()
}
resetLogHtml () {
this.logHtmlResponse = []
this.terminalApi.logHtml(html)
}
log (message) {
this.logResponse.push(message)
this.renderComponent()
this.resetLog()
}
resetLog () {
this.logResponse = []
this.terminalApi.log(message)
}
render () {
@ -126,9 +112,11 @@ class Terminal extends Plugin {
}
renderComponent () {
const onReady = (api) => { this.terminalApi = api }
ReactDOM.render(
<RemixUiTerminal
plugin = {this}
plugin={this}
onReady={onReady}
/>,
this.element
)

@ -56,6 +56,7 @@ class ContractDropdownUI {
}
this.exEnvironment = this.blockchain.getProvider()
this.networkName = network.name
this.networkId = network.id
const savedConfig = window.localStorage.getItem(`ipfs/${this.exEnvironment}/${this.networkName}`)
@ -309,10 +310,10 @@ class ContractDropdownUI {
const data = self.runView.compilersArtefacts.getCompilerAbstract(contractObject.contract.file)
self.runView.compilersArtefacts.addResolvedContract(helper.addressToString(address), data)
if (self.ipfsCheckedState) {
_paq.push(['trackEvent', 'udapp', 'DeployAndPublish', this.networkName])
_paq.push(['trackEvent', 'udapp', 'DeployAndPublish', this.networkName, this.networkId])
publishToStorage('ipfs', self.runView.fileProvider, self.runView.fileManager, selectedContract)
} else {
_paq.push(['trackEvent', 'udapp', 'DeployOnly', this.networkName])
_paq.push(['trackEvent', 'udapp', 'DeployOnly', this.networkName, this.networkId])
}
}
@ -346,7 +347,7 @@ class ContractDropdownUI {
}
deployContract (selectedContract, args, contractMetadata, compilerContracts, callbacks, confirmationCb) {
_paq.push(['trackEvent', 'udapp', 'DeployContractTo', this.networkName])
_paq.push(['trackEvent', 'udapp', 'DeployContractTo', this.networkName, this.networkId])
const { statusCb } = callbacks
if (!contractMetadata || (contractMetadata && contractMetadata.autoDeployLib)) {
return this.blockchain.deployContractAndLibraries(selectedContract, args, contractMetadata, compilerContracts, callbacks, confirmationCb)

@ -7,7 +7,7 @@ var async = require('async')
var tooltip = require('../ui/tooltip')
var Renderer = require('../ui/renderer')
var css = require('./styles/test-tab-styles')
var { UnitTestRunner } = require('@remix-project/remix-tests')
var { UnitTestRunner, assertLibCode } = require('@remix-project/remix-tests')
const _paq = window._paq = window._paq || []
@ -16,7 +16,7 @@ const TestTabLogic = require('./testTab/testTab')
const profile = {
name: 'solidityUnitTesting',
displayName: 'Solidity unit testing',
methods: ['testFromPath', 'testFromSource', 'setTestFolderPath'],
methods: ['testFromPath', 'testFromSource', 'setTestFolderPath', 'getTestlibs'],
events: [],
icon: 'assets/img/unitTesting.webp',
description: 'Fast tool to generate unit tests for your contracts',
@ -42,7 +42,7 @@ module.exports = class TestTab extends ViewPlugin {
this.areTestsRunning = false
this.defaultPath = 'tests'
this.offsetToLineColumnConverter = offsetToLineColumnConverter
this.allFilesInvolved = []
this.allFilesInvolved = ['.deps/remix-tests/remix_tests.sol', '.deps/remix-tests/remix_accounts.sol']
this.isDebugging = false
this.currentErrors = []
@ -74,11 +74,25 @@ module.exports = class TestTab extends ViewPlugin {
}
}
getTestlibs () {
return { assertLibCode, accountsLibCode: this.testRunner.accountsLibCode }
}
async createTestLibs () {
const provider = await this.fileManager.currentFileProvider()
if (provider) {
provider.addExternal('.deps/remix-tests/remix_tests.sol', assertLibCode, 'remix_tests.sol')
provider.addExternal('.deps/remix-tests/remix_accounts.sol', this.testRunner.accountsLibCode, 'remix_accounts.sol')
}
}
async onActivation () {
const isSolidityActive = await this.call('manager', 'isActive', 'solidity')
if (!isSolidityActive) {
await this.call('manager', 'activatePlugin', 'solidity')
}
await this.testRunner.init()
await this.createTestLibs()
this.updateRunAction()
}
@ -110,9 +124,13 @@ module.exports = class TestTab extends ViewPlugin {
this.setCurrentPath(this.defaultPath)
})
this.on('filePanel', 'workspaceCreated', async () => {
this.createTestLibs()
})
this.testRunner.event.on('compilationFinished', (success, data, source) => {
if (success) {
this.allFilesInvolved = Object.keys(data.sources)
this.allFilesInvolved.push(...Object.keys(data.sources))
// forwarding the event to the appManager infra
// This is listened by compilerArtefacts to show data while debugging
this.emit('compilationFinished', source.target, source, 'soljson', data)
@ -526,8 +544,8 @@ module.exports = class TestTab extends ViewPlugin {
this.fileManager.readFile(testFilePath).then((content) => {
const runningTests = {}
runningTests[testFilePath] = { content }
const { currentVersion, evmVersion, optimize, runs } = this.compileTab.getCurrentCompilerConfig()
const currentCompilerUrl = urlFromVersion(currentVersion)
const { currentVersion, evmVersion, optimize, runs, isUrl } = this.compileTab.getCurrentCompilerConfig()
const currentCompilerUrl = isUrl ? currentVersion : urlFromVersion(currentVersion)
const compilerConfig = {
currentCompilerUrl,
evmVersion,

@ -79,10 +79,17 @@ export class ThemeModule extends Plugin {
throw new Error(`Theme ${themeName} doesn't exist`)
}
const next = themeName || this.active // Name
if (next === this.active) return
_paq.push(['trackEvent', 'themeModule', 'switchTo', next])
const nextTheme = this.themes[next] // Theme
if (!this.forced) this._deps.config.set('settings/theme', next)
document.getElementById('theme-link').setAttribute('href', nextTheme.url)
document.getElementById('theme-link').remove()
const theme = yo`<link rel="stylesheet" href="${nextTheme.url}" id="theme-link"/>`
theme.addEventListener('load', () => {
this.emit('themeLoaded', nextTheme)
this.events.emit('themeLoaded', nextTheme)
})
document.head.insertBefore(theme, document.head.firstChild)
document.documentElement.style.setProperty('--theme', nextTheme.quality)
if (themeName) this.active = themeName
// TODO: Only keep `this.emit` (issue#2210)

File diff suppressed because one or more lines are too long

@ -21,7 +21,7 @@ module.exports = config => {
mode: 'production',
devtool: 'source-map',
optimization: {
minimize: true,
minimize: false,
minimizer: [new TerserPlugin()]
}
}

@ -143,12 +143,17 @@ export const CompilerApiMixin = (Base) => class extends Base {
// This function is used for passing the compiler configuration to 'remix-tests'
getCurrentCompilerConfig () {
const compilerState = this.getCompilerState()
return {
let compilerDetails: any = {
currentVersion: compilerState.currentVersion,
evmVersion: compilerState.evmVersion,
optimize: compilerState.optimize,
runs: compilerState.runs
}
if (this.data.loading) {
compilerDetails.currentVersion = this.data.loadingUrl
compilerDetails.isUrl = true
}
return compilerDetails
}
/**
@ -193,8 +198,9 @@ export const CompilerApiMixin = (Base) => class extends Base {
if (this.onContentChanged) this.onContentChanged()
})
this.data.eventHandlers.onLoadingCompiler = () => {
this.data.eventHandlers.onLoadingCompiler = (url) => {
this.data.loading = true
this.data.loadingUrl = url
this.emit('statusChanged', { key: 'loading', title: 'loading compiler...', type: 'info' })
}
this.compiler.event.register('loadingCompiler', this.data.eventHandlers.onLoadingCompiler)

@ -1,7 +1,6 @@
'use strict'
import { Plugin } from '@remixproject/engine'
import { RemixURLResolver } from '@remix-project/remix-url-resolver'
const remixTests = require('@remix-project/remix-tests')
const profile = {
name: 'contentImport',
@ -117,7 +116,6 @@ export class CompilerImports extends Plugin {
* @returns {Promise} - string content
*/
async resolveAndSave (url, targetPath) {
if (url.indexOf('remix_tests.sol') !== -1) return remixTests.assertLibCode
try {
const provider = await this.call('fileManager', 'getProviderOf', url)
if (provider) {

@ -15,6 +15,7 @@ export class TxRunnerVM {
blocks
logsManager
commonContext
nextNonceForCall: number
getVMObject: () => any
constructor (vmaccounts, api, getVMObject) {
@ -31,6 +32,13 @@ export class TxRunnerVM {
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)
so we increment this value after each call.
For this to function we also need to skip nonce validation, in the vm: `{ skipNonce: true }`
*/
this.nextNonceForCall = 0
}
execute (args, confirmationCb, gasEstimationForceSend, promptCb, callback) {
@ -75,7 +83,7 @@ export class TxRunnerVM {
let tx
if (!EIP1559) {
tx = Transaction.fromTxData({
nonce: new BN(res.nonce),
nonce: useCall ? this.nextNonceForCall : new BN(res.nonce),
gasPrice: '0x1',
gasLimit: gasLimit,
to: to,
@ -84,7 +92,7 @@ export class TxRunnerVM {
}, { common: this.commonContext }).sign(account.privateKey)
} else {
tx = FeeMarketEIP1559Transaction.fromTxData({
nonce: new BN(res.nonce),
nonce: useCall ? this.nextNonceForCall : new BN(res.nonce),
maxPriorityFeePerGas: '0x01',
maxFeePerGas: '0x1',
gasLimit: gasLimit,
@ -93,6 +101,7 @@ export class TxRunnerVM {
data: Buffer.from(data.slice(2), 'hex')
}).sign(account.privateKey)
}
if (useCall) this.nextNonceForCall++
const coinbases = ['0x0e9281e9c6a0808672eaba6bd1220e144c9bb07a', '0x8945a1288dc78a6d8952a92c77aee6730b414778', '0x94d76e24f818426ae84aa404140e8d5f60e10e7e']
const difficulties = [new BN('69762765929000', 10), new BN('70762765929000', 10), new BN('71762765929000', 10)]
@ -127,7 +136,7 @@ export class TxRunnerVM {
}
runBlockInVm (tx, block, callback) {
this.getVMObject().vm.runBlock({ block: block, generate: true, skipBlockValidation: true, skipBalance: false }).then((results) => {
this.getVMObject().vm.runBlock({ block: block, generate: true, skipBlockValidation: true, skipBalance: false, skipNonce: true }).then((results) => {
const result = results.results[0]
if (result) {
const status = result.execResult.exceptionError ? 0 : 1

@ -304,7 +304,9 @@ export class Web3VmProvider {
nextKey: null
})
}
cb('unable to retrieve storage ' + txIndex + ' ' + address)
// Before https://github.com/ethereum/remix-project/pull/1703, it used to throw error as
// 'unable to retrieve storage ' + txIndex + ' ' + address
cb(null, { storage: {} })
}
getBlockNumber (cb) { cb(null, 'vm provider') }

@ -3,7 +3,7 @@ module.exports = `// SPDX-License-Identifier: GPL-3.0
pragma solidity >=0.4.22 <0.9.0;
library TestsAccounts {
function getAccount(uint index) public returns (address) {
function getAccount(uint index) pure public returns (address) {
>accounts<
return accounts[index];
}

@ -12,13 +12,13 @@ function regexIndexOf (inputString: string, regex: RegExp, startpos = 0) {
return (indexOf >= 0) ? (indexOf + (startpos)) : indexOf
}
function writeTestAccountsContract (accounts: string[]) {
export function writeTestAccountsContract (accounts: string[]) {
const testAccountContract = require('../sol/tests_accounts.sol')
let body = `address[${accounts.length}] memory accounts;`
if (!accounts.length) body += ';'
else {
accounts.map((address, index) => {
body += `\naccounts[${index}] = ${address};\n`
body += `\n\t\taccounts[${index}] = ${address};\n`
})
}
return testAccountContract.replace('>accounts<', body)
@ -172,14 +172,7 @@ export function compileFileOrFiles (filename: string, isDirectory: boolean, opts
*/
export function compileContractSources (sources: SrcIfc, compilerConfig: CompilerConfiguration, importFileCb: any, opts: any, cb): void {
let compiler
const accounts: string[] = opts.accounts || []
const filepath = opts.testFilePath || ''
// Iterate over sources keys. Inject test libraries. Inject test library import statements.
if (!('remix_tests.sol' in sources) && !('tests.sol' in sources)) {
sources['tests.sol'] = { content: require('../sol/tests.sol.js') }
sources['remix_tests.sol'] = { content: require('../sol/tests.sol.js') }
sources['remix_accounts.sol'] = { content: writeTestAccountsContract(accounts) }
}
const testFileImportRegEx = /^(import)\s['"](remix_tests.sol|tests.sol)['"];/gm
const includeTestLibs = '\nimport \'remix_tests.sol\';\n'

@ -11,18 +11,12 @@ import { compilationInterface } from './types'
* @param callback Callback
*/
export function deployAll (compileResult: compilationInterface, web3: Web3, withDoubleGas: boolean, deployCb, callback) {
export function deployAll (compileResult: compilationInterface, web3: Web3, testsAccounts, withDoubleGas: boolean, deployCb, callback) {
const compiledObject = {}
const contracts = {}
let accounts: string[] = []
const accounts: string[] = testsAccounts
async.waterfall([
function getAccountList (next) {
web3.eth.getAccounts((_err, _accounts) => {
accounts = _accounts
next()
})
},
function getContractData (next) {
for (const contractFile in compileResult) {
for (const contractName in compileResult[contractFile]) {

@ -3,3 +3,4 @@ export { UnitTestRunner } from './runTestSources'
export { runTest } from './testRunner'
export * from './types'
export const assertLibCode = require('../sol/tests.sol')
export { writeTestAccountsContract } from './compiler'

@ -61,13 +61,13 @@ export function runTestFiles (filepath: string, isDirectory: boolean, web3: Web3
for (const filename in asts) {
if (filename.endsWith('_test.sol')) { sourceASTs[filename] = asts[filename].ast }
}
deployAll(compilationResult, web3, false, null, (err, contracts) => {
deployAll(compilationResult, web3, accounts, false, null, (err, contracts) => {
if (err) {
// If contract deployment fails because of 'Out of Gas' error, try again with double gas
// This is temporary, should be removed when remix-tests will have a dedicated UI to
// accept deployment params from UI
if (err.message.includes('The contract code couldn\'t be stored, please check your gas limit')) {
deployAll(compilationResult, web3, true, null, (error, contracts) => {
deployAll(compilationResult, web3, accounts, true, null, (error, contracts) => {
if (error) next([{ message: 'contract deployment failed after trying twice: ' + error.message, severity: 'error' }]) // IDE expects errors in array
else next(null, compilationResult, contracts)
})

@ -1,9 +1,7 @@
import async, { ErrorCallback } from 'async'
import { compileContractSources } from './compiler'
import { compileContractSources, writeTestAccountsContract } from './compiler'
import { deployAll } from './deployer'
import { runTest } from './testRunner'
import Web3 from 'web3'
import { EventEmitter } from 'events'
import { Provider, extend } from '@remix-project/remix-simulator'
@ -15,13 +13,22 @@ require('colors')
export class UnitTestRunner {
event
accountsLibCode
testsAccounts: string[] | null
web3
constructor () {
this.event = new EventEmitter()
}
async createWeb3Provider () {
const web3 = new Web3()
async init (web3 = null, accounts = null) {
this.web3 = await this.createWeb3Provider(web3)
this.testsAccounts = accounts || await this.web3.eth.getAccounts()
this.accountsLibCode = writeTestAccountsContract(this.testsAccounts)
}
async createWeb3Provider (optWeb3) {
const web3 = optWeb3 || new Web3()
const provider: any = new Provider()
await provider.init()
web3.setProvider(provider)
@ -42,30 +49,23 @@ export class UnitTestRunner {
async runTestSources (contractSources: SrcIfc, compilerConfig: CompilerConfiguration, testCallback, resultCallback, deployCb:any, finalCallback: any, importFileCb, opts: Options) {
opts = opts || {}
const sourceASTs: any = {}
const web3 = opts.web3 || await this.createWeb3Provider()
let accounts: string[] | null = opts.accounts || null
if (opts.web3 || opts.accounts) this.init(opts.web3, opts.accounts)
async.waterfall([
function getAccountList (next) {
if (accounts) return next()
web3.eth.getAccounts((_err, _accounts) => {
accounts = _accounts
next()
})
},
(next) => {
compileContractSources(contractSources, compilerConfig, importFileCb, { accounts, testFilePath: opts.testFilePath, event: this.event }, next)
compileContractSources(contractSources, compilerConfig, importFileCb, { accounts: this.testsAccounts, testFilePath: opts.testFilePath, event: this.event }, next)
},
function deployAllContracts (compilationResult: compilationInterface, asts: ASTInterface, next) {
(compilationResult: compilationInterface, asts: ASTInterface, next) => {
for (const filename in asts) {
if (filename.endsWith('_test.sol')) { sourceASTs[filename] = asts[filename].ast }
}
deployAll(compilationResult, web3, false, deployCb, (err, contracts) => {
deployAll(compilationResult, this.web3, this.testsAccounts, false, deployCb, (err, contracts) => {
if (err) {
// If contract deployment fails because of 'Out of Gas' error, try again with double gas
// This is temporary, should be removed when remix-tests will have a dedicated UI to
// accept deployment params from UI
if (err.message.includes('The contract code couldn\'t be stored, please check your gas limit')) {
deployAll(compilationResult, web3, true, deployCb, (error, contracts) => {
deployAll(compilationResult, this.web3, this.testsAccounts, true, deployCb, (error, contracts) => {
if (error) next([{ message: 'contract deployment failed after trying twice: ' + error.message, severity: 'error' }]) // IDE expects errors in array
else next(null, compilationResult, contracts)
})
@ -88,7 +88,7 @@ export class UnitTestRunner {
}
next(null, contractsToTest, contractsToTestDetails, contracts)
},
function runTests (contractsToTest: string[], contractsToTestDetails: any[], contracts: any, next) {
(contractsToTest: string[], contractsToTestDetails: any[], contracts: any, next) => {
let totalPassing = 0
let totalFailing = 0
let totalTime = 0
@ -111,7 +111,7 @@ export class UnitTestRunner {
async.eachOfLimit(contractsToTest, 1, (contractName: string, index: string | number, cb: ErrorCallback) => {
const fileAST: AstNode = sourceASTs[contracts[contractName]['filename']]
runTest(contractName, contracts[contractName], contractsToTestDetails[index], fileAST, { accounts, web3 }, _testCallback, (err, result) => {
runTest(contractName, contracts[contractName], contractsToTestDetails[index], fileAST, { accounts: this.testsAccounts, web3: this.web3 }, _testCallback, (err, result) => {
if (err) {
return cb(err)
}

@ -67,7 +67,7 @@ async function compileAndDeploy(filename: string, callback: Function) {
}
try {
compilationData = compilationResult
deployAll(compilationResult, web3, false, null, next)
deployAll(compilationResult, web3, accounts, false, null, next)
} catch (e) {
throw e
}

@ -1,6 +1,7 @@
import React, { useState, useRef, useEffect, useReducer } from 'react' // eslint-disable-line
import Editor from '@monaco-editor/react'
import { reducerActions, reducerListener, initialState } from './actions/editor'
import { language } from './syntax'
import './remix-ui-editor.css'
@ -77,13 +78,39 @@ export const EditorUI = (props: EditorUIProps) => {
const [editorModelsState, dispatch] = useReducer(reducerActions, initialState)
const defineAndSetDarkTheme = (monaco) => {
// see https://microsoft.github.io/monaco-editor/playground.html#customizing-the-appearence-exposed-colors
const lightColor = window.getComputedStyle(document.documentElement).getPropertyValue('--light').trim()
const infoColor = window.getComputedStyle(document.documentElement).getPropertyValue('--info').trim()
const darkColor = window.getComputedStyle(document.documentElement).getPropertyValue('--dark').trim()
const grayColor = window.getComputedStyle(document.documentElement).getPropertyValue('--gray-dark').trim()
monaco.editor.defineTheme('remix-dark', {
base: 'vs-dark',
inherit: true, // can also be false to completely replace the builtin rules
rules: [
{ background: darkColor.replace('#', '') },
{ token: 'keyword.external', foreground: infoColor }
],
colors: {
'editor.background': darkColor,
'editorSuggestWidget.background': lightColor,
'editorSuggestWidget.selectedBackground': lightColor,
'editorSuggestWidget.highlightForeground': infoColor,
'editor.lineHighlightBorder': lightColor,
'editor.lineHighlightBackground': grayColor,
'editorGutter.background': lightColor
}
})
monacoRef.current.editor.setTheme('remix-dark')
}
useEffect(() => {
if (!monacoRef.current) return
monacoRef.current.editor.setTheme(props.theme)
if (props.theme === 'remix-dark') {
defineAndSetDarkTheme(monacoRef.current)
} else monacoRef.current.editor.setTheme(props.theme)
}, [props.theme])
if (monacoRef.current) monacoRef.current.editor.setTheme(props.theme)
const setAnnotationsbyFile = (uri) => {
if (props.sourceAnnotationsPerFile[uri]) {
const model = editorModelsState[uri]?.model
@ -137,8 +164,10 @@ export const EditorUI = (props: EditorUIProps) => {
useEffect(() => {
if (!editorRef.current) return
currentFileRef.current = props.currentFile
editorRef.current.setModel(editorModelsState[props.currentFile].model)
const file = editorModelsState[props.currentFile]
editorRef.current.setModel(file.model)
editorRef.current.updateOptions({ readOnly: editorModelsState[props.currentFile].readOnly })
if (file.language === 'sol') monacoRef.current.editor.setModelLanguage(file.model, 'remix-solidity')
setAnnotationsbyFile(props.currentFile)
setMarkerbyFile(props.currentFile)
}, [props.currentFile])
@ -207,7 +236,9 @@ export const EditorUI = (props: EditorUIProps) => {
function handleEditorDidMount (editor) {
editorRef.current = editor
monacoRef.current.editor.setTheme(props.theme)
if (props.theme === 'remix-dark') {
defineAndSetDarkTheme(monacoRef.current)
} else monacoRef.current.editor.setTheme(props.theme)
reducerListener(props.plugin, dispatch, monacoRef.current, editorRef.current, props.events)
props.events.onEditorMounted()
editor.onMouseUp((e) => {
@ -215,29 +246,20 @@ export const EditorUI = (props: EditorUIProps) => {
(window as any).addRemixBreakpoint(e.target.position)
}
})
editor.addCommand(monacoRef.current.KeyMod.CtrlCmd | monacoRef.current.KeyCode.US_EQUAL, () => {
editor.updateOptions({ fontSize: editor.getOption(42).fontSize + 1 })
})
editor.addCommand(monacoRef.current.KeyMod.CtrlCmd | monacoRef.current.KeyCode.US_MINUS, () => {
editor.updateOptions({ fontSize: editor.getOption(42).fontSize - 1 })
})
}
function handleEditorWillMount (monaco) {
monacoRef.current = monaco
// see https://microsoft.github.io/monaco-editor/playground.html#customizing-the-appearence-exposed-colors
const lightColor = window.getComputedStyle(document.documentElement).getPropertyValue('--light').trim()
const infoColor = window.getComputedStyle(document.documentElement).getPropertyValue('--info').trim()
const darkColor = window.getComputedStyle(document.documentElement).getPropertyValue('--dark').trim()
const grayColor = window.getComputedStyle(document.documentElement).getPropertyValue('--gray-dark').trim()
monaco.editor.defineTheme('remix-dark', {
base: 'vs-dark',
inherit: true, // can also be false to completely replace the builtin rules
rules: [{ background: darkColor.replace('#', '') }],
colors: {
'editor.background': darkColor,
'editorSuggestWidget.background': lightColor,
'editorSuggestWidget.selectedBackground': lightColor,
'editorSuggestWidget.highlightForeground': infoColor,
'editor.lineHighlightBorder': lightColor,
'editor.lineHighlightBackground': grayColor,
'editorGutter.background': lightColor
}
})
// Register a new language
monacoRef.current.languages.register({ id: 'remix-solidity' })
// Register a tokens provider for the language
monacoRef.current.languages.setMonarchTokensProvider('remix-solidity', language)
}
return (

File diff suppressed because it is too large Load Diff

@ -1,6 +1,6 @@
.remix-ui-tabs {
display: -webkit-box;
max-height: 42px
max-height: 2.15rem;
}
.remix-ui-tabs li {
display: inline-block;
@ -28,12 +28,11 @@
}
.close-tabs {
visibility: hidden;
padding-top: 4px;
font-size: medium;
}
.iconImage {
width: 16px;
height: 16px;
width: 1rem;
height: 1rem;
}
.active {
border: 1px solid transparent;
@ -44,11 +43,6 @@
overflow-x: auto;
overflow-y: hidden;
white-space: nowrap;
max-width: 1000px;
}
.left-icon {
width: 70px;
height: 49px;
}
/* Hide scrollbar for Chrome, Safari and Opera */

@ -1,6 +1,5 @@
import React, { useState, useRef, useEffect, useReducer } from 'react' // eslint-disable-line
import { Tab, Tabs, TabList, TabPanel } from 'react-tabs'
// import { Tab, Tabs, TabList, TabPanel } from 'react-tabs'
import './remix-ui-tabs.css'
/* eslint-disable-next-line */
@ -18,7 +17,10 @@ export interface TabsUIApi {
active: () => string
}
declare var ReactTabs: any
export const TabsUI = (props: TabsUIProps) => {
const { Tab, Tabs, TabList, TabPanel } = ReactTabs
const [selectedIndex, setSelectedIndex] = useState(-1)
const currentIndexRef = useRef(-1)
const tabsRef = useRef({})
@ -33,12 +35,12 @@ export const TabsUI = (props: TabsUIProps) => {
const renderTab = (tab, index) => {
const classNameImg = 'my-1 mr-1 text-dark ' + tab.iconClass
const classNameTab = 'nav-item nav-link tab' + (index === currentIndexRef.current ? ' active' : '')
const classNameTab = 'nav-item nav-link d-flex justify-content-center align-items-center px-2 py-1 tab' + (index === currentIndexRef.current ? ' active' : '')
return (
<div ref={el => { tabsRef.current[index] = el }} className={classNameTab} title={tab.tooltip}>
<div onClick={() => { props.onSelect(index); currentIndexRef.current = index; setSelectedIndex(index) }} ref={el => { tabsRef.current[index] = el }} className={classNameTab} title={tab.tooltip}>
{tab.icon ? (<img className="my-1 mr-1 iconImage" src={tab.icon} />) : (<i className={classNameImg}></i>)}
<span className="title-tabs">{tab.title}</span>
<span className="close-tabs" onClick={() => props.onClose(index)}>
<span className="close-tabs" onClick={(event) => { props.onClose(index); event.stopPropagation() }}>
<i className="text-dark fas fa-times"></i>
</span>
</div>
@ -62,16 +64,23 @@ export const TabsUI = (props: TabsUIProps) => {
}, [])
return (
<div className="remix-ui-tabs header nav nav-tabs">
<div className="d-flex flex-row justify-content-center align-items-center left-icon">
<span data-id="tabProxyZoomOut" className="btn btn-sm px-1 fas fa-search-minus text-dark" title="Zoom out" onClick={() => props.onZoomOut()}></span>
<span data-id="tabProxyZoomIn" className="btn btn-sm px-1 fas fa-search-plus text-dark" title="Zoom in" onClick={() => props.onZoomIn()}></span>
<i className="d-flex flex-row justify-content-center align-items-center far fa-sliders-v px-1" title="press F1 when focusing the editor to show advanced configuration settings"></i>
<div className="remix-ui-tabs d-flex justify-content-between border-0 header nav-tabs">
<div className="d-flex flex-row" style={ { maxWidth: 'fit-content', width: '97%' } }>
<div className="d-flex flex-row justify-content-center align-items-center m-1 mt-2">
<span data-id="tabProxyZoomOut" className="btn btn-sm px-2 fas fa-search-minus text-dark" title="Zoom out" onClick={() => props.onZoomOut()}></span>
<span data-id="tabProxyZoomIn" className="btn btn-sm px-2 fas fa-search-plus text-dark" title="Zoom in" onClick={() => props.onZoomIn()}></span>
</div>
<Tabs
className="tab-scroll"
selectedIndex={selectedIndex}
>
<TabList className="d-flex flex-row justify-content-center align-items-center">
{props.tabs.map((tab, i) => <Tab className="py-1" key={tab.name}>{renderTab(tab, i)}</Tab>)}
</TabList>
{props.tabs.map((tab) => <TabPanel key={tab.name} ></TabPanel>)}
</Tabs>
</div>
<Tabs className="tab-scroll" selectedIndex={selectedIndex} onSelect={index => { props.onSelect(index); currentIndexRef.current = index; setSelectedIndex(index) }} >
<TabList>{props.tabs.map((tab, i) => <Tab key={tab.name}>{renderTab(tab, i)}</Tab>)}</TabList>
{props.tabs.map((tab) => <TabPanel key={tab.name} ></TabPanel>)}
</Tabs>
<i className="mt-2 mr-2 fas fa-arrows-alt-h" title="Scroll to see all tabs"></i>
</div>
)
}

@ -12,7 +12,7 @@ const Context = ({ opts, blockchain }) => {
const val = data.value
let hash = data.hash ? helper.shortenHexData(data.hash) : ''
const input = data.input ? helper.shortenHexData(data.input) : ''
const logs = data.logs && data.logs.decoded && data.logs.decoded.length ? data.logs.decoded.length : 0
const logs = opts.logs && opts.logs.decoded && opts.logs.decoded.length ? opts.logs.decoded.length : 0
const block = data.receipt ? data.receipt.blockNumber : data.blockNumber || ''
const i = data.receipt ? data.transactionIndex : data.transactionIndex
const value = val ? typeConversion.toInt(val) : 0

@ -21,7 +21,7 @@ const RenderKnownTransactions = ({ tx, receipt, resolvedData, logs, index, plugi
const from = tx.from
const to = resolvedData.contractName + '.' + resolvedData.fn
const txType = 'knownTx'
const options = { from, to, tx }
const options = { from, to, tx, logs }
return (
<span id={`tx${tx.hash}`} key={index}>
<div className="log" onClick={(event) => txDetails(event, tx)}>

@ -36,125 +36,219 @@ const showTable = (opts, showTableHash) => {
}
const val = opts.val != null ? typeConversion.toInt(opts.val) : 0
return (
<table className={`txTable ${showTableHash.includes(opts.hash) ? 'active' : ''}`} id='txTable' data-id={`txLoggerTable${opts.hash}`}>
<table
className={`txTable ${showTableHash.includes(opts.hash) ? 'active' : ''}`}
id="txTable"
data-id={`txLoggerTable${opts.hash}`}
>
<tbody>
<tr className='tr'>
<td className='td' data-shared={`key_${opts.hash}`}>status</td>
<td className='td' data-id={`txLoggerTableStatus${opts.hash}`} data-shared={`pair_${opts.hash}`}>{`${opts.status} ${msg}`}</td>
</tr>
{opts.hash ? (<tr className='tr'>
<td className='td' data-shared={`key_${opts.hash}`}>transaction hash</td>
<td className='td' data-id={`txLoggerTableHash${opts.hash}`} data-shared={`pair_${opts.hash}`}>{opts.hash}
<CopyToClipboard content={opts.hash}/>
</td>
</tr>) : null }
{
opts.contractAddress ? (
<tr className='tr'>
<td className='td' data-shared={`key_${opts.hash}`}>contract address</td>
<td className='td' data-id={`txLoggerTableContractAddress${opts.hash}`} data-shared={`pair_${opts.hash}`}>{opts.contractAddress}
<CopyToClipboard content={opts.contractAddress}/>
</td>
</tr>
) : null
}
{
opts.from ? (
<tr className='tr'>
<td className='td tableTitle' data-shared={`key_${opts.hash}`}>from</td>
<td className='td' data-id={`txLoggerTableFrom${opts.hash}`} data-shared={`pair_${opts.hash}`}>{opts.from}
<CopyToClipboard content={opts.from}/>
</td>
</tr>
) : null
}
{
opts.to ? (
<tr className='tr'>
<td className='td' data-shared={`key_${opts.hash}`}>to</td>
<td className='td' data-id={`txLoggerTableTo${opts.hash}`} data-shared={`pair_${opts.hash}`}>{toHash}
<CopyToClipboard content={data.to ? data.to : toHash}/>
</td>
</tr>
) : null
}
{
opts.gas ? (
<tr className='tr'>
<td className='td' data-shared={`key_${opts.hash}`}>gas</td>
<td className='td' data-id={`txLoggerTableGas${opts.hash}`} data-shared={`pair_${opts.hash}`}>{opts.gas} gas
<CopyToClipboard content={opts.gas}/>
</td>
</tr>
) : null
}
{
opts.transactionCost ? (
<tr className='tr'>
<td className='td' data-shared={`key_${opts.hash}`}>transaction cost</td>
<td className='td' data-id={`txLoggerTableTransactionCost${opts.hash}`} data-shared={`pair_${opts.hash}`}>{opts.transactionCost} gas {callWarning}
<CopyToClipboard content={opts.transactionCost}/>
</td>
</tr>
) : null
}
{
opts.executionCost ? (
<tr className='tr'>
<td className='td' data-shared={`key_${opts.hash}`}>execution cost</td>
<td className='td' data-id={`txLoggerTableExecutionHash${opts.hash}`} data-shared={`pair_${opts.hash}`}>{opts.executionCost} gas {callWarning}
<CopyToClipboard content={opts.executionCost}/>
</td>
</tr>
) : null
}
{opts.status !== undefined ? (
<tr className="tr">
<td className="td" data-shared={`key_${opts.hash}`}>
status
</td>
<td
className="td"
data-id={`txLoggerTableStatus${opts.hash}`}
data-shared={`pair_${opts.hash}`}
>{`${opts.status} ${msg}`}</td>
</tr>
) : null}
{opts.hash && !opts.isCall ? (
<tr className="tr">
<td className="td" data-shared={`key_${opts.hash}`}>
transaction hash
</td>
<td
className="td"
data-id={`txLoggerTableHash${opts.hash}`}
data-shared={`pair_${opts.hash}`}
>
{opts.hash}
<CopyToClipboard content={opts.hash} />
</td>
</tr>
) : null}
{opts.contractAddress ? (
<tr className="tr">
<td className="td" data-shared={`key_${opts.hash}`}>
contract address
</td>
<td
className="td"
data-id={`txLoggerTableContractAddress${opts.hash}`}
data-shared={`pair_${opts.hash}`}
>
{opts.contractAddress}
<CopyToClipboard content={opts.contractAddress} />
</td>
</tr>
) : null}
{opts.from ? (
<tr className="tr">
<td className="td tableTitle" data-shared={`key_${opts.hash}`}>
from
</td>
<td
className="td"
data-id={`txLoggerTableFrom${opts.hash}`}
data-shared={`pair_${opts.hash}`}
>
{opts.from}
<CopyToClipboard content={opts.from} />
</td>
</tr>
) : null}
{opts.to ? (
<tr className="tr">
<td className="td" data-shared={`key_${opts.hash}`}>
to
</td>
<td
className="td"
data-id={`txLoggerTableTo${opts.hash}`}
data-shared={`pair_${opts.hash}`}
>
{toHash}
<CopyToClipboard content={data.to ? data.to : toHash} />
</td>
</tr>
) : null}
{opts.gas ? (
<tr className="tr">
<td className="td" data-shared={`key_${opts.hash}`}>
gas
</td>
<td
className="td"
data-id={`txLoggerTableGas${opts.hash}`}
data-shared={`pair_${opts.hash}`}
>
{opts.gas} gas
<CopyToClipboard content={opts.gas} />
</td>
</tr>
) : null}
{opts.transactionCost ? (
<tr className="tr">
<td className="td" data-shared={`key_${opts.hash}`}>
transaction cost
</td>
<td
className="td"
data-id={`txLoggerTableTransactionCost${opts.hash}`}
data-shared={`pair_${opts.hash}`}
>
{opts.transactionCost} gas {callWarning}
<CopyToClipboard content={opts.transactionCost} />
</td>
</tr>
) : null}
{opts.executionCost ? (
<tr className="tr">
<td className="td" data-shared={`key_${opts.hash}`}>
execution cost
</td>
<td
className="td"
data-id={`txLoggerTableExecutionHash${opts.hash}`}
data-shared={`pair_${opts.hash}`}
>
{opts.executionCost} gas {callWarning}
<CopyToClipboard content={opts.executionCost} />
</td>
</tr>
) : null}
{opts.hash ? (
<tr className='tr'>
<td className='td' data-shared={`key_${opts.hash}`}>hash</td>
<td className='td' data-id={`txLoggerTableHash${opts.hash}`} data-shared={`pair_${opts.hash}`}>{opts.hash}
<CopyToClipboard content={opts.hash}/>
<tr className="tr">
<td className="td" data-shared={`key_${opts.hash}`}>
hash
</td>
<td
className="td"
data-id={`txLoggerTableHash${opts.hash}`}
data-shared={`pair_${opts.hash}`}
>
{opts.hash}
<CopyToClipboard content={opts.hash} />
</td>
</tr>
) : null}
{opts.input ? (
<tr className='tr'>
<td className='td' data-shared={`key_${opts.hash}`}>input</td>
<td className='td' data-id={`txLoggerTableHash${opts.hash}`} data-shared={`pair_${opts.hash}`}>{helper.shortenHexData(opts.input)}
<CopyToClipboard content={opts.input}/>
<tr className="tr">
<td className="td" data-shared={`key_${opts.hash}`}>
input
</td>
<td
className="td"
data-id={`txLoggerTableHash${opts.hash}`}
data-shared={`pair_${opts.hash}`}
>
{helper.shortenHexData(opts.input)}
<CopyToClipboard content={opts.input} />
</td>
</tr>
) : null}
{opts['decoded input'] ? (
<tr className='tr'>
<td className='td' data-shared={`key_${opts.hash}`}>decoded input</td>
<td className='td' data-id={`txLoggerTableHash${opts.hash}`} data-shared={`pair_${opts.hash}`}>{opts['decoded input'].trim()}
<CopyToClipboard content={opts['decoded input']}/>
<tr className="tr">
<td className="td" data-shared={`key_${opts.hash}`}>
decoded input
</td>
<td
className="td"
data-id={`txLoggerTableHash${opts.hash}`}
data-shared={`pair_${opts.hash}`}
>
{opts['decoded input'].trim()}
<CopyToClipboard content={opts['decoded input']} />
</td>
</tr>
) : null}
{opts['decoded output'] ? (
<tr className='tr'>
<td className='td' data-shared={`key_${opts.hash}`}>decoded output</td>
<td className='td' data-id={`txLoggerTableHash${opts.hash}`} data-shared={`pair_${opts.hash}`}>{opts['decoded output']}
<CopyToClipboard content={opts['decoded output']}/>
<tr className="tr">
<td className="td" data-shared={`key_${opts.hash}`}>
decoded output
</td>
<td
className="td"
data-id={`txLoggerTableHash${opts.hash}`}
data-shared={`pair_${opts.hash}`}
>
{opts['decoded output']}
<CopyToClipboard content={opts['decoded output']} />
</td>
</tr>
) : null}
{opts.logs ? (
<tr className='tr'>
<td className='td' data-shared={`key_${opts.hash}`}>logs</td>
<td className='td' data-id={`txLoggerTableHash${opts.hash}`} data-shared={`pair_${opts.hash}`}>
<tr className="tr">
<td className="td" data-shared={`key_${opts.hash}`}>
logs
</td>
<td
className="td"
data-id={`txLoggerTableHash${opts.hash}`}
data-shared={`pair_${opts.hash}`}
>
{JSON.stringify(stringified, null, '\t')}
<CopyToClipboard content={JSON.stringify(stringified, null, '\t')}/>
<CopyToClipboard content={JSON.stringify(opts.logs.raw || '0')}/>
<CopyToClipboard
content={JSON.stringify(stringified, null, '\t')}
/>
<CopyToClipboard content={JSON.stringify(opts.logs.raw || '0')} />
</td>
</tr>
) : null}
{opts.val ? (
<tr className='tr'>
<td className='td' data-shared={`key_${opts.hash}`}>val</td>
<td className='td' data-id={`txLoggerTableHash${opts.hash}`} data-shared={`pair_${opts.hash}`}>{val} wei
<CopyToClipboard content={`${val} wei`}/>
<tr className="tr">
<td className="td" data-shared={`key_${opts.hash}`}>
val
</td>
<td
className="td"
data-id={`txLoggerTableHash${opts.hash}`}
data-shared={`pair_${opts.hash}`}
>
{val} wei
<CopyToClipboard content={`${val} wei`} />
</td>
</tr>
) : null}

@ -24,7 +24,7 @@ export interface ClipboardEvent<T = Element> extends SyntheticEvent<T, any> {
}
export const RemixUiTerminal = (props: RemixUiTerminalProps) => {
const { call, _deps, on, config, event, gistHandler, logHtmlResponse, logResponse, version } = props.plugin
const { call, _deps, on, config, event, gistHandler, version } = props.plugin
const [toggleDownUp, setToggleDownUp] = useState('fa-angle-double-down')
const [_cmdIndex, setCmdIndex] = useState(-1)
const [_cmdTemp, setCmdTemp] = useState('')
@ -84,12 +84,15 @@ export const RemixUiTerminal = (props: RemixUiTerminalProps) => {
}
useEffect(() => {
scriptRunnerDispatch({ type: 'html', payload: { message: logHtmlResponse } })
}, [logHtmlResponse])
useEffect(() => {
scriptRunnerDispatch({ type: 'log', payload: { message: logResponse } })
}, [logResponse])
props.onReady({
logHtml: (html) => {
scriptRunnerDispatch({ type: 'html', payload: { message: [html.innerText] } })
},
log: (message) => {
scriptRunnerDispatch({ type: 'log', payload: { message: [message] } })
}
})
}, [])
// events
useEffect(() => {
@ -115,7 +118,7 @@ export const RemixUiTerminal = (props: RemixUiTerminalProps) => {
useEffect(() => {
scrollToBottom()
}, [newstate.journalBlocks.length, logHtmlResponse.length, toaster])
}, [newstate.journalBlocks.length, toaster])
function execute (file, cb) {
function _execute (content, cb) {

@ -24,5 +24,6 @@ export const LISTEN_ON_NETWORK = 'listenOnNetWork'
export const CMD_HISTORY = 'cmdHistory'
export interface RemixUiTerminalProps {
plugin: any
plugin: any,
onReady: (api: any) => void
}

Loading…
Cancel
Save