Merge branch 'master' into nx3

pull/3397/head
bunsenstraat 2 years ago committed by GitHub
commit ab8ccf2e75
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 21
      apps/remix-ide-e2e/src/tests/proxy.test.ts
  2. 2
      apps/remix-ide/src/app/components/preload.tsx
  3. 19
      apps/remix-ide/src/app/editor/editor.js
  4. 2
      apps/remix-ide/src/app/files/fileManager.ts
  5. 61
      apps/remix-ide/src/app/plugins/solidity-umlgen.tsx
  6. 35
      apps/remix-ide/src/blockchain/blockchain.js
  7. 19
      libs/remix-core-plugin/src/types/contract.ts
  8. 43
      libs/remix-ui/helper/src/lib/components/custom-dropdown.tsx
  9. 25
      libs/remix-ui/helper/src/lib/helper-components.tsx
  10. 12
      libs/remix-ui/helper/src/lib/remix-ui-helper.ts
  11. 4
      libs/remix-ui/modal-dialog/src/lib/remix-ui-modal-dialog.tsx
  12. 4
      libs/remix-ui/modal-dialog/src/lib/types/index.ts
  13. 6
      libs/remix-ui/run-tab/src/lib/actions/actions.ts
  14. 60
      libs/remix-ui/run-tab/src/lib/actions/deploy.ts
  15. 12
      libs/remix-ui/run-tab/src/lib/actions/events.ts
  16. 4
      libs/remix-ui/run-tab/src/lib/actions/index.ts
  17. 30
      libs/remix-ui/run-tab/src/lib/actions/payload.ts
  18. 10
      libs/remix-ui/run-tab/src/lib/components/contractDropdownUI.tsx
  19. 145
      libs/remix-ui/run-tab/src/lib/components/contractGUI.tsx
  20. 4
      libs/remix-ui/run-tab/src/lib/constants/index.ts
  21. 2
      libs/remix-ui/run-tab/src/lib/css/run-tab.css
  22. 144
      libs/remix-ui/run-tab/src/lib/reducers/runTab.ts
  23. 12
      libs/remix-ui/run-tab/src/lib/run-tab.tsx
  24. 2
      libs/remix-ui/run-tab/src/lib/types/blockchain.d.ts
  25. 122
      libs/remix-ui/run-tab/src/lib/types/index.ts
  26. 6
      libs/remix-ui/solidity-uml-gen/src/lib/css/solidity-uml-gen.css
  27. 117
      libs/remix-ui/solidity-uml-gen/src/lib/solidity-uml-gen.tsx
  28. 14
      libs/remix-ui/solidity-uml-gen/src/types/index.ts
  29. 12
      libs/remix-ui/workspace/src/lib/actions/events.ts
  30. 2
      libs/remix-ui/workspace/src/lib/actions/workspace.ts
  31. 14
      libs/remixd/src/services/remixdClient.ts
  32. 2
      libs/remixd/src/types/index.ts
  33. 1
      package.json
  34. 60
      yarn.lock

@ -4,6 +4,8 @@ import init from '../helpers/init'
let firstProxyAddress: string let firstProxyAddress: string
let lastProxyAddress: string let lastProxyAddress: string
let shortenedFirstAddress: string
let shortenedLastAddress: string
module.exports = { module.exports = {
'@disabled': true, '@disabled': true,
before: function (browser: NightwatchBrowser, done: VoidFunction) { before: function (browser: NightwatchBrowser, done: VoidFunction) {
@ -94,6 +96,7 @@ module.exports = {
browser browser
.getAddressAtPosition(1, (address) => { .getAddressAtPosition(1, (address) => {
firstProxyAddress = address firstProxyAddress = address
shortenedFirstAddress = address.slice(0, 5) + '...' + address.slice(address.length - 5, address.length)
}) })
.clickInstance(1) .clickInstance(1)
.perform((done) => { .perform((done) => {
@ -143,6 +146,7 @@ module.exports = {
browser browser
.getAddressAtPosition(1, (address) => { .getAddressAtPosition(1, (address) => {
lastProxyAddress = address lastProxyAddress = address
shortenedLastAddress = address.slice(0, 5) + '...' + address.slice(address.length - 5, address.length)
}) })
.clickInstance(1) .clickInstance(1)
.perform((done) => { .perform((done) => {
@ -157,7 +161,7 @@ module.exports = {
}) })
}, },
'Should upgrade contract using last deployed proxy address (MyTokenV1 to MyTokenV2) #group1': function (browser: NightwatchBrowser) { 'Should upgrade contract by selecting a previously deployed proxy address from dropdown (MyTokenV1 to MyTokenV2) #group1': function (browser: NightwatchBrowser) {
browser browser
.waitForElementPresent('[data-id="deployAndRunClearInstances"]') .waitForElementPresent('[data-id="deployAndRunClearInstances"]')
.click('[data-id="deployAndRunClearInstances"]') .click('[data-id="deployAndRunClearInstances"]')
@ -171,10 +175,12 @@ module.exports = {
.click('select.udapp_contractNames option[value=MyTokenV2]') .click('select.udapp_contractNames option[value=MyTokenV2]')
.waitForElementPresent('[data-id="contractGUIUpgradeImplementationLabel"]') .waitForElementPresent('[data-id="contractGUIUpgradeImplementationLabel"]')
.click('[data-id="contractGUIUpgradeImplementationLabel"]') .click('[data-id="contractGUIUpgradeImplementationLabel"]')
.waitForElementPresent('[data-id="contractGUIProxyAddressLabel"]') .waitForElementPresent('[data-id="toggleProxyAddressDropdown"]')
.click('[data-id="contractGUIProxyAddressLabel"]') .click('[data-id="toggleProxyAddressDropdown"]')
.waitForElementPresent('[data-id="lastDeployedERC1967Address"]') .waitForElementVisible('[data-id="proxy-dropdown-items"]')
.assert.containsText('[data-id="lastDeployedERC1967Address"]', lastProxyAddress) .assert.textContains('[data-id="proxy-dropdown-items"]', shortenedFirstAddress)
.assert.textContains('[data-id="proxy-dropdown-items"]', shortenedLastAddress)
.click('[data-id="proxyAddress1"]')
.createContract('') .createContract('')
.waitForElementContainsText('[data-id="udappNotifyModalDialogModalTitle-react"]', 'Deploy Implementation & Update Proxy') .waitForElementContainsText('[data-id="udappNotifyModalDialogModalTitle-react"]', 'Deploy Implementation & Update Proxy')
.waitForElementVisible('[data-id="udappNotify-modal-footer-ok-react"]') .waitForElementVisible('[data-id="udappNotify-modal-footer-ok-react"]')
@ -212,9 +218,8 @@ module.exports = {
.click('select.udapp_contractNames') .click('select.udapp_contractNames')
.click('select.udapp_contractNames option[value=MyTokenV2]') .click('select.udapp_contractNames option[value=MyTokenV2]')
.waitForElementPresent('[data-id="contractGUIUpgradeImplementationLabel"]') .waitForElementPresent('[data-id="contractGUIUpgradeImplementationLabel"]')
.waitForElementPresent('[data-id="contractGUIProxyAddressLabel"]') .waitForElementPresent('[data-id="toggleProxyAddressDropdown"]')
.click('[data-id="contractGUIProxyAddressLabel"]') .clearValue('[data-id="ERC1967AddressInput"]')
.waitForElementPresent('[data-id="ERC1967AddressInput"]')
.setValue('[data-id="ERC1967AddressInput"]', firstProxyAddress) .setValue('[data-id="ERC1967AddressInput"]', firstProxyAddress)
.createContract('') .createContract('')
.waitForElementContainsText('[data-id="udappNotifyModalDialogModalTitle-react"]', 'Deploy Implementation & Update Proxy') .waitForElementContainsText('[data-id="udappNotifyModalDialogModalTitle-react"]', 'Deploy Implementation & Update Proxy')

@ -35,7 +35,7 @@ export const Preload = () => {
}) })
}).catch(err => { }).catch(err => {
_paq.push(['trackEvent', 'Preload', 'error', err && err.message]) _paq.push(['trackEvent', 'Preload', 'error', err && err.message])
console.log('Error loading Remix:', err) console.error('Error loading Remix:', err)
setError(true) setError(true)
}) })
} }

@ -13,7 +13,7 @@ const profile = {
name: 'editor', name: 'editor',
description: 'service - editor', description: 'service - editor',
version: packageJson.version, version: packageJson.version,
methods: ['highlight', 'discardHighlight', 'clearAnnotations', 'addLineText', 'discardLineTexts', 'addAnnotation', 'gotoLine', 'revealRange', 'getCursorPosition', 'open', 'addModel','addErrorMarker', 'clearErrorMarkers'], methods: ['highlight', 'discardHighlight', 'clearAnnotations', 'addLineText', 'discardLineTexts', 'addAnnotation', 'gotoLine', 'revealRange', 'getCursorPosition', 'open', 'addModel','addErrorMarker', 'clearErrorMarkers', 'getText'],
} }
class Editor extends Plugin { class Editor extends Plugin {
@ -280,11 +280,22 @@ class Editor extends Plugin {
/** /**
* Set the text in the current session, if any. * Set the text in the current session, if any.
* @param {string} url Address of the text to replace.
* @param {string} text New text to be place. * @param {string} text New text to be place.
*/ */
setText (text) { setText (url, text) {
if (this.currentFile && this.sessions[this.currentFile]) { if (this.sessions[url]) {
this.sessions[this.currentFile].setValue(text) this.sessions[url].setValue(text)
}
}
/**
* Get the text in the current session, if any.
* @param {string} url Address of the content to retrieve.
*/
getText (url) {
if (this.sessions[url]) {
return this.sessions[url].getValue()
} }
} }

@ -795,7 +795,7 @@ class FileManager extends Plugin {
if (provider) { if (provider) {
try{ try{
const content = await provider.get(currentFile) const content = await provider.get(currentFile)
if(content) this.editor.setText(content) if(content) this.editor.setText(currentFile, content)
}catch(error){ }catch(error){
console.log(error) console.log(error)
} }

@ -3,7 +3,7 @@ import { ViewPlugin } from '@remixproject/engine-web'
import React from 'react' import React from 'react'
// eslint-disable-next-line @nrwl/nx/enforce-module-boundaries // eslint-disable-next-line @nrwl/nx/enforce-module-boundaries
import { RemixUiSolidityUmlGen } from '@remix-ui/solidity-uml-gen' import { RemixUiSolidityUmlGen } from '@remix-ui/solidity-uml-gen'
import { ISolidityUmlGen } from 'libs/remix-ui/solidity-uml-gen/src/types' import { ISolidityUmlGen, ThemeQualityType, ThemeSummary } from 'libs/remix-ui/solidity-uml-gen/src/types'
import { RemixAppManager } from 'libs/remix-ui/plugin-manager/src/types' import { RemixAppManager } from 'libs/remix-ui/plugin-manager/src/types'
import { concatSourceFiles, getDependencyGraph } from 'libs/remix-ui/solidity-compiler/src/lib/logic/flattenerUtilities' import { concatSourceFiles, getDependencyGraph } from 'libs/remix-ui/solidity-compiler/src/lib/logic/flattenerUtilities'
import { convertUmlClasses2Dot } from 'sol2uml/lib/converterClasses2Dot' import { convertUmlClasses2Dot } from 'sol2uml/lib/converterClasses2Dot'
@ -22,13 +22,33 @@ const profile = {
events: [], events: [],
} }
const themeCollection = [
{ themeName: 'HackerOwl', backgroundColor: '--body-bg', actualHex: '#011628'},
{ themeName: 'Cerulean', backgroundColor: '--body-bg', actualHex: '#fff'},
{ themeName: 'Cyborg', backgroundColor: '--body-bg', actualHex: '#060606'},
{ themeName: 'Dark', backgroundColor: '--body-bg', actualHex: '#222336'},
{ themeName: 'Flatly', backgroundColor: '--body-bg', actualHex: '#fff'},
{ themeName: 'Black', backgroundColor: '--body-bg', actualHex: '#1a1a1a'},
{ themeName: 'Light', backgroundColor: '--body-bg', actualHex: '#eef1f6'},
{ themeName: 'Midcentuary', backgroundColor: '--body-bg', actualHex: '#DBE2E0'},
{ themeName: 'Spacelab', backgroundColor: '--body-bg', actualHex: '#fff'},
{ themeName: 'Candy', backgroundColor: '--body-bg', actualHex: '#d5efff'},
]
/**
* add context menu which will offer download as pdf and download png.
* add menu under the first download button to download
*/
export class SolidityUmlGen extends ViewPlugin implements ISolidityUmlGen { export class SolidityUmlGen extends ViewPlugin implements ISolidityUmlGen {
element: HTMLDivElement element: HTMLDivElement
currentFile: string currentFile: string
svgPayload: string svgPayload: string
updatedSvg: string updatedSvg: string
currentlySelectedTheme: string currentlySelectedTheme: string
themeName: string
loading: boolean loading: boolean
themeCollection: ThemeSummary[]
appManager: RemixAppManager appManager: RemixAppManager
dispatch: React.Dispatch<any> = () => {} dispatch: React.Dispatch<any> = () => {}
@ -39,14 +59,18 @@ export class SolidityUmlGen extends ViewPlugin implements ISolidityUmlGen {
this.updatedSvg = '' this.updatedSvg = ''
this.loading = false this.loading = false
this.currentlySelectedTheme = '' this.currentlySelectedTheme = ''
this.themeName = ''
this.themeCollection = themeCollection
this.appManager = appManager this.appManager = appManager
this.element = document.createElement('div') this.element = document.createElement('div')
this.element.setAttribute('id', 'sol-uml-gen') this.element.setAttribute('id', 'sol-uml-gen')
} }
onActivation(): void { onActivation(): void {
if (this.currentFile.length < 1)
this.on('solidity', 'compilationFinished', async (file, source, languageVersion, data, input, version) => { this.on('solidity', 'compilationFinished', async (file, source, languageVersion, data, input, version) => {
const currentTheme: ThemeQualityType = await this.call('theme', 'currentTheme')
this.currentlySelectedTheme = currentTheme.quality
this.themeName = currentTheme.name
let result = '' let result = ''
try { try {
if (data.sources && Object.keys(data.sources).length > 1) { // we should flatten first as there are multiple asts if (data.sources && Object.keys(data.sources).length > 1) { // we should flatten first as there are multiple asts
@ -56,12 +80,11 @@ export class SolidityUmlGen extends ViewPlugin implements ISolidityUmlGen {
const umlClasses = convertAST2UmlClasses(ast, this.currentFile) const umlClasses = convertAST2UmlClasses(ast, this.currentFile)
const umlDot = convertUmlClasses2Dot(umlClasses) const umlDot = convertUmlClasses2Dot(umlClasses)
const payload = vizRenderStringSync(umlDot) const payload = vizRenderStringSync(umlDot)
const currentTheme = await this.call('theme', 'currentTheme')
this.currentlySelectedTheme = currentTheme.quality
this.updatedSvg = payload this.updatedSvg = payload
this.renderComponent() this.renderComponent()
await this.call('tabs', 'focus', 'solidityumlgen')
} catch (error) { } catch (error) {
console.log({ error }) console.log('error', error)
} }
}) })
this.on('theme', 'themeChanged', (theme) => { this.on('theme', 'themeChanged', (theme) => {
@ -72,13 +95,16 @@ export class SolidityUmlGen extends ViewPlugin implements ISolidityUmlGen {
async mangleSvgPayload(svgPayload: string) : Promise<string> { async mangleSvgPayload(svgPayload: string) : Promise<string> {
const parser = new DOMParser() const parser = new DOMParser()
const themeQuality = await this.call('theme', 'currentTheme') const themeQuality: ThemeQualityType = await this.call('theme', 'currentTheme')
const parsedDocument = parser.parseFromString(svgPayload, 'image/svg+xml') const parsedDocument = parser.parseFromString(svgPayload, 'image/svg+xml')
const res = parsedDocument.documentElement const element = parsedDocument.getElementsByTagName('svg')
parsedDocument.bgColor = '#cccabc' themeCollection.forEach((theme) => {
res.style.filter = themeQuality.quality === 'dark' ? 'invert(1)' : 'invert(0)' if (theme.themeName === themeQuality.name) {
parsedDocument.documentElement.setAttribute('style', `background-color: var(${themeQuality.name === theme.themeName ? theme.backgroundColor : '--body-bg'})`)
element[0].setAttribute('fill', theme.actualHex)
}
})
const stringifiedSvg = new XMLSerializer().serializeToString(parsedDocument) const stringifiedSvg = new XMLSerializer().serializeToString(parsedDocument)
console.log({ parsedDocument, themeQuality, stringifiedSvg })
return stringifiedSvg return stringifiedSvg
} }
@ -87,6 +113,7 @@ export class SolidityUmlGen extends ViewPlugin implements ISolidityUmlGen {
} }
generateCustomAction = async (action: customAction) => { generateCustomAction = async (action: customAction) => {
this.updatedSvg = this.updatedSvg.startsWith('<?xml') ? '' : this.updatedSvg
this.currentFile = action.path[0] this.currentFile = action.path[0]
await this.generateUml(action.path[0]) await this.generateUml(action.path[0])
} }
@ -105,14 +132,11 @@ export class SolidityUmlGen extends ViewPlugin implements ISolidityUmlGen {
* @returns {Promise<string>} * @returns {Promise<string>}
*/ */
async flattenContract (source: any, filePath: string, data: any) { async flattenContract (source: any, filePath: string, data: any) {
const hold = { data, source, filePath } const dependencyGraph = getDependencyGraph(data.sources, filePath)
const ast = data.sources
const dependencyGraph = getDependencyGraph(ast, filePath)
const sorted = dependencyGraph.isEmpty() const sorted = dependencyGraph.isEmpty()
? [filePath] ? [filePath]
: dependencyGraph.sort().reverse() : dependencyGraph.sort().reverse()
const sources = source.sources const result = concatSourceFiles(sorted, source.sources)
const result = concatSourceFiles(sorted, sources)
await this.call('fileManager', 'writeFile', `${filePath}_flattened.sol`, result) await this.call('fileManager', 'writeFile', `${filePath}_flattened.sol`, result)
return result return result
} }
@ -143,16 +167,19 @@ export class SolidityUmlGen extends ViewPlugin implements ISolidityUmlGen {
...this, ...this,
updatedSvg: this.updatedSvg, updatedSvg: this.updatedSvg,
loading: this.loading, loading: this.loading,
themeSelected: this.currentlySelectedTheme themeSelected: this.currentlySelectedTheme,
themeName: this.themeName,
themeCollection: this.themeCollection
}) })
} }
updateComponent(state: any) { updateComponent(state: any) {
return <RemixUiSolidityUmlGen return <RemixUiSolidityUmlGen
plugin={state}
updatedSvg={state.updatedSvg} updatedSvg={state.updatedSvg}
loading={state.loading} loading={state.loading}
themeSelected={state.currentlySelectedTheme} themeSelected={state.currentlySelectedTheme}
themeName={state.themeName}
themeCollection={state.themeCollection}
/> />
} }
} }

@ -180,9 +180,8 @@ export class Blockchain extends Plugin {
_paq.push(['trackEvent', 'blockchain', 'Deploy With Proxy', 'Proxy deployment failed: ' + error]) _paq.push(['trackEvent', 'blockchain', 'Deploy With Proxy', 'Proxy deployment failed: ' + error])
return this.call('terminal', 'logHtml', log) return this.call('terminal', 'logHtml', log)
} }
if (networkInfo.name === 'VM') this.config.set('vm/proxy', address)
else this.config.set(`${networkInfo.name}/${networkInfo.currentFork}/${networkInfo.id}/proxy`, address)
await this.saveDeployedContractStorageLayout(implementationContractObject, address, networkInfo) await this.saveDeployedContractStorageLayout(implementationContractObject, address, networkInfo)
this.events.emit('newProxyDeployment', address, new Date().toISOString(), implementationContractObject.contractName)
_paq.push(['trackEvent', 'blockchain', 'Deploy With Proxy', 'Proxy deployment successful']) _paq.push(['trackEvent', 'blockchain', 'Deploy With Proxy', 'Proxy deployment successful'])
this.call('udapp', 'addInstance', addressToString(address), implementationContractObject.abi, implementationContractObject.name) this.call('udapp', 'addInstance', addressToString(address), implementationContractObject.abi, implementationContractObject.name)
} }
@ -236,23 +235,40 @@ export class Blockchain extends Plugin {
} }
async saveDeployedContractStorageLayout (contractObject, proxyAddress, networkInfo) { async saveDeployedContractStorageLayout (contractObject, proxyAddress, networkInfo) {
const { contractName, implementationAddress, contract } = contractObject const { contractName, implementationAddress } = contractObject
const hasPreviousDeploys = await this.call('fileManager', 'exists', `.deploys/upgradeable-contracts/${networkInfo.name}/UUPS.json`) const networkName = networkInfo.name === 'custom' ? networkInfo.name + '-' + networkInfo.id : networkInfo.name
const hasPreviousDeploys = await this.call('fileManager', 'exists', `.deploys/upgradeable-contracts/${networkName}/UUPS.json`)
// TODO: make deploys folder read only. // TODO: make deploys folder read only.
if (hasPreviousDeploys) { if (hasPreviousDeploys) {
const deployments = await this.call('fileManager', 'readFile', `.deploys/upgradeable-contracts/${networkInfo.name}/UUPS.json`) const deployments = await this.call('fileManager', 'readFile', `.deploys/upgradeable-contracts/${networkName}/UUPS.json`)
const parsedDeployments = JSON.parse(deployments) const parsedDeployments = JSON.parse(deployments)
const proxyDeployment = parsedDeployments.deployments[proxyAddress]
if (proxyDeployment) {
const oldImplementationAddress = proxyDeployment.implementationAddress
const hasPreviousBuild = await this.call('fileManager', 'exists', `.deploys/upgradeable-contracts/${networkName}/solc-${oldImplementationAddress}.json`)
if (hasPreviousBuild) await this.call('fileManager', 'remove', `.deploys/upgradeable-contracts/${networkName}/solc-${oldImplementationAddress}.json`)
}
parsedDeployments.deployments[proxyAddress] = { parsedDeployments.deployments[proxyAddress] = {
date: new Date().toISOString(), date: new Date().toISOString(),
contractName: contractName, contractName: contractName,
fork: networkInfo.currentFork, fork: networkInfo.currentFork,
implementationAddress: implementationAddress, implementationAddress: implementationAddress,
layout: contract.object.storageLayout solcOutput: contractObject.compiler.data,
solcInput: contractObject.compiler.source
} }
await this.call('fileManager', 'writeFile', `.deploys/upgradeable-contracts/${networkInfo.name}/UUPS.json`, JSON.stringify(parsedDeployments, null, 2)) await this.call('fileManager', 'writeFile', `.deploys/upgradeable-contracts/${networkName}/solc-${implementationAddress}.json`, JSON.stringify({
solcInput: contractObject.compiler.source,
solcOutput: contractObject.compiler.data
}, null, 2))
await this.call('fileManager', 'writeFile', `.deploys/upgradeable-contracts/${networkName}/UUPS.json`, JSON.stringify(parsedDeployments, null, 2))
} else { } else {
await this.call('fileManager', 'writeFile', `.deploys/upgradeable-contracts/${networkInfo.name}/UUPS.json`, JSON.stringify({ await this.call('fileManager', 'writeFile', `.deploys/upgradeable-contracts/${networkName}/solc-${implementationAddress}.json`, JSON.stringify({
solcInput: contractObject.compiler.source,
solcOutput: contractObject.compiler.data
}, null, 2))
await this.call('fileManager', 'writeFile', `.deploys/upgradeable-contracts/${networkName}/UUPS.json`, JSON.stringify({
id: networkInfo.id, id: networkInfo.id,
network: networkInfo.name, network: networkInfo.name,
deployments: { deployments: {
@ -260,8 +276,7 @@ export class Blockchain extends Plugin {
date: new Date().toISOString(), date: new Date().toISOString(),
contractName: contractName, contractName: contractName,
fork: networkInfo.currentFork, fork: networkInfo.currentFork,
implementationAddress: implementationAddress, implementationAddress: implementationAddress
layout: contract.object.storageLayout
} }
} }
}, null, 2)) }, null, 2))

@ -1,3 +1,4 @@
import { SolcInput, SolcOutput } from "@openzeppelin/upgrades-core"
export interface FuncABI { export interface FuncABI {
name: string, name: string,
type: string, type: string,
@ -183,3 +184,21 @@ export interface ContractSources {
} }
} }
} }
export interface NetworkDeploymentFile {
id: string,
network: string,
deployments: {
[proxyAddress: string]: {
date: Date,
contractName: string,
fork: string,
implementationAddress: string
}
}[]
}
export interface SolcBuildFile {
solcInput: SolcInput,
solcOutput: SolcOutput
}

@ -55,3 +55,46 @@ export const CustomMenu = React.forwardRef(
) )
}, },
) )
export const ProxyAddressToggle = React.forwardRef(({ address, onClick, className = '', onChange }: { address: string, onClick: (e) => void, className: string, onChange: (e: React.ChangeEvent<HTMLInputElement>) => void }, ref: Ref<HTMLDivElement>) => (
<div
ref={ref}
onClick={(e) => {
e.preventDefault()
onClick(e)
}}
className={'d-flex '+ className.replace('dropdown-toggle', '')}
data-id="toggleProxyAddressDropdown"
>
<input
onChange={(e) => {
e.preventDefault()
onChange(e)
}}
className="udapp_input form-control"
value={address}
placeholder="Enter Proxy Address"
style={{ width: '100%' }}
data-id="ERC1967AddressInput"
/>
</div>
))
export const ProxyDropdownMenu = React.forwardRef(
({ children, style, className, 'aria-labelledby': labeledBy }: { children: React.ReactNode, style?: React.CSSProperties, className: string, 'aria-labelledby'?: string }, ref: Ref<HTMLDivElement>) => {
return (
<div
ref={ref}
style={style}
className={className}
aria-labelledby={labeledBy}
>
<ul className="list-unstyled mb-0">
{
children
}
</ul>
</div>
)
},
)

@ -1,3 +1,4 @@
import { LayoutCompatibilityReport } from '@openzeppelin/upgrades-core/dist/storage/report'
import React from 'react' import React from 'react'
export const fileChangedToastMsg = (from: string, path: string) => ( export const fileChangedToastMsg = (from: string, path: string) => (
@ -115,3 +116,27 @@ export const upgradeWithProxyMsg = () => (
</ol> </ol>
</div> </div>
) )
export const unavailableProxyLayoutMsg = () => (
<div>
<p>Previous contract implementation is NOT available for upgrade comparison. <br /> A new storage layout will be saved for future upgrades.</p>
</div>
)
export const upgradeReportMsg = (report: LayoutCompatibilityReport) => (
<div>
<div className="py-2 ml-2 mb-1 align-self-end mb-2 d-flex">
<span className="align-self-center pl-4 mt-1">
<i className="pr-2 text-warning far fa-exclamation-triangle" aria-hidden="true" style={{ fontSize: 'xxx-large', fontWeight: 'lighter' }}></i>
</span>
<div className="d-flex flex-column">
<span className="pl-4 mt-1">The storage layout of new implementation is NOT</span>
<span className="pl-4 mt-1">compatible with the previous implementation.</span>
<span className="pl-4 mt-1">Your contract's storage may be partially or fully erased!</span>
</div>
</div>
<div className='pl-4 text-danger'>
{ report.explain() }
</div>
</div>
)

@ -128,3 +128,15 @@ export const addSlash = (file: string) => {
if (!file.startsWith('/'))file = '/' + file if (!file.startsWith('/'))file = '/' + file
return file return file
} }
export const shortenProxyAddress = (address: string) => {
const len = address.length
return address.slice(0, 5) + '...' + address.slice(len - 5, len)
}
export const shortenDate = (dateString: string) => {
const date = new Date(dateString)
return date.toLocaleDateString(undefined, { month: "short", day: "numeric" }) + ', ' + date.toLocaleTimeString(undefined, { hour: '2-digit', minute: '2-digit' })
}

@ -98,7 +98,7 @@ export const ModalDialog = (props: ModalDialogProps) => {
{/* todo add autofocus ^^ */} {/* todo add autofocus ^^ */}
{ props.okLabel && <button { props.okLabel && <button
data-id={`${props.id}-modal-footer-ok-react`} data-id={`${props.id}-modal-footer-ok-react`}
className={'modal-ok btn btn-sm ' + (state.toggleBtn ? 'btn-dark' : 'btn-light')} className={'modal-ok btn btn-sm ' + (props.okBtnClass ? props.okBtnClass : state.toggleBtn ? 'btn-dark' : 'btn-light')}
disabled={props.validation && !props.validation.valid} disabled={props.validation && !props.validation.valid}
onClick={() => { onClick={() => {
if (props.validation && !props.validation.valid) return if (props.validation && !props.validation.valid) return
@ -111,7 +111,7 @@ export const ModalDialog = (props: ModalDialogProps) => {
} }
{ props.cancelLabel && <button { props.cancelLabel && <button
data-id={`${props.id}-modal-footer-cancel-react`} data-id={`${props.id}-modal-footer-cancel-react`}
className={'modal-cancel btn btn-sm ' + (state.toggleBtn ? 'btn-light' : 'btn-dark')} className={'modal-cancel btn btn-sm ' + (props.cancelBtnClass ? props.cancelBtnClass : state.toggleBtn ? 'btn-light' : 'btn-dark')}
data-dismiss="modal" data-dismiss="modal"
onClick={() => { onClick={() => {
if (props.cancelFn) props.cancelFn() if (props.cancelFn) props.cancelFn()

@ -22,5 +22,7 @@ export interface ModalDialogProps {
children?: React.ReactNode, children?: React.ReactNode,
resolve?: (value?:any) => void, resolve?: (value?:any) => void,
next?: () => void, next?: () => void,
data?: any data?: any,
okBtnClass?: string,
cancelBtnClass?: string
} }

@ -1,5 +1,5 @@
import { ContractData } from "@remix-project/core-plugin" import { ContractData } from "@remix-project/core-plugin"
import { addNewInstance, addProvider, clearAllInstances, clearRecorderCount, hidePopUp, removeExistingInstance, removeProvider, setBaseFeePerGas, setConfirmSettings, setCurrentContract, setExecutionEnvironment, setExternalEndpoint, setGasLimit, setGasPrice, setGasPriceStatus, setMatchPassphrase, setMaxFee, setMaxPriorityFee, setNetworkName, setPassphrase, setPathToScenario, setSelectedAccount, setSendUnit, setSendValue } from "./payload" import { addNewInstance, addProvider, clearAllInstances, clearRecorderCount, hidePopUp, newProxyDeployment, removeExistingInstance, removeProvider, setBaseFeePerGas, setConfirmSettings, setCurrentContract, setExecutionEnvironment, setExternalEndpoint, setGasLimit, setGasPrice, setGasPriceStatus, setMatchPassphrase, setMaxFee, setMaxPriorityFee, setNetworkName, setPassphrase, setPathToScenario, setSelectedAccount, setSendUnit, setSendValue } from "./payload"
export const setAccount = (dispatch: React.Dispatch<any>, account: string) => { export const setAccount = (dispatch: React.Dispatch<any>, account: string) => {
dispatch(setSelectedAccount(account)) dispatch(setSelectedAccount(account))
@ -90,3 +90,7 @@ export const updateScenarioPath = (dispatch: React.Dispatch<any>, path: string)
export const setSendTransactionValue = (dispatch: React.Dispatch<any>, value: string) => { export const setSendTransactionValue = (dispatch: React.Dispatch<any>, value: string) => {
dispatch(setSendValue(value)) dispatch(setSendValue(value))
} }
export const addNewProxyDeployment = (dispatch: React.Dispatch<any>, address: string, date: string, contractName: string) => {
dispatch(newProxyDeployment({ address, date, contractName }))
}

@ -1,9 +1,12 @@
import { ContractData, FuncABI } from "@remix-project/core-plugin" import { ContractData, FuncABI, NetworkDeploymentFile, SolcBuildFile } from "@remix-project/core-plugin"
import { RunTab } from "../types/run-tab" import { RunTab } from "../types/run-tab"
import { CompilerAbstract as CompilerAbstractType } from '@remix-project/remix-solidity' import { CompilerAbstract as CompilerAbstractType } from '@remix-project/remix-solidity'
import * as remixLib from '@remix-project/remix-lib' import * as remixLib from '@remix-project/remix-lib'
import { SolcInput, SolcOutput } from "@openzeppelin/upgrades-core"
// Used direct path to UpgradeableContract class to fix cyclic dependency error from @openzeppelin/upgrades-core library
import { UpgradeableContract } from '../../../../../../node_modules/@openzeppelin/upgrades-core/dist/standalone'
import { DeployMode, MainnetPrompt } from "../types" import { DeployMode, MainnetPrompt } from "../types"
import { displayNotification, displayPopUp, setDecodedResponse } from "./payload" import { displayNotification, displayPopUp, fetchProxyDeploymentsSuccess, setDecodedResponse } from "./payload"
import { addInstance } from "./actions" import { addInstance } from "./actions"
import { addressToString, logBuilder } from "@remix-ui/helper" import { addressToString, logBuilder } from "@remix-ui/helper"
import Web3 from "web3" import Web3 from "web3"
@ -336,3 +339,56 @@ export const isValidContractAddress = async (plugin: RunTab, address: string) =>
} }
} }
} }
export const getNetworkProxyAddresses = async (plugin: RunTab, dispatch: React.Dispatch<any>) => {
const network = plugin.blockchain.networkStatus.network
const identifier = network.name === 'custom' ? network.name + '-' + network.id : network.name
const networkDeploymentsExists = await plugin.call('fileManager', 'exists', `.deploys/upgradeable-contracts/${identifier}/UUPS.json`)
if (networkDeploymentsExists) {
const networkFile: string = await plugin.call('fileManager', 'readFile', `.deploys/upgradeable-contracts/${identifier}/UUPS.json`)
const parsedNetworkFile: NetworkDeploymentFile = JSON.parse(networkFile)
const deployments = []
for (const proxyAddress in Object.keys(parsedNetworkFile.deployments)) {
const solcBuildExists = await plugin.call('fileManager', 'exists', `.deploys/upgradeable-contracts/${identifier}/solc-${parsedNetworkFile.deployments[proxyAddress].implementationAddress}.json`)
if (solcBuildExists) deployments.push({ address: proxyAddress, date: parsedNetworkFile.deployments[proxyAddress].date, contractName: parsedNetworkFile.deployments[proxyAddress].contractName })
}
dispatch(fetchProxyDeploymentsSuccess(deployments))
} else {
dispatch(fetchProxyDeploymentsSuccess([]))
}
}
export const isValidContractUpgrade = async (plugin: RunTab, proxyAddress: string, newContractName: string, solcInput: SolcInput, solcOutput: SolcOutput) => {
// build current contract first to get artefacts.
const network = plugin.blockchain.networkStatus.network
const identifier = network.name === 'custom' ? network.name + '-' + network.id : network.name
const networkDeploymentsExists = await plugin.call('fileManager', 'exists', `.deploys/upgradeable-contracts/${identifier}/UUPS.json`)
if (networkDeploymentsExists) {
const networkFile: string = await plugin.call('fileManager', 'readFile', `.deploys/upgradeable-contracts/${identifier}/UUPS.json`)
const parsedNetworkFile: NetworkDeploymentFile = JSON.parse(networkFile)
if (parsedNetworkFile.deployments[proxyAddress]) {
const solcBuildExists = await plugin.call('fileManager', 'exists', `.deploys/upgradeable-contracts/${identifier}/solc-${parsedNetworkFile.deployments[proxyAddress].implementationAddress}.json`)
if (solcBuildExists) {
const solcFile: string = await plugin.call('fileManager', 'readFile', `.deploys/upgradeable-contracts/${identifier}/solc-${parsedNetworkFile.deployments[proxyAddress].implementationAddress}.json`)
const parsedSolcFile: SolcBuildFile = JSON.parse(solcFile)
const oldImpl = new UpgradeableContract(parsedNetworkFile.deployments[proxyAddress].contractName, parsedSolcFile.solcInput, parsedSolcFile.solcOutput, { kind: 'uups' })
const newImpl = new UpgradeableContract(newContractName, solcInput, solcOutput, { kind: 'uups' })
const report = oldImpl.getStorageUpgradeReport(newImpl, { kind: 'uups' })
return report
} else {
return { ok: false, pass: false, warning: true }
}
} else {
return { ok: false, pass: false, warning: true }
}
} else {
return { ok: false, pass: false, warning: true }
}
}

@ -1,12 +1,14 @@
import { envChangeNotification } from "@remix-ui/helper" import { envChangeNotification } from "@remix-ui/helper"
import { RunTab } from "../types/run-tab" import { RunTab } from "../types/run-tab"
import { setExecutionContext, setFinalContext, updateAccountBalances } from "./account" import { setExecutionContext, setFinalContext, updateAccountBalances } from "./account"
import { addExternalProvider, addInstance, removeExternalProvider, setNetworkNameFromProvider } from "./actions" import { addExternalProvider, addInstance, addNewProxyDeployment, removeExternalProvider, setNetworkNameFromProvider } from "./actions"
import { addDeployOption, clearAllInstances, clearRecorderCount, fetchContractListSuccess, resetUdapp, setCurrentContract, setCurrentFile, setLoadType, setProxyEnvAddress, setRecorderCount, setRemixDActivated, setSendValue } from "./payload" import { addDeployOption, clearAllInstances, clearRecorderCount, fetchContractListSuccess, resetProxyDeployments, resetUdapp, setCurrentContract, setCurrentFile, setLoadType, setRecorderCount, setRemixDActivated, setSendValue } from "./payload"
import { CompilerAbstract } from '@remix-project/remix-solidity' import { CompilerAbstract } from '@remix-project/remix-solidity'
import BN from 'bn.js' import BN from 'bn.js'
import Web3 from 'web3' import Web3 from 'web3'
import { Plugin } from "@remixproject/engine" import { Plugin } from "@remixproject/engine"
import { getNetworkProxyAddresses } from "./deploy"
const _paq = window._paq = window._paq || [] const _paq = window._paq = window._paq || []
export const setupEvents = (plugin: RunTab, dispatch: React.Dispatch<any>) => { export const setupEvents = (plugin: RunTab, dispatch: React.Dispatch<any>) => {
@ -21,6 +23,8 @@ export const setupEvents = (plugin: RunTab, dispatch: React.Dispatch<any>) => {
}) })
plugin.blockchain.event.register('contextChanged', (context, silent) => { plugin.blockchain.event.register('contextChanged', (context, silent) => {
dispatch(resetProxyDeployments())
if (!context.startsWith('vm')) getNetworkProxyAddresses(plugin, dispatch)
setFinalContext(plugin, dispatch) setFinalContext(plugin, dispatch)
}) })
@ -35,14 +39,14 @@ export const setupEvents = (plugin: RunTab, dispatch: React.Dispatch<any>) => {
const netUI = !networkProvider().startsWith('vm') ? `${network.name} (${network.id || '-'}) network` : 'VM' const netUI = !networkProvider().startsWith('vm') ? `${network.name} (${network.id || '-'}) network` : 'VM'
setNetworkNameFromProvider(dispatch, netUI) setNetworkNameFromProvider(dispatch, netUI)
if (network.name === 'VM') dispatch(setProxyEnvAddress(plugin.config.get('vm/proxy')))
else dispatch(setProxyEnvAddress(plugin.config.get(`${network.name}/${network.currentFork}/${network.id}/proxy`)))
}) })
plugin.blockchain.event.register('addProvider', provider => addExternalProvider(dispatch, provider)) plugin.blockchain.event.register('addProvider', provider => addExternalProvider(dispatch, provider))
plugin.blockchain.event.register('removeProvider', name => removeExternalProvider(dispatch, name)) plugin.blockchain.event.register('removeProvider', name => removeExternalProvider(dispatch, name))
plugin.blockchain.events.on('newProxyDeployment', (address, date, contractName) => addNewProxyDeployment(dispatch, address, date, contractName))
plugin.on('solidity', 'compilationFinished', (file, source, languageVersion, data, input, version) => broadcastCompilationResult('remix', plugin, dispatch, file, source, languageVersion, data, input)) plugin.on('solidity', 'compilationFinished', (file, source, languageVersion, data, input, version) => broadcastCompilationResult('remix', plugin, dispatch, file, source, languageVersion, data, input))
plugin.on('vyper', 'compilationFinished', (file, source, languageVersion, data) => broadcastCompilationResult('vyper', plugin, dispatch, file, source, languageVersion, data)) plugin.on('vyper', 'compilationFinished', (file, source, languageVersion, data) => broadcastCompilationResult('vyper', plugin, dispatch, file, source, languageVersion, data))

@ -6,11 +6,12 @@ import { createNewBlockchainAccount, fillAccountsList, setExecutionContext, sign
import { clearInstances, clearPopUp, removeInstance, setAccount, setGasFee, setMatchPassphrasePrompt, import { clearInstances, clearPopUp, removeInstance, setAccount, setGasFee, setMatchPassphrasePrompt,
setNetworkNameFromProvider, setPassphrasePrompt, setSelectedContract, setSendTransactionValue, setUnit, setNetworkNameFromProvider, setPassphrasePrompt, setSelectedContract, setSendTransactionValue, setUnit,
updateBaseFeePerGas, updateConfirmSettings, updateGasPrice, updateGasPriceStatus, updateMaxFee, updateMaxPriorityFee, updateScenarioPath } from './actions' updateBaseFeePerGas, updateConfirmSettings, updateGasPrice, updateGasPriceStatus, updateMaxFee, updateMaxPriorityFee, updateScenarioPath } from './actions'
import { createInstance, getContext, getFuncABIInputs, getSelectedContract, loadAddress, runTransactions, updateInstanceBalance, syncContractsInternal, isValidContractAddress } from './deploy' import { createInstance, getContext, getFuncABIInputs, getSelectedContract, loadAddress, runTransactions, updateInstanceBalance, syncContractsInternal, isValidContractAddress, isValidContractUpgrade } from './deploy'
import { CompilerAbstract as CompilerAbstractType } from '@remix-project/remix-solidity' import { CompilerAbstract as CompilerAbstractType } from '@remix-project/remix-solidity'
import { ContractData, FuncABI } from "@remix-project/core-plugin" import { ContractData, FuncABI } from "@remix-project/core-plugin"
import { DeployMode, MainnetPrompt } from '../types' import { DeployMode, MainnetPrompt } from '../types'
import { runCurrentScenario, storeScenario } from './recorder' import { runCurrentScenario, storeScenario } from './recorder'
import { SolcInput, SolcOutput } from '@openzeppelin/upgrades-core'
declare global { declare global {
interface Window { interface Window {
@ -63,3 +64,4 @@ export const setNetworkName = (networkName: string) => setNetworkNameFromProvide
export const updateSelectedContract = (contractName) => setSelectedContract(dispatch, contractName) export const updateSelectedContract = (contractName) => setSelectedContract(dispatch, contractName)
export const syncContracts = () => syncContractsInternal(plugin) export const syncContracts = () => syncContractsInternal(plugin)
export const isValidProxyAddress = (address: string) => isValidContractAddress(plugin, address) export const isValidProxyAddress = (address: string) => isValidContractAddress(plugin, address)
export const isValidProxyUpgrade = (proxyAddress: string, contractName: string, solcInput: SolcInput, solcOuput: SolcOutput) => isValidContractUpgrade(plugin, proxyAddress, contractName, solcInput, solcOuput)

@ -1,7 +1,6 @@
import { ContractList } from '../reducers/runTab'
import { ContractData } from '@remix-project/core-plugin' import { ContractData } from '@remix-project/core-plugin'
import { ADD_DEPLOY_OPTION, ADD_INSTANCE, ADD_PROVIDER, CLEAR_INSTANCES, CLEAR_RECORDER_COUNT, DISPLAY_NOTIFICATION, DISPLAY_POPUP_MESSAGE, FETCH_ACCOUNTS_LIST_FAILED, FETCH_ACCOUNTS_LIST_REQUEST, FETCH_ACCOUNTS_LIST_SUCCESS, FETCH_CONTRACT_LIST_FAILED, FETCH_CONTRACT_LIST_REQUEST, FETCH_CONTRACT_LIST_SUCCESS, HIDE_NOTIFICATION, HIDE_POPUP_MESSAGE, REMOVE_DEPLOY_OPTION, REMOVE_INSTANCE, REMOVE_PROVIDER, RESET_STATE, SET_BASE_FEE_PER_GAS, SET_CONFIRM_SETTINGS, SET_CURRENT_CONTRACT, SET_CURRENT_FILE, SET_DECODED_RESPONSE, SET_DEPLOY_OPTIONS, SET_EXECUTION_ENVIRONMENT, SET_EXTERNAL_WEB3_ENDPOINT, SET_GAS_LIMIT, SET_GAS_PRICE, SET_GAS_PRICE_STATUS, SET_IPFS_CHECKED_STATE, SET_LOAD_TYPE, SET_MATCH_PASSPHRASE, SET_MAX_FEE, SET_MAX_PRIORITY_FEE, SET_NETWORK_NAME, SET_PASSPHRASE, SET_PATH_TO_SCENARIO, SET_PERSONAL_MODE, SET_PROXY_ENV_ADDRESS, SET_RECORDER_COUNT, SET_SELECTED_ACCOUNT, SET_SEND_UNIT, SET_SEND_VALUE, SET_REMIXD_ACTIVATED } from '../constants' import { ADD_DEPLOY_OPTION, ADD_INSTANCE, ADD_PROVIDER, CLEAR_INSTANCES, CLEAR_RECORDER_COUNT, DISPLAY_NOTIFICATION, DISPLAY_POPUP_MESSAGE, FETCH_ACCOUNTS_LIST_FAILED, FETCH_ACCOUNTS_LIST_REQUEST, FETCH_ACCOUNTS_LIST_SUCCESS, FETCH_CONTRACT_LIST_FAILED, FETCH_CONTRACT_LIST_REQUEST, FETCH_CONTRACT_LIST_SUCCESS, HIDE_NOTIFICATION, HIDE_POPUP_MESSAGE, REMOVE_DEPLOY_OPTION, REMOVE_INSTANCE, REMOVE_PROVIDER, RESET_STATE, SET_BASE_FEE_PER_GAS, SET_CONFIRM_SETTINGS, SET_CURRENT_CONTRACT, SET_CURRENT_FILE, SET_DECODED_RESPONSE, SET_DEPLOY_OPTIONS, SET_EXECUTION_ENVIRONMENT, SET_EXTERNAL_WEB3_ENDPOINT, SET_GAS_LIMIT, SET_GAS_PRICE, SET_GAS_PRICE_STATUS, SET_IPFS_CHECKED_STATE, SET_LOAD_TYPE, SET_MATCH_PASSPHRASE, SET_MAX_FEE, SET_MAX_PRIORITY_FEE, SET_NETWORK_NAME, SET_PASSPHRASE, SET_PATH_TO_SCENARIO, SET_PERSONAL_MODE, SET_RECORDER_COUNT, SET_SELECTED_ACCOUNT, SET_SEND_UNIT, SET_SEND_VALUE, SET_REMIXD_ACTIVATED, FETCH_PROXY_DEPLOYMENTS, NEW_PROXY_DEPLOYMENT, RESET_PROXY_DEPLOYMENTS } from '../constants'
import { DeployMode, DeployOptions } from '../types' import { ContractList, DeployOptions } from '../types'
export const fetchAccountsListRequest = () => { export const fetchAccountsListRequest = () => {
return { return {
@ -302,16 +301,29 @@ export const setCurrentContract = (contractName: string) => {
} }
} }
export const setProxyEnvAddress = (key: string) => { export const setRemixDActivated = (activated: boolean) => {
return { return {
payload: key, payload: activated,
type: SET_PROXY_ENV_ADDRESS type: SET_REMIXD_ACTIVATED
} }
} }
export const setRemixDActivated = (activated: boolean) => { export const fetchProxyDeploymentsSuccess = (deployments: { address: string, date: string, contractName: string }[]) => {
return { return {
payload: activated, type: FETCH_PROXY_DEPLOYMENTS,
type: SET_REMIXD_ACTIVATED payload: deployments
}
}
export const newProxyDeployment = (deployment: { address: string, date: string, contractName: string }) => {
return {
type: NEW_PROXY_DEPLOYMENT,
payload: deployment
}
}
export const resetProxyDeployments = () => {
return {
type: RESET_PROXY_DEPLOYMENTS,
} }
} }

@ -32,7 +32,7 @@ export function ContractDropdownUI (props: ContractDropdownProps) {
const [compilerName, setCompilerName] = useState<string>('') const [compilerName, setCompilerName] = useState<string>('')
const contractsRef = useRef<HTMLSelectElement>(null) const contractsRef = useRef<HTMLSelectElement>(null)
const atAddressValue = useRef<HTMLInputElement>(null) const atAddressValue = useRef<HTMLInputElement>(null)
const { contractList, loadType, currentFile, compilationSource, currentContract, compilationCount, deployOptions, proxyKey } = props.contracts const { contractList, loadType, currentFile, compilationSource, currentContract, compilationCount, deployOptions } = props.contracts
useEffect(() => { useEffect(() => {
enableContractNames(Object.keys(props.contracts.contractList).length > 0) enableContractNames(Object.keys(props.contracts.contractList).length > 0)
@ -234,6 +234,10 @@ export function ContractDropdownUI (props: ContractDropdownProps) {
props.setSelectedContract(value) props.setSelectedContract(value)
} }
const isValidProxyUpgrade = (proxyAddress: string) => {
return props.isValidProxyUpgrade(proxyAddress, loadedContractData.name, loadedContractData.compiler.source, loadedContractData.compiler.data)
}
const checkSumWarning = () => { const checkSumWarning = () => {
return ( return (
<span className="text-start"> <span className="text-start">
@ -311,8 +315,10 @@ export function ContractDropdownUI (props: ContractDropdownProps) {
widthClass='w-50' widthClass='w-50'
evmBC={loadedContractData.bytecodeObject} evmBC={loadedContractData.bytecodeObject}
lookupOnly={false} lookupOnly={false}
savedProxyAddress={proxyKey} proxy={props.proxy}
isValidProxyAddress={props.isValidProxyAddress} isValidProxyAddress={props.isValidProxyAddress}
isValidProxyUpgrade={isValidProxyUpgrade}
modal={props.modal}
/> />
<div className="d-flex py-1 align-items-center custom-control custom-checkbox"> <div className="d-flex py-1 align-items-center custom-control custom-checkbox">
<input <input

@ -4,7 +4,8 @@ import { FormattedMessage, useIntl } from 'react-intl'
import * as remixLib from '@remix-project/remix-lib' import * as remixLib from '@remix-project/remix-lib'
import { ContractGUIProps } from '../types' import { ContractGUIProps } from '../types'
import { CopyToClipboard } from '@remix-ui/clipboard' import { CopyToClipboard } from '@remix-ui/clipboard'
import { CustomTooltip } from '@remix-ui/helper' import { CustomTooltip, ProxyAddressToggle, ProxyDropdownMenu, shortenDate, shortenProxyAddress, unavailableProxyLayoutMsg, upgradeReportMsg } from '@remix-ui/helper'
import { Dropdown } from 'react-bootstrap'
const txFormat = remixLib.execution.txFormat const txFormat = remixLib.execution.txFormat
const txHelper = remixLib.execution.txHelper const txHelper = remixLib.execution.txHelper
@ -21,9 +22,9 @@ export function ContractGUI (props: ContractGUIProps) {
const [toggleDeployProxy, setToggleDeployProxy] = useState<boolean>(false) const [toggleDeployProxy, setToggleDeployProxy] = useState<boolean>(false)
const [toggleUpgradeImp, setToggleUpgradeImp] = useState<boolean>(false) const [toggleUpgradeImp, setToggleUpgradeImp] = useState<boolean>(false)
const [deployState, setDeployState] = useState<{ deploy: boolean, upgrade: boolean }>({ deploy: false, upgrade: false }) const [deployState, setDeployState] = useState<{ deploy: boolean, upgrade: boolean }>({ deploy: false, upgrade: false })
const [useLastProxy, setUseLastProxy] = useState<boolean>(false)
const [proxyAddress, setProxyAddress] = useState<string>('') const [proxyAddress, setProxyAddress] = useState<string>('')
const [proxyAddressError, setProxyAddressError] = useState<string>('') const [proxyAddressError, setProxyAddressError] = useState<string>('')
const [showDropdown, setShowDropdown] = useState<boolean>(false)
const multiFields = useRef<Array<HTMLInputElement | null>>([]) const multiFields = useRef<Array<HTMLInputElement | null>>([])
const initializeFields = useRef<Array<HTMLInputElement | null>>([]) const initializeFields = useRef<Array<HTMLInputElement | null>>([])
const basicInputRef = useRef<HTMLInputElement>() const basicInputRef = useRef<HTMLInputElement>()
@ -171,13 +172,38 @@ export function ContractGUI (props: ContractGUIProps) {
} }
} }
const handleActionClick = () => { const handleActionClick = async () => {
if (deployState.deploy) { if (deployState.deploy) {
const proxyInitializeString = getMultiValsString(initializeFields.current) const proxyInitializeString = getMultiValsString(initializeFields.current)
props.clickCallBack(props.initializerOptions.inputs.inputs, proxyInitializeString, ['Deploy with Proxy']) props.clickCallBack(props.initializerOptions.inputs.inputs, proxyInitializeString, ['Deploy with Proxy'])
} else if (deployState.upgrade) { } else if (deployState.upgrade) {
if (proxyAddress === '') {
setProxyAddressError('proxy address cannot be empty')
} else {
const isValidProxyAddress = await props.isValidProxyAddress(proxyAddress)
if (isValidProxyAddress) {
setProxyAddressError('')
const upgradeReport: any = await props.isValidProxyUpgrade(proxyAddress)
if (upgradeReport.ok) {
!proxyAddressError && props.clickCallBack(props.funcABI.inputs, proxyAddress, ['Upgrade with Proxy']) !proxyAddressError && props.clickCallBack(props.funcABI.inputs, proxyAddress, ['Upgrade with Proxy'])
} else {
if (upgradeReport.warning) {
props.modal('Proxy Upgrade Warning', unavailableProxyLayoutMsg(), 'Proceed', () => {
!proxyAddressError && props.clickCallBack(props.funcABI.inputs, proxyAddress, ['Upgrade with Proxy'])
}, 'Cancel', () => {}, 'btn-warning', 'btn-secondary')
} else {
props.modal('Proxy Upgrade Error', upgradeReportMsg(upgradeReport), 'Continue anyway ', () => {
!proxyAddressError && props.clickCallBack(props.funcABI.inputs, proxyAddress, ['Upgrade with Proxy'])
}, 'Cancel', () => {}, 'btn-warning', 'btn-secondary')
}
}
} else {
setProxyAddressError('not a valid contract address')
}
}
} else { } else {
props.clickCallBack(props.funcABI.inputs, basicInput) props.clickCallBack(props.funcABI.inputs, basicInput)
} }
@ -217,43 +243,22 @@ export function ContractGUI (props: ContractGUIProps) {
setToggleUpgradeImp(value) setToggleUpgradeImp(value)
if (value) { if (value) {
setToggleDeployProxy(false) setToggleDeployProxy(false)
if (useLastProxy) setProxyAddress(props.savedProxyAddress)
} }
setDeployState({ deploy: false, upgrade: value }) setDeployState({ deploy: false, upgrade: value })
} }
const handleUseLastProxySelect = (e) => { const switchProxyAddress = (address: string) => {
const value = e.target.checked
const address = props.savedProxyAddress
if (value) {
if (address) {
setProxyAddress(address) setProxyAddress(address)
setProxyAddressError('')
} else {
setProxyAddressError('No proxy address available')
setProxyAddress('')
}
}
setUseLastProxy(value)
} }
const handleSetProxyAddress = (e) => { const toggleDropdown = (isOpen: boolean) => {
const value = e.target.value setShowDropdown(isOpen)
setProxyAddress(value)
} }
const validateProxyAddress = async (address: string) => { const handleAddressChange = (e: React.ChangeEvent<HTMLInputElement>) => {
if (address === '') { const address = e.target.value
setProxyAddressError('proxy address cannot be empty')
} else { setProxyAddress(address)
if (await props.isValidProxyAddress(address)) {
setProxyAddressError('')
} else {
setProxyAddressError('not a valid contract address')
}
}
} }
return ( return (
@ -269,22 +274,23 @@ export function ContractGUI (props: ContractGUIProps) {
<div <div
className="udapp_contractActionsContainerSingle pt-2" className="udapp_contractActionsContainerSingle pt-2"
style={{ display: toggleContainer ? "none" : "flex" }} style={{ display: toggleContainer ? "none" : "flex" }}
>
<CustomTooltip
placement={"right-start"}
tooltipClasses="text-wrap"
tooltipId="remixUdappInstanceButtonTooltip"
tooltipText={buttonOptions.title}
> >
<button <button
onClick={handleActionClick} onClick={handleActionClick}
className={`udapp_instanceButton ${props.widthClass} btn btn-sm ${buttonOptions.classList}`} className={`udapp_instanceButton ${props.widthClass} btn btn-sm ${buttonOptions.classList}`}
data-id={buttonOptions.dataId} data-id={buttonOptions.dataId}
data-title={buttonOptions.title} data-title={buttonOptions.title}
disabled={toggleUpgradeImp && !proxyAddress}
> >
{title} <CustomTooltip
</button> placement={"right-start"}
tooltipClasses="text-wrap"
tooltipId="remixUdappInstanceButtonTooltip"
tooltipText={toggleUpgradeImp && !proxyAddress ? 'Proxy address cannot be empty' : buttonOptions.title}
>
<div>{title}</div>
</CustomTooltip> </CustomTooltip>
</button>
<CustomTooltip <CustomTooltip
placement={"right"} placement={"right"}
tooltipClasses="text-nowrap" tooltipClasses="text-nowrap"
@ -518,45 +524,40 @@ export function ContractGUI (props: ContractGUIProps) {
toggleUpgradeImp ? "d-flex" : "d-none" toggleUpgradeImp ? "d-flex" : "d-none"
}`} }`}
> >
<div className={`flex-column 'd-flex'}`}> <div data-id="proxy-dropdown-items">
<div className="d-flex py-1 align-items-center custom-control custom-checkbox"> <Dropdown onToggle={toggleDropdown} show={showDropdown}>
<input <Dropdown.Toggle id="dropdown-custom-components" as={ProxyAddressToggle} address={proxyAddress} onChange={handleAddressChange} className="d-inline-block border border-dark bg-dark" />
id="proxyAddress"
data-id="contractGUIProxyAddress" { props.proxy.deployments.length > 0 &&
className="form-check-input custom-control-input" <Dropdown.Menu as={ProxyDropdownMenu} className='w-100 custom-dropdown-items' style={{ overflow: 'hidden' }}>
type="checkbox" {
onChange={handleUseLastProxySelect} props.proxy.deployments.map((deployment, index) => (
checked={useLastProxy}
/>
<CustomTooltip <CustomTooltip
tooltipText={<FormattedMessage id='udapp.proxyAddressTooltip' />} placement={"right"}
tooltipId="proxyAddressTooltip" tooltipClasses="text-nowrap"
placement="auto" tooltipId={`proxyAddressTooltip${index}`}
tooltipClasses="text-wrap" tooltipText={'Deployed ' + shortenDate(deployment.date)}
> >
<label <Dropdown.Item
htmlFor="proxyAddress" key={index}
data-id="contractGUIProxyAddressLabel" onClick={() => {
className="m-0 form-check-label custom-control-label udapp_checkboxAlign" switchProxyAddress(deployment.address)
style={{ fontSize: 12 }} }}
data-id={`proxyAddress${index}`}
> >
<FormattedMessage id='udapp.useLastDeployedERC1967Contract' /> <span>{ proxyAddress === deployment.address ? <span>&#10003; { deployment.contractName + ' ' + shortenProxyAddress(deployment.address) } </span> : <span className="pl-3">{ deployment.contractName + ' ' + shortenProxyAddress(deployment.address) }</span> }</span>
</label> </Dropdown.Item>
</CustomTooltip> </CustomTooltip>
))
}
</Dropdown.Menu>
}
</Dropdown>
</div> </div>
{ <div className='d-flex'>
!useLastProxy ?
<div className="mb-2"> <div className="mb-2">
<label className="mt-2 text-left d-block"> { proxyAddressError && <span className='text-lowercase text-danger' data-id="errorMsgProxyAddress" style={{ fontSize: '.8em' }}>{ proxyAddressError }</span> }
<FormattedMessage id='udapp.proxyAddressLabel' /> : </div>
</label>
<CustomTooltip placement="right" tooltipText={<FormattedMessage id='udapp.proxyAddressInputTooltip' />}>
<input style={{ height: 32 }} className="form-control udapp_input" data-id="ERC1967AddressInput" placeholder={intl.formatMessage({ id: 'udapp.proxyAddressPlaceholder' })} onChange={handleSetProxyAddress} onBlur={() => validateProxyAddress(proxyAddress) } />
</CustomTooltip>
{ proxyAddressError && <span className='text-lowercase' data-id="errorMsgProxyAddress" style={{ fontSize: '.8em' }}>{ proxyAddressError }</span> }
</div> :
<span className='text-capitalize' data-id="lastDeployedERC1967Address" style={{ fontSize: '.8em' }}>{ proxyAddress || proxyAddressError }</span>
}
</div> </div>
</div> </div>
</> </>

@ -44,5 +44,7 @@ export const ADD_DEPLOY_OPTION = 'ADD_DEPLOY_OPTION'
export const REMOVE_DEPLOY_OPTION = 'REMOVE_DEPLOY_OPTION' export const REMOVE_DEPLOY_OPTION = 'REMOVE_DEPLOY_OPTION'
export const SET_DEPLOY_OPTIONS = 'SET_DEPLOY_OPTIONS' export const SET_DEPLOY_OPTIONS = 'SET_DEPLOY_OPTIONS'
export const SET_CURRENT_CONTRACT = 'SET_CURRENT_CONTRACT' export const SET_CURRENT_CONTRACT = 'SET_CURRENT_CONTRACT'
export const SET_PROXY_ENV_ADDRESS = 'SET_PROXY_ENV_ADDRESS'
export const SET_REMIXD_ACTIVATED = 'SET_REMIXD_ACTIVATED' export const SET_REMIXD_ACTIVATED = 'SET_REMIXD_ACTIVATED'
export const FETCH_PROXY_DEPLOYMENTS = 'FETCH_PROXY_DEPLOYMENTS'
export const NEW_PROXY_DEPLOYMENT = 'NEW_PROXY_DEPLOYMENT'
export const RESET_PROXY_DEPLOYMENTS = 'RESET_PROXY_DEPLOYMENTS'

@ -362,8 +362,6 @@
} }
.udapp_contractProperty button:disabled { .udapp_contractProperty button:disabled {
cursor: not-allowed; cursor: not-allowed;
background-color: white;
border-color: lightgray;
} }
.udapp_contractProperty.udapp_constant button { .udapp_contractProperty.udapp_constant button {
min-width: 100px; min-width: 100px;

@ -1,108 +1,12 @@
import { CompilerAbstract } from '@remix-project/remix-solidity'
import { ContractData } from '@remix-project/core-plugin' import { ContractData } from '@remix-project/core-plugin'
import { DeployOptions } from '../types' import { ContractList, DeployOptions, RunTabState } from '../types'
import { ADD_INSTANCE, ADD_PROVIDER, CLEAR_INSTANCES, CLEAR_RECORDER_COUNT, DISPLAY_NOTIFICATION, DISPLAY_POPUP_MESSAGE, FETCH_ACCOUNTS_LIST_FAILED, FETCH_ACCOUNTS_LIST_REQUEST, FETCH_ACCOUNTS_LIST_SUCCESS, FETCH_CONTRACT_LIST_FAILED, FETCH_CONTRACT_LIST_REQUEST, FETCH_CONTRACT_LIST_SUCCESS, FETCH_PROVIDER_LIST_FAILED, FETCH_PROVIDER_LIST_REQUEST, FETCH_PROVIDER_LIST_SUCCESS, HIDE_NOTIFICATION, HIDE_POPUP_MESSAGE, REMOVE_INSTANCE, REMOVE_PROVIDER, RESET_STATE, SET_BASE_FEE_PER_GAS, SET_CONFIRM_SETTINGS, SET_CURRENT_CONTRACT, SET_CURRENT_FILE, SET_DECODED_RESPONSE, SET_DEPLOY_OPTIONS, SET_EXECUTION_ENVIRONMENT, SET_EXTERNAL_WEB3_ENDPOINT, SET_GAS_LIMIT, SET_GAS_PRICE, SET_GAS_PRICE_STATUS, SET_IPFS_CHECKED_STATE, SET_LOAD_TYPE, SET_MATCH_PASSPHRASE, SET_MAX_FEE, SET_MAX_PRIORITY_FEE, SET_NETWORK_NAME, SET_PASSPHRASE, SET_PATH_TO_SCENARIO, SET_PERSONAL_MODE, SET_RECORDER_COUNT, SET_SELECTED_ACCOUNT, SET_SEND_UNIT, SET_SEND_VALUE, SET_PROXY_ENV_ADDRESS, ADD_DEPLOY_OPTION, REMOVE_DEPLOY_OPTION, SET_REMIXD_ACTIVATED } from '../constants' import { ADD_INSTANCE, ADD_PROVIDER, CLEAR_INSTANCES, CLEAR_RECORDER_COUNT, DISPLAY_NOTIFICATION, DISPLAY_POPUP_MESSAGE, FETCH_ACCOUNTS_LIST_FAILED, FETCH_ACCOUNTS_LIST_REQUEST, FETCH_ACCOUNTS_LIST_SUCCESS, FETCH_CONTRACT_LIST_FAILED, FETCH_CONTRACT_LIST_REQUEST, FETCH_CONTRACT_LIST_SUCCESS, FETCH_PROVIDER_LIST_FAILED, FETCH_PROVIDER_LIST_REQUEST, FETCH_PROVIDER_LIST_SUCCESS, HIDE_NOTIFICATION, HIDE_POPUP_MESSAGE, REMOVE_INSTANCE, REMOVE_PROVIDER, RESET_STATE, SET_BASE_FEE_PER_GAS, SET_CONFIRM_SETTINGS, SET_CURRENT_CONTRACT, SET_CURRENT_FILE, SET_DECODED_RESPONSE, SET_DEPLOY_OPTIONS, SET_EXECUTION_ENVIRONMENT, SET_EXTERNAL_WEB3_ENDPOINT, SET_GAS_LIMIT, SET_GAS_PRICE, SET_GAS_PRICE_STATUS, SET_IPFS_CHECKED_STATE, SET_LOAD_TYPE, SET_MATCH_PASSPHRASE, SET_MAX_FEE, SET_MAX_PRIORITY_FEE, SET_NETWORK_NAME, SET_PASSPHRASE, SET_PATH_TO_SCENARIO, SET_PERSONAL_MODE, SET_RECORDER_COUNT, SET_SELECTED_ACCOUNT, SET_SEND_UNIT, SET_SEND_VALUE, ADD_DEPLOY_OPTION, REMOVE_DEPLOY_OPTION, SET_REMIXD_ACTIVATED, FETCH_PROXY_DEPLOYMENTS, NEW_PROXY_DEPLOYMENT, RESET_PROXY_DEPLOYMENTS } from '../constants'
declare const window: any declare const window: any
interface Action { interface Action {
type: string type: string
payload: any payload: any
} }
export interface Contract {
name: string,
alias: string,
file: string,
compiler: CompilerAbstract,
compilerName: string
}
export interface ContractList {
[file: string]: Contract[]
}
export interface RunTabState {
accounts: {
loadedAccounts: Record<string, string>,
isRequesting: boolean,
isSuccessful: boolean,
error: string,
selectedAccount: string
},
sendValue: string,
sendUnit: 'ether' | 'finney' | 'gwei' | 'wei',
gasLimit: number,
selectExEnv: string,
personalMode: boolean,
networkName: string,
providers: {
providerList: {
id?: string,
dataId?: string,
title?: string,
value: string,
fork?: string
content: string
}[],
isRequesting: boolean,
isSuccessful: boolean,
error: string
},
notification: {
title: string,
message: string,
actionOk: () => void,
actionCancel: (() => void) | null,
labelOk: string,
labelCancel: string
},
externalEndpoint: string,
popup: string,
passphrase: string,
matchPassphrase: string,
contracts: {
contractList: {
[file: string]: {
name: string,
alias: string,
file: string,
compiler: CompilerAbstract
compilerName: string
}[]
},
deployOptions: { [file: string]: { [name: string]: DeployOptions } },
proxyKey: string,
loadType: 'abi' | 'sol' | 'other'
currentFile: string,
compilationSource: string,
currentContract: string,
compilationCount: number,
isRequesting: boolean,
isSuccessful: boolean,
error: string
},
ipfsChecked: boolean,
gasPriceStatus: boolean,
confirmSettings: boolean,
maxFee: string,
maxPriorityFee: string,
baseFeePerGas: string,
gasPrice: string,
instances: {
instanceList: {
contractData?: ContractData,
address: string,
balance?: number,
name: string,
decodedResponse?: Record<number, any>,
abi?: any
}[],
error: string
},
recorder: {
pathToScenario: string,
transactionCount: number
}
remixdActivated: boolean
}
export const runTabInitialState: RunTabState = { export const runTabInitialState: RunTabState = {
accounts: { accounts: {
@ -139,7 +43,6 @@ export const runTabInitialState: RunTabState = {
contracts: { contracts: {
contractList: {}, contractList: {},
deployOptions: {} as any, deployOptions: {} as any,
proxyKey: '',
compilationSource: '', compilationSource: '',
loadType: 'other', loadType: 'other',
currentFile: '', currentFile: '',
@ -164,7 +67,10 @@ export const runTabInitialState: RunTabState = {
pathToScenario: 'scenario.json', pathToScenario: 'scenario.json',
transactionCount: 0 transactionCount: 0
}, },
remixdActivated: false remixdActivated: false,
proxy: {
deployments: []
}
} }
type AddProvider = { type AddProvider = {
@ -697,23 +603,45 @@ export const runTabReducer = (state: RunTabState = runTabInitialState, action: A
} }
} }
case SET_PROXY_ENV_ADDRESS: { case SET_REMIXD_ACTIVATED: {
const payload: string = action.payload const payload: boolean = action.payload
return {
...state,
remixdActivated: payload
}
}
case FETCH_PROXY_DEPLOYMENTS: {
const payload: { address: string, date: string, contractName: string }[] = action.payload
return { return {
...state, ...state,
contracts: { proxy: {
...state.contracts, ...state.proxy,
proxyKey: payload deployments: payload
} }
} }
} }
case SET_REMIXD_ACTIVATED: { case NEW_PROXY_DEPLOYMENT: {
const payload: boolean = action.payload const payload: { address: string, date: string, contractName: string } = action.payload
return { return {
...state, ...state,
remixdActivated: payload proxy: {
...state.proxy,
deployments: [...state.proxy.deployments, payload]
}
}
}
case RESET_PROXY_DEPLOYMENTS: {
return {
...state,
proxy: {
...state.proxy,
deployments: []
}
} }
} }

@ -27,7 +27,7 @@ import {
storeNewScenario, runScenario, storeNewScenario, runScenario,
setScenarioPath, getFuncABIValues, setScenarioPath, getFuncABIValues,
setNetworkName, updateSelectedContract, setNetworkName, updateSelectedContract,
syncContracts, isValidProxyAddress syncContracts, isValidProxyAddress, isValidProxyUpgrade
} from './actions' } from './actions'
import './css/run-tab.css' import './css/run-tab.css'
import { PublishToStorage } from '@remix-ui/publish-to-storage' import { PublishToStorage } from '@remix-ui/publish-to-storage'
@ -81,7 +81,9 @@ export function RunTabUI (props: RunTabProps) {
okLabel: modals[0].okLabel, okLabel: modals[0].okLabel,
okFn: modals[0].okFn, okFn: modals[0].okFn,
cancelLabel: modals[0].cancelLabel, cancelLabel: modals[0].cancelLabel,
cancelFn: modals[0].cancelFn cancelFn: modals[0].cancelFn,
okBtnClass: modals[0].okBtnClass,
cancelBtnClass: modals[0].cancelBtnClass
} }
return focusModal return focusModal
}) })
@ -120,9 +122,9 @@ export function RunTabUI (props: RunTabProps) {
dispatch(setIpfsCheckedState(value)) dispatch(setIpfsCheckedState(value))
} }
const modal = (title: string, message: string | JSX.Element, okLabel: string, okFn: () => void, cancelLabel?: string, cancelFn?: () => void) => { const modal = (title: string, message: string | JSX.Element, okLabel: string, okFn: () => void, cancelLabel?: string, cancelFn?: () => void, okBtnClass?: string, cancelBtnClass?: string) => {
setModals(modals => { setModals(modals => {
modals.push({ message, title, okLabel, okFn, cancelLabel, cancelFn }) modals.push({ message, title, okLabel, okFn, cancelLabel, cancelFn, okBtnClass, cancelBtnClass })
return [...modals] return [...modals]
}) })
} }
@ -242,6 +244,8 @@ export function RunTabUI (props: RunTabProps) {
setSelectedContract={updateSelectedContract} setSelectedContract={updateSelectedContract}
remixdActivated={runTab.remixdActivated} remixdActivated={runTab.remixdActivated}
isValidProxyAddress={isValidProxyAddress} isValidProxyAddress={isValidProxyAddress}
isValidProxyUpgrade={isValidProxyUpgrade}
proxy={runTab.proxy}
/> />
<RecorderUI <RecorderUI
gasEstimationPrompt={gasEstimationPrompt} gasEstimationPrompt={gasEstimationPrompt}

@ -10,9 +10,11 @@ export class Blockchain extends Plugin<any, any> {
txRunner: any; txRunner: any;
networkcallid: number; networkcallid: number;
networkStatus: { networkStatus: {
network: {
name: string; name: string;
id: string; id: string;
}; };
};
setupEvents(): void; setupEvents(): void;
getCurrentNetworkStatus(): { getCurrentNetworkStatus(): {
name: string; name: string;

@ -1,12 +1,111 @@
import { Ref } from 'react' import { Ref } from 'react'
import { CompilerAbstract } from '@remix-project/remix-solidity' import { CompilerAbstract } from '@remix-project/remix-solidity'
import { ContractData, FuncABI } from '@remix-project/core-plugin' import { ContractData, FuncABI } from '@remix-project/core-plugin'
import { ContractList } from '../reducers/runTab'
import { RunTab } from './run-tab' import { RunTab } from './run-tab'
import { SolcInput, SolcOutput } from '@openzeppelin/upgrades-core'
import { LayoutCompatibilityReport } from '@openzeppelin/upgrades-core/dist/storage/report'
export interface RunTabProps { export interface RunTabProps {
plugin: RunTab plugin: RunTab
} }
export interface Contract {
name: string,
alias: string,
file: string,
compiler: CompilerAbstract,
compilerName: string
}
export interface ContractList {
[file: string]: Contract[]
}
export interface RunTabState {
accounts: {
loadedAccounts: Record<string, string>,
isRequesting: boolean,
isSuccessful: boolean,
error: string,
selectedAccount: string
},
sendValue: string,
sendUnit: 'ether' | 'finney' | 'gwei' | 'wei',
gasLimit: number,
selectExEnv: string,
personalMode: boolean,
networkName: string,
providers: {
providerList: {
id?: string,
dataId?: string,
title?: string,
value: string,
fork?: string
content: string
}[],
isRequesting: boolean,
isSuccessful: boolean,
error: string
},
notification: {
title: string,
message: string,
actionOk: () => void,
actionCancel: (() => void) | null,
labelOk: string,
labelCancel: string
},
externalEndpoint: string,
popup: string,
passphrase: string,
matchPassphrase: string,
contracts: {
contractList: {
[file: string]: {
name: string,
alias: string,
file: string,
compiler: CompilerAbstract
compilerName: string
}[]
},
deployOptions: { [file: string]: { [name: string]: DeployOptions } },
loadType: 'abi' | 'sol' | 'other'
currentFile: string,
compilationSource: string,
currentContract: string,
compilationCount: number,
isRequesting: boolean,
isSuccessful: boolean,
error: string
},
ipfsChecked: boolean,
gasPriceStatus: boolean,
confirmSettings: boolean,
maxFee: string,
maxPriorityFee: string,
baseFeePerGas: string,
gasPrice: string,
instances: {
instanceList: {
contractData?: ContractData,
address: string,
balance?: number,
name: string,
decodedResponse?: Record<number, any>,
abi?: any
}[],
error: string
},
recorder: {
pathToScenario: string,
transactionCount: number
}
remixdActivated: boolean,
proxy: {
deployments: { address: string, date: string, contractName: string }[]
}
}
export interface SettingsProps { export interface SettingsProps {
selectExEnv: string, selectExEnv: string,
accounts: { accounts: {
@ -41,7 +140,7 @@ export interface SettingsProps {
createNewBlockchainAccount: (cbMessage: JSX.Element) => void, createNewBlockchainAccount: (cbMessage: JSX.Element) => void,
setPassphrase: (passphrase: string) => void, setPassphrase: (passphrase: string) => void,
setMatchPassphrase: (passphrase: string) => void, setMatchPassphrase: (passphrase: string) => void,
modal: (title: string, message: string | JSX.Element, okLabel: string, okFn: () => void, cancelLabel?: string, cancelFn?: () => void) => void, modal: (title: string, message: string | JSX.Element, okLabel: string, okFn: () => void, cancelLabel?: string, cancelFn?: () => void, okBtnClass?: string, cancelBtnClass?: string) => void,
tooltip: (toasterMsg: string) => void, tooltip: (toasterMsg: string) => void,
signMessageWithAddress: (account: string, message: string, modalContent: (hash: string, data: string) => JSX.Element, passphrase?: string) => void, signMessageWithAddress: (account: string, message: string, modalContent: (hash: string, data: string) => JSX.Element, passphrase?: string) => void,
passphrase: string, passphrase: string,
@ -85,7 +184,7 @@ export interface AccountProps {
setPassphrase: (passphrase: string) => void, setPassphrase: (passphrase: string) => void,
setMatchPassphrase: (passphrase: string) => void, setMatchPassphrase: (passphrase: string) => void,
tooltip: (toasterMsg: string) => void, tooltip: (toasterMsg: string) => void,
modal: (title: string, message: string | JSX.Element, okLabel: string, okFn: () => void, cancelLabel?: string, cancelFn?: () => void) => void, modal: (title: string, message: string | JSX.Element, okLabel: string, okFn: () => void, cancelLabel?: string, cancelFn?: () => void, okBtnClass?: string, cancelBtnClass?: string) => void,
signMessageWithAddress: (account: string, message: string, modalContent: (hash: string, data: string) => JSX.Element, passphrase?: string) => void, signMessageWithAddress: (account: string, message: string, modalContent: (hash: string, data: string) => JSX.Element, passphrase?: string) => void,
passphrase: string passphrase: string
} }
@ -129,7 +228,6 @@ export interface ContractDropdownProps {
contracts: { contracts: {
contractList: ContractList, contractList: ContractList,
deployOptions: { [file: string]: { [name: string]: DeployOptions } }, deployOptions: { [file: string]: { [name: string]: DeployOptions } },
proxyKey: string,
loadType: 'abi' | 'sol' | 'other', loadType: 'abi' | 'sol' | 'other',
currentFile: string, currentFile: string,
compilationSource: string compilationSource: string
@ -141,7 +239,7 @@ export interface ContractDropdownProps {
}, },
syncContracts: () => void, syncContracts: () => void,
getSelectedContract: (contractName: string, compiler: CompilerAbstract) => ContractData, getSelectedContract: (contractName: string, compiler: CompilerAbstract) => ContractData,
modal: (title: string, message: string | JSX.Element, okLabel: string, okFn: () => void, cancelLabel?: string, cancelFn?: () => void) => void, modal: (title: string, message: string | JSX.Element, okLabel: string, okFn: () => void, cancelLabel?: string, cancelFn?: () => void, okBtnClass?: string, cancelBtnClass?: string) => void,
passphrase: string, passphrase: string,
setPassphrase: (passphrase: string) => void, setPassphrase: (passphrase: string) => void,
createInstance: ( createInstance: (
@ -166,7 +264,9 @@ export interface ContractDropdownProps {
setNetworkName: (name: string) => void, setNetworkName: (name: string) => void,
setSelectedContract: (contractName: string) => void setSelectedContract: (contractName: string) => void
remixdActivated: boolean, remixdActivated: boolean,
isValidProxyAddress?: (address: string) => Promise<boolean> isValidProxyAddress?: (address: string) => Promise<boolean>,
isValidProxyUpgrade?: (proxyAddress: string, contractName: string, solcInput: SolcInput, solcOuput: SolcOutput) => Promise<LayoutCompatibilityReport | { ok: boolean, pass: boolean, warning: boolean }>,
proxy: { deployments: { address: string, date: string, contractName: string }[] }
} }
export interface RecorderProps { export interface RecorderProps {
@ -223,7 +323,9 @@ export interface Modal {
okLabel: string okLabel: string
okFn: () => void okFn: () => void
cancelLabel: string cancelLabel: string
cancelFn: () => void cancelFn: () => void,
okBtnClass?: string,
cancelBtnClass?: string
} }
export type DeployMode = 'Deploy with Proxy' | 'Upgrade with Proxy' export type DeployMode = 'Deploy with Proxy' | 'Upgrade with Proxy'
@ -261,8 +363,10 @@ export interface ContractGUIProps {
isDeploy?: boolean, isDeploy?: boolean,
deployOption?: { title: DeployMode, active: boolean }[], deployOption?: { title: DeployMode, active: boolean }[],
initializerOptions?: DeployOption, initializerOptions?: DeployOption,
savedProxyAddress?: string, proxy?: { deployments: { address: string, date: string, contractName: string }[] },
isValidProxyAddress?: (address: string) => Promise<boolean> isValidProxyAddress?: (address: string) => Promise<boolean>,
isValidProxyUpgrade?: (proxyAddress: string) => Promise<LayoutCompatibilityReport | { ok: boolean, pass: boolean, warning: boolean }>,
modal?: (title: string, message: string | JSX.Element, okLabel: string, okFn: () => void, cancelLabel?: string, cancelFn?: () => void, okBtnClass?: string, cancelBtnClass?: string) => void
} }
export interface MainnetProps { export interface MainnetProps {
network: Network, network: Network,

@ -1,3 +1,9 @@
.remixui_default-message { .remixui_default-message {
margin-top: 100px; margin-top: 100px;
} }
.remixui_no-shadow {
border-width: 1px;
border-style: solid;
border-color: var(--info);
}

@ -1,55 +1,29 @@
import { CustomTooltip } from '@remix-ui/helper'
import React, { Fragment, useEffect, useState } from 'react' import React, { Fragment, useEffect, useState } from 'react'
import { TransformComponent, TransformWrapper } from 'react-zoom-pan-pinch' import { TransformComponent, TransformWrapper } from 'react-zoom-pan-pinch'
import { ISolidityUmlGen } from '../types' import { ThemeSummary } from '../types'
import './css/solidity-uml-gen.css' import './css/solidity-uml-gen.css'
export interface RemixUiSolidityUmlGenProps { export interface RemixUiSolidityUmlGenProps {
plugin?: ISolidityUmlGen
updatedSvg?: string updatedSvg?: string
loading?: boolean loading?: boolean
themeSelected?: string themeSelected?: string
} themeName: string
themeCollection: ThemeSummary[]
type ButtonAction = {
svgValid: () => boolean
action: () => void
buttonText: string
icon?: string
customcss?: string
} }
interface ActionButtonsProps { interface ActionButtonsProps {
buttons: ButtonAction[] actions: {
zoomIn: () => void,
zoomOut: () => void,
resetTransform: () => void
}
} }
const ActionButtons = ({ buttons }: ActionButtonsProps) => (
<>
{buttons.map(btn => (
<CustomTooltip
key={btn.buttonText}
placement="top"
tooltipText={btn.buttonText}
tooltipId={btn.buttonText}
>
<button
key={btn.buttonText}
className={`btn btn-primary btn-sm rounded-circle ${btn.customcss}`}
disabled={!btn.svgValid}
onClick={btn.action}
>
<i className={btn.icon}></i>
</button>
</CustomTooltip>
))}
</>
)
export function RemixUiSolidityUmlGen ({ plugin, updatedSvg, loading, themeSelected }: RemixUiSolidityUmlGenProps) {
const [showViewer, setShowViewer] = useState(false)
const [svgPayload, setSVGPayload] = useState<string>('')
const [validSvg, setValidSvg] = useState(false)
export function RemixUiSolidityUmlGen ({ updatedSvg, loading }: RemixUiSolidityUmlGenProps) {
const [showViewer, setShowViewer] = useState(false)
const [validSvg, setValidSvg] = useState(false)
useEffect(() => { useEffect(() => {
setValidSvg (updatedSvg.startsWith('<?xml') && updatedSvg.includes('<svg')) setValidSvg (updatedSvg.startsWith('<?xml') && updatedSvg.includes('<svg'))
@ -57,20 +31,50 @@ export function RemixUiSolidityUmlGen ({ plugin, updatedSvg, loading, themeSelec
} }
, [updatedSvg]) , [updatedSvg])
const buttons: ButtonAction[] = [
{ const encoder = new TextEncoder()
buttonText: 'Download as PDF', const data = encoder.encode(updatedSvg)
svgValid: () => validSvg, const final = btoa(String.fromCharCode.apply(null, data))
action: () => console.log('generated!!'),
icon: 'fa mr-1 pt-1 pb-1 fa-file' function ActionButtons({ actions: { zoomIn, zoomOut, resetTransform }}: ActionButtonsProps) {
},
{ return (
buttonText: 'Download as PNG', <>
svgValid: () => validSvg, <div
action: () => console.log('generated!!'), className="position-absolute bg-transparent mt-2"
icon: 'fa fa-picture-o' id="buttons"
style={{ zIndex: 3, top: "10", right: "2em" }}
>
<div className="py-2 px-2 d-flex justify-content-center align-items-center">
<button
className="btn btn-outline-info d-none rounded-circle mr-2"
onClick={() => resetTransform()}
>
<i className="far fa-arrow-to-bottom align-item-center d-flex justify-content-center"></i>
</button>
<button
className="badge badge-info remixui_no-shadow p-2 rounded-circle mr-2"
onClick={() => zoomIn()}
>
<i className="far fa-plus "></i>
</button>
<button
className="badge badge-info remixui_no-shadow p-2 rounded-circle mr-2"
onClick={() => zoomOut()}
>
<i className="far fa-minus align-item-center d-flex justify-content-center"></i>
</button>
<button
className="badge badge-info remixui_no-shadow p-2 rounded-circle mr-2"
onClick={() => resetTransform()}
>
<i className="far fa-undo align-item-center d-flex justify-content-center"></i>
</button>
</div>
</div>
</>
)
} }
]
const DefaultInfo = () => ( const DefaultInfo = () => (
<div className="d-flex flex-column justify-content-center align-items-center mt-5"> <div className="d-flex flex-column justify-content-center align-items-center mt-5">
@ -80,10 +84,8 @@ export function RemixUiSolidityUmlGen ({ plugin, updatedSvg, loading, themeSelec
</div> </div>
) )
const Display = () => { const Display = () => {
const invert = themeSelected === 'dark' ? 'invert(0.8)' : 'invert(0)'
return ( return (
<div className="d-flex flex-column justify-content-center align-items-center"> <div id="umlImageHolder" className="w-100 px-2 py-2 d-flex">
<div id="umlImageHolder" className="w-100 px-2 py-2">
{ validSvg && showViewer ? ( { validSvg && showViewer ? (
<TransformWrapper <TransformWrapper
initialScale={1} initialScale={1}
@ -91,12 +93,14 @@ export function RemixUiSolidityUmlGen ({ plugin, updatedSvg, loading, themeSelec
{ {
({ zoomIn, zoomOut, resetTransform }) => ( ({ zoomIn, zoomOut, resetTransform }) => (
<Fragment> <Fragment>
<TransformComponent> <ActionButtons actions={{ zoomIn, zoomOut, resetTransform }} />
<TransformComponent contentStyle={{ zIndex: 2 }}>
<img <img
src={`data:image/svg+xml;base64,${btoa(plugin.updatedSvg ?? svgPayload)}`} id="umlImage"
src={`data:image/svg+xml;base64,${final}`}
width={'100%'} width={'100%'}
height={'auto'} height={'auto'}
style={{ filter: invert }} className="position-relative"
/> />
</TransformComponent> </TransformComponent>
</Fragment> </Fragment>
@ -107,7 +111,6 @@ export function RemixUiSolidityUmlGen ({ plugin, updatedSvg, loading, themeSelec
<i className="fas fa-spinner fa-spin fa-4x"></i> <i className="fas fa-spinner fa-spin fa-4x"></i>
</div> : <DefaultInfo />} </div> : <DefaultInfo />}
</div> </div>
</div>
)} )}
return (<> return (<>
{ <Display /> } { <Display /> }

@ -1,4 +1,5 @@
import { ViewPlugin } from '@remixproject/engine-web' import { ViewPlugin } from '@remixproject/engine-web'
import { customAction } from '@remixproject/plugin-api'
import React from 'react' import React from 'react'
export interface ISolidityUmlGen extends ViewPlugin { export interface ISolidityUmlGen extends ViewPlugin {
@ -6,9 +7,22 @@ export interface ISolidityUmlGen extends ViewPlugin {
currentFile: string currentFile: string
svgPayload: string svgPayload: string
updatedSvg: string updatedSvg: string
currentlySelectedTheme: string
themeName: string
loading: boolean
themeCollection: ThemeSummary[]
showUmlDiagram(path: string, svgPayload: string): void showUmlDiagram(path: string, svgPayload: string): void
updateComponent(state: any): JSX.Element updateComponent(state: any): JSX.Element
setDispatch(dispatch: React.Dispatch<any>): void setDispatch(dispatch: React.Dispatch<any>): void
mangleSvgPayload(svgPayload: string) : Promise<string>
generateCustomAction(action: customAction): Promise<void>
flattenContract (source: any, filePath: string, data: any): Promise<string>
hideSpinner(): void
renderComponent (): void
render(): JSX.Element render(): JSX.Element
} }
export type ThemeQualityType = { name: string, quality: 'light' | 'dark', url: string }
export type ThemeSummary = { themeName: string, backgroundColor: string, actualHex: string }

@ -112,16 +112,22 @@ export const listenOnProviderEvents = (provider) => (reducerDispatch: React.Disp
const config = plugin.registry.get('config').api const config = plugin.registry.get('config').api
const editor = plugin.registry.get('editor').api const editor = plugin.registry.get('editor').api
if (config.get('currentFile') === path && editor.currentContent() !== content) { if (editor.getText(path) === content) return
if (provider.isReadOnly(path)) return editor.setText(content) if (provider.isReadOnly(path)) return editor.setText(path, content)
if (config.get('currentFile') === path) {
// if it's the current file and the content is different:
dispatch(displayNotification( dispatch(displayNotification(
path + ' changed', path + ' changed',
'This file has been changed outside of Remix IDE.', 'This file has been changed outside of Remix IDE.',
'Replace by the new content', 'Keep the content displayed in Remix', 'Replace by the new content', 'Keep the content displayed in Remix',
() => { () => {
editor.setText(content) editor.setText(path, content)
} }
)) ))
} else {
// this isn't the current file, we can silently update the model
editor.setText(path, content)
} }
}) })

@ -374,7 +374,7 @@ export const uploadFile = async (target, targetFolder: string, cb?: (err: Error,
const editor = plugin.registry.get('editor').api const editor = plugin.registry.get('editor').api
if ((config.get('currentFile') === name) && (editor.currentContent() !== event.target.result)) { if ((config.get('currentFile') === name) && (editor.currentContent() !== event.target.result)) {
editor.setText(event.target.result) editor.setText(name, event.target.result)
} }
} }
fileReader.readAsText(file) fileReader.readAsText(file)

@ -1,5 +1,5 @@
import { PluginClient } from '@remixproject/plugin' import { PluginClient } from '@remixproject/plugin'
import { SharedFolderArgs, TrackDownStreamUpdate, Filelist, ResolveDirectory, FileContent } from '../types' // eslint-disable-line import { SharedFolderArgs, Filelist, ResolveDirectory, FileContent } from '../types' // eslint-disable-line
import * as WS from 'ws' // eslint-disable-line import * as WS from 'ws' // eslint-disable-line
import * as utils from '../utils' import * as utils from '../utils'
import * as chokidar from 'chokidar' import * as chokidar from 'chokidar'
@ -8,7 +8,6 @@ import * as isbinaryfile from 'isbinaryfile'
export class RemixdClient extends PluginClient { export class RemixdClient extends PluginClient {
methods: Array<string> methods: Array<string>
trackDownStreamUpdate: TrackDownStreamUpdate = {}
websocket: WS websocket: WS
currentSharedFolder: string currentSharedFolder: string
watcher: chokidar.FSWatcher watcher: chokidar.FSWatcher
@ -101,7 +100,6 @@ export class RemixdClient extends PluginClient {
console.log('trying to write "undefined" ! stopping.') console.log('trying to write "undefined" ! stopping.')
return reject(new Error('trying to write "undefined" ! stopping.')) return reject(new Error('trying to write "undefined" ! stopping.'))
} }
this.trackDownStreamUpdate[path] = path
if (!exists && args.path.indexOf('/') !== -1) { if (!exists && args.path.indexOf('/') !== -1) {
// the last element is the filename and we should remove it // the last element is the filename and we should remove it
this.createDir({ path: args.path.substr(0, args.path.lastIndexOf('/')) }) this.createDir({ path: args.path.substr(0, args.path.lastIndexOf('/')) })
@ -250,7 +248,7 @@ export class RemixdClient extends PluginClient {
const absPath = utils.absolutePath('./', path) const absPath = utils.absolutePath('./', path)
if (!isRealPath(absPath)) return if (!isRealPath(absPath)) return
this.watcher = chokidar.watch(path, { depth: 0, ignorePermissionErrors: true }) this.watcher = chokidar.watch(path, { depth: 2, ignorePermissionErrors: true })
console.log('setup notifications for ' + path) console.log('setup notifications for ' + path)
/* we can't listen on created file / folder /* we can't listen on created file / folder
watcher.on('add', (f, stat) => { watcher.on('add', (f, stat) => {
@ -265,11 +263,9 @@ export class RemixdClient extends PluginClient {
}) })
*/ */
this.watcher.on('change', async (f: string) => { this.watcher.on('change', async (f: string) => {
if (this.trackDownStreamUpdate[f]) { const currentContent = await this.call('editor', 'getText' as any, f)
delete this.trackDownStreamUpdate[f] const newContent = fs.readFileSync(f)
return if (currentContent !== newContent && this.isLoaded) {
}
if (this.isLoaded) {
this.emit('changed', utils.relativePath(f, this.currentSharedFolder)) this.emit('changed', utils.relativePath(f, this.currentSharedFolder))
} }
}) })

@ -42,8 +42,6 @@ export type FileContent = {
readonly: boolean readonly: boolean
} }
export type TrackDownStreamUpdate = KeyPairString
export type SharedFolderArgs = FolderArgs & KeyPairString export type SharedFolderArgs = FolderArgs & KeyPairString
export type WS = typeof Websocket export type WS = typeof Websocket

@ -130,6 +130,7 @@
"@isomorphic-git/lightning-fs": "^4.4.1", "@isomorphic-git/lightning-fs": "^4.4.1",
"@monaco-editor/react": "4.4.5", "@monaco-editor/react": "4.4.5",
"@openzeppelin/contracts": "^4.7.3", "@openzeppelin/contracts": "^4.7.3",
"@openzeppelin/upgrades-core": "^1.22.0",
"@openzeppelin/wizard": "^0.1.1", "@openzeppelin/wizard": "^0.1.1",
"@remixproject/engine": "^0.3.31", "@remixproject/engine": "^0.3.31",
"@remixproject/engine-web": "^0.3.31", "@remixproject/engine-web": "^0.3.31",

@ -5090,6 +5090,19 @@
resolved "https://registry.yarnpkg.com/@openzeppelin/contracts/-/contracts-4.7.3.tgz#939534757a81f8d69cc854c7692805684ff3111e" resolved "https://registry.yarnpkg.com/@openzeppelin/contracts/-/contracts-4.7.3.tgz#939534757a81f8d69cc854c7692805684ff3111e"
integrity sha512-dGRS0agJzu8ybo44pCIf3xBaPQN/65AIXNgK8+4gzKd5kbvlqyxryUYVLJv7fK98Seyd2hDZzVEHSWAh0Bt1Yw== integrity sha512-dGRS0agJzu8ybo44pCIf3xBaPQN/65AIXNgK8+4gzKd5kbvlqyxryUYVLJv7fK98Seyd2hDZzVEHSWAh0Bt1Yw==
"@openzeppelin/upgrades-core@^1.22.0":
version "1.22.0"
resolved "https://registry.yarnpkg.com/@openzeppelin/upgrades-core/-/upgrades-core-1.22.0.tgz#41ffda6a9161845fc6b82bd945e530529feefa00"
integrity sha512-TcTabzRbYOzWJnwiToj0LRzje25d9QbDPe2dOT9eHlLDRhOMiep39FDibJjkYd5IdF3s8M9IcK+YSnf49renEg==
dependencies:
cbor "^8.0.0"
chalk "^4.1.0"
compare-versions "^5.0.0"
debug "^4.1.1"
ethereumjs-util "^7.0.3"
proper-lockfile "^4.1.1"
solidity-ast "^0.4.15"
"@openzeppelin/wizard@^0.1.1": "@openzeppelin/wizard@^0.1.1":
version "0.1.1" version "0.1.1"
resolved "https://registry.yarnpkg.com/@openzeppelin/wizard/-/wizard-0.1.1.tgz#8c183e2c5748869bc3a5317c0330aa36a9ad44fe" resolved "https://registry.yarnpkg.com/@openzeppelin/wizard/-/wizard-0.1.1.tgz#8c183e2c5748869bc3a5317c0330aa36a9ad44fe"
@ -9161,6 +9174,13 @@ catering@^2.1.0, catering@^2.1.1:
resolved "https://registry.yarnpkg.com/catering/-/catering-2.1.1.tgz#66acba06ed5ee28d5286133982a927de9a04b510" resolved "https://registry.yarnpkg.com/catering/-/catering-2.1.1.tgz#66acba06ed5ee28d5286133982a927de9a04b510"
integrity sha512-K7Qy8O9p76sL3/3m7/zLKbRkyOlSZAgzEaLhyj2mXS8PsCud2Eo4hAb8aLtZqHh0QGqLcb9dlJSu6lHRVENm1w== integrity sha512-K7Qy8O9p76sL3/3m7/zLKbRkyOlSZAgzEaLhyj2mXS8PsCud2Eo4hAb8aLtZqHh0QGqLcb9dlJSu6lHRVENm1w==
cbor@^8.0.0:
version "8.1.0"
resolved "https://registry.yarnpkg.com/cbor/-/cbor-8.1.0.tgz#cfc56437e770b73417a2ecbfc9caf6b771af60d5"
integrity sha512-DwGjNW9omn6EwP70aXsn7FQJx5kO12tX0bZkaTjzdVFM6/7nhA4t0EENocKGx6D2Bch9PE2KzCUf5SceBdeijg==
dependencies:
nofilter "^3.1.0"
center-align@^0.1.1: center-align@^0.1.1:
version "0.1.3" version "0.1.3"
resolved "https://registry.yarnpkg.com/center-align/-/center-align-0.1.3.tgz#aa0d32629b6ee972200411cbd4461c907bc2b7ad" resolved "https://registry.yarnpkg.com/center-align/-/center-align-0.1.3.tgz#aa0d32629b6ee972200411cbd4461c907bc2b7ad"
@ -9847,6 +9867,11 @@ compare-func@^2.0.0:
array-ify "^1.0.0" array-ify "^1.0.0"
dot-prop "^5.1.0" dot-prop "^5.1.0"
compare-versions@^5.0.0:
version "5.0.3"
resolved "https://registry.yarnpkg.com/compare-versions/-/compare-versions-5.0.3.tgz#a9b34fea217472650ef4a2651d905f42c28ebfd7"
integrity sha512-4UZlZP8Z99MGEY+Ovg/uJxJuvoXuN4M6B3hKaiackiHrgzQFEe3diJi1mf1PNHbFujM7FvLrK2bpgIaImbtZ1A==
component-emitter@^1.2.1: component-emitter@^1.2.1:
version "1.3.0" version "1.3.0"
resolved "https://registry.yarnpkg.com/component-emitter/-/component-emitter-1.3.0.tgz#16e4070fba8ae29b679f2215853ee181ab2eabc0" resolved "https://registry.yarnpkg.com/component-emitter/-/component-emitter-1.3.0.tgz#16e4070fba8ae29b679f2215853ee181ab2eabc0"
@ -12299,6 +12324,17 @@ ethereumjs-util@^7.0.10, ethereumjs-util@^7.1.0, ethereumjs-util@^7.1.3:
ethereum-cryptography "^0.1.3" ethereum-cryptography "^0.1.3"
rlp "^2.2.4" rlp "^2.2.4"
ethereumjs-util@^7.0.3:
version "7.1.5"
resolved "https://registry.yarnpkg.com/ethereumjs-util/-/ethereumjs-util-7.1.5.tgz#9ecf04861e4fbbeed7465ece5f23317ad1129181"
integrity sha512-SDl5kKrQAudFBUe5OJM9Ac6WmMyYmXX/6sTmLZ3ffG2eY6ZIGBes3pEDxNN6V72WyOw4CPD5RomKdsa8DAAwLg==
dependencies:
"@types/bn.js" "^5.1.0"
bn.js "^5.1.2"
create-hash "^1.1.2"
ethereum-cryptography "^0.1.3"
rlp "^2.2.4"
ethers@^5.4.2: ethers@^5.4.2:
version "5.5.1" version "5.5.1"
resolved "https://registry.yarnpkg.com/ethers/-/ethers-5.5.1.tgz#d3259a95a42557844aa543906c537106c0406fbf" resolved "https://registry.yarnpkg.com/ethers/-/ethers-5.5.1.tgz#d3259a95a42557844aa543906c537106c0406fbf"
@ -19579,6 +19615,11 @@ nodent@>=2.6.12:
nodent-runtime "^3.2.1" nodent-runtime "^3.2.1"
resolve "^1.5.0" resolve "^1.5.0"
nofilter@^3.1.0:
version "3.1.0"
resolved "https://registry.yarnpkg.com/nofilter/-/nofilter-3.1.0.tgz#c757ba68801d41ff930ba2ec55bab52ca184aa66"
integrity sha512-l2NNj07e9afPnhAhvgVrCD/oy2Ai1yfLpuo3EpiO1jFTsB4sFz6oIfAfSZyQzVpkZQ9xS8ZS5g1jCBgq4Hwo0g==
"nopt@2 || 3", nopt@~3.0.1: "nopt@2 || 3", nopt@~3.0.1:
version "3.0.6" version "3.0.6"
resolved "https://registry.yarnpkg.com/nopt/-/nopt-3.0.6.tgz#c6465dbf08abcd4db359317f79ac68a646b28ff9" resolved "https://registry.yarnpkg.com/nopt/-/nopt-3.0.6.tgz#c6465dbf08abcd4db359317f79ac68a646b28ff9"
@ -21894,6 +21935,15 @@ prop-types@^15.8.1:
object-assign "^4.1.1" object-assign "^4.1.1"
react-is "^16.13.1" react-is "^16.13.1"
proper-lockfile@^4.1.1:
version "4.1.2"
resolved "https://registry.yarnpkg.com/proper-lockfile/-/proper-lockfile-4.1.2.tgz#c8b9de2af6b2f1601067f98e01ac66baa223141f"
integrity sha512-TjNPblN4BwAWMXU8s9AEz4JmQxnD1NNL7bNOY/AKUzyamc379FWASUhc/K1pL2noVb+XmZKLL68cjzLsiOAMaA==
dependencies:
graceful-fs "^4.2.4"
retry "^0.12.0"
signal-exit "^3.0.2"
proto-list@~1.2.1: proto-list@~1.2.1:
version "1.2.4" version "1.2.4"
resolved "https://registry.yarnpkg.com/proto-list/-/proto-list-1.2.4.tgz#212d5bfe1318306a420f6402b8e26ff39647a849" resolved "https://registry.yarnpkg.com/proto-list/-/proto-list-1.2.4.tgz#212d5bfe1318306a420f6402b8e26ff39647a849"
@ -23231,6 +23281,11 @@ retry@^0.10.0, retry@~0.10.1:
resolved "https://registry.yarnpkg.com/retry/-/retry-0.10.1.tgz#e76388d217992c252750241d3d3956fed98d8ff4" resolved "https://registry.yarnpkg.com/retry/-/retry-0.10.1.tgz#e76388d217992c252750241d3d3956fed98d8ff4"
integrity sha1-52OI0heZLCUnUCQdPTlW/tmNj/Q= integrity sha1-52OI0heZLCUnUCQdPTlW/tmNj/Q=
retry@^0.12.0:
version "0.12.0"
resolved "https://registry.yarnpkg.com/retry/-/retry-0.12.0.tgz#1b42a6266a21f07421d1b0b54b7dc167b01c013b"
integrity sha512-9LkiTwjUh6rT555DtE9rTX+BKByPfrMzEAtnlEtdEwr3Nkffwiihqe2bWADg+OQRjt9gl6ICdmB/ZFDCGAtSow==
retry@^0.13.1: retry@^0.13.1:
version "0.13.1" version "0.13.1"
resolved "https://registry.yarnpkg.com/retry/-/retry-0.13.1.tgz#185b1587acf67919d63b357349e03537b2484658" resolved "https://registry.yarnpkg.com/retry/-/retry-0.13.1.tgz#185b1587acf67919d63b357349e03537b2484658"
@ -24107,6 +24162,11 @@ solc@0.7.4:
semver "^5.5.0" semver "^5.5.0"
tmp "0.0.33" tmp "0.0.33"
solidity-ast@^0.4.15:
version "0.4.44"
resolved "https://registry.yarnpkg.com/solidity-ast/-/solidity-ast-0.4.44.tgz#dd6732bd65bb1d01777fc537de99cbb3d4e0089d"
integrity sha512-Ct3ppqWS0uTWNYxM2cgruUeWYzqYmeikANsCHgGBnMjAMsqONgqnYrlpifQxNFwXOPHD3vZQLmCjaYnQ+i3eQA==
solidity-comments-extractor@^0.0.7: solidity-comments-extractor@^0.0.7:
version "0.0.7" version "0.0.7"
resolved "https://registry.yarnpkg.com/solidity-comments-extractor/-/solidity-comments-extractor-0.0.7.tgz#99d8f1361438f84019795d928b931f4e5c39ca19" resolved "https://registry.yarnpkg.com/solidity-comments-extractor/-/solidity-comments-extractor-0.0.7.tgz#99d8f1361438f84019795d928b931f4e5c39ca19"

Loading…
Cancel
Save