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

pull/2774/head
bunsenstraat 2 years ago
commit 57ca08ecff
  1. 1
      apps/remix-ide-e2e/src/tests/debugger.test.ts
  2. 12
      apps/remix-ide-e2e/src/tests/gist.test.ts
  3. 17
      apps/remix-ide-e2e/src/tests/vyper_api.ts
  4. 6
      apps/remix-ide/src/app/tabs/debugger-tab.js
  5. 3
      apps/remix-ide/src/app/tabs/styles/debugger-tab-styles.js
  6. 1
      apps/remix-ide/src/assets/css/themes/bootstrap-cerulean.min.css
  7. 4
      apps/remix-ide/src/assets/css/themes/bootstrap-cyborg.min.css
  8. 4
      apps/solidity-compiler/src/app/compiler-api.ts
  9. 7
      apps/vyper/src/app/app.tsx
  10. 51
      apps/vyper/src/app/components/CompilerButton.tsx
  11. 1
      apps/vyper/src/app/components/LocalUrl.tsx
  12. 17
      apps/vyper/src/app/components/VyperResult.tsx
  13. 161
      apps/vyper/src/app/examples/ballot.tsx
  14. 4
      apps/vyper/src/app/utils/compiler.tsx
  15. 53
      apps/vyper/src/app/utils/remix-client.tsx
  16. 3
      libs/remix-solidity/src/compiler/compiler-worker.ts
  17. 14
      libs/remix-solidity/src/compiler/compiler.ts
  18. 2
      libs/remix-solidity/src/compiler/types.ts
  19. 4
      libs/remix-tests/src/compiler.ts
  20. 4
      libs/remix-ui/debugger-ui/src/lib/debugger-ui.css
  21. 84
      libs/remix-ui/debugger-ui/src/lib/debugger-ui.tsx
  22. 2
      libs/remix-ui/debugger-ui/src/lib/vm-debugger/vm-debugger-head.tsx
  23. 5
      libs/remix-ui/editor/src/lib/remix-ui-editor.tsx
  24. 1
      libs/remix-ui/helper/src/index.ts
  25. 15
      libs/remix-ui/helper/src/lib/bleach.ts
  26. 1
      libs/remix-ui/modal-dialog/src/lib/remix-ui-modal-dialog.css
  27. 4
      libs/remix-ui/panel/src/lib/plugins/panel-header.tsx
  28. 4
      libs/remix-ui/solidity-compiler/src/lib/actions/compiler.ts
  29. 22
      libs/remix-ui/solidity-compiler/src/lib/compiler-container.tsx
  30. 2
      libs/remix-ui/solidity-unit-testing/src/lib/solidity-unit-testing.tsx
  31. 4
      libs/remix-ui/static-analyser/src/lib/remix-ui-static-analyser.tsx
  32. 8
      libs/remix-ui/vertical-icons-panel/src/lib/reducers/iconBadgeReducer.ts

@ -205,6 +205,7 @@ module.exports = {
.clickLaunchIcon('debugger')
.waitForElementVisible('*[data-id="slider"]')
.goToVMTraceStep(154)
.scrollInto('*[data-id="stepdetail"]')
.waitForElementContainsText('*[data-id="stepdetail"]', 'vm trace step:\n154', 60000)
},

@ -9,10 +9,11 @@ const testData = {
// 99266d6da54cc12f37f11586e8171546c7700d67
module.exports = {
'@disabled': true,
before: function (browser: NightwatchBrowser, done: VoidFunction) {
init(browser, done)
},
UploadToGists: function (browser: NightwatchBrowser) {
'UploadToGists #group1': function (browser: NightwatchBrowser) {
/*
- set the access token
- publish to gist
@ -68,7 +69,7 @@ module.exports = {
*/
},
'Load Gist Modal': function (browser: NightwatchBrowser) {
'Load Gist Modal #group1': function (browser: NightwatchBrowser) {
browser.clickLaunchIcon('home')
.waitForElementVisible('*[data-id="remixIdeIconPanel"]', 10000)
.clickLaunchIcon('filePanel')
@ -84,7 +85,7 @@ module.exports = {
.modalFooterCancelClick('gisthandler')
},
'Display Error Message For Invalid Gist ID': function (browser: NightwatchBrowser) {
'Display Error Message For Invalid Gist ID #group1': function (browser: NightwatchBrowser) {
browser
.pause(1000)
.waitForElementVisible('*[data-id="remixIdeIconPanel"]', 10000)
@ -101,7 +102,7 @@ module.exports = {
.modalFooterOKClick('gisthandler')
},
'Display Error Message For Missing Gist Token When Publishing': function (browser: NightwatchBrowser) {
'Display Error Message For Missing Gist Token When Publishing #group1': function (browser: NightwatchBrowser) {
browser
.pause(1000)
.waitForElementVisible('*[data-id="remixIdeIconPanel"]', 10000)
@ -125,7 +126,7 @@ module.exports = {
.click('[data-id="fileSystemModalDialogModalFooter-react"] .modal-ok')
},
'Import From Gist For Valid Gist ID': function (browser: NightwatchBrowser) {
'Import From Gist For Valid Gist ID #group2': function (browser: NightwatchBrowser) {
browser
.waitForElementVisible('*[data-id="remixIdeIconPanel"]', 10000)
.clickLaunchIcon('settings')
@ -140,6 +141,7 @@ module.exports = {
})
.setValue('*[data-id="gisthandlerModalDialogModalBody-react"] input[data-id="modalDialogCustomPromp"]', testData.validGistId)
.modalFooterOKClick('gisthandler')
.pause(10000)
.openFile(`gist-${testData.validGistId}/README.txt`)
.waitForElementVisible(`div[title='default_workspace/gist-${testData.validGistId}/README.txt']`)
.assert.containsText(`div[title='default_workspace/gist-${testData.validGistId}/README.txt'] > span`, 'README.txt')

@ -21,25 +21,30 @@ module.exports = {
.frame(0)
},
'Should add the Ballot.vy #group1': function (browser: NightwatchBrowser) {
browser.click('button[data-id="add-ballot"]')
'Should clone the Vyper repo #group1': function (browser: NightwatchBrowser) {
browser.click('button[data-id="add-repository"]')
.frameParent()
.openFile('ballot.vy')
.waitForElementContainsText('*[data-shared="tooltipPopup"]', 'Vyper repository cloned', 30000)
.openFile('examples')
.openFile('examples/auctions')
.openFile('examples/auctions/blind_auction.vy')
},
'Compile ballot.vy should error #group1': function (browser: NightwatchBrowser) {
'Compile blind_auction should success #group1': function (browser: NightwatchBrowser) {
browser.clickLaunchIcon('vyper')
// @ts-ignore
.frame(0)
.click('[data-id="remote-compiler"]')
.click('[data-id="compile"]')
.assert.containsText('[data-id="error-message"]', 'unexpected indent')
.waitForElementVisible('[data-id="copy-abi"]')
},
'Compile test contract should success #group1': function (browser: NightwatchBrowser) {
'Compile test contract and deploy to remix VM #group1': function (browser: NightwatchBrowser) {
let contractAddress
browser
.frameParent()
.clickLaunchIcon('filePanel')
.switchWorkspace('default_workspace')
.addFile('test.vy', { content: testContract })
.clickLaunchIcon('vyper')
// @ts-ignore

@ -3,7 +3,7 @@ import { DebuggerApiMixin } from '@remixproject/debugger-plugin' // eslint-disab
import { ViewPlugin } from '@remixproject/engine-web'
import * as packageJson from '../../../../../package.json'
import React from 'react' // eslint-disable-line
import * as remixBleach from '../../lib/remixBleach'
import { bleach } from '@remix-ui/helper'
import { compilationFinishedToastMsg, compilingToastMsg, localCompilationToastMsg, notFoundToastMsg, sourceVerificationNotAvailableToastMsg } from '@remix-ui/helper'
const css = require('./styles/debugger-tab-styles')
@ -51,7 +51,7 @@ export class DebuggerTab extends DebuggerApiMixin(ViewPlugin) {
this.on('fetchAndCompile', 'sourceVerificationNotAvailable', () => {
this.call('notification', 'toast', sourceVerificationNotAvailableToastMsg())
})
return <div className={css.debuggerTabView} id='debugView'><DebuggerUI debuggerAPI={this} /></div>
return <div className="overflow-hidden px-1" id='debugView'><DebuggerUI debuggerAPI={this} /></div>
}
showMessage (title, message) {
@ -59,7 +59,7 @@ export class DebuggerTab extends DebuggerApiMixin(ViewPlugin) {
this.call('notification', 'alert', {
id: 'debuggerTabShowMessage',
title,
message: remixBleach.sanitize(message)
message: bleach.sanitize(message)
})
} catch (e) {
console.log(e)

@ -1,9 +1,6 @@
var csjs = require('csjs-inject')
const css = csjs`
.debuggerTabView {
padding: 2%;
}
.debugger {
margin-bottom: 1%;
}

@ -34,6 +34,7 @@
--dark:#343a40;
--body-bg: #fff;
--text-bg-mark: #fcf8e3;
--custom-select: #fff;
--breakpoint-xs:0;
--breakpoint-sm:576px;
--breakpoint-md:768px;

@ -9,8 +9,7 @@
* Copyright 2011-2020 The Bootstrap Authors
* Copyright 2011-2020 Twitter, Inc.
* Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)
*/@import url(https://fonts.googleapis.com/css2?family=Roboto:wght@400;
700&display=swap);
*/@import url(https://fonts.googleapis.com/css2?family=Roboto:wght@400;700&display=swap);
:root {
--blue:#2a9fd6;
--indigo:#6610f2;
@ -36,6 +35,7 @@
--dark:#adafae;
--body-bg: #060606;
--text-bg-mark: #fcf8e3;
--custom-select: #fff;
--breakpoint-xs:0;
--breakpoint-sm:576px;
--breakpoint-md:768px;

@ -223,10 +223,10 @@ export const CompilerApiMixin = (Base) => class extends Base {
}
this.compiler.event.register('loadingCompiler', this.data.eventHandlers.onLoadingCompiler)
this.data.eventHandlers.onCompilerLoaded = (version) => {
this.data.eventHandlers.onCompilerLoaded = (version, license) => {
this.data.loading = false
this.statusChanged({ key: 'none' })
this.emit('compilerLoaded', version)
this.emit('compilerLoaded', version, license)
}
this.compiler.event.register('compilerLoaded', this.data.eventHandlers.onCompilerLoaded)

@ -39,11 +39,14 @@ const App: React.FC = () => {
try {
await remixClient.loaded()
remixClient.onFileChange(name => setContract(name))
const name = await remixClient.getContractName()
setContract(name)
remixClient.onNoFileSelected(() => setContract(''))
} catch (err) {
console.log(err)
}
try {
const name = await remixClient.getContractName() // throw if no file are selected
setContract(name)
} catch (e) {}
}
start()
}, [])

@ -28,22 +28,60 @@ function CompilerButton({ contract, setOutput, compilerUrl }: Props) {
/** Compile a Contract */
async function compileContract() {
try {
const _contract = await remixClient.getContract()
await remixClient.discardHighlight()
let _contract: any
try {
_contract = await remixClient.getContract()
} catch (e: any) {
setOutput('', { status: 'failed', message: e.message})
return
}
remixClient.changeStatus({
key: 'loading',
type: 'info',
title: 'Compiling'
})
const output = await compile(compilerUrl, _contract)
let output
try {
output = await compile(compilerUrl, _contract)
} catch (e: any) {
setOutput(_contract.name, { status: 'failed', message: e.message})
return
}
setOutput(_contract.name, output)
// ERROR
if (isCompilationError(output)) {
const line = output.line
const lineColumnPos = {
start: { line: line - 1 },
end: { line: line - 1 }
if (line) {
const lineColumnPos = {
start: { line: line - 1, column: 10 },
end: { line: line - 1, column: 10 }
}
remixClient.highlight(lineColumnPos as any, _contract.name, '#e0b4b4')
} else {
const regex = output.message.match(/line ((\d+):(\d+))+/g)
const errors = output.message.split(/line ((\d+):(\d+))+/g) // extract error message
if (regex) {
let errorIndex = 0
regex.map((errorLocation) => {
const location = errorLocation.replace('line ', '').split(':')
let message = errors[errorIndex]
errorIndex = errorIndex + 4
if (message && message.split('\n\n').length > 0) {
try {
message = message.split('\n\n')[1]
} catch (e) {}
}
if (location.length > 0) {
const lineColumnPos = {
start: { line: parseInt(location[0]) - 1, column: 10 },
end: { line: parseInt(location[0]) - 1, column: 10 }
}
remixClient.highlight(lineColumnPos as any, _contract.name, message)
}
})
}
}
remixClient.highlight(lineColumnPos as any, _contract.name, '#e0b4b4')
throw new Error(output.message)
}
// SUCCESS
@ -61,7 +99,6 @@ function CompilerButton({ contract, setOutput, compilerUrl }: Props) {
type: 'error',
title: err.message
})
console.error(err)
}
}

@ -26,7 +26,6 @@ function LocalUrlInput({ url, setUrl, environment }: Props) {
type="email"
placeholder="eg http://localhost:8000/compile" />
<Form.Text className="text-muted">
The url to your local compiler
</Form.Text>
</Form.Group>
</Form>

@ -7,7 +7,6 @@ import {
} from '../utils';
import Tabs from 'react-bootstrap/Tabs'
import Tab from 'react-bootstrap/Tab'
import { Ballot } from '../examples/ballot';
import Button from 'react-bootstrap/Button';
import JSONTree from 'react-json-view'
import { CopyToClipboard } from '@remix-ui/clipboard'
@ -17,14 +16,20 @@ interface VyperResultProps {
output?: VyperCompilationOutput;
}
export type ExampleContract = {
name: string,
address: string
}
function VyperResult({ output }: VyperResultProps) {
const [ active, setActive ] = useState<keyof VyperCompilationResult>('abi');
const [ active, setActive ] = useState<keyof VyperCompilationResult>('abi')
if (!output) return (
<div id="result">
<p>No contract compiled yet.</p>
<Button data-id="add-ballot" variant="info" onClick={() => remixClient.loadContract(Ballot)}>
Create Ballot.vy example
<Button data-id="add-repository" variant="info" onClick={() => remixClient.cloneVyperRepo()}>
Clone Vyper repository and play with the contract examples
</Button>
</div>
)
@ -33,7 +38,7 @@ function VyperResult({ output }: VyperResultProps) {
return (
<div id="result" className="error">
<i className="fas fa-exclamation-circle text-danger"></i>
<p data-id="error-message" className="alert alert-danger">{output.message}</p>
<pre data-id="error-message" className="alert alert-danger">{output.message}</pre>
</div>)
}
@ -41,7 +46,7 @@ function VyperResult({ output }: VyperResultProps) {
<Tabs id="result" activeKey={active} onSelect={(key: any) => setActive(key)}>
<Tab eventKey="abi" title="ABI">
<CopyToClipboard getContent={() => JSON.stringify(output.abi)}>
<Button variant="info" className="copy">Copy ABI</Button>
<Button variant="info" className="copy" data-id="copy-abi">Copy ABI</Button>
</CopyToClipboard>
<JSONTree src={output.abi} />
</Tab>

@ -1,161 +0,0 @@
export const Ballot = {
name: 'browser/ballot.vy',
content: `# Voting with delegation.
# Information about voters
struct Voter:
# weight is accumulated by delegation
weight: int128
# if true, that person already voted (which includes voting by delegating)
voted: bool
# person delegated to
delegate: address
# index of the voted proposal, which is not meaningful unless 'voted' is True.
vote: int128
# Users can create proposals
struct Proposal:
# short name (up to 32 bytes)
name: bytes32
# number of accumulated votes
voteCount: int128
voters: public(map(address, Voter))
proposals: public(map(int128, Proposal))
voterCount: public(int128)
chairperson: public(address)
int128Proposals: public(int128)
@public
@constant
def delegated(addr: address) -> bool:
return self.voters[addr].delegate != ZERO_ADDRESS
@public
@constant
def directlyVoted(addr: address) -> bool:
return self.voters[addr].voted and (self.voters[addr].delegate == ZERO_ADDRESS)
# Setup global variables
@public
def __init__(_proposalNames: bytes32[2]):
self.chairperson = msg.sender
self.voterCount = 0
for i in range(2):
self.proposals[i] = Proposal({
name: _proposalNames[i],
voteCount: 0
})
self.int128Proposals += 1
# Give a 'voter' the right to vote on this ballot.
# This may only be called by the 'chairperson'.
@public
def giveRightToVote(voter: address):
# Throws if the sender is not the chairperson.
assert msg.sender == self.chairperson
# Throws if the voter has already voted.
assert not self.voters[voter].voted
# Throws if the voter's voting weight isn't 0.
assert self.voters[voter].weight == 0
self.voters[voter].weight = 1
self.voterCount += 1
# Used by 'delegate' below, and can be called by anyone.
@public
def forwardWeight(delegate_with_weight_to_forward: address):
assert self.delegated(delegate_with_weight_to_forward)
# Throw if there is nothing to do:
assert self.voters[delegate_with_weight_to_forward].weight > 0
target: address = self.voters[delegate_with_weight_to_forward].delegate
for i in range(4):
if self.delegated(target):
target = self.voters[target].delegate
# The following effectively detects cycles of length <= 5,
# in which the delegation is given back to the delegator.
# This could be done for any int128ber of loops,
# or even infinitely with a while loop.
# However, cycles aren't actually problematic for correctness;
# they just result in spoiled votes.
# So, in the production version, this should instead be
# the responsibility of the contract's client, and this
# check should be removed.
assert target != delegate_with_weight_to_forward
else:
# Weight will be moved to someone who directly voted or
# hasn't voted.
break
weight_to_forward: int128 = self.voters[delegate_with_weight_to_forward].weight
self.voters[delegate_with_weight_to_forward].weight = 0
self.voters[target].weight += weight_to_forward
if self.directlyVoted(target):
self.proposals[self.voters[target].vote].voteCount += weight_to_forward
self.voters[target].weight = 0
# To reiterate: if target is also a delegate, this function will need
# to be called again, similarly to as above.
# Delegate your vote to the voter 'to'.
@public
def delegate(to: address):
# Throws if the sender has already voted
assert not self.voters[msg.sender].voted
# Throws if the sender tries to delegate their vote to themselves or to
# the default address value of 0x0000000000000000000000000000000000000000
# (the latter might not be problematic, but I don't want to think about it).
assert to != msg.sender
assert to != ZERO_ADDRESS
self.voters[msg.sender].voted = True
self.voters[msg.sender].delegate = to
# This call will throw if and only if this delegation would cause a loop
# of length <= 5 that ends up delegating back to the delegator.
self.forwardWeight(msg.sender)
# Give your vote (including votes delegated to you)
# to proposal 'proposals[proposal].name'.
@public
def vote(proposal: int128):
# can't vote twice
assert not self.voters[msg.sender].voted
# can only vote on legitimate proposals
assert proposal < self.int128Proposals
self.voters[msg.sender].vote = proposal
self.voters[msg.sender].voted = True
# transfer msg.sender's weight to proposal
self.proposals[proposal].voteCount += self.voters[msg.sender].weight
self.voters[msg.sender].weight = 0
# Computes the winning proposal taking all
# previous votes into account.
@public
@constant
def winningProposal() -> int128:
winning_vote_count: int128 = 0
winning_proposal: int128 = 0
for i in range(2):
if self.proposals[i].voteCount > winning_vote_count:
winning_vote_count = self.proposals[i].voteCount
winning_proposal = i
return winning_proposal
# Calls winningProposal() function to get the index
# of the winner contained in the proposals array and then
# returns the name of the winner
@public
@constant
def winnerName() -> bytes32:
return self.proposals[self.winningProposal()].name
`
}

@ -18,8 +18,8 @@ export interface VyperCompilationResult {
export interface VyperCompilationError {
status: 'failed'
column: number
line: number
column?: number
line?: number
message: string
}

@ -3,6 +3,7 @@ import { Api, Status } from '@remixproject/plugin-utils';
import { createClient } from '@remixproject/plugin-webview'
import { PluginClient } from '@remixproject/plugin';
import { Contract } from './compiler';
import { ExampleContract } from '../components/VyperResult';
export class RemixClient extends PluginClient {
private client = createClient<Api, Readonly<RemixApi>>(this);
@ -14,34 +15,70 @@ export class RemixClient extends PluginClient {
/** Emit an event when file changed */
async onFileChange(cb: (contract: string) => any) {
this.client.on('fileManager', 'currentFileChanged', async (name: string) => {
if (!name) return
cb(name)
})
}
/** Emit an event when file changed */
async onNoFileSelected(cb: () => any) {
this.client.on('fileManager', 'noFileSelected', async () => {
cb()
})
}
/** Load Ballot contract example into the file manager */
async loadContract({name, content}: Contract) {
async loadContract({name, address}: ExampleContract) {
try {
await this.client.call('fileManager', 'setFile', name, content)
await this.client.call('fileManager', 'switchFile', name)
const content = await this.client.call('contentImport', 'resolve', address)
await this.client.call('fileManager', 'setFile', content.cleanUrl, content.content)
await this.client.call('fileManager', 'switchFile', content.cleanUrl)
} catch (err) {
console.log(err)
}
}
async cloneVyperRepo() {
try {
// @ts-ignore
this.call('notification', 'toast', 'cloning Vyper repository...')
await this.call('manager', 'activatePlugin', 'dGitProvider')
// @ts-ignore
await this.call('dGitProvider', 'clone', { url: 'https://github.com/vyperlang/vyper', token: null }, 'vyper-lang')
// @ts-ignore
this.call('notification', 'toast', 'Vyper repository cloned, the workspace Vyper has been created.')
} catch (e) {
// @ts-ignore
this.call('notification', 'toast', e.message)
}
}
/** Update the status of the plugin in remix */
changeStatus(status: Status) {
this.client.emit('statusChanged', status);
}
/** Highlight a part of the editor */
highlight(lineColumnPos: HighlightPosition, name: string, color: string) {
return this.client.call('editor', 'highlight', lineColumnPos, name, color)
async highlight(lineColumnPos: HighlightPosition, name: string, message: string) {
await this.client.call('editor', 'highlight', lineColumnPos, name)
/*
column: -1
row: -1
text: "browser/Untitled1.sol: Warning: SPDX license identifier not provided in source file. Before publishing, consider adding a comment containing "SPDX-License-Identifier: <SPDX-License>" to each source file. Use "SPDX-License-Identifier: UNLICENSED" for non-open-source code. Please see https://spdx.org for more information.↵"
type: "warning"
*/
const annotation = {
column: 0,
row: lineColumnPos.start.line,
type: 'error',
text: message
}
await this.client.call('editor', 'addAnnotation', annotation, name)
}
/** Remove current Hightlight */
discardHighlight() {
return this.client.call('editor', 'discardHighlight')
async discardHighlight() {
await this.client.call('editor', 'discardHighlight')
await this.client.call('editor', 'clearAnnotations')
}
/** Get the name of the current contract */

@ -33,7 +33,8 @@ export default function (self) { // eslint-disable-line @typescript-eslint/expli
}
self.postMessage({
cmd: 'versionLoaded',
data: compiler.version()
data: compiler.version(),
license: compiler.license()
})
break
}

@ -25,6 +25,7 @@ export class Compiler {
compileJSON: null,
worker: null,
currentVersion: null,
compilerLicense: null,
optimize: false,
runs: 200,
evmVersion: null,
@ -95,9 +96,10 @@ export class Compiler {
* @param version compiler version
*/
onCompilerLoaded(version: string): void {
onCompilerLoaded (version: string, license: string): void {
this.state.currentVersion = version
this.event.trigger('compilerLoaded', [version])
this.state.compilerLicense = license
this.event.trigger('compilerLoaded', [version, license])
}
/**
@ -132,7 +134,7 @@ export class Compiler {
}
this.onCompilationFinished(result, missingInputs, source, input, this.state.currentVersion)
}
this.onCompilerLoaded(compiler.version())
this.onCompilerLoaded(compiler.version(), compiler.license())
}
}
@ -185,6 +187,7 @@ export class Compiler {
if (err) {
console.error('Error in loading remote solc compiler: ', err)
} else {
let license
this.state.compileJSON = (source: SourceWithTarget) => {
const missingInputs: string[] = []
const missingInputsCallback = (path: string) => {
@ -204,13 +207,14 @@ export class Compiler {
}
result = JSON.parse(remoteCompiler.compile(input, { import: missingInputsCallback }))
license = remoteCompiler.license()
}
} catch (exception) {
result = { error: { formattedMessage: 'Uncaught JavaScript exception:\n' + exception, severity: 'error', mode: 'panic' } }
}
this.onCompilationFinished(result, missingInputs, source, input, version)
}
this.onCompilerLoaded(version)
this.onCompilerLoaded(version, license)
}
})
}
@ -277,7 +281,7 @@ export class Compiler {
}
switch (data.cmd) {
case 'versionLoaded':
if (data.data) this.onCompilerLoaded(data.data)
if (data.data && data.license) this.onCompilerLoaded(data.data, data.license)
break
case 'compiled':
{

@ -163,6 +163,7 @@ export interface CompilerState {
compileJSON: ((input: SourceWithTarget) => void) | null,
worker: any,
currentVersion: string| null| undefined,
compilerLicense: string| null
optimize: boolean,
runs: number
evmVersion: EVMVersion| null,
@ -193,6 +194,7 @@ export interface MessageToWorker {
export interface MessageFromWorker {
cmd: string,
license?: string,
job?: number,
missingInputs?: string[],
input?: any,

@ -134,7 +134,7 @@ export function compileFileOrFiles (filename: string, isDirectory: boolean, opts
if (runs) compiler.set('runs', runs)
if (currentCompilerUrl) {
compiler.loadRemoteVersion(currentCompilerUrl)
compiler.event.register('compilerLoaded', this, function (version) {
compiler.event.register('compilerLoaded', this, function (version, license) {
next()
})
} else {
@ -198,7 +198,7 @@ export function compileContractSources (sources: SrcIfc, newCompConfig: any, imp
compiler.set('runs', runs)
compiler.loadVersion(usingWorker, currentCompilerUrl)
// @ts-ignore
compiler.event.register('compilerLoaded', this, (version) => {
compiler.event.register('compilerLoaded', this, (version, license) => {
next()
})
} else {

@ -17,3 +17,7 @@
.validationError {
overflow-wrap: break-word;
}
.debuggerPanels {
overflow-y: scroll;
height: fit-content;
}

@ -1,4 +1,4 @@
import React, { useState, useEffect } from 'react' // eslint-disable-line
import React, { useState, useEffect, useRef } from 'react' // eslint-disable-line
import TxBrowser from './tx-browser/tx-browser' // eslint-disable-line
import StepManager from './step-manager/step-manager' // eslint-disable-line
import VmDebugger from './vm-debugger/vm-debugger' // eslint-disable-line
@ -36,6 +36,28 @@ export const DebuggerUI = (props: DebuggerUIProps) => {
sourceLocationStatus: ''
})
const panelsRef = useRef<HTMLDivElement>(null)
const debuggerTopRef = useRef(null)
const handleResize = () => {
if (panelsRef.current && debuggerTopRef.current) {
panelsRef.current.style.height = (window.innerHeight - debuggerTopRef.current.clientHeight) - debuggerTopRef.current.offsetTop - 7 +'px'
}
}
useEffect(() => {
handleResize()
}, [])
useEffect(() => {
window.addEventListener('resize', handleResize)
// TODO: not a good way to wait on the ref doms element to be rendered of course
setTimeout(() =>
handleResize(), 2000)
return () => window.removeEventListener('resize', handleResize)
}, [state.debugging, state.isActive])
useEffect(() => {
return unLoad()
}, [])
@ -260,34 +282,35 @@ export const DebuggerUI = (props: DebuggerUIProps) => {
})
setTimeout(async() => {
try {
await debuggerInstance.debug(blockNumber, txNumber, tx, () => {
listenToEvents(debuggerInstance, currentReceipt)
try {
await debuggerInstance.debug(blockNumber, txNumber, tx, () => {
listenToEvents(debuggerInstance, currentReceipt)
setState(prevState => {
return {
...prevState,
blockNumber,
txNumber,
debugging: true,
currentReceipt,
currentBlock,
currentTransaction,
debugger: debuggerInstance,
toastMessage: `debugging ${txNumber}`,
validationError: ''
}
})
})
} catch (error) {
unLoad()
setState(prevState => {
return {
...prevState,
blockNumber,
txNumber,
debugging: true,
currentReceipt,
currentBlock,
currentTransaction,
debugger: debuggerInstance,
toastMessage: `debugging ${txNumber}`,
validationError: ''
validationError: error.message || error
}
})
})
} catch (error) {
unLoad()
setState(prevState => {
return {
...prevState,
validationError: error.message || error
}
})
}
}, 300)
}
}, 300)
handleResize()
}
const debug = (txHash, web3?) => {
@ -315,17 +338,18 @@ export const DebuggerUI = (props: DebuggerUIProps) => {
traceLength: state.debugger && state.debugger.step_manager ? state.debugger.step_manager.traceLength : null,
registerEvent: state.debugger && state.debugger.step_manager ? state.debugger.step_manager.event.register.bind(state.debugger.step_manager.event) : null
}
const vmDebugger = {
registerEvent: state.debugger && state.debugger.vmDebuggerLogic ? state.debugger.vmDebuggerLogic.event.register.bind(state.debugger.vmDebuggerLogic.event) : null,
triggerEvent: state.debugger && state.debugger.vmDebuggerLogic ? state.debugger.vmDebuggerLogic.event.trigger.bind(state.debugger.vmDebuggerLogic.event) : null
}
return (
<div>
<Toaster message={state.toastMessage} />
<div className="px-2">
<div className="px-2" ref={debuggerTopRef}>
<div>
<p className="my-2 debuggerLabel">Debugger Configuration</p>
<div className="mt-2 mb-2 debuggerConfig custom-control custom-checkbox">
<div className="mb-2 debuggerConfig custom-control custom-checkbox" title="Using Generated Sources lets you step into compiler outputs while debugging.">
<input className="custom-control-input" id="debugGeneratedSourcesInput" onChange={({ target: { checked } }) => {
setState(prevState => {
return { ...prevState, opt: { ...prevState.opt, debugWithGeneratedSources: checked } }
@ -333,7 +357,7 @@ export const DebuggerUI = (props: DebuggerUIProps) => {
}} type="checkbox" title="Debug with generated sources" />
<label data-id="debugGeneratedSourcesLabel" className="form-check-label custom-control-label" htmlFor="debugGeneratedSourcesInput">Use generated sources (Solidity {'>='} v0.7.2)</label>
</div>
{ state.isLocalNodeUsed && <div className="mt-2 mb-2 debuggerConfig custom-control custom-checkbox">
{ state.isLocalNodeUsed && <div className="mb-2 debuggerConfig custom-control custom-checkbox">
<input className="custom-control-input" id="debugWithLocalNodeInput" onChange={({ target: { checked } }) => {
setState(prevState => {
return { ...prevState, opt: { ...prevState.opt, debugWithLocalNode: checked } }
@ -356,9 +380,11 @@ export const DebuggerUI = (props: DebuggerUIProps) => {
</span>
</div> }
{ state.debugging && <StepManager stepManager={ stepManager } /> }
</div>
<div className="debuggerPanels" ref={panelsRef}>
{ state.debugging && <VmDebuggerHead vmDebugger={ vmDebugger } /> }
{ state.debugging && <VmDebugger vmDebugger={ vmDebugger } currentBlock={ state.currentBlock } currentReceipt={ state.currentReceipt } currentTransaction={ state.currentTransaction } /> }
</div>
{ state.debugging && <VmDebugger vmDebugger={ vmDebugger } currentBlock={ state.currentBlock } currentReceipt={ state.currentReceipt } currentTransaction={ state.currentTransaction } /> }
</div>
)
}

@ -98,7 +98,7 @@ export const VmDebuggerHead = ({ vmDebugger: { registerEvent, triggerEvent } })
}, [registerEvent])
return (
<div id='vmheadView' className="mt-1 px-0">
<div id='vmheadView' className="mt-1 px-2">
<div className='d-flex flex-column'>
<div className='w-100'>
<FunctionPanel data={functionPanel} />

@ -75,6 +75,7 @@ type errorMarker = {
},
file: string
}
loader.config({ paths: { vs: 'assets/js/monaco-editor/dev/vs' } })
export type DecorationsReturn = {
@ -442,7 +443,6 @@ export const EditorUI = (props: EditorUIProps) => {
allMarkersPerfile[filePath].push(markerData)
}
}
console.log(allMarkersPerfile)
for (const filePath in allMarkersPerfile) {
const model = editorModelsState[filePath]?.model
if (model) {
@ -587,8 +587,7 @@ export const EditorUI = (props: EditorUIProps) => {
}
}
function handleEditorWillMount(monaco: Monaco) {
function handleEditorWillMount(monaco) {
monacoRef.current = monaco
// Register a new language
monacoRef.current.languages.register({ id: 'remix-solidity' })

@ -1,4 +1,5 @@
export * from './lib/remix-ui-helper'
export * from './lib/bleach'
export * from './lib/helper-components'
export * from './lib/components/PluginViewWrapper'
export * from './lib/components/custom-dropdown'

@ -5,7 +5,7 @@
*/
import * as he from 'he'
const remixBleach = {
export const bleach = {
matcher: /<\/?([a-zA-Z0-9]+)*(.*?)\/?>/igm,
@ -24,7 +24,7 @@ const remixBleach = {
let match
// extract all tags
while ((match = remixBleach.matcher.exec(html)) != null) {
while ((match = bleach.matcher.exec(html)) != null) {
const attrr = match[2].split(' ')
const attrs = []
@ -45,7 +45,7 @@ const remixBleach = {
if (attr.name) attrs.push(attr)
})
var tag = {
const tag = {
full: match[0],
name: match[1],
attr: attrs
@ -57,14 +57,13 @@ const remixBleach = {
return matches
},
sanitize: function (html, options) {
sanitize: function (html, options = { mode: 'white', list: bleach.whitelist, encode_entities: false}) {
html = String(html) || ''
options = options || {}
const mode = options.mode || 'white'
const list = options.list || remixBleach.whitelist
const list = options.list || bleach.whitelist
var matches = remixBleach.analyze(html)
const matches = bleach.analyze(html)
if ((mode === 'white' && list.indexOf('script') === -1) ||
(mode === 'black' && list.indexOf('script') !== -1)) {
@ -95,5 +94,3 @@ const remixBleach = {
return html
}
}
module.exports = remixBleach

@ -8,6 +8,7 @@
.remixModalBody {
overflow-y: auto;
max-height: 600px;
white-space: pre-line;
}
@-webkit-keyframes animatetop {
from {top: -300px; opacity: 0}

@ -27,8 +27,8 @@ const RemixUIPanelHeader = (props: RemixPanelProps) => {
return (
<header className='d-flex flex-column'>
<div className="swapitHeader px-3 pt-2 pb-0 d-flex flex-row">
<h6 className="mb-2" data-id='sidePanelSwapitTitle'>{plugin?.profile.displayName || plugin?.profile.name}</h6>
<div className="mt-2 d-flex flex-row">
<h6 className="mb-3" data-id='sidePanelSwapitTitle'>{plugin?.profile.displayName || plugin?.profile.name}</h6>
<div className="d-flex flex-row">
<div className="d-flex flex-row">
{plugin?.profile?.maintainedBy?.toLowerCase() === "remix" && (<i aria-hidden="true" className="text-success mt-1 px-1 fas fa-check" title="Maintained by Remix"></i>)}
</div>

@ -46,8 +46,8 @@ export const listenToEvents = (compileTabLogic: CompileTabLogic, api) => (dispat
dispatch(setCompilerMode('loadingCompiler'))
})
compileTabLogic.compiler.event.register('compilerLoaded', () => {
dispatch(setCompilerMode('compilerLoaded'))
compileTabLogic.compiler.event.register('compilerLoaded', (version, license) => {
dispatch(setCompilerMode('compilerLoaded', version, license))
})
compileTabLogic.compiler.event.register('compilationFinished', (success, data, source, input, version) => {

@ -48,6 +48,7 @@ export const CompilerContainer = (props: CompilerContainerProps) => {
timeout: 300,
allversions: [],
customVersions: [],
compilerLicense: null,
selectedVersion: null,
defaultVersion: 'soljson-v0.8.7+commit.e28d00a7.js', // this default version is defined: in makeMockCompiler (for browser test)
runs: '',
@ -185,7 +186,7 @@ export const CompilerContainer = (props: CompilerContainerProps) => {
loadingCompiler()
break
case 'compilerLoaded':
compilerLoaded()
compilerLoaded(compilerContainer.compiler.args[1])
break
case 'compilationFinished':
compilationFinished()
@ -432,14 +433,20 @@ export const CompilerContainer = (props: CompilerContainerProps) => {
if (!compileIcon.current) return
compileIcon.current.setAttribute('title', 'compiler is loading, please wait a few moments.')
compileIcon.current.classList.add('remixui_spinningIcon')
setState(prevState => {
return { ...prevState, compilerLicense: 'Compiler is loading. License will be displayed once compiler is loaded'}
})
_updateLanguageSelector()
setDisableCompileButton(true)
}
const compilerLoaded = () => {
const compilerLoaded = (license) => {
if (!compileIcon.current) return
compileIcon.current.setAttribute('title', '')
compileIcon.current.classList.remove('remixui_spinningIcon')
setState(prevState => {
return { ...prevState, compilerLicense: license ? license : 'Could not retreive license for selected compiler version' }
})
if (state.autoCompile) compile()
const isDisabled = !compiledFileName || (compiledFileName && !isSolFileSelected(compiledFileName))
@ -554,6 +561,10 @@ export const CompilerContainer = (props: CompilerContainerProps) => {
modal('Add a custom compiler', promptMessage('URL'), 'OK', addCustomCompiler, 'Cancel', () => {})
}
const showCompilerLicense = () => {
modal('Compiler License', state.compilerLicense ? state.compilerLicense : 'License not available', 'OK', () => {})
}
const promptMessage = (message) => {
return (
<>
@ -701,10 +712,9 @@ export const CompilerContainer = (props: CompilerContainerProps) => {
<article>
<div className='pt-0 remixui_compilerSection'>
<div className="mb-1">
<label className="remixui_compilerLabel form-check-label" htmlFor="versionSelector">
Compiler
<button className="far fa-plus btn-light border-0 p-0 mx-2 btn-sm" onClick={promptCompiler} title="Add a custom compiler with URL"></button>
</label>
<label className="remixui_compilerLabel form-check-label" htmlFor="versionSelector">Compiler</label>
<span className="far fa-plus border-0 p-0 ml-3" onClick={() => promptCompiler()} title="Add a custom compiler with URL"></span>
<span className="fa fa-file-text-o border-0 p-0 ml-2" onClick={() => showCompilerLicense()} title="See compiler license"></span>
<select value={ state.selectedVersion || state.defaultVersion } onChange={(e) => handleLoadVersion(e.target.value) } className="custom-select" id="versionSelector" disabled={state.allversions.length <= 0}>
{ state.allversions.length <= 0 && <option disabled data-id={state.selectedVersion === state.defaultVersion ? 'selected' : ''}>{ state.defaultVersion }</option> }
{ state.allversions.length <= 0 && <option disabled data-id={state.selectedVersion === 'builtin' ? 'selected' : ''}>builtin</option> }

@ -164,7 +164,7 @@ export const SolidityUnitTesting = (props: Record<string, any>) => { // eslint-d
testTab.fileManager.events.on('currentFileChanged', async (file: string) => {
await updateForNewCurrent(file)
})
testTab.on('solidity', 'compilerLoaded', async (version: string) => {
testTab.on('solidity', 'compilerLoaded', async (version: string, license: string) => {
const { currentVersion } = testTab.compileTab.getCurrentCompilerConfig()
if (!semver.gt(truncateVersion(currentVersion), '0.4.12')) {

@ -140,8 +140,8 @@ export const RemixUiStaticAnalyser = (props: RemixUiStaticAnalyserProps) => {
setSlitherEnabled(false)
}
})
props.analysisModule.on('solidity', 'compilerLoaded', async (version: string) => {
// eslint-disable-next-line @typescript-eslint/no-unused-vars
props.analysisModule.on('solidity', 'compilerLoaded', async (version: string, license: string) => {
setDisableForRun(version)
})
return () => { }

@ -1,5 +1,7 @@
import { checkSpecialChars } from '@remix-ui/helper'
import { BadgeStatus, IconStatus } from '../components/Icon'
import { bleach } from '@remix-ui/helper'
export type IconBadgeReducerAction = {
readonly type: string
@ -20,13 +22,13 @@ function setIconStatus(name: string, status: IconStatus) {
if (typeof status.key === 'number') {
key = status.key
text = key
} else key = checkSpecialChars(status.key) ? '' : status.key
} else key = checkSpecialChars(status.key) ? bleach.sanitize(status.key) : status.key
let thisType = ''
if (status.type === 'error') {
thisType = 'danger' // to use with bootstrap
} else thisType = checkSpecialChars(status.type) ? '' : status.type!
const title = checkSpecialChars(status.title) ? '' : status.title
} else thisType = checkSpecialChars(status.type) ? bleach.sanitize(status.type) : status.type
const title = checkSpecialChars(status.title) ? bleach.sanitize(status.title) : status.title
const pluginName = status.pluginName
return { title, type: thisType, key, text, pluginName }
}

Loading…
Cancel
Save