pull/5370/head
filip mertens 3 years ago
commit 53e014a04f
  1. 9
      apps/remix-ide-e2e/src/tests/ballot.test.ts
  2. 2
      apps/remix-ide-e2e/src/tests/debugger.spec.ts
  3. 4
      apps/remix-ide-e2e/src/tests/specialFunctions.test.ts
  4. 22
      apps/remix-ide-e2e/src/tests/transactionExecution.spec.ts
  5. 2
      apps/remix-ide/src/app/tabs/settings-tab.js
  6. 2
      apps/remix-ide/src/app/ui/landing-page/landing-page.js
  7. 21
      apps/remix-ide/src/app/ui/txLogger.js
  8. 8
      apps/remix-ide/src/app/ui/universal-dapp-ui.js
  9. 5
      apps/remix-ide/src/blockchain/blockchain.js
  10. 11
      libs/remix-debug/src/solidity-decoder/decodeInfo.ts
  11. 4
      libs/remix-debug/src/solidity-decoder/stateDecoder.ts
  12. 2
      libs/remix-debug/src/solidity-decoder/types/Mapping.ts
  13. 3
      libs/remix-debug/src/solidity-decoder/types/Struct.ts
  14. 30
      libs/remix-lib/src/execution/txExecution.ts
  15. 7
      libs/remix-lib/src/util.ts
  16. 19
      libs/remix-ui/debugger-ui/src/lib/vm-debugger/dropdown-panel.tsx
  17. 2
      libs/remix-ui/debugger-ui/src/lib/vm-debugger/memory-panel.tsx
  18. 2
      libs/remix-ui/debugger-ui/src/lib/vm-debugger/stack-panel.tsx
  19. 2
      libs/remix-ui/debugger-ui/src/lib/vm-debugger/step-detail.tsx
  20. 5
      libs/remix-ui/debugger-ui/src/types/index.ts
  21. 20
      libs/remixd/README.md
  22. 6
      libs/remixd/src/bin/remixd.ts
  23. 11
      package-lock.json
  24. 3
      package.json

@ -121,7 +121,8 @@ const stateCheck = {
chairperson: {
value: '0xCA35B7D915458EF540ADE6068DFE2F44E8FA733C',
type: 'address',
constant: false
constant: false,
immutable: false
},
voters: {
value: {
@ -148,7 +149,8 @@ const stateCheck = {
}
},
type: 'mapping(address => struct Ballot.Voter)',
constant: false
constant: false,
immutable: false
},
proposals: {
value: [
@ -168,7 +170,8 @@ const stateCheck = {
],
length: '0x1',
type: 'struct Ballot.Proposal[]',
constant: false
constant: false,
immutable: false
}
}

@ -228,7 +228,7 @@ module.exports = {
.waitForElementVisible('*[data-id="solidityLocals"]', 60000)
.pause(10000)
.checkVariableDebug('soliditylocals', { num: { value: '2', type: 'uint256' } })
.checkVariableDebug('soliditystate', { number: { value: '0', type: 'uint256', constant: false } })
.checkVariableDebug('soliditystate', { number: { value: '0', type: 'uint256', constant: false, immutable: false } })
.end()
}
}

@ -177,10 +177,10 @@ module.exports = {
.pause(1000)
.perform((done) => {
browser.getAddressAtPosition(4, (address) => {
browser.sendLowLevelTx(address, '1', '0xaa')
browser.sendLowLevelTx(address, '999999998765257135', '0xaa')
.pause(1000)
.journalLastChildIncludes('to: CheckSpecials.(fallback)')
.journalLastChildIncludes('value: 1 wei')
.journalLastChildIncludes('value: 999999998765257135 wei')
.journalLastChildIncludes('data: 0xaa')
.perform(done)
})

@ -148,9 +148,14 @@ module.exports = {
.click('.instance:nth-of-type(3) > div > button')
.clickFunction('g - transact (not payable)')
.journalLastChildIncludes('Error provided by the contract:')
.journalLastChildIncludes('CustomError')
.journalLastChildIncludes('CustomError : error description')
.journalLastChildIncludes('Parameters:')
.journalLastChildIncludes('2,3,error_string_2')
.journalLastChildIncludes('"value": "2",')
.journalLastChildIncludes('"value": "3",')
.journalLastChildIncludes('"value": "error_string_2",')
.journalLastChildIncludes('"documentation": "param1"')
.journalLastChildIncludes('"documentation": "param2"')
.journalLastChildIncludes('"documentation": "param3"')
.journalLastChildIncludes('Debug the transaction to get more information.')
},
@ -163,9 +168,14 @@ module.exports = {
.click('.instance:nth-of-type(2) > div > button')
.clickFunction('g - transact (not payable)')
.journalLastChildIncludes('Error provided by the contract:')
.journalLastChildIncludes('CustomError')
.journalLastChildIncludes('CustomError : error description')
.journalLastChildIncludes('Parameters:')
.journalLastChildIncludes('2,3,error_string_2')
.journalLastChildIncludes('"value": "2",')
.journalLastChildIncludes('"value": "3",')
.journalLastChildIncludes('"value": "error_string_2",')
.journalLastChildIncludes('"documentation": "param1"')
.journalLastChildIncludes('"documentation": "param2"')
.journalLastChildIncludes('"documentation": "param3"')
.journalLastChildIncludes('Debug the transaction to get more information.')
.end()
}
@ -256,6 +266,10 @@ contract C {
pragma solidity ^0.8.4;
/// error description
/// @param a param1
/// @param b param2
/// @param c param3
error CustomError(uint a, uint b, string c);
contract C {
function f() public pure {

@ -100,7 +100,7 @@ module.exports = class SettingsTab extends ViewPlugin {
</div>
`
this._view.optionVM = yo`<input onchange=${onchangeOption} class="custom-control-input" id="alwaysUseVM" data-id="settingsTabAlwaysUseVM" type="checkbox">`
this._view.optionVMLabel = yo`<label class="form-check-label custom-control-label align-middle" for="alwaysUseVM">Always use Ethereum VM at Load</label>`
this._view.optionVMLabel = yo`<label class="form-check-label custom-control-label align-middle" for="alwaysUseVM">Always use JavaScript VM at Load</label>`
if (this.config.get('settings/always-use-vm') === undefined) this.config.set('settings/always-use-vm', true)
if (this.config.get('settings/always-use-vm')) this._view.optionVM.setAttribute('checked', '')
elementStateChanged(self._view.optionVMLabel, !this.config.get('settings/always-use-vm'))

@ -534,7 +534,7 @@ export class LandingPage extends ViewPlugin {
<i class="far fa-hdd"></i>
<span class="ml-1 ${css.text}" onclick=${() => connectToLocalhost()}>Connect to Localhost</span>
</p>
<p class="mt-3 mb-0"><label>IMPORT FROM:</label></p>
<p class="mt-3 mb-0"><label>LOAD FROM:</label></p>
<div class="btn-group">
<button class="btn mr-1 btn-secondary" data-id="landingPageImportFromGistButton" onclick="${() => importFromGist()}">Gist</button>
<button class="btn mx-1 btn-secondary" onclick="${() => load('Github', 'github URL', ['https://github.com/0xcert/ethereum-erc721/src/contracts/tokens/nf-token-metadata.sol', 'https://github.com/OpenZeppelin/openzeppelin-solidity/blob/67bca857eedf99bf44a4b6a0fc5b5ed553135316/contracts/access/Roles.sol'])}">GitHub</button>

@ -354,6 +354,17 @@ module.exports = TxLogger
// helpers
function isDescendant (parent, child) {
var node = child.parentNode
while (node != null) {
if (node === parent) {
return true
}
node = node.parentNode
}
return false
}
function txDetails (e, tx, data, obj) {
const from = obj.from
const to = obj.to
@ -368,9 +379,13 @@ function txDetails (e, tx, data, obj) {
} else break
}
let table = blockElement.querySelector(`#${tx.id} [class^="txTable"]`)
const log = blockElement.querySelector(`#${tx.id} [class^='log']`)
const arrow = blockElement.querySelector(`#${tx.id} [class^='arrow']`)
const tables = blockElement.querySelectorAll(`#${tx.id} [class^="txTable"]`)
const logs = blockElement.querySelectorAll(`#${tx.id} [class^='log']`)
const arrows = blockElement.querySelectorAll(`#${tx.id} [class^='arrow']`)
let table = [...tables].filter((t) => isDescendant(tx, t))[0]
const log = [...logs].filter((t) => isDescendant(tx, t))[0]
const arrow = [...arrows].filter((t) => isDescendant(tx, t))[0]
if (table && table.parentNode) {
tx.removeChild(table)

@ -44,14 +44,14 @@ UniversalDAppUI.prototype.renderInstance = function (contract, address, contract
noInstances.parentNode.removeChild(noInstances)
}
const abi = txHelper.sortAbiFunction(contract.abi)
return this.renderInstanceFromABI(abi, address, contractName)
return this.renderInstanceFromABI(abi, address, contractName, contract)
}
// TODO this function was named before "appendChild".
// this will render an instance: contract name, contract address, and all the public functions
// basically this has to be called for the "atAddress" (line 393) and when a contract creation succeed
// this returns a DOM element
UniversalDAppUI.prototype.renderInstanceFromABI = function (contractABI, address, contractName) {
UniversalDAppUI.prototype.renderInstanceFromABI = function (contractABI, address, contractName, contract) {
const self = this
address = (address.slice(0, 2) === '0x' ? '' : '0x') + address.toString('hex')
address = ethJSUtil.toChecksumAddress(address)
@ -117,7 +117,8 @@ UniversalDAppUI.prototype.renderInstanceFromABI = function (contractABI, address
funABI: funABI,
address: address,
contractABI: contractABI,
contractName: contractName
contractName: contractName,
contract
}))
})
@ -255,6 +256,7 @@ UniversalDAppUI.prototype.runTransaction = function (lookupOnly, args, valArr, i
args.contractName,
args.contractABI,
args.funABI,
args.contract,
inputsValues,
args.address,
params,

@ -249,7 +249,7 @@ class Blockchain {
return txlistener
}
runOrCallContractMethod (contractName, contractAbi, funABI, value, address, callType, lookupOnly, logMsg, logCallback, outputCb, confirmationCb, continueCb, promptCb) {
runOrCallContractMethod (contractName, contractAbi, funABI, contract, value, address, callType, lookupOnly, logMsg, logCallback, outputCb, confirmationCb, continueCb, promptCb) {
// contractsDetails is used to resolve libraries
txFormat.buildData(contractName, contractAbi, {}, false, funABI, callType, (error, data) => {
if (error) {
@ -265,6 +265,7 @@ class Blockchain {
if (data) {
data.contractName = contractName
data.contractABI = contractAbi
data.contract = contract
}
const useCall = funABI.stateMutability === 'view' || funABI.stateMutability === 'pure'
this.runTx({ to: address, data, useCall }, confirmationCb, continueCb, promptCb, (error, txResult, _address, returnValue) => {
@ -490,7 +491,7 @@ class Blockchain {
if (execResult) {
// if it's not the VM, we don't have return value. We only have the transaction, and it does not contain the return value.
returnValue = execResult ? execResult.returnValue : toBuffer(addHexPrefix(txResult.result) || '0x0000000000000000000000000000000000000000000000000000000000000000')
const vmError = txExecution.checkVMError(execResult, args.data.contractABI)
const vmError = txExecution.checkVMError(execResult, args.data.contractABI, args.data.contract)
if (vmError.error) {
return cb(vmError.message)
}

@ -336,7 +336,9 @@ function computeOffsets (types, stateDefinitions, contractName, location) {
console.log('unable to retrieve decode info of ' + variable.typeDescriptions.typeString)
return null
}
if (!variable.constant && storagelocation.offset + type.storageBytes > 32) {
const immutable = variable.mutability === 'immutable'
const hasStorageSlots = !immutable && !variable.constant
if (hasStorageSlots && storagelocation.offset + type.storageBytes > 32) {
storagelocation.slot++
storagelocation.offset = 0
}
@ -344,12 +346,13 @@ function computeOffsets (types, stateDefinitions, contractName, location) {
name: variable.name,
type: type,
constant: variable.constant,
immutable,
storagelocation: {
offset: variable.constant ? 0 : storagelocation.offset,
slot: variable.constant ? 0 : storagelocation.slot
offset: !hasStorageSlots ? 0 : storagelocation.offset,
slot: !hasStorageSlots ? 0 : storagelocation.slot
}
})
if (!variable.constant) {
if (hasStorageSlots) {
if (type.storageSlots === 1 && storagelocation.offset + type.storageBytes <= 32) {
storagelocation.offset += type.storageBytes
} else {

@ -15,9 +15,13 @@ export async function decodeState (stateVars, storageResolver) {
try {
const decoded = await stateVar.type.decodeFromStorage(stateVar.storagelocation, storageResolver)
decoded.constant = stateVar.constant
decoded.immutable = stateVar.immutable
if (decoded.constant) {
decoded.value = '<constant>'
}
if (decoded.immutable) {
decoded.value = '<immutable>'
}
ret[stateVar.name] = decoded
} catch (e) {
console.log(e)

@ -38,7 +38,7 @@ export class Mapping extends RefType {
decodeFromMemoryInternal (offset, memory) {
// mappings can only exist in storage and not in memory
// so this should never be called
return { value: '<not implemented>', length: '0x', type: this.typeName }
return { value: '', length: '0x0', type: this.typeName }
}
async decodeMappingsLocation (preimages, location, storageResolver) {

@ -1,6 +1,7 @@
'use strict'
import { add } from './util'
import { RefType } from './RefType'
import { Mapping } from './Mapping'
export class Struct extends RefType {
members
@ -33,7 +34,7 @@ export class Struct extends RefType {
var contentOffset = offset
var member = item.type.decodeFromMemory(contentOffset, memory)
ret[item.name] = member
offset += 32
if (!(item.type instanceof Mapping)) offset += 32
})
return { value: ret, type: this.typeName }
}

@ -57,7 +57,7 @@ export function callFunction (from, to, data, value, gasLimit, funAbi, txRunner,
* @param {Object} execResult - execution result given by the VM
* @return {Object} - { error: true/false, message: DOMNode }
*/
export function checkVMError (execResult, abi) {
export function checkVMError (execResult, abi, contract) {
const errorCode = {
OUT_OF_GAS: 'out of gas',
STACK_UNDERFLOW: 'stack underflow',
@ -92,7 +92,7 @@ export function checkVMError (execResult, abi) {
const returnDataHex = returnData.slice(0, 4).toString('hex')
let customError
if (abi) {
let decodedCustomErrorInputs
let decodedCustomErrorInputsClean
for (const item of abi) {
if (item.type === 'error') {
// ethers doesn't crash anymore if "error" type is specified, but it doesn't extract the errors. see:
@ -104,16 +104,36 @@ export function checkVMError (execResult, abi) {
if (!sign) continue
if (returnDataHex === sign.replace('0x', '')) {
customError = item.name
decodedCustomErrorInputs = fn.decodeFunctionData(fn.getFunction(item.name), returnData)
const functionDesc = fn.getFunction(item.name)
// decoding error parameters
const decodedCustomErrorInputs = fn.decodeFunctionData(functionDesc, returnData)
decodedCustomErrorInputsClean = {}
let devdoc = {}
// "contract" reprensents the compilation result containing the NATSPEC documentation
if (contract && fn.functions && Object.keys(fn.functions).length) {
const functionSignature = Object.keys(fn.functions)[0]
// we check in the 'devdoc' if there's a developer documentation for this error
devdoc = contract.object.devdoc.errors[functionSignature][0] || {}
// we check in the 'userdoc' if there's an user documentation for this error
const userdoc = contract.object.userdoc.errors[functionSignature][0] || {}
if (userdoc) customError += ' : ' + (userdoc as any).notice // we append the user doc if any
}
for (const input of functionDesc.inputs) {
const v = decodedCustomErrorInputs[input.name]
decodedCustomErrorInputsClean[input.name] = {
value: v.toString ? v.toString() : v,
documentation: (devdoc as any).params[input.name] // we add the developer documentation for this input parameter if any
}
}
break
}
}
}
if (decodedCustomErrorInputs) {
if (decodedCustomErrorInputsClean) {
msg = '\tThe transaction has been reverted to the initial state.\nError provided by the contract:'
msg += `\n${customError}`
msg += '\nParameters:'
msg += `\n${decodedCustomErrorInputs}`
msg += `\n${JSON.stringify(decodedCustomErrorInputsClean, null, ' ')}`
}
}
if (!customError) {

@ -1,5 +1,6 @@
'use strict'
import { BN, bufferToHex, keccak, setLengthLeft, toBuffer, addHexPrefix } from 'ethereumjs-util'
import stringSimilarity from 'string-similarity'
/*
contains misc util: @TODO should be splitted
@ -222,9 +223,11 @@ export function compareByteCode (code1, code2) {
code2 = this.extractSwarmHash(code2)
code2 = this.extractcborMetadata(code2)
if (code1 && code2 && code1.indexOf(code2) === 0) {
return true
if (code1 && code2) {
const compare = stringSimilarity.compareTwoStrings(code1, code2)
return compare > 0.93
}
return false
}
/* util extracted out from remix-ide. @TODO split this file, cause it mix real util fn with solidity related stuff ... */

@ -7,7 +7,7 @@ import './styles/dropdown-panel.css'
export const DropdownPanel = (props: DropdownPanelProps) => {
const [calldataObj, dispatch] = useReducer(reducer, initialState)
const { dropdownName, dropdownMessage, calldata, header, loading, extractFunc, formatSelfFunc, registerEvent, triggerEvent, loadMoreEvent, loadMoreCompletedEvent } = props
const { dropdownName, dropdownMessage, calldata, header, loading, extractFunc, formatSelfFunc, registerEvent, triggerEvent, loadMoreEvent, loadMoreCompletedEvent, headStyle, bodyStyle, hexHighlight } = props
const extractDataDefault: ExtractFunc = (item, parent?) => {
const ret: ExtractData = {}
@ -34,10 +34,21 @@ export const DropdownPanel = (props: DropdownPanelProps) => {
return ret
}
const formatSelfDefault = (key: string | number, data: ExtractData) => {
let value
if (hexHighlight && typeof (data.self) === 'string') {
const isHex = data.self.startsWith('0x') || hexHighlight
if (isHex) {
const regex = /^(0+)(.*)/g
const split = regex.exec(data.self.replace('0x', ''))
if (split && split[1]) {
value = (<span><span className="m-0 label_value">0x</span><span className="m-0 label_value">{split[1]}</span>{ split[2] && <span className="m-0 label_value font-weight-bold text-dark">{split[2]}</span> }</span>)
} else value = (<span><span className="m-0 label_value">0x</span><span className="m-0 label_value font-weight-bold text-dark">{data.self.replace('0x', '')}</span></span>)
} else value = <span className="m-0 label_value">{data.self}</span>
} else value = <span className="m-0 label_value">{data.self}</span>
return (
<div className="d-flex mr-1 flex-row label_item">
<label className="small font-weight-bold mb-0 pr-1 label_key">{key}:</label>
<label className="m-0 label_value">{data.self}</label>
<label className="m-0 label_value">{value}</label>
</div>
)
}
@ -184,14 +195,14 @@ export const DropdownPanel = (props: DropdownPanelProps) => {
return (
<div className="border rounded px-1 mt-1 bg-light">
<div className="py-0 px-1 title">
<div className="py-0 px-1 title" style={headStyle}>
<div className={state.toggleDropdown ? 'icon fas fa-caret-down' : 'icon fas fa-caret-right'} onClick={handleToggle}></div>
<div className="name" data-id={`dropdownPanel${uniquePanelName}`} onClick={handleToggle}>{dropdownName}</div><span className="nameDetail" onClick={handleToggle}>{header}</span>
<CopyToClipboard content={state.copiableContent} data-id={`dropdownPanelCopyToClipboard${uniquePanelName}`} />
</div>
<div className='dropdownpanel' style={{ display: state.toggleDropdown ? 'block' : 'none' }}>
<i className="refresh fas fa-sync" style={{ display: state.updating ? 'inline-block' : 'none' }} aria-hidden="true"></i>
<div className='dropdowncontent' style={{ display: state.dropdownContent.display }}>
<div className='dropdowncontent' style={{ display: state.dropdownContent.display, ...bodyStyle }}>
{
state.data &&
<TreeView id="treeView">

@ -3,7 +3,7 @@ import DropdownPanel from './dropdown-panel' // eslint-disable-line
export const MemoryPanel = ({ calldata }) => {
return (
<DropdownPanel dropdownName='Memory' calldata={calldata || {}} />
<DropdownPanel hexHighlight={true} bodyStyle={{ fontFamily: 'monospace' }} dropdownName='Memory' calldata={calldata || {}} />
)
}

@ -4,7 +4,7 @@ import DropdownPanel from './dropdown-panel' // eslint-disable-line
export const StackPanel = ({ calldata }) => {
return (
<div id='stackpanel'>
<DropdownPanel dropdownName='Stack' calldata={calldata || {}} />
<DropdownPanel hexHighlight={true} bodyStyle={{ fontFamily: 'monospace' }} dropdownName='Stack' calldata={calldata || {}} />
</div>
)
}

@ -4,7 +4,7 @@ import DropdownPanel from './dropdown-panel' // eslint-disable-line
export const StepDetail = ({ stepDetail }) => {
return (
<div id='stepdetail' data-id='stepdetail'>
<DropdownPanel dropdownName='Step details' calldata={stepDetail || {}} />
<DropdownPanel hexHighlight={false} dropdownName='Step details' calldata={stepDetail || {}} />
</div>
)
}

@ -27,7 +27,10 @@ export interface DropdownPanelProps {
registerEvent?: Function,
triggerEvent?: Function,
loadMoreEvent?: string,
loadMoreCompletedEvent?: string
loadMoreCompletedEvent?: string,
bodyStyle?: React.CSSProperties,
headStyle?: React.CSSProperties,
hexHighlight?: boolean // highlight non zero value of hex value
}
export type FormatSelfFunc = (key: string | number, data: ExtractData) => JSX.Element

@ -26,18 +26,20 @@ If you were using the old one you need to:
## HELP SECTION
```
Usage: remixd -s <shared folder> --remix-ide https://remix.ethereum.org
Usage: remixd -s <shared folder>
Provide a two-way connection between the local computer and Remix IDE.
Provide a two-way connection between the local computer and Remix IDE
Options:
Options:
-v, --version output the version number
-u, --remix-ide <url> URL of remix instance allowed to connect to this web sockect connection
-s, --shared-folder <path> Folder to share with Remix IDE
-r, --read-only Treat shared folder as read-only (experimental)
-h, --help output usage information
--remix-ide <url> URL of remix instance allowed to connect to this
web sockect connection
-s, --shared-folder <path> Folder to share with Remix IDE
--read-only Treat shared folder as read-only (experimental)
-h, --help output usage information
Example:
remixd -s ./ -u http://localhost:8080
```

@ -58,11 +58,11 @@ function errorHandler (error: any, service: string) {
program
.usage('-s <shared folder>')
.description('Provide a two-way connection between the local computer and Remix IDE')
.option('--remix-ide <url>', 'URL of remix instance allowed to connect to this web sockect connection')
.option('-u, --remix-ide <url>', 'URL of remix instance allowed to connect to this web sockect connection')
.option('-s, --shared-folder <path>', 'Folder to share with Remix IDE')
.option('--read-only', 'Treat shared folder as read-only (experimental)')
.option('-r, --read-only', 'Treat shared folder as read-only (experimental)')
.on('--help', function () {
console.log('\nExample:\n\n remixd -s ./ --remix-ide http://localhost:8080')
console.log('\nExample:\n\n remixd -s ./ -u http://localhost:8080')
}).parse(process.argv)
// eslint-disable-next-line

11
package-lock.json generated

@ -8443,9 +8443,9 @@
"dev": true
},
"ace-mode-zokrates": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/ace-mode-zokrates/-/ace-mode-zokrates-1.0.1.tgz",
"integrity": "sha512-+rTOLj1AJzV/XRXsMLNkWIjNQCIa8TYjWRunCTGJ620iUy7WRlMkU7uVRydq//t4GUdr0j2TkNM0fSqVs0zNWw==",
"version": "1.0.4",
"resolved": "https://registry.npmjs.org/ace-mode-zokrates/-/ace-mode-zokrates-1.0.4.tgz",
"integrity": "sha512-jLpIg+PhJTlCWKu52U/EdJPQPJez9mMB0uzvCiyHgCJsX6+FY+s7jmBDrpxGdgNdNWJPQ20/MKzOx3oUnSF27A==",
"dev": true
},
"acorn": {
@ -36078,6 +36078,11 @@
}
}
},
"string-similarity": {
"version": "4.0.4",
"resolved": "https://registry.npmjs.org/string-similarity/-/string-similarity-4.0.4.tgz",
"integrity": "sha512-/q/8Q4Bl4ZKAPjj8WerIBJWALKkaPRfrvhfF8k/B23i4nzrlRj2/go1m90In7nG/3XDSbOo0+pu6RvCTM9RGMQ=="
},
"string-width": {
"version": "2.1.1",
"resolved": "https://registry.npmjs.org/string-width/-/string-width-2.1.1.tgz",

@ -172,6 +172,7 @@
"react-dom": "16.13.1",
"selenium": "^2.20.0",
"signale": "^1.4.0",
"string-similarity": "^4.0.4",
"time-stamp": "^2.2.0",
"tslib": "^2.3.0",
"web3": "1.2.4",
@ -219,7 +220,7 @@
"ace-mode-lexon": "^1.*.*",
"ace-mode-move": "0.0.1",
"ace-mode-solidity": "^0.1.0",
"ace-mode-zokrates": "^1.0.0",
"ace-mode-zokrates": "^1.0.4",
"babel-eslint": "^10.0.0",
"babel-jest": "25.1.0",
"babel-plugin-add-module-exports": "^1.0.2",

Loading…
Cancel
Save