Merge branch 'master' into mixins

pull/3391/head
bunsenstraat 2 years ago committed by GitHub
commit e9ac6f5f0f
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. 159
      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. 6
      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. 121
      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 lastProxyAddress: string
let shortenedFirstAddress: string
let shortenedLastAddress: string
module.exports = {
'@disabled': true,
before: function (browser: NightwatchBrowser, done: VoidFunction) {
@ -94,6 +96,7 @@ module.exports = {
browser
.getAddressAtPosition(1, (address) => {
firstProxyAddress = address
shortenedFirstAddress = address.slice(0, 5) + '...' + address.slice(address.length - 5, address.length)
})
.clickInstance(1)
.perform((done) => {
@ -143,6 +146,7 @@ module.exports = {
browser
.getAddressAtPosition(1, (address) => {
lastProxyAddress = address
shortenedLastAddress = address.slice(0, 5) + '...' + address.slice(address.length - 5, address.length)
})
.clickInstance(1)
.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
.waitForElementPresent('[data-id="deployAndRunClearInstances"]')
.click('[data-id="deployAndRunClearInstances"]')
@ -171,10 +175,12 @@ module.exports = {
.click('select.udapp_contractNames option[value=MyTokenV2]')
.waitForElementPresent('[data-id="contractGUIUpgradeImplementationLabel"]')
.click('[data-id="contractGUIUpgradeImplementationLabel"]')
.waitForElementPresent('[data-id="contractGUIProxyAddressLabel"]')
.click('[data-id="contractGUIProxyAddressLabel"]')
.waitForElementPresent('[data-id="lastDeployedERC1967Address"]')
.assert.containsText('[data-id="lastDeployedERC1967Address"]', lastProxyAddress)
.waitForElementPresent('[data-id="toggleProxyAddressDropdown"]')
.click('[data-id="toggleProxyAddressDropdown"]')
.waitForElementVisible('[data-id="proxy-dropdown-items"]')
.assert.textContains('[data-id="proxy-dropdown-items"]', shortenedFirstAddress)
.assert.textContains('[data-id="proxy-dropdown-items"]', shortenedLastAddress)
.click('[data-id="proxyAddress1"]')
.createContract('')
.waitForElementContainsText('[data-id="udappNotifyModalDialogModalTitle-react"]', 'Deploy Implementation & Update Proxy')
.waitForElementVisible('[data-id="udappNotify-modal-footer-ok-react"]')
@ -212,9 +218,8 @@ module.exports = {
.click('select.udapp_contractNames')
.click('select.udapp_contractNames option[value=MyTokenV2]')
.waitForElementPresent('[data-id="contractGUIUpgradeImplementationLabel"]')
.waitForElementPresent('[data-id="contractGUIProxyAddressLabel"]')
.click('[data-id="contractGUIProxyAddressLabel"]')
.waitForElementPresent('[data-id="ERC1967AddressInput"]')
.waitForElementPresent('[data-id="toggleProxyAddressDropdown"]')
.clearValue('[data-id="ERC1967AddressInput"]')
.setValue('[data-id="ERC1967AddressInput"]', firstProxyAddress)
.createContract('')
.waitForElementContainsText('[data-id="udappNotifyModalDialogModalTitle-react"]', 'Deploy Implementation & Update Proxy')

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

@ -13,7 +13,7 @@ const profile = {
name: 'editor',
description: 'service - editor',
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 {
@ -280,11 +280,22 @@ class Editor extends Plugin {
/**
* 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.
*/
setText (text) {
if (this.currentFile && this.sessions[this.currentFile]) {
this.sessions[this.currentFile].setValue(text)
setText (url, text) {
if (this.sessions[url]) {
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) {
try{
const content = await provider.get(currentFile)
if(content) this.editor.setText(content)
if(content) this.editor.setText(currentFile, content)
}catch(error){
console.log(error)
}

@ -3,7 +3,7 @@ import { ViewPlugin } from '@remixproject/engine-web'
import React from 'react'
// eslint-disable-next-line @nrwl/nx/enforce-module-boundaries
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 { concatSourceFiles, getDependencyGraph } from 'libs/remix-ui/solidity-compiler/src/lib/logic/flattenerUtilities'
import { convertUmlClasses2Dot } from 'sol2uml/lib/converterClasses2Dot'
@ -22,13 +22,33 @@ const profile = {
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 {
element: HTMLDivElement
currentFile: string
svgPayload: string
updatedSvg: string
currentlySelectedTheme: string
themeName: string
loading: boolean
themeCollection: ThemeSummary[]
appManager: RemixAppManager
dispatch: React.Dispatch<any> = () => {}
@ -39,14 +59,18 @@ export class SolidityUmlGen extends ViewPlugin implements ISolidityUmlGen {
this.updatedSvg = ''
this.loading = false
this.currentlySelectedTheme = ''
this.themeName = ''
this.themeCollection = themeCollection
this.appManager = appManager
this.element = document.createElement('div')
this.element.setAttribute('id', 'sol-uml-gen')
}
onActivation(): void {
if (this.currentFile.length < 1)
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 = ''
try {
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 umlDot = convertUmlClasses2Dot(umlClasses)
const payload = vizRenderStringSync(umlDot)
const currentTheme = await this.call('theme', 'currentTheme')
this.currentlySelectedTheme = currentTheme.quality
this.updatedSvg = payload
this.renderComponent()
await this.call('tabs', 'focus', 'solidityumlgen')
} catch (error) {
console.log({ error })
console.log('error', error)
}
})
this.on('theme', 'themeChanged', (theme) => {
@ -72,13 +95,16 @@ export class SolidityUmlGen extends ViewPlugin implements ISolidityUmlGen {
async mangleSvgPayload(svgPayload: string) : Promise<string> {
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 res = parsedDocument.documentElement
parsedDocument.bgColor = '#cccabc'
res.style.filter = themeQuality.quality === 'dark' ? 'invert(1)' : 'invert(0)'
const element = parsedDocument.getElementsByTagName('svg')
themeCollection.forEach((theme) => {
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)
console.log({ parsedDocument, themeQuality, stringifiedSvg })
return stringifiedSvg
}
@ -87,6 +113,7 @@ export class SolidityUmlGen extends ViewPlugin implements ISolidityUmlGen {
}
generateCustomAction = async (action: customAction) => {
this.updatedSvg = this.updatedSvg.startsWith('<?xml') ? '' : this.updatedSvg
this.currentFile = action.path[0]
await this.generateUml(action.path[0])
}
@ -105,14 +132,11 @@ export class SolidityUmlGen extends ViewPlugin implements ISolidityUmlGen {
* @returns {Promise<string>}
*/
async flattenContract (source: any, filePath: string, data: any) {
const hold = { data, source, filePath }
const ast = data.sources
const dependencyGraph = getDependencyGraph(ast, filePath)
const dependencyGraph = getDependencyGraph(data.sources, filePath)
const sorted = dependencyGraph.isEmpty()
? [filePath]
: dependencyGraph.sort().reverse()
const sources = source.sources
const result = concatSourceFiles(sorted, sources)
const result = concatSourceFiles(sorted, source.sources)
await this.call('fileManager', 'writeFile', `${filePath}_flattened.sol`, result)
return result
}
@ -143,16 +167,19 @@ export class SolidityUmlGen extends ViewPlugin implements ISolidityUmlGen {
...this,
updatedSvg: this.updatedSvg,
loading: this.loading,
themeSelected: this.currentlySelectedTheme
themeSelected: this.currentlySelectedTheme,
themeName: this.themeName,
themeCollection: this.themeCollection
})
}
updateComponent(state: any) {
return <RemixUiSolidityUmlGen
plugin={state}
updatedSvg={state.updatedSvg}
loading={state.loading}
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])
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)
this.events.emit('newProxyDeployment', address, new Date().toISOString(), implementationContractObject.contractName)
_paq.push(['trackEvent', 'blockchain', 'Deploy With Proxy', 'Proxy deployment successful'])
this.call('udapp', 'addInstance', addressToString(address), implementationContractObject.abi, implementationContractObject.name)
}
@ -236,23 +235,40 @@ export class Blockchain extends Plugin {
}
async saveDeployedContractStorageLayout (contractObject, proxyAddress, networkInfo) {
const { contractName, implementationAddress, contract } = contractObject
const hasPreviousDeploys = await this.call('fileManager', 'exists', `.deploys/upgradeable-contracts/${networkInfo.name}/UUPS.json`)
const { contractName, implementationAddress } = contractObject
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.
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 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] = {
date: new Date().toISOString(),
contractName: contractName,
fork: networkInfo.currentFork,
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 {
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,
network: networkInfo.name,
deployments: {
@ -260,8 +276,7 @@ export class Blockchain extends Plugin {
date: new Date().toISOString(),
contractName: contractName,
fork: networkInfo.currentFork,
implementationAddress: implementationAddress,
layout: contract.object.storageLayout
implementationAddress: implementationAddress
}
}
}, null, 2))

@ -1,3 +1,4 @@
import { SolcInput, SolcOutput } from "@openzeppelin/upgrades-core"
export interface FuncABI {
name: 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'
export const fileChangedToastMsg = (from: string, path: string) => (
@ -115,3 +116,27 @@ export const upgradeWithProxyMsg = () => (
</ol>
</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
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 ^^ */}
{ props.okLabel && <button
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}
onClick={() => {
if (props.validation && !props.validation.valid) return
@ -111,7 +111,7 @@ export const ModalDialog = (props: ModalDialogProps) => {
}
{ props.cancelLabel && <button
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"
onClick={() => {
if (props.cancelFn) props.cancelFn()

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

@ -1,5 +1,5 @@
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) => {
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) => {
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 { CompilerAbstract as CompilerAbstractType } from '@remix-project/remix-solidity'
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 { displayNotification, displayPopUp, setDecodedResponse } from "./payload"
import { displayNotification, displayPopUp, fetchProxyDeploymentsSuccess, setDecodedResponse } from "./payload"
import { addInstance } from "./actions"
import { addressToString, logBuilder } from "@remix-ui/helper"
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 { RunTab } from "../types/run-tab"
import { setExecutionContext, setFinalContext, updateAccountBalances } from "./account"
import { addExternalProvider, addInstance, removeExternalProvider, setNetworkNameFromProvider } from "./actions"
import { addDeployOption, clearAllInstances, clearRecorderCount, fetchContractListSuccess, resetUdapp, setCurrentContract, setCurrentFile, setLoadType, setProxyEnvAddress, setRecorderCount, setRemixDActivated, setSendValue } from "./payload"
import { addExternalProvider, addInstance, addNewProxyDeployment, removeExternalProvider, setNetworkNameFromProvider } from "./actions"
import { addDeployOption, clearAllInstances, clearRecorderCount, fetchContractListSuccess, resetProxyDeployments, resetUdapp, setCurrentContract, setCurrentFile, setLoadType, setRecorderCount, setRemixDActivated, setSendValue } from "./payload"
import { CompilerAbstract } from '@remix-project/remix-solidity'
import BN from 'bn.js'
import Web3 from 'web3'
import { Plugin } from "@remixproject/engine"
import { getNetworkProxyAddresses } from "./deploy"
const _paq = window._paq = window._paq || []
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) => {
dispatch(resetProxyDeployments())
if (!context.startsWith('vm')) getNetworkProxyAddresses(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'
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('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('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,
setNetworkNameFromProvider, setPassphrasePrompt, setSelectedContract, setSendTransactionValue, setUnit,
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 { ContractData, FuncABI } from "@remix-project/core-plugin"
import { DeployMode, MainnetPrompt } from '../types'
import { runCurrentScenario, storeScenario } from './recorder'
import { SolcInput, SolcOutput } from '@openzeppelin/upgrades-core'
declare global {
interface Window {
@ -63,3 +64,4 @@ export const setNetworkName = (networkName: string) => setNetworkNameFromProvide
export const updateSelectedContract = (contractName) => setSelectedContract(dispatch, contractName)
export const syncContracts = () => syncContractsInternal(plugin)
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 { 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 { DeployMode, DeployOptions } from '../types'
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 { ContractList, DeployOptions } from '../types'
export const fetchAccountsListRequest = () => {
return {
@ -302,16 +301,29 @@ export const setCurrentContract = (contractName: string) => {
}
}
export const setProxyEnvAddress = (key: string) => {
export const setRemixDActivated = (activated: boolean) => {
return {
payload: key,
type: SET_PROXY_ENV_ADDRESS
payload: activated,
type: SET_REMIXD_ACTIVATED
}
}
export const setRemixDActivated = (activated: boolean) => {
export const fetchProxyDeploymentsSuccess = (deployments: { address: string, date: string, contractName: string }[]) => {
return {
payload: activated,
type: SET_REMIXD_ACTIVATED
type: FETCH_PROXY_DEPLOYMENTS,
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 contractsRef = useRef<HTMLSelectElement>(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(() => {
enableContractNames(Object.keys(props.contracts.contractList).length > 0)
@ -234,6 +234,10 @@ export function ContractDropdownUI (props: ContractDropdownProps) {
props.setSelectedContract(value)
}
const isValidProxyUpgrade = (proxyAddress: string) => {
return props.isValidProxyUpgrade(proxyAddress, loadedContractData.name, loadedContractData.compiler.source, loadedContractData.compiler.data)
}
const checkSumWarning = () => {
return (
<span className="text-start">
@ -311,8 +315,10 @@ export function ContractDropdownUI (props: ContractDropdownProps) {
widthClass='w-50'
evmBC={loadedContractData.bytecodeObject}
lookupOnly={false}
savedProxyAddress={proxyKey}
proxy={props.proxy}
isValidProxyAddress={props.isValidProxyAddress}
isValidProxyUpgrade={isValidProxyUpgrade}
modal={props.modal}
/>
<div className="d-flex py-1 align-items-center custom-control custom-checkbox">
<input

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

@ -1,108 +1,12 @@
import { CompilerAbstract } from '@remix-project/remix-solidity'
import { ContractData } from '@remix-project/core-plugin'
import { DeployOptions } 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 { 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, ADD_DEPLOY_OPTION, REMOVE_DEPLOY_OPTION, SET_REMIXD_ACTIVATED, FETCH_PROXY_DEPLOYMENTS, NEW_PROXY_DEPLOYMENT, RESET_PROXY_DEPLOYMENTS } from '../constants'
declare const window: any
interface Action {
type: string
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 = {
accounts: {
@ -139,7 +43,6 @@ export const runTabInitialState: RunTabState = {
contracts: {
contractList: {},
deployOptions: {} as any,
proxyKey: '',
compilationSource: '',
loadType: 'other',
currentFile: '',
@ -164,7 +67,10 @@ export const runTabInitialState: RunTabState = {
pathToScenario: 'scenario.json',
transactionCount: 0
},
remixdActivated: false
remixdActivated: false,
proxy: {
deployments: []
}
}
type AddProvider = {
@ -697,23 +603,45 @@ export const runTabReducer = (state: RunTabState = runTabInitialState, action: A
}
}
case SET_PROXY_ENV_ADDRESS: {
const payload: string = action.payload
case SET_REMIXD_ACTIVATED: {
const payload: boolean = action.payload
return {
...state,
remixdActivated: payload
}
}
case FETCH_PROXY_DEPLOYMENTS: {
const payload: { address: string, date: string, contractName: string }[] = action.payload
return {
...state,
contracts: {
...state.contracts,
proxyKey: payload
proxy: {
...state.proxy,
deployments: payload
}
}
}
case SET_REMIXD_ACTIVATED: {
const payload: boolean = action.payload
case NEW_PROXY_DEPLOYMENT: {
const payload: { address: string, date: string, contractName: string } = action.payload
return {
...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,
setScenarioPath, getFuncABIValues,
setNetworkName, updateSelectedContract,
syncContracts, isValidProxyAddress
syncContracts, isValidProxyAddress, isValidProxyUpgrade
} from './actions'
import './css/run-tab.css'
import { PublishToStorage } from '@remix-ui/publish-to-storage'
@ -81,7 +81,9 @@ export function RunTabUI (props: RunTabProps) {
okLabel: modals[0].okLabel,
okFn: modals[0].okFn,
cancelLabel: modals[0].cancelLabel,
cancelFn: modals[0].cancelFn
cancelFn: modals[0].cancelFn,
okBtnClass: modals[0].okBtnClass,
cancelBtnClass: modals[0].cancelBtnClass
}
return focusModal
})
@ -120,9 +122,9 @@ export function RunTabUI (props: RunTabProps) {
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 => {
modals.push({ message, title, okLabel, okFn, cancelLabel, cancelFn })
modals.push({ message, title, okLabel, okFn, cancelLabel, cancelFn, okBtnClass, cancelBtnClass })
return [...modals]
})
}
@ -242,6 +244,8 @@ export function RunTabUI (props: RunTabProps) {
setSelectedContract={updateSelectedContract}
remixdActivated={runTab.remixdActivated}
isValidProxyAddress={isValidProxyAddress}
isValidProxyUpgrade={isValidProxyUpgrade}
proxy={runTab.proxy}
/>
<RecorderUI
gasEstimationPrompt={gasEstimationPrompt}

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

@ -1,12 +1,111 @@
import { Ref } from 'react'
import { CompilerAbstract } from '@remix-project/remix-solidity'
import { ContractData, FuncABI } from '@remix-project/core-plugin'
import { ContractList } from '../reducers/runTab'
import { RunTab } from './run-tab'
import { SolcInput, SolcOutput } from '@openzeppelin/upgrades-core'
import { LayoutCompatibilityReport } from '@openzeppelin/upgrades-core/dist/storage/report'
export interface RunTabProps {
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 {
selectExEnv: string,
accounts: {
@ -41,7 +140,7 @@ export interface SettingsProps {
createNewBlockchainAccount: (cbMessage: JSX.Element) => void,
setPassphrase: (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,
signMessageWithAddress: (account: string, message: string, modalContent: (hash: string, data: string) => JSX.Element, passphrase?: string) => void,
passphrase: string,
@ -85,7 +184,7 @@ export interface AccountProps {
setPassphrase: (passphrase: string) => void,
setMatchPassphrase: (passphrase: 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,
passphrase: string
}
@ -129,7 +228,6 @@ export interface ContractDropdownProps {
contracts: {
contractList: ContractList,
deployOptions: { [file: string]: { [name: string]: DeployOptions } },
proxyKey: string,
loadType: 'abi' | 'sol' | 'other',
currentFile: string,
compilationSource: string
@ -141,7 +239,7 @@ export interface ContractDropdownProps {
},
syncContracts: () => void,
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,
setPassphrase: (passphrase: string) => void,
createInstance: (
@ -166,7 +264,9 @@ export interface ContractDropdownProps {
setNetworkName: (name: string) => void,
setSelectedContract: (contractName: string) => void
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 {
@ -223,7 +323,9 @@ export interface Modal {
okLabel: string
okFn: () => void
cancelLabel: string
cancelFn: () => void
cancelFn: () => void,
okBtnClass?: string,
cancelBtnClass?: string
}
export type DeployMode = 'Deploy with Proxy' | 'Upgrade with Proxy'
@ -261,8 +363,10 @@ export interface ContractGUIProps {
isDeploy?: boolean,
deployOption?: { title: DeployMode, active: boolean }[],
initializerOptions?: DeployOption,
savedProxyAddress?: string,
isValidProxyAddress?: (address: string) => Promise<boolean>
proxy?: { deployments: { address: string, date: string, contractName: string }[] },
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 {
network: Network,

@ -1,3 +1,9 @@
.remixui_default-message {
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 { TransformComponent, TransformWrapper } from 'react-zoom-pan-pinch'
import { ISolidityUmlGen } from '../types'
import { ThemeSummary } from '../types'
import './css/solidity-uml-gen.css'
export interface RemixUiSolidityUmlGenProps {
plugin?: ISolidityUmlGen
updatedSvg?: string
loading?: boolean
themeSelected?: string
}
type ButtonAction = {
svgValid: () => boolean
action: () => void
buttonText: string
icon?: string
customcss?: string
themeName: string
themeCollection: ThemeSummary[]
}
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(() => {
setValidSvg (updatedSvg.startsWith('<?xml') && updatedSvg.includes('<svg'))
@ -57,20 +31,50 @@ export function RemixUiSolidityUmlGen ({ plugin, updatedSvg, loading, themeSelec
}
, [updatedSvg])
const buttons: ButtonAction[] = [
{
buttonText: 'Download as PDF',
svgValid: () => validSvg,
action: () => console.log('generated!!'),
icon: 'fa mr-1 pt-1 pb-1 fa-file'
},
{
buttonText: 'Download as PNG',
svgValid: () => validSvg,
action: () => console.log('generated!!'),
icon: 'fa fa-picture-o'
}
]
const encoder = new TextEncoder()
const data = encoder.encode(updatedSvg)
const final = btoa(String.fromCharCode.apply(null, data))
function ActionButtons({ actions: { zoomIn, zoomOut, resetTransform }}: ActionButtonsProps) {
return (
<>
<div
className="position-absolute bg-transparent mt-2"
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 = () => (
<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>
)
const Display = () => {
const invert = themeSelected === 'dark' ? 'invert(0.8)' : 'invert(0)'
return (
<div className="d-flex flex-column justify-content-center align-items-center">
<div id="umlImageHolder" className="w-100 px-2 py-2">
<div id="umlImageHolder" className="w-100 px-2 py-2 d-flex">
{ validSvg && showViewer ? (
<TransformWrapper
initialScale={1}
@ -91,14 +93,16 @@ export function RemixUiSolidityUmlGen ({ plugin, updatedSvg, loading, themeSelec
{
({ zoomIn, zoomOut, resetTransform }) => (
<Fragment>
<TransformComponent>
<ActionButtons actions={{ zoomIn, zoomOut, resetTransform }} />
<TransformComponent contentStyle={{ zIndex: 2 }}>
<img
src={`data:image/svg+xml;base64,${btoa(plugin.updatedSvg ?? svgPayload)}`}
id="umlImage"
src={`data:image/svg+xml;base64,${final}`}
width={'100%'}
height={'auto'}
style={{ filter: invert }}
className="position-relative"
/>
</TransformComponent>
</TransformComponent>
</Fragment>
)
}
@ -107,7 +111,6 @@ export function RemixUiSolidityUmlGen ({ plugin, updatedSvg, loading, themeSelec
<i className="fas fa-spinner fa-spin fa-4x"></i>
</div> : <DefaultInfo />}
</div>
</div>
)}
return (<>
{ <Display /> }

@ -1,4 +1,5 @@
import { ViewPlugin } from '@remixproject/engine-web'
import { customAction } from '@remixproject/plugin-api'
import React from 'react'
export interface ISolidityUmlGen extends ViewPlugin {
@ -6,9 +7,22 @@ export interface ISolidityUmlGen extends ViewPlugin {
currentFile: string
svgPayload: string
updatedSvg: string
currentlySelectedTheme: string
themeName: string
loading: boolean
themeCollection: ThemeSummary[]
showUmlDiagram(path: string, svgPayload: string): void
updateComponent(state: any): JSX.Element
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
}
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 editor = plugin.registry.get('editor').api
if (config.get('currentFile') === path && editor.currentContent() !== content) {
if (provider.isReadOnly(path)) return editor.setText(content)
if (editor.getText(path) === content) return
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(
path + ' changed',
'This file has been changed outside of Remix IDE.',
'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
if ((config.get('currentFile') === name) && (editor.currentContent() !== event.target.result)) {
editor.setText(event.target.result)
editor.setText(name, event.target.result)
}
}
fileReader.readAsText(file)

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

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

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

@ -5048,6 +5048,19 @@
resolved "https://registry.yarnpkg.com/@openzeppelin/contracts/-/contracts-4.7.3.tgz#939534757a81f8d69cc854c7692805684ff3111e"
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":
version "0.1.1"
resolved "https://registry.yarnpkg.com/@openzeppelin/wizard/-/wizard-0.1.1.tgz#8c183e2c5748869bc3a5317c0330aa36a9ad44fe"
@ -9119,6 +9132,13 @@ catering@^2.1.0, catering@^2.1.1:
resolved "https://registry.yarnpkg.com/catering/-/catering-2.1.1.tgz#66acba06ed5ee28d5286133982a927de9a04b510"
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:
version "0.1.3"
resolved "https://registry.yarnpkg.com/center-align/-/center-align-0.1.3.tgz#aa0d32629b6ee972200411cbd4461c907bc2b7ad"
@ -9805,6 +9825,11 @@ compare-func@^2.0.0:
array-ify "^1.0.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:
version "1.3.0"
resolved "https://registry.yarnpkg.com/component-emitter/-/component-emitter-1.3.0.tgz#16e4070fba8ae29b679f2215853ee181ab2eabc0"
@ -12257,6 +12282,17 @@ ethereumjs-util@^7.0.10, ethereumjs-util@^7.1.0, ethereumjs-util@^7.1.3:
ethereum-cryptography "^0.1.3"
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:
version "5.5.1"
resolved "https://registry.yarnpkg.com/ethers/-/ethers-5.5.1.tgz#d3259a95a42557844aa543906c537106c0406fbf"
@ -19537,6 +19573,11 @@ nodent@>=2.6.12:
nodent-runtime "^3.2.1"
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:
version "3.0.6"
resolved "https://registry.yarnpkg.com/nopt/-/nopt-3.0.6.tgz#c6465dbf08abcd4db359317f79ac68a646b28ff9"
@ -21857,6 +21898,15 @@ prop-types@^15.8.1:
object-assign "^4.1.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:
version "1.2.4"
resolved "https://registry.yarnpkg.com/proto-list/-/proto-list-1.2.4.tgz#212d5bfe1318306a420f6402b8e26ff39647a849"
@ -23194,6 +23244,11 @@ retry@^0.10.0, retry@~0.10.1:
resolved "https://registry.yarnpkg.com/retry/-/retry-0.10.1.tgz#e76388d217992c252750241d3d3956fed98d8ff4"
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:
version "0.13.1"
resolved "https://registry.yarnpkg.com/retry/-/retry-0.13.1.tgz#185b1587acf67919d63b357349e03537b2484658"
@ -24070,6 +24125,11 @@ solc@0.7.4:
semver "^5.5.0"
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:
version "0.0.7"
resolved "https://registry.yarnpkg.com/solidity-comments-extractor/-/solidity-comments-extractor-0.0.7.tgz#99d8f1361438f84019795d928b931f4e5c39ca19"

Loading…
Cancel
Save