diff --git a/libs/remix-core-plugin/src/lib/openzeppelin-proxy.ts b/libs/remix-core-plugin/src/lib/openzeppelin-proxy.ts index f188f712f0..c94c0d3971 100644 --- a/libs/remix-core-plugin/src/lib/openzeppelin-proxy.ts +++ b/libs/remix-core-plugin/src/lib/openzeppelin-proxy.ts @@ -107,16 +107,24 @@ export class OpenZeppelinProxy extends Plugin { this.blockchain = blockchain } - async isConcerned(ast: ContractAST, contracts: ContractABI): Promise { + async isConcerned(ast: ContractAST, contracts: ContractABI) { // check in the AST if it's an upgradable contract if (ast.nodes.find(node => node.absolutePath === UUPS)) { - this.kind = 'UUPS' - Object.keys(contracts).map(name => { + const inputs = Object.keys(contracts).map(name => { const abi = contracts[name].abi + const initializeInput = abi.find(node => node.name === 'initialize') + + if (initializeInput) { + return { + [name]: initializeInput + } + } }) - return true + if (inputs.length > 0) { + this.kind = 'UUPS' + return { inputs } + } } - return false } async execute(implAddress: string, _data: string = '') { diff --git a/libs/remix-ui/run-tab/src/lib/actions/account.ts b/libs/remix-ui/run-tab/src/lib/actions/account.ts new file mode 100644 index 0000000000..9fba9a0723 --- /dev/null +++ b/libs/remix-ui/run-tab/src/lib/actions/account.ts @@ -0,0 +1,135 @@ +import { shortenAddress, web3Dialog } from "@remix-ui/helper" +import { RunTab } from "../types/run-tab" +import { clearInstances, setAccount, setExecEnv } from "./actions" +import { displayNotification, displayPopUp, fetchAccountsListFailed, fetchAccountsListRequest, fetchAccountsListSuccess, setExternalEndpoint, setMatchPassphrase, setPassphrase } from "./payload" + +export const updateAccountBalances = (plugin: RunTab, dispatch: React.Dispatch) => { + const accounts = plugin.REACT_API.accounts.loadedAccounts + + Object.keys(accounts).map((value) => { + plugin.blockchain.getBalanceInEther(value, (err, balance) => { + if (err) return + const updated = shortenAddress(value, balance) + + accounts[value] = updated + }) + }) + dispatch(fetchAccountsListSuccess(accounts)) +} + +export const fillAccountsList = async (plugin: RunTab, dispatch: React.Dispatch) => { + try { + dispatch(fetchAccountsListRequest()) + const promise = plugin.blockchain.getAccounts() + + promise.then(async (accounts: string[]) => { + const loadedAccounts = {} + + if (!accounts) accounts = [] + // allSettled is undefined.. + // so the current promise (all) will finish when: + // - all the promises resolve + // - at least one reject + // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise/all + await (Promise as any).all(accounts.map((account) => { + return new Promise((resolve, reject) => { + plugin.blockchain.getBalanceInEther(account, (err, balance) => { + if (err) return reject(err) + const updated = shortenAddress(account, balance) + + loadedAccounts[account] = updated + resolve(account) + }) + }) + })) + const provider = plugin.blockchain.getProvider() + + if (provider === 'injected') { + const selectedAddress = plugin.blockchain.getInjectedWeb3Address() + + if (!(Object.keys(loadedAccounts).includes(selectedAddress))) setAccount(dispatch, null) + } + dispatch(fetchAccountsListSuccess(loadedAccounts)) + }).catch((e) => { + dispatch(fetchAccountsListFailed(e.message)) + }) + } catch (e) { + dispatch(displayPopUp(`Cannot get account list: ${e}`)) + } +} + +export const setFinalContext = (plugin: RunTab, dispatch: React.Dispatch) => { + // set the final context. Cause it is possible that this is not the one we've originaly selected + const value = _getProviderDropdownValue(plugin) + + setExecEnv(dispatch, value) + clearInstances(dispatch) +} + +const _getProviderDropdownValue = (plugin: RunTab): string => { + const provider = plugin.blockchain.getProvider() + const fork = plugin.blockchain.getCurrentFork() + + return provider === 'vm' ? provider + '-' + fork : provider +} + +export const setExecutionContext = (plugin: RunTab, dispatch: React.Dispatch, executionContext: { context: string, fork: string }) => { + const displayContent = web3Dialog(plugin.REACT_API.externalEndpoint, (endpoint: string) => { + dispatch(setExternalEndpoint(endpoint)) + }) + + plugin.blockchain.changeExecutionContext(executionContext, () => { + plugin.call('notification', 'modal', { + id: 'envNotification', + title: 'External node request', + message: displayContent, + okLabel: 'OK', + cancelLabel: 'Cancel', + okFn: () => { + plugin.blockchain.setProviderFromEndpoint(plugin.REACT_API.externalEndpoint, executionContext, (alertMsg) => { + if (alertMsg) plugin.call('notification', 'toast', alertMsg) + setFinalContext(plugin, dispatch) + }) + }, + cancelFn: () => { + setFinalContext(plugin, dispatch) + } + }) + }, (alertMsg) => { + plugin.call('notification', 'toast', alertMsg) + }, () => { setFinalContext(plugin, dispatch) }) +} + +export const createNewBlockchainAccount = async (plugin: RunTab, dispatch: React.Dispatch, cbMessage: JSX.Element) => { + plugin.blockchain.newAccount( + '', + (cb) => { + dispatch(displayNotification('Enter Passphrase', cbMessage, 'OK', 'Cancel', async () => { + if (plugin.REACT_API.passphrase === plugin.REACT_API.matchPassphrase) { + cb(plugin.REACT_API.passphrase) + } else { + dispatch(displayNotification('Error', 'Passphase does not match', 'OK', null)) + } + setPassphrase('') + setMatchPassphrase('') + }, () => {})) + }, + async (error, address) => { + if (error) { + return dispatch(displayPopUp('Cannot create an account: ' + error)) + } + dispatch(displayPopUp(`account ${address} created`)) + await fillAccountsList(plugin, dispatch) + } + ) +} + + +export const signMessageWithAddress = (plugin: RunTab, dispatch: React.Dispatch, account: string, message: string, modalContent: (hash: string, data: string) => JSX.Element, passphrase?: string) => { + plugin.blockchain.signMessage(message, account, passphrase, (err, msgHash, signedData) => { + if (err) { + return displayPopUp(err) + } + dispatch(displayNotification('Signed Message', modalContent(msgHash, signedData), 'OK', null, () => {}, null)) + }) +} \ No newline at end of file diff --git a/libs/remix-ui/run-tab/src/lib/actions/actions.ts b/libs/remix-ui/run-tab/src/lib/actions/actions.ts new file mode 100644 index 0000000000..a99a8b3800 --- /dev/null +++ b/libs/remix-ui/run-tab/src/lib/actions/actions.ts @@ -0,0 +1,96 @@ +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, setTxFeeContent } from "./payload" + +export const setAccount = (dispatch: React.Dispatch, account: string) => { + dispatch(setSelectedAccount(account)) +} + +export const setUnit = (dispatch: React.Dispatch, unit: 'ether' | 'finney' | 'gwei' | 'wei') => { + dispatch(setSendUnit(unit)) +} + +export const setGasFee = (dispatch: React.Dispatch, value: number) => { + dispatch(setGasLimit(value)) +} + +export const setExecEnv = (dispatch: React.Dispatch, env: string) => { + dispatch(setExecutionEnvironment(env)) +} + +export const setNetworkNameFromProvider = (dispatch: React.Dispatch, networkName: string) => { + dispatch(setNetworkName(networkName)) +} + +export const addExternalProvider = (dispatch: React.Dispatch, network) => { + dispatch(addProvider(network)) +} + +export const removeExternalProvider = (dispatch: React.Dispatch, name) => { + dispatch(removeProvider(name)) +} + +export const clearPopUp = async (dispatch: React.Dispatch) => { + dispatch(hidePopUp()) +} + +export const setPassphrasePrompt = (dispatch: React.Dispatch, passphrase: string) => { + dispatch(setPassphrase(passphrase)) +} + +export const setMatchPassphrasePrompt = (dispatch: React.Dispatch, passphrase: string) => { + dispatch(setMatchPassphrase(passphrase)) +} + +export const updateGasPriceStatus = (dispatch: React.Dispatch, status: boolean) => { + dispatch(setGasPriceStatus(status)) +} + +export const updateConfirmSettings = (dispatch: React.Dispatch, confirmation: boolean) => { + dispatch(setConfirmSettings(confirmation)) +} + +export const updateMaxFee = (dispatch: React.Dispatch, fee: string) => { + dispatch(setMaxFee(fee)) +} + +export const updateMaxPriorityFee = (dispatch: React.Dispatch, fee: string) => { + dispatch(setMaxPriorityFee(fee)) +} + +export const updateBaseFeePerGas = (dispatch: React.Dispatch, baseFee: string) => { + dispatch(setBaseFeePerGas(baseFee)) +} + +export const updateGasPrice = (dispatch: React.Dispatch, price: string) => { + dispatch(setGasPrice(price)) +} + +export const updateTxFeeContent = (dispatch: React.Dispatch, content: string) => { + dispatch(setTxFeeContent(content)) +} + +export const addInstance = (dispatch: React.Dispatch, instance: { contractData?: ContractData, address: string, name: string, abi?: any, decodedResponse?: Record }) => { + instance.decodedResponse = {} + dispatch(addNewInstance(instance)) +} + +export const removeInstance = (dispatch: React.Dispatch, index: number) => { + dispatch(removeExistingInstance(index)) +} + +export const clearInstances = (dispatch: React.Dispatch) => { + dispatch(clearAllInstances()) + dispatch(clearRecorderCount()) +} + +export const setSelectedContract = (dispatch: React.Dispatch, contractName: string) => { + dispatch(setCurrentContract(contractName)) +} + +export const updateScenarioPath = (dispatch: React.Dispatch, path: string) => { + dispatch(setPathToScenario(path)) +} + +export const setSendTransactionValue = (dispatch: React.Dispatch, value: string) => { + dispatch(setSendValue(value)) +} \ No newline at end of file diff --git a/libs/remix-ui/run-tab/src/lib/actions/deploy.ts b/libs/remix-ui/run-tab/src/lib/actions/deploy.ts new file mode 100644 index 0000000000..73b3425216 --- /dev/null +++ b/libs/remix-ui/run-tab/src/lib/actions/deploy.ts @@ -0,0 +1,301 @@ +import { ContractData, FuncABI } from "@remix-project/core-plugin" +import { RunTab } from "../types/run-tab" +import { CompilerAbstract as CompilerAbstractType } from '@remix-project/remix-solidity-ts' +import * as remixLib from '@remix-project/remix-lib' +import { DeployMode, MainnetPrompt } from "../types" +import { displayNotification, displayPopUp, setDecodedResponse } from "./payload" +import { addInstance } from "./actions" +import { addressToString } from "@remix-ui/helper" + +declare global { + interface Window { + _paq: any + } +} + +const _paq = window._paq = window._paq || [] //eslint-disable-line +const txHelper = remixLib.execution.txHelper +const txFormat = remixLib.execution.txFormat + +const loadContractFromAddress = (plugin: RunTab, address, confirmCb, cb) => { + if (/.(.abi)$/.exec(plugin.config.get('currentFile'))) { + confirmCb(() => { + let abi + try { + abi = JSON.parse(plugin.editor.currentContent()) + } catch (e) { + return cb('Failed to parse the current file as JSON ABI.') + } + _paq.push(['trackEvent', 'udapp', 'AtAddressLoadWithABI']) + cb(null, 'abi', abi) + }) + } else { + _paq.push(['trackEvent', 'udapp', 'AtAddressLoadWithArtifacts']) + cb(null, 'instance') + } +} + +export const getSelectedContract = (contractName: string, compiler: CompilerAbstractType): ContractData => { + if (!contractName) return null + // const compiler = plugin.compilersArtefacts[compilerAtributeName] + + if (!compiler) return null + + const contract = compiler.getContract(contractName) + + return { + name: contractName, + contract: contract, + compiler: compiler, + abi: contract.object.abi, + bytecodeObject: contract.object.evm.bytecode.object, + bytecodeLinkReferences: contract.object.evm.bytecode.linkReferences, + object: contract.object, + deployedBytecode: contract.object.evm.deployedBytecode, + getConstructorInterface: () => { + return txHelper.getConstructorInterface(contract.object.abi) + }, + getConstructorInputs: () => { + const constructorInteface = txHelper.getConstructorInterface(contract.object.abi) + + return txHelper.inputParametersDeclarationToString(constructorInteface.inputs) + }, + isOverSizeLimit: () => { + const deployedBytecode = contract.object.evm.deployedBytecode + + return (deployedBytecode && deployedBytecode.object.length / 2 > 24576) + }, + metadata: contract.object.metadata + } +} + +const getCompilerContracts = (plugin: RunTab) => { + return plugin.compilersArtefacts.__last.getData().contracts +} + +export const terminalLogger = (plugin: RunTab, view: JSX.Element) => { + plugin.call('terminal', 'logHtml', view) +} + +export const confirmationHandler = (plugin: RunTab, dispatch: React.Dispatch, confirmDialogContent: MainnetPrompt, network, tx, gasEstimation, continueTxExecution, cancelCb) => { + if (network.name !== 'Main') { + return continueTxExecution(null) + } + const amount = plugin.blockchain.fromWei(tx.value, true, 'ether') + const content = confirmDialogContent(tx, network, amount, gasEstimation, plugin.blockchain.determineGasFees(tx), plugin.blockchain.determineGasPrice.bind(plugin.blockchain)) + + dispatch(displayNotification('Confirm transaction', content, 'Confirm', 'Cancel', () => { + plugin.blockchain.config.setUnpersistedProperty('doNotShowTransactionConfirmationAgain', plugin.REACT_API.confirmSettings) + // TODO: check if this is check is still valid given the refactor + if (!plugin.REACT_API.gasPriceStatus) { + cancelCb('Given transaction fee is not correct') + } else { + continueTxExecution({ maxFee: plugin.REACT_API.maxFee, maxPriorityFee: plugin.REACT_API.maxPriorityFee, baseFeePerGas: plugin.REACT_API.baseFeePerGas, gasPrice: plugin.REACT_API.gasPrice }) + } + }, () => { + return cancelCb('Transaction canceled by user.') + })) +} + +const getConfirmationCb = (plugin: RunTab, dispatch: React.Dispatch, confirmDialogContent: MainnetPrompt) => { + // this code is the same as in recorder.js. TODO need to be refactored out + return (network, tx, gasEstimation, continueTxExecution, cancelCb) => { + confirmationHandler(plugin, dispatch, confirmDialogContent, network, tx, gasEstimation, continueTxExecution, cancelCb) + } +} + +export const continueHandler = (dispatch: React.Dispatch, gasEstimationPrompt: (msg: string) => JSX.Element, error, continueTxExecution, cancelCb) => { + if (error) { + const msg = typeof error !== 'string' ? error.message : error + + dispatch(displayNotification('Gas estimation failed', gasEstimationPrompt(msg), 'Send Transaction', 'Cancel Transaction', () => { + continueTxExecution() + }, () => { + cancelCb() + })) + } else { + continueTxExecution() + } +} + +export const promptHandler = (dispatch: React.Dispatch, passphrasePrompt, okCb, cancelCb) => { + dispatch(displayNotification('Passphrase requested', passphrasePrompt('Personal mode is enabled. Please provide passphrase of account'), 'OK', 'Cancel', okCb, cancelCb)) +} + +export const createInstance = async ( + plugin: RunTab, + dispatch: React.Dispatch, + selectedContract: ContractData, + gasEstimationPrompt: (msg: string) => JSX.Element, + passphrasePrompt: (msg: string) => JSX.Element, + logBuilder: (msg: string) => JSX.Element, + publishToStorage: (storage: 'ipfs' | 'swarm', + contract: ContractData) => void, + mainnetPrompt: MainnetPrompt, + isOverSizePrompt: () => JSX.Element, + args, + deployMode: DeployMode[]) => { + const statusCb = (msg: string) => { + const log = logBuilder(msg) + + return terminalLogger(plugin, log) + } + + const finalCb = (error, contractObject, address) => { + if (error) { + const log = logBuilder(error) + + return terminalLogger(plugin, log) + } + addInstance(dispatch, { contractData: contractObject, address, name: contractObject.name }) + const data = plugin.compilersArtefacts.getCompilerAbstract(contractObject.contract.file) + + plugin.compilersArtefacts.addResolvedContract(addressToString(address), data) + if (plugin.REACT_API.ipfsChecked) { + _paq.push(['trackEvent', 'udapp', 'DeployAndPublish', plugin.REACT_API.networkName]) + publishToStorage('ipfs', selectedContract) + } else { + _paq.push(['trackEvent', 'udapp', 'DeployOnly', plugin.REACT_API.networkName]) + } + deployMode.forEach(async (mode) => { + const owner = plugin.REACT_API.accounts.selectedAccount + + if (mode === 'Deploy with Proxy') await plugin.call('openzeppelin-proxy', 'execute', address, owner) + }) + } + + let contractMetadata + try { + contractMetadata = await plugin.call('compilerMetadata', 'deployMetadataOf', selectedContract.name, selectedContract.contract.file) + } catch (error) { + return statusCb(`creation of ${selectedContract.name} errored: ${error.message ? error.message : error}`) + } + + const compilerContracts = getCompilerContracts(plugin) + const confirmationCb = getConfirmationCb(plugin, dispatch, mainnetPrompt) + + if (selectedContract.isOverSizeLimit()) { + return dispatch(displayNotification('Contract code size over limit', isOverSizePrompt(), 'Force Send', 'Cancel', () => { + deployContract(plugin, selectedContract, args, contractMetadata, compilerContracts, { + continueCb: (error, continueTxExecution, cancelCb) => { + continueHandler(dispatch, gasEstimationPrompt, error, continueTxExecution, cancelCb) + }, + promptCb: (okCb, cancelCb) => { + promptHandler(dispatch, passphrasePrompt, okCb, cancelCb) + }, + statusCb, + finalCb + }, confirmationCb) + }, () => { + const log = logBuilder(`creation of ${selectedContract.name} canceled by user.`) + + return terminalLogger(plugin, log) + })) + } + deployContract(plugin, selectedContract, args, contractMetadata, compilerContracts, { + continueCb: (error, continueTxExecution, cancelCb) => { + continueHandler(dispatch, gasEstimationPrompt, error, continueTxExecution, cancelCb) + }, + promptCb: (okCb, cancelCb) => { + promptHandler(dispatch, passphrasePrompt, okCb, cancelCb) + }, + statusCb, + finalCb + }, confirmationCb) +} + +const deployContract = (plugin: RunTab, selectedContract, args, contractMetadata, compilerContracts, callbacks, confirmationCb) => { + _paq.push(['trackEvent', 'udapp', 'DeployContractTo', plugin.REACT_API.networkName]) + const { statusCb } = callbacks + + if (!contractMetadata || (contractMetadata && contractMetadata.autoDeployLib)) { + return plugin.blockchain.deployContractAndLibraries(selectedContract, args, contractMetadata, compilerContracts, callbacks, confirmationCb) + } + if (Object.keys(selectedContract.bytecodeLinkReferences).length) statusCb(`linking ${JSON.stringify(selectedContract.bytecodeLinkReferences, null, '\t')} using ${JSON.stringify(contractMetadata.linkReferences, null, '\t')}`) + plugin.blockchain.deployContractWithLibrary(selectedContract, args, contractMetadata, compilerContracts, callbacks, confirmationCb) +} + +export const loadAddress = (plugin: RunTab, dispatch: React.Dispatch, contract: ContractData, address: string) => { + loadContractFromAddress(plugin, address, + (cb) => { + dispatch(displayNotification('At Address', `Do you really want to interact with ${address} using the current ABI definition?`, 'OK', 'Cancel', cb, null)) + }, + (error, loadType, abi) => { + if (error) { + return dispatch(displayNotification('Alert', error, 'OK', null)) + } + if (loadType === 'abi') { + return addInstance(dispatch, { abi, address, name: '' }) + } else if (loadType === 'instance') { + if (!contract) return dispatch(displayPopUp('No compiled contracts found.')) + const currentFile = plugin.REACT_API.contracts.currentFile + const compiler = plugin.REACT_API.contracts.contractList[currentFile].find(item => item.alias === contract.name) + const contractData = getSelectedContract(contract.name, compiler.compiler) + return addInstance(dispatch, { contractData, address, name: contract.name }) + } + } + ) + } + +export const getContext = (plugin: RunTab) => { + return plugin.blockchain.context() +} + +export const runTransactions = ( + plugin: RunTab, + dispatch: React.Dispatch, + instanceIndex: number, + lookupOnly: boolean, + funcABI: FuncABI, + inputsValues: string, + contractName: string, + contractABI, contract, + address, + logMsg:string, + logBuilder: (msg: string) => JSX.Element, + mainnetPrompt: MainnetPrompt, + gasEstimationPrompt: (msg: string) => JSX.Element, + passphrasePrompt: (msg: string) => JSX.Element, + funcIndex?: number) => { + let callinfo = '' + if (lookupOnly) callinfo = 'call' + else if (funcABI.type === 'fallback' || funcABI.type === 'receive') callinfo = 'lowLevelInteracions' + else callinfo = 'transact' + _paq.push(['trackEvent', 'udapp', callinfo, plugin.blockchain.getCurrentNetworkStatus().network.name]) + + const params = funcABI.type !== 'fallback' ? inputsValues : '' + plugin.blockchain.runOrCallContractMethod( + contractName, + contractABI, + funcABI, + contract, + inputsValues, + address, + params, + lookupOnly, + logMsg, + (msg) => { + const log = logBuilder(msg) + + return terminalLogger(plugin, log) + }, + (returnValue) => { + const response = txFormat.decodeResponse(returnValue, funcABI) + + dispatch(setDecodedResponse(instanceIndex, response, funcIndex)) + }, + (network, tx, gasEstimation, continueTxExecution, cancelCb) => { + confirmationHandler(plugin, dispatch, mainnetPrompt, network, tx, gasEstimation, continueTxExecution, cancelCb) + }, + (error, continueTxExecution, cancelCb) => { + continueHandler(dispatch, gasEstimationPrompt, error, continueTxExecution, cancelCb) + }, + (okCb, cancelCb) => { + promptHandler(dispatch, passphrasePrompt, okCb, cancelCb) + } + ) +} + +export const getFuncABIInputs = (plugin: RunTab, funcABI: FuncABI) => { + return plugin.blockchain.getInputs(funcABI) +} \ No newline at end of file diff --git a/libs/remix-ui/run-tab/src/lib/actions/events.ts b/libs/remix-ui/run-tab/src/lib/actions/events.ts new file mode 100644 index 0000000000..723f2d33c7 --- /dev/null +++ b/libs/remix-ui/run-tab/src/lib/actions/events.ts @@ -0,0 +1,143 @@ +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 { clearAllInstances, clearRecorderCount, fetchContractListSuccess, resetUdapp, setCurrentFile, setDeployOptions, setLoadType, setRecorderCount, setSendValue } from "./payload" +import { CompilerAbstract } from '@remix-project/remix-solidity' +import * as ethJSUtil from 'ethereumjs-util' +import Web3 from 'web3' + +export const setupEvents = (plugin: RunTab, dispatch: React.Dispatch) => { + plugin.blockchain.events.on('newTransaction', (tx, receipt) => { + plugin.emit('newTransaction', tx, receipt) + }) + + plugin.blockchain.event.register('transactionExecuted', (error, from, to, data, lookupOnly, txResult) => { + if (!lookupOnly) dispatch(setSendValue('0')) + if (error) return + updateAccountBalances(plugin, dispatch) + }) + + plugin.blockchain.event.register('contextChanged', (context, silent) => { + setFinalContext(plugin, dispatch) + }) + + plugin.blockchain.event.register('networkStatus', ({ error, network }) => { + if (error) { + const netUI = 'can\'t detect network ' + setNetworkNameFromProvider(dispatch, netUI) + + return + } + const networkProvider = plugin.networkModule.getNetworkProvider.bind(plugin.networkModule) + const netUI = (networkProvider() !== 'vm') ? `${network.name} (${network.id || '-'}) network` : 'VM' + + setNetworkNameFromProvider(dispatch, netUI) + }) + + plugin.blockchain.event.register('addProvider', provider => addExternalProvider(dispatch, provider)) + + plugin.blockchain.event.register('removeProvider', name => removeExternalProvider(dispatch, name)) + + plugin.on('solidity', 'compilationFinished', (file, source, languageVersion, data, input, version) => broadcastCompilationResult(plugin, dispatch, file, source, languageVersion, data, input)) + + plugin.on('vyper', 'compilationFinished', (file, source, languageVersion, data) => broadcastCompilationResult(plugin, dispatch, file, source, languageVersion, data)) + + plugin.on('lexon', 'compilationFinished', (file, source, languageVersion, data) => broadcastCompilationResult(plugin, dispatch, file, source, languageVersion, data)) + + plugin.on('yulp', 'compilationFinished', (file, source, languageVersion, data) => broadcastCompilationResult(plugin, dispatch, file, source, languageVersion, data)) + + plugin.on('nahmii-compiler', 'compilationFinished', (file, source, languageVersion, data) => broadcastCompilationResult(plugin, dispatch, file, source, languageVersion, data)) + + plugin.on('udapp', 'setEnvironmentModeReducer', (env: { context: string, fork: string }, from: string) => { + plugin.call('notification', 'toast', envChangeNotification(env, from)) + setExecutionContext(plugin, dispatch, env) + }) + + plugin.on('udapp', 'clearAllInstancesReducer', () => { + dispatch(clearAllInstances()) + }) + + plugin.on('udapp', 'addInstanceReducer', (address, abi, name) => { + addInstance(dispatch, { abi, address, name }) + }) + + plugin.on('filePanel', 'setWorkspace', () => { + dispatch(resetUdapp()) + resetAndInit(plugin) + }) + + plugin.fileManager.events.on('currentFileChanged', (currentFile: string) => { + if (/.(.abi)$/.exec(currentFile)) { + dispatch(setLoadType('abi')) + } else if (/.(.sol)$/.exec(currentFile) || + /.(.vy)$/.exec(currentFile) || // vyper + /.(.lex)$/.exec(currentFile) || // lexon + /.(.contract)$/.exec(currentFile)) { + dispatch(setLoadType('sol')) + } else { + dispatch(setLoadType('other')) + } + dispatch(setCurrentFile(currentFile)) + }) + + plugin.recorder.event.register('recorderCountChange', (count) => { + dispatch(setRecorderCount(count)) + }) + + plugin.event.register('cleared', () => { + dispatch(clearRecorderCount()) + }) +} + +const broadcastCompilationResult = async (plugin: RunTab, dispatch: React.Dispatch, file, source, languageVersion, data, input?) => { + // TODO check whether the tab is configured + const compiler = new CompilerAbstract(languageVersion, data, source, input) + + plugin.compilersArtefacts[languageVersion] = compiler + plugin.compilersArtefacts.__last = compiler + + const contracts = getCompiledContracts(compiler).map((contract) => { + return { name: languageVersion, alias: contract.name, file: contract.file, compiler } + }) + const upgradeable = await plugin.call('openzeppelin-proxy', 'isConcerned', data.sources[file].ast, data.contracts[file]) + + if (upgradeable) dispatch(setDeployOptions([{ title: 'Deploy with Proxy', active: false, inputs: upgradeable.inputs }])) + else dispatch(setDeployOptions([])) + dispatch(fetchContractListSuccess({ [file]: contracts })) + dispatch(setCurrentFile(file)) +} + +const getCompiledContracts = (compiler) => { + const contracts = [] + + compiler.visitContracts((contract) => { + contracts.push(contract) + }) + return contracts +} + +export const resetAndInit = (plugin: RunTab) => { + plugin.blockchain.resetAndInit(plugin.config, { + getAddress: (cb) => { + cb(null, plugin.REACT_API.accounts.selectedAccount) + }, + getValue: (cb) => { + try { + const number = plugin.REACT_API.sendValue + const unit = plugin.REACT_API.sendUnit + + cb(null, Web3.utils.toWei(number, unit)) + } catch (e) { + cb(e) + } + }, + getGasLimit: (cb) => { + try { + cb(null, '0x' + new ethJSUtil.BN(plugin.REACT_API.gasLimit, 10).toString(16)) + } catch (e) { + cb(e.message) + } + } + }) +} \ No newline at end of file diff --git a/libs/remix-ui/run-tab/src/lib/actions/index.ts b/libs/remix-ui/run-tab/src/lib/actions/index.ts index f3b1adb745..121c7d7ae6 100644 --- a/libs/remix-ui/run-tab/src/lib/actions/index.ts +++ b/libs/remix-ui/run-tab/src/lib/actions/index.ts @@ -1,17 +1,15 @@ // eslint-disable-next-line no-unused-vars import React from 'react' -import * as ethJSUtil from 'ethereumjs-util' -import Web3 from 'web3' -import { addressToString, createNonClashingNameAsync, envChangeNotification, extractNameFromKey, shortenAddress, web3Dialog } from '@remix-ui/helper' -import { addNewInstance, addProvider, clearAllInstances, clearRecorderCount, displayNotification, displayPopUp, fetchAccountsListFailed, fetchAccountsListRequest, fetchAccountsListSuccess, fetchContractListSuccess, hidePopUp, removeExistingInstance, removeProvider, resetUdapp, setBaseFeePerGas, setConfirmSettings, setCurrentFile, setDecodedResponse, setDeployOptions, setExecutionEnvironment, setExternalEndpoint, setGasLimit, setGasPrice, setGasPriceStatus, setLoadType, setMatchPassphrase, setMaxFee, setMaxPriorityFee, setNetworkName, setPassphrase, setPathToScenario, setRecorderCount, setSelectedAccount, setSendUnit, setSendValue, setTxFeeContent } from './payload' import { RunTab } from '../types/run-tab' -import { CompilerAbstract } from '@remix-project/remix-solidity' -import * as remixLib from '@remix-project/remix-lib' -import { DeployMode, MainnetPrompt } from '../types' -import { ContractData, FuncABI, } from '@remix-project/core-plugin' +import { resetAndInit, setupEvents } from './events' +import { createNewBlockchainAccount, fillAccountsList, setExecutionContext, signMessageWithAddress } from './account' +import { clearInstances, clearPopUp, removeInstance, setAccount, setGasFee, setMatchPassphrasePrompt, setNetworkNameFromProvider, setPassphrasePrompt, setSendTransactionValue, setUnit, updateBaseFeePerGas, updateConfirmSettings, updateGasPrice, updateGasPriceStatus, updateMaxFee, updateMaxPriorityFee, updateScenarioPath, updateTxFeeContent } from './actions' +import { createInstance, getContext, getFuncABIInputs, getSelectedContract, loadAddress, runTransactions } from './deploy' import { CompilerAbstract as CompilerAbstractType } from '@remix-project/remix-solidity-ts' +import { ContractData, FuncABI } from "@remix-project/core-plugin" +import { DeployMode, MainnetPrompt } from '../types' +import { runCurrentScenario, storeScenario } from './recorder' -const txFormat = remixLib.execution.txFormat declare global { interface Window { _paq: any @@ -19,718 +17,44 @@ declare global { } const _paq = window._paq = window._paq || [] //eslint-disable-line -const txHelper = remixLib.execution.txHelper let plugin: RunTab, dispatch: React.Dispatch export const initRunTab = (udapp: RunTab) => async (reducerDispatch: React.Dispatch) => { plugin = udapp dispatch = reducerDispatch - resetAndInit() - setupEvents() + resetAndInit(plugin) + setupEvents(plugin, dispatch) setInterval(() => { - fillAccountsList() + fillAccountsList(plugin, dispatch) }, 1000) } -const setupEvents = () => { - plugin.blockchain.events.on('newTransaction', (tx, receipt) => { - plugin.emit('newTransaction', tx, receipt) - }) - - plugin.blockchain.event.register('transactionExecuted', (error, from, to, data, lookupOnly, txResult) => { - if (!lookupOnly) dispatch(setSendValue('0')) - if (error) return - updateAccountBalances() - }) - - plugin.blockchain.event.register('contextChanged', (context, silent) => { - setFinalContext() - }) - - plugin.blockchain.event.register('networkStatus', ({ error, network }) => { - if (error) { - const netUI = 'can\'t detect network ' - setNetworkNameFromProvider(netUI) - - return - } - const networkProvider = plugin.networkModule.getNetworkProvider.bind(plugin.networkModule) - const netUI = (networkProvider() !== 'vm') ? `${network.name} (${network.id || '-'}) network` : 'VM' - - setNetworkNameFromProvider(netUI) - }) - - plugin.blockchain.event.register('addProvider', provider => addExternalProvider(provider)) - - plugin.blockchain.event.register('removeProvider', name => removeExternalProvider(name)) - - plugin.on('solidity', 'compilationFinished', (file, source, languageVersion, data, input, version) => broadcastCompilationResult(file, source, languageVersion, data, input)) - - plugin.on('vyper', 'compilationFinished', (file, source, languageVersion, data) => broadcastCompilationResult(file, source, languageVersion, data)) - - plugin.on('lexon', 'compilationFinished', (file, source, languageVersion, data) => broadcastCompilationResult(file, source, languageVersion, data)) - - plugin.on('yulp', 'compilationFinished', (file, source, languageVersion, data) => broadcastCompilationResult(file, source, languageVersion, data)) - - plugin.on('nahmii-compiler', 'compilationFinished', (file, source, languageVersion, data) => broadcastCompilationResult(file, source, languageVersion, data)) - - plugin.on('udapp', 'setEnvironmentModeReducer', (env: { context: string, fork: string }, from: string) => { - plugin.call('notification', 'toast', envChangeNotification(env, from)) - setExecutionContext(env) - }) - - plugin.on('udapp', 'clearAllInstancesReducer', () => { - dispatch(clearAllInstances()) - }) - - plugin.on('udapp', 'addInstanceReducer', (address, abi, name) => { - addInstance({ abi, address, name }) - }) - - plugin.on('filePanel', 'setWorkspace', () => { - dispatch(resetUdapp()) - resetAndInit() - }) - - plugin.fileManager.events.on('currentFileChanged', (currentFile: string) => { - if (/.(.abi)$/.exec(currentFile)) { - dispatch(setLoadType('abi')) - } else if (/.(.sol)$/.exec(currentFile) || - /.(.vy)$/.exec(currentFile) || // vyper - /.(.lex)$/.exec(currentFile) || // lexon - /.(.contract)$/.exec(currentFile)) { - dispatch(setLoadType('sol')) - } else { - dispatch(setLoadType('other')) - } - dispatch(setCurrentFile(currentFile)) - }) - - plugin.recorder.event.register('recorderCountChange', (count) => { - dispatch(setRecorderCount(count)) - }) - - plugin.event.register('cleared', () => { - dispatch(clearRecorderCount()) - }) -} - -const updateAccountBalances = () => { - const accounts = plugin.REACT_API.accounts.loadedAccounts - - Object.keys(accounts).map((value) => { - plugin.blockchain.getBalanceInEther(value, (err, balance) => { - if (err) return - const updated = shortenAddress(value, balance) - - accounts[value] = updated - }) - }) - dispatch(fetchAccountsListSuccess(accounts)) -} - -const fillAccountsList = async () => { - try { - dispatch(fetchAccountsListRequest()) - const promise = plugin.blockchain.getAccounts() - - promise.then(async (accounts: string[]) => { - const loadedAccounts = {} - - if (!accounts) accounts = [] - // allSettled is undefined.. - // so the current promise (all) will finish when: - // - all the promises resolve - // - at least one reject - // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise/all - await (Promise as any).all(accounts.map((account) => { - return new Promise((resolve, reject) => { - plugin.blockchain.getBalanceInEther(account, (err, balance) => { - if (err) return reject(err) - const updated = shortenAddress(account, balance) - - loadedAccounts[account] = updated - resolve(account) - }) - }) - })) - const provider = plugin.blockchain.getProvider() - - if (provider === 'injected') { - const selectedAddress = plugin.blockchain.getInjectedWeb3Address() - - if (!(Object.keys(loadedAccounts).includes(selectedAddress))) setAccount(null) - } - dispatch(fetchAccountsListSuccess(loadedAccounts)) - }).catch((e) => { - dispatch(fetchAccountsListFailed(e.message)) - }) - } catch (e) { - dispatch(displayPopUp(`Cannot get account list: ${e}`)) - } -} - -export const setAccount = (account: string) => { - dispatch(setSelectedAccount(account)) -} - -export const setUnit = (unit: 'ether' | 'finney' | 'gwei' | 'wei') => { - dispatch(setSendUnit(unit)) -} - -export const setGasFee = (value: number) => { - dispatch(setGasLimit(value)) -} - -const setFinalContext = () => { - // set the final context. Cause it is possible that this is not the one we've originaly selected - const value = _getProviderDropdownValue() - - setExecEnv(value) - clearInstances() -} - -const _getProviderDropdownValue = (): string => { - const provider = plugin.blockchain.getProvider() - const fork = plugin.blockchain.getCurrentFork() - - return provider === 'vm' ? provider + '-' + fork : provider -} - -const setExecEnv = (env: string) => { - dispatch(setExecutionEnvironment(env)) -} - -export const setNetworkNameFromProvider = (networkName: string) => { - dispatch(setNetworkName(networkName)) -} - -const addExternalProvider = (network) => { - dispatch(addProvider(network)) -} - -const removeExternalProvider = (name) => { - dispatch(removeProvider(name)) -} - -export const setExecutionContext = (executionContext: { context: string, fork: string }) => { - const displayContent = web3Dialog(plugin.REACT_API.externalEndpoint, setWeb3Endpoint) - - plugin.blockchain.changeExecutionContext(executionContext, () => { - plugin.call('notification', 'modal', { - id: 'envNotification', - title: 'External node request', - message: displayContent, - okLabel: 'OK', - cancelLabel: 'Cancel', - okFn: () => { - plugin.blockchain.setProviderFromEndpoint(plugin.REACT_API.externalEndpoint, executionContext, (alertMsg) => { - if (alertMsg) plugin.call('notification', 'toast', alertMsg) - setFinalContext() - }) - }, - cancelFn: () => { - setFinalContext() - } - }) - }, (alertMsg) => { - plugin.call('notification', 'toast', alertMsg) - }, () => { setFinalContext() }) -} - -export const setWeb3Endpoint = (endpoint: string) => { - dispatch(setExternalEndpoint(endpoint)) -} - -export const clearPopUp = async () => { - dispatch(hidePopUp()) -} - -export const createNewBlockchainAccount = async (cbMessage: JSX.Element) => { - plugin.blockchain.newAccount( - '', - (cb) => { - dispatch(displayNotification('Enter Passphrase', cbMessage, 'OK', 'Cancel', async () => { - if (plugin.REACT_API.passphrase === plugin.REACT_API.matchPassphrase) { - cb(plugin.REACT_API.passphrase) - } else { - dispatch(displayNotification('Error', 'Passphase does not match', 'OK', null)) - } - setPassphrase('') - setMatchPassphrase('') - }, () => {})) - }, - async (error, address) => { - if (error) { - return dispatch(displayPopUp('Cannot create an account: ' + error)) - } - dispatch(displayPopUp(`account ${address} created`)) - await fillAccountsList() - } - ) -} - -export const setPassphrasePrompt = (passphrase: string) => { - dispatch(setPassphrase(passphrase)) -} - -export const setMatchPassphrasePrompt = (passphrase: string) => { - dispatch(setMatchPassphrase(passphrase)) -} - -export const signMessageWithAddress = (account: string, message: string, modalContent: (hash: string, data: string) => JSX.Element, passphrase?: string) => { - plugin.blockchain.signMessage(message, account, passphrase, (err, msgHash, signedData) => { - if (err) { - return displayPopUp(err) - } - dispatch(displayNotification('Signed Message', modalContent(msgHash, signedData), 'OK', null, () => {}, null)) - }) -} - -const broadcastCompilationResult = async (file, source, languageVersion, data, input?) => { - // TODO check whether the tab is configured - const compiler = new CompilerAbstract(languageVersion, data, source, input) - - plugin.compilersArtefacts[languageVersion] = compiler - plugin.compilersArtefacts.__last = compiler - - const contracts = getCompiledContracts(compiler).map((contract) => { - return { name: languageVersion, alias: contract.name, file: contract.file, compiler } - }) - const isUpgradeable = await plugin.call('openzeppelin-proxy', 'isConcerned', data.sources[file].ast) - console.log('data: ', data) - - if (isUpgradeable) dispatch(setDeployOptions([{ title: 'Deploy with Proxy', active: false }])) - else dispatch(setDeployOptions([])) - dispatch(fetchContractListSuccess({ [file]: contracts })) - dispatch(setCurrentFile(file)) -} - -const loadContractFromAddress = (address, confirmCb, cb) => { - if (/.(.abi)$/.exec(plugin.config.get('currentFile'))) { - confirmCb(() => { - let abi - try { - abi = JSON.parse(plugin.editor.currentContent()) - } catch (e) { - return cb('Failed to parse the current file as JSON ABI.') - } - _paq.push(['trackEvent', 'udapp', 'AtAddressLoadWithABI']) - cb(null, 'abi', abi) - }) - } else { - _paq.push(['trackEvent', 'udapp', 'AtAddressLoadWithArtifacts']) - cb(null, 'instance') - } -} - -const getCompiledContracts = (compiler) => { - const contracts = [] - - compiler.visitContracts((contract) => { - contracts.push(contract) - }) - return contracts -} - -export const getSelectedContract = (contractName: string, compiler: CompilerAbstractType): ContractData => { - if (!contractName) return null - // const compiler = plugin.compilersArtefacts[compilerAtributeName] - - if (!compiler) return null - - const contract = compiler.getContract(contractName) - - return { - name: contractName, - contract: contract, - compiler: compiler, - abi: contract.object.abi, - bytecodeObject: contract.object.evm.bytecode.object, - bytecodeLinkReferences: contract.object.evm.bytecode.linkReferences, - object: contract.object, - deployedBytecode: contract.object.evm.deployedBytecode, - getConstructorInterface: () => { - return txHelper.getConstructorInterface(contract.object.abi) - }, - getConstructorInputs: () => { - const constructorInteface = txHelper.getConstructorInterface(contract.object.abi) - - return txHelper.inputParametersDeclarationToString(constructorInteface.inputs) - }, - isOverSizeLimit: () => { - const deployedBytecode = contract.object.evm.deployedBytecode - - return (deployedBytecode && deployedBytecode.object.length / 2 > 24576) - }, - metadata: contract.object.metadata - } -} - -const getCompilerContracts = () => { - return plugin.compilersArtefacts.__last.getData().contracts -} - -const terminalLogger = (view: JSX.Element) => { - plugin.call('terminal', 'logHtml', view) -} - -const confirmationHandler = (confirmDialogContent: MainnetPrompt, network, tx, gasEstimation, continueTxExecution, cancelCb) => { - if (network.name !== 'Main') { - return continueTxExecution(null) - } - const amount = plugin.blockchain.fromWei(tx.value, true, 'ether') - const content = confirmDialogContent(tx, network, amount, gasEstimation, plugin.blockchain.determineGasFees(tx), plugin.blockchain.determineGasPrice.bind(plugin.blockchain)) - - dispatch(displayNotification('Confirm transaction', content, 'Confirm', 'Cancel', () => { - plugin.blockchain.config.setUnpersistedProperty('doNotShowTransactionConfirmationAgain', plugin.REACT_API.confirmSettings) - // TODO: check if this is check is still valid given the refactor - if (!plugin.REACT_API.gasPriceStatus) { - cancelCb('Given transaction fee is not correct') - } else { - continueTxExecution({ maxFee: plugin.REACT_API.maxFee, maxPriorityFee: plugin.REACT_API.maxPriorityFee, baseFeePerGas: plugin.REACT_API.baseFeePerGas, gasPrice: plugin.REACT_API.gasPrice }) - } - }, () => { - return cancelCb('Transaction canceled by user.') - })) -} - -const getConfirmationCb = (confirmDialogContent: MainnetPrompt) => { - // this code is the same as in recorder.js. TODO need to be refactored out - return (network, tx, gasEstimation, continueTxExecution, cancelCb) => { - confirmationHandler(confirmDialogContent, network, tx, gasEstimation, continueTxExecution, cancelCb) - } -} - -const continueHandler = (gasEstimationPrompt: (msg: string) => JSX.Element, error, continueTxExecution, cancelCb) => { - if (error) { - const msg = typeof error !== 'string' ? error.message : error - - dispatch(displayNotification('Gas estimation failed', gasEstimationPrompt(msg), 'Send Transaction', 'Cancel Transaction', () => { - continueTxExecution() - }, () => { - cancelCb() - })) - } else { - continueTxExecution() - } -} - -const promptHandler = (passphrasePrompt, okCb, cancelCb) => { - dispatch(displayNotification('Passphrase requested', passphrasePrompt('Personal mode is enabled. Please provide passphrase of account'), 'OK', 'Cancel', okCb, cancelCb)) -} - -export const createInstance = async ( - selectedContract: ContractData, - gasEstimationPrompt: (msg: string) => JSX.Element, - passphrasePrompt: (msg: string) => JSX.Element, - logBuilder: (msg: string) => JSX.Element, - publishToStorage: (storage: 'ipfs' | 'swarm', - contract: ContractData) => void, - mainnetPrompt: MainnetPrompt, - isOverSizePrompt: () => JSX.Element, - args, - deployMode: DeployMode[]) => { - const statusCb = (msg: string) => { - const log = logBuilder(msg) - - return terminalLogger(log) - } - - const finalCb = (error, contractObject, address) => { - if (error) { - const log = logBuilder(error) - - return terminalLogger(log) - } - addInstance({ contractData: contractObject, address, name: contractObject.name }) - const data = plugin.compilersArtefacts.getCompilerAbstract(contractObject.contract.file) - - plugin.compilersArtefacts.addResolvedContract(addressToString(address), data) - if (plugin.REACT_API.ipfsChecked) { - _paq.push(['trackEvent', 'udapp', 'DeployAndPublish', plugin.REACT_API.networkName]) - publishToStorage('ipfs', selectedContract) - } else { - _paq.push(['trackEvent', 'udapp', 'DeployOnly', plugin.REACT_API.networkName]) - } - deployMode.forEach(async (mode) => { - const owner = plugin.REACT_API.accounts.selectedAccount - - if (mode === 'Deploy with Proxy') await plugin.call('openzeppelin-proxy', 'execute', address, owner) - }) - } - - let contractMetadata - try { - contractMetadata = await plugin.call('compilerMetadata', 'deployMetadataOf', selectedContract.name, selectedContract.contract.file) - } catch (error) { - return statusCb(`creation of ${selectedContract.name} errored: ${error.message ? error.message : error}`) - } - - const compilerContracts = getCompilerContracts() - const confirmationCb = getConfirmationCb(mainnetPrompt) - - if (selectedContract.isOverSizeLimit()) { - return dispatch(displayNotification('Contract code size over limit', isOverSizePrompt(), 'Force Send', 'Cancel', () => { - deployContract(selectedContract, args, contractMetadata, compilerContracts, { - continueCb: (error, continueTxExecution, cancelCb) => { - continueHandler(gasEstimationPrompt, error, continueTxExecution, cancelCb) - }, - promptCb: (okCb, cancelCb) => { - promptHandler(passphrasePrompt, okCb, cancelCb) - }, - statusCb, - finalCb - }, confirmationCb) - }, () => { - const log = logBuilder(`creation of ${selectedContract.name} canceled by user.`) - - return terminalLogger(log) - })) - } - deployContract(selectedContract, args, contractMetadata, compilerContracts, { - continueCb: (error, continueTxExecution, cancelCb) => { - continueHandler(gasEstimationPrompt, error, continueTxExecution, cancelCb) - }, - promptCb: (okCb, cancelCb) => { - promptHandler(passphrasePrompt, okCb, cancelCb) - }, - statusCb, - finalCb - }, confirmationCb) -} - -const deployContract = (selectedContract, args, contractMetadata, compilerContracts, callbacks, confirmationCb) => { - _paq.push(['trackEvent', 'udapp', 'DeployContractTo', plugin.REACT_API.networkName]) - const { statusCb } = callbacks - - if (!contractMetadata || (contractMetadata && contractMetadata.autoDeployLib)) { - return plugin.blockchain.deployContractAndLibraries(selectedContract, args, contractMetadata, compilerContracts, callbacks, confirmationCb) - } - if (Object.keys(selectedContract.bytecodeLinkReferences).length) statusCb(`linking ${JSON.stringify(selectedContract.bytecodeLinkReferences, null, '\t')} using ${JSON.stringify(contractMetadata.linkReferences, null, '\t')}`) - plugin.blockchain.deployContractWithLibrary(selectedContract, args, contractMetadata, compilerContracts, callbacks, confirmationCb) -} - -export const updateGasPriceStatus = (status: boolean) => { - dispatch(setGasPriceStatus(status)) -} - -export const updateConfirmSettings = (confirmation: boolean) => { - dispatch(setConfirmSettings(confirmation)) -} - -export const updateMaxFee = (fee: string) => { - dispatch(setMaxFee(fee)) -} - -export const updateMaxPriorityFee = (fee: string) => { - dispatch(setMaxPriorityFee(fee)) -} - -export const updateBaseFeePerGas = (baseFee: string) => { - dispatch(setBaseFeePerGas(baseFee)) -} - -export const updateGasPrice = (price: string) => { - dispatch(setGasPrice(price)) -} - -export const updateTxFeeContent = (content: string) => { - dispatch(setTxFeeContent(content)) -} - -export const addInstance = (instance: { contractData?: ContractData, address: string, name: string, abi?: any, decodedResponse?: Record }) => { - instance.decodedResponse = {} - dispatch(addNewInstance(instance)) -} - -export const removeInstance = (index: number) => { - dispatch(removeExistingInstance(index)) -} - -export const clearInstances = () => { - dispatch(clearAllInstances()) - dispatch(clearRecorderCount()) -} - -export const loadAddress = (contract: ContractData, address: string) => { - loadContractFromAddress(address, - (cb) => { - dispatch(displayNotification('At Address', `Do you really want to interact with ${address} using the current ABI definition?`, 'OK', 'Cancel', cb, null)) - }, - (error, loadType, abi) => { - if (error) { - return dispatch(displayNotification('Alert', error, 'OK', null)) - } - if (loadType === 'abi') { - return addInstance({ abi, address, name: '' }) - } else if (loadType === 'instance') { - if (!contract) return dispatch(displayPopUp('No compiled contracts found.')) - const currentFile = plugin.REACT_API.contracts.currentFile - const compiler = plugin.REACT_API.contracts.contractList[currentFile].find(item => item.alias === contract.name) - const contractData = getSelectedContract(contract.name, compiler.compiler) - return addInstance({ contractData, address, name: contract.name }) - } - } - ) - } - -export const getContext = () => { - return plugin.blockchain.context() -} - -export const runTransactions = ( - instanceIndex: number, - lookupOnly: boolean, - funcABI: FuncABI, - inputsValues: string, - contractName: string, - contractABI, contract, - address, - logMsg:string, - logBuilder: (msg: string) => JSX.Element, - mainnetPrompt: MainnetPrompt, - gasEstimationPrompt: (msg: string) => JSX.Element, - passphrasePrompt: (msg: string) => JSX.Element, - funcIndex?: number) => { - let callinfo = '' - if (lookupOnly) callinfo = 'call' - else if (funcABI.type === 'fallback' || funcABI.type === 'receive') callinfo = 'lowLevelInteracions' - else callinfo = 'transact' - _paq.push(['trackEvent', 'udapp', callinfo, plugin.blockchain.getCurrentNetworkStatus().network.name]) - - const params = funcABI.type !== 'fallback' ? inputsValues : '' - plugin.blockchain.runOrCallContractMethod( - contractName, - contractABI, - funcABI, - contract, - inputsValues, - address, - params, - lookupOnly, - logMsg, - (msg) => { - const log = logBuilder(msg) - - return terminalLogger(log) - }, - (returnValue) => { - const response = txFormat.decodeResponse(returnValue, funcABI) - - dispatch(setDecodedResponse(instanceIndex, response, funcIndex)) - }, - (network, tx, gasEstimation, continueTxExecution, cancelCb) => { - confirmationHandler(mainnetPrompt, network, tx, gasEstimation, continueTxExecution, cancelCb) - }, - (error, continueTxExecution, cancelCb) => { - continueHandler(gasEstimationPrompt, error, continueTxExecution, cancelCb) - }, - (okCb, cancelCb) => { - promptHandler(passphrasePrompt, okCb, cancelCb) - } - ) -} - -const saveScenario = async (newPath: string, provider, promptCb, cb) => { - const txJSON = JSON.stringify(plugin.recorder.getAll(), null, 2) - - promptCb(async () => { - try { - await provider.set(newPath, txJSON) - await plugin.fileManager.open(newPath) - } catch (error) { - if (error) return cb('Failed to create file. ' + newPath + ' ' + error) - } - }) -} - -export const storeScenario = async (prompt: (msg: string, defaultValue: string) => JSX.Element) => { - const path = plugin.fileManager.currentPath() - const fileProvider = await plugin.fileManager.fileProviderOf(path) - - if (!fileProvider) return displayNotification('Alert', 'Invalid File Provider', 'OK', null) - const newPath = await createNonClashingNameAsync(path + '/' + plugin.REACT_API.recorder.pathToScenario, plugin.fileManager) - const newName = extractNameFromKey(newPath) - - saveScenario(newPath, fileProvider, - (cb) => { - dispatch(displayNotification('Save transactions as scenario', prompt('Transactions will be saved in a file under ' + path, newName), 'OK', 'Cancel', cb, null)) - }, - (error) => { - if (error) return dispatch(displayNotification('Alert', error, 'OK', null)) - } - ) -} - -const runScenario = (file: string, gasEstimationPrompt: (msg: string) => JSX.Element, passphrasePrompt: (msg: string) => JSX.Element, confirmDialogContent: MainnetPrompt, logBuilder: (msg: string) => JSX.Element) => { - if (!file) return dispatch(displayNotification('Alert', 'Unable to run scenerio, no specified scenario file', 'OK', null)) - - plugin.fileManager.readFile(file).then((json) => { - // TODO: there is still a UI dependency to remove here, it's still too coupled at this point to remove easily - plugin.recorder.runScenario( - json, - (error, continueTxExecution, cancelCb) => { - continueHandler(gasEstimationPrompt, error, continueTxExecution, cancelCb) - }, (okCb, cancelCb) => { - promptHandler(passphrasePrompt, okCb, cancelCb) - }, (msg) => { - dispatch(displayNotification('Alert', msg, 'OK', null)) - }, (network, tx, gasEstimation, continueTxExecution, cancelCb) => { - confirmationHandler(confirmDialogContent, network, tx, gasEstimation, continueTxExecution, cancelCb) - }, (msg: string) => { - const log = logBuilder(msg) - - return terminalLogger(log) - }, (error, abi, address, contractName) => { - if (error) { - return dispatch(displayNotification('Alert', error, 'OK', null)) - } - addInstance({ name: contractName, address, abi }) - }) - }).catch((error) => dispatch(displayNotification('Alert', error, 'OK', null))) -} - -export const runCurrentScenario = (gasEstimationPrompt: (msg: string) => JSX.Element, passphrasePrompt: (msg: string) => JSX.Element, confirmDialogContent: MainnetPrompt, logBuilder: (msg: string) => JSX.Element) => { - const file = plugin.config.get('currentFile') - - if (!file) return dispatch(displayNotification('Alert', 'A scenario file has to be selected', 'Ok', null)) - runScenario(file, gasEstimationPrompt, passphrasePrompt, confirmDialogContent, logBuilder) -} - -export const updateScenarioPath = (path: string) => { - dispatch(setPathToScenario(path)) -} - -export const getFuncABIInputs = (funcABI: FuncABI) => { - return plugin.blockchain.getInputs(funcABI) -} - -export const setSendTransactionValue = (value: string) => { - dispatch(setSendValue(value)) -} - -const resetAndInit = () => { - plugin.blockchain.resetAndInit(plugin.config, { - getAddress: (cb) => { - cb(null, plugin.REACT_API.accounts.selectedAccount) - }, - getValue: (cb) => { - try { - const number = plugin.REACT_API.sendValue - const unit = plugin.REACT_API.sendUnit - - cb(null, Web3.utils.toWei(number, unit)) - } catch (e) { - cb(e) - } - }, - getGasLimit: (cb) => { - try { - cb(null, '0x' + new ethJSUtil.BN(plugin.REACT_API.gasLimit, 10).toString(16)) - } catch (e) { - cb(e.message) - } - } - }) -} +export const setAccountAddress = (account: string) => setAccount(dispatch, account) +export const setUnitValue = (unit: 'ether' | 'finney' | 'gwei' | 'wei') => setUnit(dispatch, unit) +export const setGasFeeAmount = (value: number) => setGasFee(dispatch, value) +export const setExecutionEnvironment = (executionContext: { context: string, fork: string }) => setExecutionContext(plugin, dispatch, executionContext) +export const hideToaster = () => clearPopUp(dispatch) +export const createNewAddress = (cbMessage: JSX.Element) => createNewBlockchainAccount(plugin, dispatch, cbMessage) +export const setPassphraseModal = (passphrase: string) => setPassphrasePrompt(dispatch, passphrase) +export const setMatchPassphraseModal = (passphrase: string) => setMatchPassphrasePrompt(dispatch, passphrase) +export const signMessage = (account: string, message: string, modalContent: (hash: string, data: string) => JSX.Element, passphrase?: string) => signMessageWithAddress(plugin, dispatch, account, message, modalContent, passphrase) +export const fetchSelectedContract = (contractName: string, compiler: CompilerAbstractType) => getSelectedContract(contractName, compiler) +export const createNewInstance = async (selectedContract: ContractData, gasEstimationPrompt: (msg: string) => JSX.Element, passphrasePrompt: (msg: string) => JSX.Element, logBuilder: (msg: string) => JSX.Element, publishToStorage: (storage: 'ipfs' | 'swarm', contract: ContractData) => void, mainnetPrompt: MainnetPrompt, isOverSizePrompt: () => JSX.Element, args, deployMode: DeployMode[]) => createInstance(plugin, dispatch, selectedContract, gasEstimationPrompt, passphrasePrompt, logBuilder, publishToStorage, mainnetPrompt, isOverSizePrompt, args, deployMode) +export const setSendValue = (value: string) => setSendTransactionValue(dispatch, value) +export const setBaseFeePerGas = (baseFee: string) => updateBaseFeePerGas(dispatch, baseFee) +export const setConfirmSettings = (confirmation: boolean) => updateConfirmSettings(dispatch, confirmation) +export const setGasPrice = (price: string) => updateGasPrice(dispatch, price) +export const setGasPriceStatus = (status: boolean) => updateGasPriceStatus(dispatch, status) +export const setMaxFee = (fee: string) => updateMaxFee(dispatch, fee) +export const setMaxPriorityFee = (fee: string) => updateMaxPriorityFee(dispatch, fee) +export const setTxFeeContent = (content: string) => updateTxFeeContent(dispatch, content) +export const removeInstances = () => clearInstances(dispatch) +export const removeSingleInstance = (index: number) => removeInstance(dispatch, index) +export const getExecutionContext = () => getContext(plugin) +export const executeTransactions = (instanceIndex: number, lookupOnly: boolean, funcABI: FuncABI, inputsValues: string, contractName: string, contractABI, contract, address, logMsg:string, logBuilder: (msg: string) => JSX.Element, mainnetPrompt: MainnetPrompt, gasEstimationPrompt: (msg: string) => JSX.Element, passphrasePrompt: (msg: string) => JSX.Element, funcIndex?: number) => runTransactions(plugin, dispatch, instanceIndex, lookupOnly, funcABI, inputsValues, contractName, contractABI, contract, address, logMsg, logBuilder, mainnetPrompt, gasEstimationPrompt, passphrasePrompt, funcIndex) +export const loadFromAddress = (contract: ContractData, address: string) => loadAddress(plugin, dispatch, contract, address) +export const storeNewScenario = async (prompt: (msg: string, defaultValue: string) => JSX.Element) => storeScenario(plugin, dispatch, prompt) +export const runScenario = (gasEstimationPrompt: (msg: string) => JSX.Element, passphrasePrompt: (msg: string) => JSX.Element, confirmDialogContent: MainnetPrompt, logBuilder: (msg: string) => JSX.Element) => runCurrentScenario(plugin, dispatch, gasEstimationPrompt, passphrasePrompt, confirmDialogContent, logBuilder) +export const setScenarioPath = (path: string) => updateScenarioPath(dispatch, path) +export const getFuncABIValues = (funcABI: FuncABI) => getFuncABIInputs(plugin, funcABI) +export const setNetworkName = (networkName: string) => setNetworkNameFromProvider(dispatch, networkName) \ No newline at end of file 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 e73bd1b1d0..8b34df3bbc 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_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_TX_FEE_CONTENT } 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_TX_FEE_CONTENT } from '../constants' import { DeployMode, DeployOptions } from '../types' export const fetchAccountsListRequest = () => { @@ -300,3 +300,10 @@ export const setDeployOptions = (deployOptions: DeployOptions[]) => { type: SET_DEPLOY_OPTIONS } } + +export const setCurrentContract = (contractName: string) => { + return { + payload: contractName, + type: SET_CURRENT_CONTRACT + } +} diff --git a/libs/remix-ui/run-tab/src/lib/actions/recorder.ts b/libs/remix-ui/run-tab/src/lib/actions/recorder.ts new file mode 100644 index 0000000000..15c92dba5f --- /dev/null +++ b/libs/remix-ui/run-tab/src/lib/actions/recorder.ts @@ -0,0 +1,72 @@ +import { createNonClashingNameAsync, extractNameFromKey } from "@remix-ui/helper" +import { MainnetPrompt } from "../types" +import { RunTab } from "../types/run-tab" +import { addInstance } from "./actions" +import { confirmationHandler, continueHandler, promptHandler, terminalLogger } from "./deploy" +import { displayNotification } from "./payload" + +const saveScenario = async (plugin: RunTab, newPath: string, provider, promptCb, cb) => { + const txJSON = JSON.stringify(plugin.recorder.getAll(), null, 2) + + promptCb(async () => { + try { + await provider.set(newPath, txJSON) + await plugin.fileManager.open(newPath) + } catch (error) { + if (error) return cb('Failed to create file. ' + newPath + ' ' + error) + } + }) +} + +export const storeScenario = async (plugin: RunTab, dispatch: React.Dispatch, prompt: (msg: string, defaultValue: string) => JSX.Element) => { + const path = plugin.fileManager.currentPath() + const fileProvider = await plugin.fileManager.fileProviderOf(path) + + if (!fileProvider) return displayNotification('Alert', 'Invalid File Provider', 'OK', null) + const newPath = await createNonClashingNameAsync(path + '/' + plugin.REACT_API.recorder.pathToScenario, plugin.fileManager) + const newName = extractNameFromKey(newPath) + + saveScenario(plugin, newPath, fileProvider, + (cb) => { + dispatch(displayNotification('Save transactions as scenario', prompt('Transactions will be saved in a file under ' + path, newName), 'OK', 'Cancel', cb, null)) + }, + (error) => { + if (error) return dispatch(displayNotification('Alert', error, 'OK', null)) + } + ) +} + +const runScenario = (plugin: RunTab, dispatch: React.Dispatch, file: string, gasEstimationPrompt: (msg: string) => JSX.Element, passphrasePrompt: (msg: string) => JSX.Element, confirmDialogContent: MainnetPrompt, logBuilder: (msg: string) => JSX.Element) => { + if (!file) return dispatch(displayNotification('Alert', 'Unable to run scenerio, no specified scenario file', 'OK', null)) + + plugin.fileManager.readFile(file).then((json) => { + // TODO: there is still a UI dependency to remove here, it's still too coupled at this point to remove easily + plugin.recorder.runScenario( + json, + (error, continueTxExecution, cancelCb) => { + continueHandler(dispatch, gasEstimationPrompt, error, continueTxExecution, cancelCb) + }, (okCb, cancelCb) => { + promptHandler(dispatch, passphrasePrompt, okCb, cancelCb) + }, (msg) => { + dispatch(displayNotification('Alert', msg, 'OK', null)) + }, (network, tx, gasEstimation, continueTxExecution, cancelCb) => { + confirmationHandler(plugin, dispatch, confirmDialogContent, network, tx, gasEstimation, continueTxExecution, cancelCb) + }, (msg: string) => { + const log = logBuilder(msg) + + return terminalLogger(plugin, log) + }, (error, abi, address, contractName) => { + if (error) { + return dispatch(displayNotification('Alert', error, 'OK', null)) + } + addInstance(dispatch, { name: contractName, address, abi }) + }) + }).catch((error) => dispatch(displayNotification('Alert', error, 'OK', null))) +} + +export const runCurrentScenario = (plugin: RunTab, dispatch: React.Dispatch, gasEstimationPrompt: (msg: string) => JSX.Element, passphrasePrompt: (msg: string) => JSX.Element, confirmDialogContent: MainnetPrompt, logBuilder: (msg: string) => JSX.Element) => { + const file = plugin.config.get('currentFile') + + if (!file) return dispatch(displayNotification('Alert', 'A scenario file has to be selected', 'Ok', null)) + runScenario(plugin, dispatch, file, gasEstimationPrompt, passphrasePrompt, confirmDialogContent, logBuilder) +} \ No newline at end of file diff --git a/libs/remix-ui/run-tab/src/lib/constants/index.ts b/libs/remix-ui/run-tab/src/lib/constants/index.ts index bd05b38d51..ae8af3c708 100644 --- a/libs/remix-ui/run-tab/src/lib/constants/index.ts +++ b/libs/remix-ui/run-tab/src/lib/constants/index.ts @@ -43,4 +43,5 @@ export const CLEAR_RECORDER_COUNT = 'CLEAR_RECORDER_COUNT' export const RESET_STATE = 'RESET_STATE' export const ADD_DEPLOY_OPTION = 'ADD_DEPLOY_OPTION' export const REMOVE_DEPLOY_OPTION = 'REMOVE_DEPLOY_OPTION' -export const SET_DEPLOY_OPTIONS = 'SET_DEPLOY_OPTIONS' \ No newline at end of file +export const SET_DEPLOY_OPTIONS = 'SET_DEPLOY_OPTIONS' +export const SET_CURRENT_CONTRACT = 'SET_CURRENT_CONTRACT' \ No newline at end of file diff --git a/libs/remix-ui/run-tab/src/lib/reducers/runTab.ts b/libs/remix-ui/run-tab/src/lib/reducers/runTab.ts index 5fd9eab4fc..065cb3efd1 100644 --- a/libs/remix-ui/run-tab/src/lib/reducers/runTab.ts +++ b/libs/remix-ui/run-tab/src/lib/reducers/runTab.ts @@ -1,7 +1,7 @@ import { CompilerAbstract } from '@remix-project/remix-solidity-ts' import { ContractData } from '@remix-project/core-plugin' 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, FETCH_PROVIDER_LIST_FAILED, FETCH_PROVIDER_LIST_REQUEST, FETCH_PROVIDER_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_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_TX_FEE_CONTENT } 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, FETCH_PROVIDER_LIST_FAILED, FETCH_PROVIDER_LIST_REQUEST, FETCH_PROVIDER_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_TX_FEE_CONTENT } from '../constants' interface Action { type: string payload: any @@ -66,6 +66,7 @@ export interface RunTabState { }, loadType: 'abi' | 'sol' | 'other' currentFile: string, + currentContract: string, compilationCount: number, isRequesting: boolean, isSuccessful: boolean, @@ -159,6 +160,7 @@ export const runTabInitialState: RunTabState = { contractList: {}, loadType: 'other', currentFile: '', + currentContract: '', compilationCount: 0, isRequesting: false, isSuccessful: false, @@ -482,6 +484,18 @@ export const runTabReducer = (state: RunTabState = runTabInitialState, action: A } } + case SET_CURRENT_CONTRACT: { + const payload: string = action.payload + + return { + ...state, + contracts: { + ...state.contracts, + currentContract: payload + } + } + } + case SET_LOAD_TYPE: { const payload: 'abi' | 'sol' | 'other' = action.payload diff --git a/libs/remix-ui/run-tab/src/lib/run-tab.tsx b/libs/remix-ui/run-tab/src/lib/run-tab.tsx index 279286222c..4fd8aa8997 100644 --- a/libs/remix-ui/run-tab/src/lib/run-tab.tsx +++ b/libs/remix-ui/run-tab/src/lib/run-tab.tsx @@ -11,22 +11,22 @@ import { Modal, Network, RunTabProps, Tx } from './types' import { ContractData } from '@remix-project/core-plugin' import { runTabInitialState, runTabReducer } from './reducers/runTab' import { - initRunTab, setAccount, - setUnit, setGasFee, - setExecutionContext, setWeb3Endpoint, - clearPopUp, createNewBlockchainAccount, - setPassphrasePrompt, setMatchPassphrasePrompt, - signMessageWithAddress, getSelectedContract, - createInstance, setSendTransactionValue, - updateBaseFeePerGas, updateConfirmSettings, - updateGasPrice, updateGasPriceStatus, - updateMaxFee, updateMaxPriorityFee, - updateTxFeeContent, clearInstances, - removeInstance, getContext, - runTransactions, loadAddress, - storeScenario, runCurrentScenario, - updateScenarioPath, getFuncABIInputs, - setNetworkNameFromProvider + initRunTab, setAccountAddress, + setUnitValue, setGasFeeAmount, + setExecutionEnvironment, + hideToaster, createNewAddress, + setPassphraseModal, setMatchPassphraseModal, + signMessage, fetchSelectedContract, + createNewInstance, setSendValue, + setBaseFeePerGas, setConfirmSettings, + setGasPrice, setGasPriceStatus, + setMaxFee, setMaxPriorityFee, + setTxFeeContent, removeInstances, + removeSingleInstance, getExecutionContext, + executeTransactions, loadFromAddress, + storeNewScenario, runScenario, + setScenarioPath, getFuncABIValues, + setNetworkName } from './actions' import './css/run-tab.css' import { PublishToStorage } from '@remix-ui/publish-to-storage' @@ -134,7 +134,7 @@ export function RunTabUI (props: RunTabProps) { const handleToaster = () => { setFocusToaster('') - clearPopUp() + hideToaster() } const toast = (toasterMsg: string) => { @@ -171,11 +171,11 @@ export function RunTabUI (props: RunTabProps) { } const passphrasePrompt = (message: string) => { - return + return } const scenarioPrompt = (message: string, defaultValue) => { - return + return } const mainnetPrompt = (tx: Tx, network: Network, amount: string, gasEstimation: string, gasFees: (maxFee: string, cb: (txFeeText: string, priceStatus: boolean) => void) => void, determineGasPrice: (cb: (txFeeText: string, gasPriceValue: string, gasPriceStatus: boolean) => void) => void) => { @@ -186,13 +186,13 @@ export function RunTabUI (props: RunTabProps) { amount={amount} gasEstimation={gasEstimation} setNewGasPrice={gasFees} - updateBaseFeePerGas={updateBaseFeePerGas} - updateConfirmSettings={updateConfirmSettings} - updateGasPrice={updateGasPrice} - updateGasPriceStatus={updateGasPriceStatus} - updateMaxFee={updateMaxFee} - updateMaxPriorityFee={updateMaxPriorityFee} - setTxFeeContent={updateTxFeeContent} + updateBaseFeePerGas={setBaseFeePerGas} + updateConfirmSettings={setConfirmSettings} + updateGasPrice={setGasPrice} + updateGasPriceStatus={setGasPriceStatus} + updateMaxFee={setMaxFee} + updateMaxPriorityFee={setMaxPriorityFee} + setTxFeeContent={setTxFeeContent} txFeeContent={runTab.txFeeContent} maxFee={runTab.maxFee} maxPriorityFee={runTab.maxPriorityFee} @@ -207,33 +207,32 @@ export function RunTabUI (props: RunTabProps) { networkName={runTab.networkName} personalMode={runTab.personalMode} selectExEnv={runTab.selectExEnv} - setWeb3Endpoint={setWeb3Endpoint} accounts={runTab.accounts} - setAccount={setAccount} - setUnit={setUnit} + setAccount={setAccountAddress} + setUnit={setUnitValue} sendValue={runTab.sendValue} - setSendValue={setSendTransactionValue} + setSendValue={setSendValue} sendUnit={runTab.sendUnit} gasLimit={runTab.gasLimit} - setGasFee={setGasFee} + setGasFee={setGasFeeAmount} providers={runTab.providers} - setExecutionContext={setExecutionContext} - createNewBlockchainAccount={createNewBlockchainAccount} - setPassphrase={setPassphrasePrompt} - setMatchPassphrase={setMatchPassphrasePrompt} + setExecutionContext={setExecutionEnvironment} + createNewBlockchainAccount={createNewAddress} + setPassphrase={setPassphraseModal} + setMatchPassphrase={setMatchPassphraseModal} modal={modal} tooltip={toast} - signMessageWithAddress={signMessageWithAddress} + signMessageWithAddress={signMessage} passphrase={runTab.passphrase} /> diff --git a/libs/remix-ui/run-tab/src/lib/types/index.ts b/libs/remix-ui/run-tab/src/lib/types/index.ts index 24240a17cc..561f87a549 100644 --- a/libs/remix-ui/run-tab/src/lib/types/index.ts +++ b/libs/remix-ui/run-tab/src/lib/types/index.ts @@ -21,7 +21,6 @@ export interface SettingsProps { sendUnit: string, gasLimit: number, setGasFee: (value: number) => void, - setWeb3Endpoint: (endpoint: string) => void, personalMode: boolean, networkName: string, providers: { @@ -225,7 +224,22 @@ export interface Modal { export type DeployMode = 'Deploy with Proxy' export interface DeployOptions { title: DeployMode, - active: boolean + active: boolean, + inputs: { + [key: string]: { + inputs: [ + { + internalType: string, + name: string, + type: string + } + ], + name: "initialize", + outputs: any[], + stateMutability: string, + type: string + } + } } export interface ContractGUIProps {