diff --git a/libs/remix-ui/run-tab/src/lib/actions/events.ts b/libs/remix-ui/run-tab/src/lib/actions/events.ts index 50c2122019..60b5544875 100644 --- a/libs/remix-ui/run-tab/src/lib/actions/events.ts +++ b/libs/remix-ui/run-tab/src/lib/actions/events.ts @@ -2,10 +2,11 @@ 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, setCompilationSource, setCurrentContract, setCurrentFile, setLoadType, setProxyEnvAddress, setRecorderCount, setSendValue } from "./payload" +import { addDeployOption, clearAllInstances, clearRecorderCount, fetchContractListSuccess, resetUdapp, setCurrentContract, setCurrentFile, setLoadType, setProxyEnvAddress, setRecorderCount, setRemixDActivated, setSendValue } from "./payload" import { CompilerAbstract } from '@remix-project/remix-solidity' import * as ethJSUtil from 'ethereumjs-util' import Web3 from 'web3' +import { Plugin } from "@remixproject/engine" const _paq = window._paq = window._paq || [] export const setupEvents = (plugin: RunTab, dispatch: React.Dispatch) => { @@ -74,6 +75,21 @@ export const setupEvents = (plugin: RunTab, dispatch: React.Dispatch) => { plugin.on('filePanel', 'setWorkspace', () => { dispatch(resetUdapp()) resetAndInit(plugin) + plugin.call('manager', 'isActive', 'remixd').then((activated) => { + dispatch(setRemixDActivated(activated)) + }) + }) + + plugin.on('manager', 'pluginActivated', (plugin: Plugin) => { + if (plugin.name === 'remixd') { + dispatch(setRemixDActivated(true)) + } + }) + + plugin.on('manager', 'pluginDeactivated', (plugin: Plugin) => { + if (plugin.name === 'remixd') { + dispatch(setRemixDActivated(false)) + } }) plugin.fileManager.events.on('currentFileChanged', (currentFile: string) => { @@ -107,7 +123,7 @@ const broadcastCompilationResult = async (compilerName: string, plugin: RunTab, plugin.compilersArtefacts.__last = compiler const contracts = getCompiledContracts(compiler).map((contract) => { - return { name: languageVersion, alias: contract.name, file: contract.file, compiler } + return { name: languageVersion, alias: contract.name, file: contract.file, compiler, compilerName } }) if ((contracts.length > 0)) { const contractsInCompiledFile = contracts.filter(obj => obj.file === file) @@ -127,7 +143,6 @@ const broadcastCompilationResult = async (compilerName: string, plugin: RunTab, } dispatch(fetchContractListSuccess({ [file]: contracts })) dispatch(setCurrentFile(file)) - dispatch(setCompilationSource(compilerName)) // TODO: set current contract } diff --git a/libs/remix-ui/run-tab/src/lib/actions/payload.ts b/libs/remix-ui/run-tab/src/lib/actions/payload.ts index d7dd517715..5ec29d9fb2 100644 --- a/libs/remix-ui/run-tab/src/lib/actions/payload.ts +++ b/libs/remix-ui/run-tab/src/lib/actions/payload.ts @@ -1,6 +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_COMPILATION_SOURCE, 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 } 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_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' export const fetchAccountsListRequest = () => { @@ -168,13 +168,6 @@ export const setCurrentFile = (file: string) => { } } -export const setCompilationSource = (source: string) => { - return { - type: SET_COMPILATION_SOURCE, - payload: source - } -} - export const setIpfsCheckedState = (state: boolean) => { return { type: SET_IPFS_CHECKED_STATE, @@ -315,3 +308,10 @@ export const setProxyEnvAddress = (key: string) => { type: SET_PROXY_ENV_ADDRESS } } + +export const setRemixDActivated = (activated: boolean) => { + return { + payload: activated, + type: SET_REMIXD_ACTIVATED + } +} diff --git a/libs/remix-ui/run-tab/src/lib/components/contractDropdownUI.tsx b/libs/remix-ui/run-tab/src/lib/components/contractDropdownUI.tsx index 9b6ef579c8..b9eb9fd7c7 100644 --- a/libs/remix-ui/run-tab/src/lib/components/contractDropdownUI.tsx +++ b/libs/remix-ui/run-tab/src/lib/components/contractDropdownUI.tsx @@ -8,7 +8,7 @@ import { deployWithProxyMsg, upgradeWithProxyMsg } from '@remix-ui/helper' import { OverlayTrigger, Tooltip } from 'react-bootstrap' const _paq = window._paq = window._paq || [] -export function ContractDropdownUI (props: ContractDropdownProps) { +export function ContractDropdownUI(props: ContractDropdownProps) { const [abiLabel, setAbiLabel] = useState<{ display: string, content: string @@ -16,18 +16,19 @@ export function ContractDropdownUI (props: ContractDropdownProps) { display: '', content: '' }) - const [atAddressOptions, setAtAddressOptions] = useState<{title: string, disabled: boolean}>({ + const [atAddressOptions, setAtAddressOptions] = useState<{ title: string, disabled: boolean }>({ title: 'address of contract', disabled: true }) const [loadedAddress, setLoadedAddress] = useState('') - const [contractOptions, setContractOptions] = useState<{title: string, disabled: boolean}>({ + const [contractOptions, setContractOptions] = useState<{ title: string, disabled: boolean }>({ title: 'Please compile *.sol file to deploy or access a contract', disabled: true }) const [loadedContractData, setLoadedContractData] = useState(null) const [constructorInterface, setConstructorInterface] = useState(null) const [constructorInputs, setConstructorInputs] = useState(null) + const [compilerName, setCompilerName] = useState('') const contractsRef = useRef(null) const atAddressValue = useRef(null) const { contractList, loadType, currentFile, compilationSource, currentContract, compilationCount, deployOptions, proxyKey } = props.contracts @@ -98,20 +99,23 @@ export function ContractDropdownUI (props: ContractDropdownProps) { useEffect(() => { initSelectedContract() + updateCompilerName() }, [contractList]) useEffect(() => { // if the file change the ui is already feed with another bunch of contracts. // we also need to update the state - const contracts = contractList[currentFile] + const contracts = contractList[currentFile] if (contracts && contracts.length > 0) { props.setSelectedContract(contracts[0].alias) } + updateCompilerName() }, [currentFile]) const initSelectedContract = () => { const contracts = contractList[currentFile] - + + if (contracts && contracts.length > 0) { const contract = contracts.find(contract => contract.alias === currentContract) @@ -123,9 +127,9 @@ export function ContractDropdownUI (props: ContractDropdownProps) { const isContractFile = (file) => { return /.(.sol)$/.exec(file) || - /.(.vy)$/.exec(file) || // vyper - /.(.lex)$/.exec(file) || // lexon - /.(.contract)$/.exec(file) + /.(.vy)$/.exec(file) || // vyper + /.(.lex)$/.exec(file) || // lexon + /.(.contract)$/.exec(file) } const enableAtAddress = (enable: boolean) => { @@ -162,20 +166,20 @@ export function ContractDropdownUI (props: ContractDropdownProps) { const createInstance = (selectedContract, args, deployMode?: DeployMode[]) => { if (selectedContract.bytecodeObject.length === 0) { - return props.modal('Alert', 'This contract may be abstract, it may not implement an abstract parent\'s methods completely or it may not invoke an inherited contract\'s constructor correctly.', 'OK', () => {}) + return props.modal('Alert', 'This contract may be abstract, it may not implement an abstract parent\'s methods completely or it may not invoke an inherited contract\'s constructor correctly.', 'OK', () => { }) } if ((selectedContract.name !== currentContract) && (selectedContract.name === 'ERC1967Proxy')) selectedContract.name = currentContract const isProxyDeployment = (deployMode || []).find(mode => mode === 'Deploy with Proxy') const isContractUpgrade = (deployMode || []).find(mode => mode === 'Upgrade with Proxy') - + if (isProxyDeployment) { props.modal('Deploy Implementation & Proxy (ERC1967)', deployWithProxyMsg(), 'Proceed', () => { props.createInstance(loadedContractData, props.gasEstimationPrompt, props.passphrasePrompt, props.publishToStorage, props.mainnetPrompt, isOverSizePrompt, args, deployMode) - }, 'Cancel', () => {}) + }, 'Cancel', () => { }) } else if (isContractUpgrade) { props.modal('Deploy Implementation & Update Proxy', upgradeWithProxyMsg(), 'Proceed', () => { props.createInstance(loadedContractData, props.gasEstimationPrompt, props.passphrasePrompt, props.publishToStorage, props.mainnetPrompt, isOverSizePrompt, args, deployMode) - }, 'Cancel', () => {}) + }, 'Cancel', () => { }) } else { props.createInstance(loadedContractData, props.gasEstimationPrompt, props.passphrasePrompt, props.publishToStorage, props.mainnetPrompt, isOverSizePrompt, args, deployMode) } @@ -212,9 +216,21 @@ export function ContractDropdownUI (props: ContractDropdownProps) { window.localStorage.setItem(`ipfs/${props.exEnvironment}/${props.networkName}`, checkedState.toString()) } + const updateCompilerName = () => { + if (contractsRef.current.value) { + contractList[currentFile].forEach(contract => { + if (contract.alias === contractsRef.current.value) { + setCompilerName(contract.compilerName) + } + }) + } else{ + setCompilerName('') + } + } + const handleContractChange = (e) => { const value = e.target.value - + updateCompilerName() props.setSelectedContract(value) } @@ -231,7 +247,7 @@ export function ContractDropdownUI (props: ContractDropdownProps) { const isOverSizePrompt = () => { return (
Contract creation initialization returns data with length of more than 24576 bytes. The deployment will likely fails.
- More info: eip-170 + More info: eip-170
) } @@ -241,33 +257,37 @@ export function ContractDropdownUI (props: ContractDropdownProps) {
-
{ Object.keys(props.contracts.contractList).length > 0 && compilationSource !== '' && }
+
{compilerName && compilerName !== '' && }
- -
Click here to import contracts compiled from an external framework.
-
This action is enabled when Remix is connected to an external framework (hardhat, truffle, foundry) through remixd.
- - }> - -
+ + + + : null}
- + {(contractList[currentFile] || []).map((contract, index) => { + return + })} - { abiLabel.content } + {abiLabel.content}
- { ((contractList[currentFile] && contractList[currentFile].filter(contract => contract)) || []).length <= 0 ? 'No compiled contracts' + {((contractList[currentFile] && contractList[currentFile].filter(contract => contract)) || []).length <= 0 ? 'No compiled contracts' : loadedContractData ?
void, setSelectedContract: (contractName: string) => void + remixdActivated: boolean } export interface RecorderProps { diff --git a/libs/remixd/src/services/hardhatClient.ts b/libs/remixd/src/services/hardhatClient.ts index 5468700dcb..c19479766e 100644 --- a/libs/remixd/src/services/hardhatClient.ts +++ b/libs/remixd/src/services/hardhatClient.ts @@ -3,7 +3,7 @@ import { PluginClient } from '@remixproject/plugin' import * as chokidar from 'chokidar' import * as utils from '../utils' import * as fs from 'fs-extra' -import { basename, join } from 'path' +import { join } from 'path' const { spawn } = require('child_process') // eslint-disable-line export class HardhatClient extends PluginClient { @@ -14,12 +14,12 @@ export class HardhatClient extends PluginClient { warnLog: boolean buildPath: string - constructor (private readOnly = false) { + constructor(private readOnly = false) { super() this.methods = ['compile', 'sync'] } - setWebSocket (websocket: WS): void { + setWebSocket(websocket: WS): void { this.websocket = websocket this.websocket.addEventListener('close', () => { this.warnLog = false @@ -27,13 +27,13 @@ export class HardhatClient extends PluginClient { }) } - sharedFolder (currentSharedFolder: string): void { + sharedFolder(currentSharedFolder: string): void { this.currentSharedFolder = currentSharedFolder this.buildPath = utils.absolutePath('artifacts/contracts', this.currentSharedFolder) this.listenOnHardhatCompilation() } - compile (configPath: string) { + compile(configPath: string) { return new Promise((resolve, reject) => { if (this.readOnly) { const errMsg = '[Hardhat Compilation]: Cannot compile in read-only mode' @@ -60,9 +60,10 @@ export class HardhatClient extends PluginClient { }) } - private async processArtifact () { + private async processArtifact() { // resolving the files const folderFiles = await fs.readdir(this.buildPath) + const targetsSynced = [] // name of folders are file names for (const file of folderFiles) { // ["artifacts/contracts/Greeter.sol/"] const contractFilePath = join(this.buildPath, file) @@ -87,15 +88,19 @@ export class HardhatClient extends PluginClient { const jsonStd = JSON.parse(contentStd) compilationResult.target = jsonStd.sourceName - // this is the full compilation result - console.log('Processing Hardhat artifact for file: ', file) + targetsSynced.push(compilationResult.target) const path = join(contractFilePath, jsonDbg.buildInfo) const content = await fs.readFile(path, { encoding: 'utf-8' }) - + await this.feedContractArtifactFile(content, compilationResult) } if (compilationResult.target) { - this.emit('compilationFinished', compilationResult.target, { sources: compilationResult.input }, 'soljson', compilationResult.output, compilationResult.solcVersion) + // we are only interested in the contracts that are in the target of the compilation + compilationResult.output = { + ...compilationResult.output, + contracts: { [compilationResult.target]: compilationResult.output.contracts[compilationResult.target] } + } + this.emit('compilationFinished', compilationResult.target, { sources: compilationResult.input }, 'soljson', compilationResult.output, compilationResult.solcVersion) } } } @@ -104,29 +109,32 @@ export class HardhatClient extends PluginClient { this.call('terminal', 'log', 'receiving compilation result from Hardhat') this.warnLog = true } + if (targetsSynced.length) { + console.log(`Processing artifacts for files: ${[...new Set(targetsSynced)].join(', ')}`) + // @ts-ignore + this.call('terminal', 'log', `synced with Hardhat: ${[...new Set(targetsSynced)].join(', ')}`) + } } - listenOnHardhatCompilation () { + listenOnHardhatCompilation() { try { this.watcher = chokidar.watch(this.buildPath, { depth: 1, ignorePermissionErrors: true, ignoreInitial: true }) - + this.watcher.on('change', () => this.processArtifact()) this.watcher.on('add', () => this.processArtifact()) // process the artifact on activation setTimeout(() => this.processArtifact(), 1000) } catch (e) { console.log(e) - } + } } - async sync () { + async sync() { console.log('syncing from Hardhat') this.processArtifact() - // @ts-ignore - this.call('terminal', 'log', 'synced with Hardhat') } - async feedContractArtifactFile (artifactContent, compilationResultPart) { + async feedContractArtifactFile(artifactContent, compilationResultPart) { const contentJSON = JSON.parse(artifactContent) compilationResultPart.solcVersion = contentJSON.solcVersion for (const file in contentJSON.input.sources) { @@ -139,10 +147,10 @@ export class HardhatClient extends PluginClient { compilationResultPart.output['sources'][file] = contentJSON.output.sources[file] compilationResultPart.output['contracts'][file] = contentJSON.output.contracts[file] if (contentJSON.output.errors && contentJSON.output.errors.length) { - compilationResultPart.output['errors'] = contentJSON.output.errors.filter(error => error.sourceLocation.file === file) + compilationResultPart.output['errors'] = contentJSON.output.errors.filter(error => error.sourceLocation.file === file) } } } - } + } } }