diff --git a/libs/remix-ui/clipboard/src/lib/copy-to-clipboard/copy-to-clipboard.tsx b/libs/remix-ui/clipboard/src/lib/copy-to-clipboard/copy-to-clipboard.tsx index a892831d99..88d91d3867 100644 --- a/libs/remix-ui/clipboard/src/lib/copy-to-clipboard/copy-to-clipboard.tsx +++ b/libs/remix-ui/clipboard/src/lib/copy-to-clipboard/copy-to-clipboard.tsx @@ -36,7 +36,7 @@ export const CopyToClipboard = (props: ICopyToClipboard) => { } const reset = () => { - setTimeout(() => setMessage('Copy'), 500) + setTimeout(() => setMessage(tip), 500) } return ( 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 2d1feaba6d..a64dc80992 100644 --- a/libs/remix-ui/run-tab/src/lib/actions/index.ts +++ b/libs/remix-ui/run-tab/src/lib/actions/index.ts @@ -3,15 +3,31 @@ import React from 'react' import * as ethJSUtil from 'ethereumjs-util' import Web3 from 'web3' import { shortenAddress } from '@remix-ui/helper' -import { addProvider, displayNotification, displayPopUp, fetchAccountsListFailed, fetchAccountsListRequest, fetchAccountsListSuccess, hidePopUp, removeProvider, setExecutionEnvironment, setExternalEndpoint, setGasLimit, setMatchPassphrase, setNetworkName, setPassphrase, setSelectedAccount, setSendUnit, setSendValue } from './payload' +import { addProvider, displayNotification, displayPopUp, fetchAccountsListFailed, fetchAccountsListRequest, fetchAccountsListSuccess, fetchContractListSuccess, hidePopUp, removeProvider, setCurrentFile, setExecutionEnvironment, setExternalEndpoint, setGasLimit, setLoadType, setMatchPassphrase, setNetworkName, setPassphrase, setSelectedAccount, setSendUnit, setSendValue } from './payload' import { RunTab } from '../types/run-tab' +import { CompilerAbstract } from '@remix-project/remix-solidity' +import * as remixLib from '@remix-project/remix-lib' +declare global { + interface Window { + _paq: any + } +} +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 setupEvents() + // setInterval(() => { + // fillAccountsList() + // }, 1000) + // fillAccountsList() + setTimeout(async () => { + await fillAccountsList() + }, 0) } const setupEvents = () => { @@ -66,17 +82,40 @@ const setupEvents = () => { }) plugin.blockchain.event.register('addProvider', provider => addExternalProvider(provider)) + plugin.blockchain.event.register('removeProvider', name => removeExternalProvider(name)) + plugin.on('manager', 'pluginActivated', addPluginProvider.bind(plugin)) + plugin.on('manager', 'pluginDeactivated', removePluginProvider.bind(plugin)) - // setInterval(() => { - // fillAccountsList() - // }, 1000) - // fillAccountsList() - setTimeout(async () => { - await fillAccountsList() - }, 0) + plugin.on('solidity', 'compilationFinished', (file, source, languageVersion, data) => + broadcastCompilationResult(file, source, languageVersion, data) + ) + 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('optimism-compiler', 'compilationFinished', (file, source, languageVersion, data) => + broadcastCompilationResult(file, source, languageVersion, data) + ) + 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')) + } + }) } const updateAccountBalances = () => { @@ -252,3 +291,92 @@ export const signMessageWithAddress = (account: string, message: string, modalCo dispatch(displayNotification('Signed Message', modalContent(msgHash, signedData), 'OK', null, () => {}, null)) }) } + +const broadcastCompilationResult = (file, source, languageVersion, data) => { + // TODO check whether the tab is configured + const compiler = new CompilerAbstract(languageVersion, data, source) + + plugin.compilersArtefacts[languageVersion] = compiler + plugin.compilersArtefacts.__last = compiler + + const contracts = getCompiledContracts(compiler).map((contract) => { + return { name: languageVersion, alias: contract.name, file: contract.file } + }) + + dispatch(fetchContractListSuccess(contracts)) + dispatch(setCurrentFile(file)) + // this.enableAtAddress(success) + // this.enableContractNames(success) + // this.setInputParamsPlaceHolder() + + // if (success) { + // this.compFails.style.display = 'none' + // } else { + // this.compFails.style.display = 'block' + // } +} + +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, compilerAtributeName: string) => { + 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 +} 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 8d47f7d060..2bfcb3f350 100644 --- a/libs/remix-ui/run-tab/src/lib/actions/payload.ts +++ b/libs/remix-ui/run-tab/src/lib/actions/payload.ts @@ -129,3 +129,37 @@ export const setMatchPassphrase = (passphrase: string) => { payload: passphrase } } + +export const fetchContractListRequest = () => { + return { + type: 'FETCH_CONTRACT_LIST_REQUEST' + } +} + +export const fetchContractListSuccess = (contracts: { name: string, alias: string, file: string }[]) => { + return { + type: 'FETCH_CONTRACT_LIST_SUCCESS', + payload: contracts + } +} + +export const fetchContractListFailed = (error: string) => { + return { + type: 'FETCH_CONTRACT_LIST_FAILED', + payload: error + } +} + +export const setLoadType = (type: 'abi' | 'sol' | 'other') => { + return { + type: 'SET_LOAD_TYPE', + payload: type + } +} + +export const setCurrentFile = (file: string) => { + return { + type: 'SET_CURRENT_FILE', + payload: file + } +} 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 0ec4f1c0b2..d45e9f02fd 100644 --- a/libs/remix-ui/run-tab/src/lib/components/contractDropdownUI.tsx +++ b/libs/remix-ui/run-tab/src/lib/components/contractDropdownUI.tsx @@ -1,7 +1,8 @@ // eslint-disable-next-line no-use-before-define -import React, { SyntheticEvent, useEffect, useRef, useState } from 'react' +import React, { useEffect, useState } from 'react' import { ContractDropdownProps } from '../types' import * as ethJSUtil from 'ethereumjs-util' +import { ContractGUI } from './contractGUI' export function ContractDropdownUI (props: ContractDropdownProps) { const [networkName, setNetworkName] = useState('') @@ -13,9 +14,31 @@ export function ContractDropdownUI (props: ContractDropdownProps) { content: '' }) const [ipfsCheckedState, setIpfsCheckedState] = useState(false) - const [loadType] = useState('other') - const atAddressButtonInput = useRef(null) - const contracts = useRef(null) + const [atAddressOptions, setAtAddressOptions] = useState<{title: string, disabled: boolean}>({ + title: 'address of contract', + disabled: true + }) + const [address, setAddress] = useState('') + const [contractOptions, setContractOptions] = useState<{title: string, disabled: boolean}>({ + title: 'Please compile *.sol file to deploy or access a contract', + disabled: true + }) + const [selectedContract, setSelectedContract] = useState('') + const [compFails, setCompFails] = useState<'none' | 'block'>('none') + const [loadedContractData, setLoadedContractData] = useState<{ + name: string, + contract: string, + compiler: any, + abi: any, + bytecodeObject: any, + bytecodeLinkReferences: any, + object: any, + deployedBytecode: any, + getConstructorInterface: () => any, + getConstructorInputs: () => any, + isOverSizeLimit: () => boolean, + metadata: any}>(null) + const { contractList, loadType, currentFile } = props.contracts useEffect(() => { enableAtAddress(false) @@ -30,62 +53,166 @@ export function ContractDropdownUI (props: ContractDropdownProps) { }, []) useEffect(() => { - if (props.exEnvironment === 'vm') setNetworkName('VM') + if (props.exEnvironment === 'vm-london' || props.exEnvironment === 'vm-berlin') setNetworkName('VM') }, [props.exEnvironment]) - const enableAtAddress = (enable) => { - const atAddress = atAddressButtonInput.current + useEffect(() => { + if (!address || !ethJSUtil.isValidAddress(address)) enableAtAddress(false) + }, [address]) + + useEffect(() => { + if (/.(.abi)$/.exec(currentFile)) { + setAbiLabel({ + display: 'block', + content: currentFile + }) + enableAtAddress(true) + } else if (/.(.sol)$/.exec(currentFile) || + /.(.vy)$/.exec(currentFile) || // vyper + /.(.lex)$/.exec(currentFile) || // lexon + /.(.contract)$/.exec(currentFile)) { + if (!selectedContract) enableAtAddress(false) + } else { + if (!selectedContract) enableAtAddress(false) + } + if (currentFile) { + enableContractNames(true) + setCompFails('none') + } else { + enableContractNames(false) + setCompFails('block') + } + if (contractList.length > 0) { + const contract = contractList.find(contract => contract.alias === selectedContract) + if (!selectedContract || !contract) setSelectedContract(contractList[0].alias) + } + }, [loadType, currentFile]) + + useEffect(() => { + if (selectedContract) { + const contract = contractList.find(contract => contract.alias === selectedContract) + + setLoadedContractData(props.getSelectedContract(selectedContract, contract.name)) + } + }, [selectedContract]) + + const enableAtAddress = (enable: boolean) => { if (enable) { - if (!atAddress.value || !ethJSUtil.isValidAddress(atAddress.value)) { - enableAtAddress(false) - return - } - atAddress.removeAttribute('disabled') - atAddress.setAttribute('title', 'Interact with the given contract.') + setAtAddressOptions({ + disabled: false, + title: 'Interact with the given contract.' + }) } else { - atAddress.setAttribute('disabled', true) - if (atAddress.value === '') { - atAddress.setAttribute('title', '⚠ Compile *.sol file or select *.abi file & then enter the address of deployed contract.') - } else { - atAddress.setAttribute('title', '⚠ Compile *.sol file or select *.abi file.') - } + setAtAddressOptions({ + disabled: true, + title: address ? '⚠ Compile *.sol file or select *.abi file.' : '⚠ Compile *.sol file or select *.abi file & then enter the address of deployed contract.' + }) } } - // constructor (blockchain, dropdownLogic, logCallback, runView) { - // this.blockchain = blockchain - // this.dropdownLogic = dropdownLogic - // this.logCallback = logCallback - // this.runView = runView - // this.event = new EventManager() - - // this.listenToEvents() - // this.ipfsCheckedState = false - // this.exEnvironment = blockchain.getProvider() - // this.listenToContextChange() - // this.loadType = 'other' - // } - // listenToEvents () { - // this.dropdownLogic.event.register('newlyCompiled', (success, data, source, compiler, compilerFullName, file) => { - // if (!this.selectContractNames) return - // this.selectContractNames.innerHTML = '' - // if (success) { - // this.dropdownLogic.getCompiledContracts(compiler, compilerFullName).forEach((contract) => { - // this.selectContractNames.appendChild(yo``) - // }) - // } - // this.enableAtAddress(success) - // this.enableContractNames(success) - // this.setInputParamsPlaceHolder() + const enableContractNames = (enable: boolean) => { + if (enable) { + setContractOptions({ + disabled: false, + title: 'Select contract for Deploy or At Address.' + }) + } else { + setContractOptions({ + disabled: true, + title: loadType === 'sol' ? '⚠ Select and compile *.sol file to deploy or access a contract.' : '⚠ Selected *.abi file allows accessing contracts, select and compile *.sol file to deploy and access one.' + }) + } + } - // if (success) { - // this.compFails.style.display = 'none' - // } else { - // this.compFails.style.display = 'block' - // } - // }) - // } + const clickCallback = (inputsValues) => { + createInstance(loadedContractData, inputsValues) + } + + const createInstance = (selectedContract, args) => { + if (selectedContract.bytecodeObject.length === 0) { + return props.modal('Alert', 'This contract may be abstract, not implement an abstract parent\'s methods completely or not invoke an inherited contract\'s constructor correctly.', 'OK', () => {}) + } + + // var continueCb = (error, continueTxExecution, cancelCb) => { + // if (error) { + // var msg = typeof error !== 'string' ? error.message : error + // modalDialog('Gas estimation failed', yo`
Gas estimation errored with the following message (see below). + // The transaction execution will likely fail. Do you want to force sending?
+ // ${msg} + //
`, + // { + // label: 'Send Transaction', + // fn: () => { + // continueTxExecution() + // } + // }, { + // label: 'Cancel Transaction', + // fn: () => { + // cancelCb() + // } + // }) + // } else { + // continueTxExecution() + // } + // } + + // const self = this + + // var promptCb = (okCb, cancelCb) => { + // modalDialogCustom.promptPassphrase('Passphrase requested', 'Personal mode is enabled. Please provide passphrase of account', '', okCb, cancelCb) + // } + + // var statusCb = (msg) => { + // return this.logCallback(msg) + // } + + // var finalCb = (error, contractObject, address) => { + // self.event.trigger('clearInstance') + + // if (error) { + // return this.logCallback(error) + // } + // self.event.trigger('newContractInstanceAdded', [contractObject, address, contractObject.name]) + + // const data = self.runView.compilersArtefacts.getCompilerAbstract(contractObject.contract.file) + // self.runView.compilersArtefacts.addResolvedContract(helper.addressToString(address), data) + // if (self.ipfsCheckedState) { + // _paq.push(['trackEvent', 'udapp', 'DeployAndPublish', this.networkName + '_' + this.networkId]) + // publishToStorage('ipfs', self.runView.fileProvider, self.runView.fileManager, selectedContract) + // } else { + // _paq.push(['trackEvent', 'udapp', 'DeployOnly', this.networkName + '_' + this.networkId]) + // } + // } + + // let contractMetadata + // try { + // contractMetadata = await this.runView.call('compilerMetadata', 'deployMetadataOf', selectedContract.name, selectedContract.contract.file) + // } catch (error) { + // return statusCb(`creation of ${selectedContract.name} errored: ${error.message ? error.message : error}`) + // } + + // const compilerContracts = this.dropdownLogic.getCompilerContracts() + // const confirmationCb = this.getConfirmationCb(modalDialog, confirmDialog) + + // if (selectedContract.isOverSizeLimit()) { + // return modalDialog('Contract code size over limit', yo`
Contract creation initialization returns data with length of more than 24576 bytes. The deployment will likely fails.
+ // More info: eip-170 + //
`, + // { + // label: 'Force Send', + // fn: () => { + // this.deployContract(selectedContract, args, contractMetadata, compilerContracts, { continueCb, promptCb, statusCb, finalCb }, confirmationCb) + // } + // }, { + // label: 'Cancel', + // fn: () => { + // this.logCallback(`creation of ${selectedContract.name} canceled by user.`) + // } + // }) + // } + // this.deployContract(selectedContract, args, contractMetadata, compilerContracts, { continueCb, promptCb, statusCb, finalCb }, confirmationCb) + } // listenToContextChange () { // this.blockchain.event.register('networkStatus', ({ error, network }) => { @@ -107,95 +234,6 @@ export function ContractDropdownUI (props: ContractDropdownProps) { // }) // } - // setCheckedState (value) { - // value = value === 'true' ? true : value === 'false' ? false : value - // this.ipfsCheckedState = value - // if (this.ipfsCheckbox) this.ipfsCheckbox.checked = value - // } - - // enableContractNames (enable) { - // if (enable) { - // if (this.selectContractNames.value === '') return - // this.selectContractNames.removeAttribute('disabled') - // this.selectContractNames.setAttribute('title', 'Select contract for Deploy or At Address.') - // } else { - // this.selectContractNames.setAttribute('disabled', true) - // if (this.loadType === 'sol') { - // this.selectContractNames.setAttribute('title', '⚠ Select and compile *.sol file to deploy or access a contract.') - // } else { - // this.selectContractNames.setAttribute('title', '⚠ Selected *.abi file allows accessing contracts, select and compile *.sol file to deploy and access one.') - // } - // } - // } - - // changeCurrentFile (currentFile) { - // if (!this.selectContractNames) return - // if (/.(.abi)$/.exec(currentFile)) { - // this.createPanel.style.display = 'none' - // this.orLabel.style.display = 'none' - // this.compFails.style.display = 'none' - // this.loadType = 'abi' - // this.contractNamesContainer.style.display = 'block' - // this.abiLabel.style.display = 'block' - // this.abiLabel.innerHTML = currentFile - // this.selectContractNames.style.display = 'none' - // this.enableContractNames(true) - // this.enableAtAddress(true) - // } else if (/.(.sol)$/.exec(currentFile) || - // /.(.vy)$/.exec(currentFile) || // vyper - // /.(.lex)$/.exec(currentFile) || // lexon - // /.(.contract)$/.exec(currentFile)) { - // this.createPanel.style.display = 'block' - // this.orLabel.style.display = 'block' - // this.contractNamesContainer.style.display = 'block' - // this.loadType = 'sol' - // this.selectContractNames.style.display = 'block' - // this.abiLabel.style.display = 'none' - // if (this.selectContractNames.value === '') this.enableAtAddress(false) - // } else { - // this.loadType = 'other' - // this.createPanel.style.display = 'block' - // this.orLabel.style.display = 'block' - // this.contractNamesContainer.style.display = 'block' - // this.selectContractNames.style.display = 'block' - // this.abiLabel.style.display = 'none' - // if (this.selectContractNames.value === '') this.enableAtAddress(false) - // } - // } - - // setInputParamsPlaceHolder () { - // this.createPanel.innerHTML = '' - // if (this.selectContractNames.selectedIndex < 0 || this.selectContractNames.children.length <= 0) { - // this.createPanel.innerHTML = 'No compiled contracts' - // return - // } - - // const selectedContract = this.getSelectedContract() - // const clickCallback = async (valArray, inputsValues) => { - // var selectedContract = this.getSelectedContract() - // this.createInstance(selectedContract, inputsValues) - // } - // const createConstructorInstance = new MultiParamManager( - // 0, - // selectedContract.getConstructorInterface(), - // clickCallback, - // selectedContract.getConstructorInputs(), - // 'Deploy', - // selectedContract.bytecodeObject, - // true - // ) - // this.createPanel.appendChild(createConstructorInstance.render()) - // this.createPanel.appendChild(this.deployCheckBox) - // } - - // getSelectedContract () { - // var contract = this.selectContractNames.children[this.selectContractNames.selectedIndex] - // var contractName = contract.getAttribute('value') - // var compilerAtributeName = contract.getAttribute('compiler') - - // return this.dropdownLogic.getSelectedContract(contractName, compilerAtributeName) - // } - // async createInstance (selectedContract, args) { // if (selectedContract.bytecodeObject.length === 0) { // return modalDialogCustom.alert('This contract may be abstract, not implement an abstract parent\'s methods completely or not invoke an inherited contract\'s constructor correctly.') @@ -324,20 +362,20 @@ export function ContractDropdownUI (props: ContractDropdownProps) { // return confirmationCb // } - const atAddressChanged = (event: SyntheticEvent) => { - const atAddress = atAddressButtonInput.current - const selectContractNames = contracts.current + const atAddressChanged = (event) => { + const value = event.target.value - if (!atAddress.value) { + if (!value) { enableAtAddress(false) } else { - if ((selectContractNames && !selectContractNames.getAttribute('disabled') && loadType === 'sol') || + if ((!contractOptions.disabled && loadType === 'sol') || loadType === 'abi') { enableAtAddress(true) } else { enableAtAddress(false) } } + setAddress(value) } const loadFromAddress = () => { @@ -376,39 +414,60 @@ export function ContractDropdownUI (props: ContractDropdownProps) { window.localStorage.setItem(`ipfs/${props.exEnvironment}/${networkName}`, ipfsCheckedState.toString()) } + const handleContractChange = (e) => { + const value = e.target.value + + setSelectedContract(value) + } + return (
- - + + { (contractList.length <= 0) && } { abiLabel.content }
-
- - -
+ { contractList.length <= 0 ? 'No compiled contracts' + : loadedContractData ?
+ +
+ + +
+
: '' + }
-
or
+
or
- - + +
diff --git a/libs/remix-ui/run-tab/src/lib/components/contractGUI.tsx b/libs/remix-ui/run-tab/src/lib/components/contractGUI.tsx new file mode 100644 index 0000000000..1b78f6f863 --- /dev/null +++ b/libs/remix-ui/run-tab/src/lib/components/contractGUI.tsx @@ -0,0 +1,195 @@ +// eslint-disable-next-line no-use-before-define +import React, { useEffect, useRef, useState } from 'react' +import * as remixLib from '@remix-project/remix-lib' +import { ContractGUIProps } from '../types' +import { CopyToClipboard } from '@remix-ui/clipboard' + +const txFormat = remixLib.execution.txFormat +export function ContractGUI (props: ContractGUIProps) { + const [title, setTitle] = useState('') + const [basicInput, setBasicInput] = useState('') + const [toggleContainer, setToggleContainer] = useState(false) + const [buttonOptions, setButtonOptions] = useState<{ + title: string, + content: string, + classList: string, + dataId: string + }>({ title: '', content: '', classList: '', dataId: '' }) + const multiFields = useRef>([]) + + useEffect(() => { + if (props.title) { + setTitle(props.title) + } else if (props.funcABI.name) { + setTitle(props.funcABI.name) + } else { + setTitle(props.funcABI.type === 'receive' ? '(receive)' : '(fallback)') + } + }, [props.title, props.funcABI]) + + useEffect(() => { + if (props.lookupOnly) { + // // call. stateMutability is either pure or view + setButtonOptions({ + title: title + ' - call', + content: 'call', + classList: 'btn-info', + dataId: title + ' - call' + }) + } else if (props.funcABI.stateMutability === 'payable' || props.funcABI.payable) { + // // transact. stateMutability = payable + setButtonOptions({ + title: title + ' - transact (payable)', + content: 'transact', + classList: 'btn-danger', + dataId: title + ' - transact (payable)' + }) + } else { + // // transact. stateMutability = nonpayable + setButtonOptions({ + title: title + ' - transact (not payable)', + content: 'transact', + classList: 'btn-warning', + dataId: title + ' - transact (not payable)' + }) + } + }, [props.lookupOnly, props.funcABI]) + + const switchMethodViewOn = () => { + setToggleContainer(true) + makeMultiVal() + } + + const switchMethodViewOff = () => { + setToggleContainer(false) + const multiValString = getMultiValsString() + + if (multiValString) setBasicInput(multiValString) + } + + const getMultiValsString = () => { + const valArray = multiFields.current + let ret = '' + const valArrayTest = [] + + for (var j = 0; j < valArray.length; j++) { + if (ret !== '') ret += ',' + let elVal = valArray[j] ? valArray[j].value : '' + + valArrayTest.push(elVal) + elVal = elVal.replace(/(^|,\s+|,)(\d+)(\s+,|,|$)/g, '$1"$2"$3') // replace non quoted number by quoted number + elVal = elVal.replace(/(^|,\s+|,)(0[xX][0-9a-fA-F]+)(\s+,|,|$)/g, '$1"$2"$3') // replace non quoted hex string by quoted hex string + try { + JSON.parse(elVal) + } catch (e) { + elVal = '"' + elVal + '"' + } + ret += elVal + } + const valStringTest = valArrayTest.join('') + + if (valStringTest) { + return ret + } else { + return '' + } + } + + const makeMultiVal = () => { + let inputString = basicInput + + if (inputString) { + inputString = inputString.replace(/(^|,\s+|,)(\d+)(\s+,|,|$)/g, '$1"$2"$3') // replace non quoted number by quoted number + inputString = inputString.replace(/(^|,\s+|,)(0[xX][0-9a-fA-F]+)(\s+,|,|$)/g, '$1"$2"$3') // replace non quoted hex string by quoted hex string + const inputJSON = JSON.parse('[' + inputString + ']') + const multiInputs = multiFields.current + + for (let k = 0; k < multiInputs.length; k++) { + if (inputJSON[k]) { + multiInputs[k].value = JSON.stringify(inputJSON[k]) + } + } + } + } + + const clipboardContent = () => { + const multiString = getMultiValsString() + const multiJSON = JSON.parse('[' + multiString + ']') + let encodeObj + + if (props.evmBC) { + encodeObj = txFormat.encodeData(props.funcABI, multiJSON, props.evmBC) + } else { + encodeObj = txFormat.encodeData(props.funcABI, multiJSON, null) + } + if (encodeObj.error) { + console.error(encodeObj.error) + // throw new Error(encodeObj.error) + return encodeObj.error + } else { + return encodeObj.data + } + } + + const handleActionClick = () => { + props.clickCallBack(props.funcABI.inputs, basicInput) + } + + const handleBasicInput = (e) => { + const value = e.target.value + + setBasicInput(value) + } + + const handleExpandMultiClick = () => { + const valsString = getMultiValsString() + + if (valsString) { + props.clickCallBack(props.funcABI.inputs, valsString) + } else { + props.clickCallBack(props.funcABI.inputs, '') + } + } + + return ( +
0) || (props.funcABI.type === 'fallback') || (props.funcABI.type === 'receive') ? 'udapp_hasArgs' : ''}`}> +
+ + 0) || (props.funcABI.type === 'fallback') || (props.funcABI.type === 'receive')) ? 'hidden' : 'visible' }}> + + 0) ? 'hidden' : 'visible' }}> +
+
+
+
+
{title}
+ +
+
+ {props.funcABI.inputs.map((inp, index) => { + return ( +
+ + { multiFields.current[index] = el }} className="form-control" placeholder={inp.type} title={inp.name} data-id={`multiParamManagerInput${inp.name}`} /> +
) + })} +
+
+ + +
+
+
+
+ ) +} diff --git a/libs/remix-ui/run-tab/src/lib/components/settingsUI.tsx b/libs/remix-ui/run-tab/src/lib/components/settingsUI.tsx index 92327c3c95..8ed940ae9b 100644 --- a/libs/remix-ui/run-tab/src/lib/components/settingsUI.tsx +++ b/libs/remix-ui/run-tab/src/lib/components/settingsUI.tsx @@ -24,49 +24,6 @@ export function SettingsUI (props: SettingsProps) { // this._deps.config.events.on('settings/personal-mode_changed', this.onPersonalChange.bind(this)) - // setInterval(() => { - // this.updateAccountBalances() - // }, 1000) - - // this.accountListCallId = 0 - // this.loadedAccounts = {} - // } - - // setExecutionContext (context) { - // this.blockchain.changeExecutionContext(context, () => { - // modalDialogCustom.prompt('External node request', this.web3ProviderDialogBody(), 'http://127.0.0.1:8545', (target) => { - // this.blockchain.setProviderFromEndpoint(target, context, (alertMsg) => { - // if (alertMsg) addTooltip(alertMsg) - // this.setFinalContext() - // }) - // }, this.setFinalContext.bind(this)) - // }, (alertMsg) => { - // addTooltip(alertMsg) - // }, this.setFinalContext.bind(this)) - // } - - // web3ProviderDialogBody () { - // const thePath = '' - - // return yo` - //
- // Note: To use Geth & https://remix.ethereum.org, configure it to allow requests from Remix:(see Geth Docs on rpc server) - //
geth --http --http.corsdomain https://remix.ethereum.org
- //
- // To run Remix & a local Geth test node, use this command: (see Geth Docs on Dev mode) - //
geth --http --http.corsdomain="${window.origin}" --http.api web3,eth,debug,personal,net --vmdebug --datadir ${thePath} --dev console
- //
- //
- // WARNING: It is not safe to use the --http.corsdomain flag with a wildcard: --http.corsdomain * - //
- //
For more info: Remix Docs on Web3 Provider - //
- //
- // Web3 Provider Endpoint - //
- // ` - // } - // /** // * generate a value used by the env dropdown list. // * @return {String} - can return 'vm-berlin, 'vm-london', 'injected' or 'web3' @@ -77,14 +34,6 @@ export function SettingsUI (props: SettingsProps) { // return provider === 'vm' ? provider + '-' + fork : provider // } - // getSelectedAccount () { - // return this.el.querySelector('#txorigin').selectedOptions[0].value - // } - - // getEnvironment () { - // return this.blockchain.getProvider() - // } - return (
diff --git a/libs/remix-ui/run-tab/src/lib/css/run-tab.css b/libs/remix-ui/run-tab/src/lib/css/run-tab.css index a6ec9c23d3..ba777bae25 100644 --- a/libs/remix-ui/run-tab/src/lib/css/run-tab.css +++ b/libs/remix-ui/run-tab/src/lib/css/run-tab.css @@ -217,3 +217,283 @@ .udapp_checkboxAlign { padding-top: 2px; } +.udapp_instanceTitleContainer { + display: flex; + align-items: center; +} +.udapp_calldataInput{ + height: 32px; +} +.udapp_title { + display: flex; + justify-content: space-between; + font-size: 11px; + width: 100%; + overflow: hidden; + word-break: break-word; + line-height: initial; + overflow: visible; + padding: 0 0 8px; + margin: 0; + background: none; + border: none; +} +.udapp_title button { + background: none; + border: none; +} +.udapp_titleLine { + display: flex; + align-items: baseline; +} +.udapp_titleText { + word-break: break-word; + width: 100%; + border: none; + overflow: hidden; +} +.udapp_spanTitleText { + line-height: 12px; + padding: 0; + font-size: 11px; + width:100%; + border: none; + background: none; + text-transform: uppercase; + overflow: hidden; +} +.udapp_inputGroupText { + width: 100%; +} +.udapp_title .udapp_copy { + color: var(--primary); +} +.udapp_titleExpander { + padding: 5px 7px; +} +.udapp_nameNbuts { + display: contents; + flex-wrap: nowrap; + width: 100%; +} +.udapp_instance { + display: block; + flex-direction: column; + margin-bottom: 12px; + background: none; + border-radius: 2px; +} +.udapp_instance.udapp_hidesub { + border-bottom: 1px solid; +} +.udapp_instance.udapp_hidesub .udapp_title { + display: flex; +} +.udapp_instance.udapp_hidesub .udapp_udappClose { + display: flex; +} +.udapp_instance.udapp_hidesub > * { + display: none; +} +.udapp_methCaret { + min-width: 12px; + width: 12px; + margin-left: 4px; + cursor: pointer; + font-size: 16px; + line-height: 0.6; + vertical-align: middle; + padding: 0; +} +.udapp_cActionsWrapper { + border-top-left-radius: 0; + border-bottom-left-radius: 0.25rem; + border-top-rightt-radius: 0; + border-bottom-right-radius: 0.25rem; + padding: 8px 10px 7px; +} +.udapp_group:after { + content: ""; + display: table; + clear: both; +} +.udapp_buttonsContainer { + margin-top: 2%; + display: flex; + overflow: hidden; +} +.udapp_instanceButton { + height: 32px; + border-radius: 3px; + white-space: nowrap; + font-size: 11px; + overflow: hidden; + text-overflow: ellipsis; +} +.udapp_closeIcon { + font-size: 12px; + cursor: pointer; + margin-left: 5px; +} +.udapp_udappClose { + display: flex; + justify-content: flex-end; +} +.udapp_contractProperty { + width:100%; +} +.udapp_contractProperty.udapp_hasArgs input { + padding: .36em; + border-radius: 5px; +} +.udapp_contractProperty .udapp_contractActionsContainerSingle input{ + border-top-left-radius: 0; + border-bottom-left-radius: 0; +} +.udapp_contractProperty button { + min-width: 100px; + width: 100px; + margin:0; + word-break: inherit; +} +.udapp_contractProperty button:disabled { + cursor: not-allowed; + background-color: white; + border-color: lightgray; +} +.udapp_contractProperty.udapp_constant button { + min-width: 100px; + width: 100px; + margin:0; + word-break: inherit; + outline: none; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; +} +.udapp_contractProperty > .udapp_value { + box-sizing: border-box; + float: left; + align-self: center; + margin-left: 4px; +} +.udapp_contractActionsContainer { + width: 100%; + margin-bottom: 8px; +} +.udapp_contractActionsContainerSingle { + display: flex; + width: 100%; +} +.udapp_contractActionsContainerSingle i { + line-height: 2; +} +.udapp_contractActionsContainerMulti { + display:none; + width: 100%; +} +.udapp_contractActionsContainerMultiInner { + width: 100%; + padding: 16px 8px 16px 14px; + border-radius: 3px; + margin-bottom: 8px; +} +.udapp_multiHeader { + display: flex; + justify-content: space-between; + align-items: center; + margin-bottom: 8px; + text-align: left; + font-size: 10px; + font-weight: bold; +} +.udapp_contractActionsContainerMultiInner .udapp_multiTitle { + padding-left: 10px; +} +.udapp_contractProperty .udapp_multiTitle { + padding: 0; + line-height: 16px; + display: inline-block; + font-size: 12px; + font-weight: bold; + cursor: default; +} +.udapp_contractProperty .udapp_contractActionsContainerMultiInner .udapp_multiArg label{ + text-align: right; +} +.udapp_multiHeader .udapp_methCaret { + float: right; + margin-right: 0; +} +.udapp_contractProperty.udapp_constant .udapp_multiTitle { + display: inline-block; + width: 90%; + /* font-size: 10px; */ + height: 25px; + padding-left: 20px; + font-weight: bold; + line-height: 25px; + cursor: default; +} +.udapp_multiArg { + display: flex; + align-items: center; + justify-content: flex-end; + margin-top: 4px; +} +.udapp_multiArg input{ + padding: 5px; +} +.udapp_multiArg label { + width: auto; + padding: 0; + margin: 0 4px 0 0; + font-size: 10px; + line-height: 12px; + text-align: right; + word-break: initial; +} +.udapp_multiArg button { + max-width: 100px; + border-radius: 3px; + border-width: 1px; + width: inherit; +} +.udapp_multiHeader button { + display: inline-block; + width: 94%; +} +.udapp_hasArgs .udapp_multiArg input { + border-left: 1px solid #dddddd; + width: 67%; +} +.udapp_hasArgs input { + display: block; + height: 32px; + border: 1px solid #dddddd; + padding: .36em; + border-left: none; + padding: 8px 8px 8px 10px; + font-size: 10px !important; +} +.udapp_hasArgs button { + border-top-right-radius: 0; + border-bottom-right-radius: 0; + border-right: 0; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; + font-size: 11px; +} +.udapp_hasArgs .udapp_contractActionsContainerMulti button { + border-radius: 3px; +} +.udapp_contractActionsContainerMultiInner .udapp_multiArg i { + padding-right: 10px; +} +.udapp_hideWarningsContainer { + display: flex; + align-items: center; + margin-left: 2% +} + 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 999d347ca9..b05a57a31d 100644 --- a/libs/remix-ui/run-tab/src/lib/reducers/runTab.ts +++ b/libs/remix-ui/run-tab/src/lib/reducers/runTab.ts @@ -41,7 +41,19 @@ export interface RunTabState { externalEndpoint: string, popup: string, passphrase: string, - matchPassphrase: string + matchPassphrase: string, + contracts: { + contractList: { + name: string, + alias: string, + file: string + }[], + loadType: 'abi' | 'sol' | 'other' + currentFile: string, + isRequesting: boolean, + isSuccessful: boolean, + error: string + }, } export const runTabInitialState: RunTabState = { @@ -102,7 +114,15 @@ export const runTabInitialState: RunTabState = { externalEndpoint: 'http://127.0.0.1:8545', popup: '', passphrase: '', - matchPassphrase: '' + matchPassphrase: '', + contracts: { + contractList: [], + loadType: 'other', + currentFile: '', + isRequesting: false, + isSuccessful: false, + error: null + } } export const runTabReducer = (state: RunTabState = runTabInitialState, action: Action) => { @@ -345,6 +365,71 @@ export const runTabReducer = (state: RunTabState = runTabInitialState, action: A } } + case 'FETCH_CONTRACT_LIST_REQUEST': { + return { + ...state, + contracts: { + ...state.contracts, + isRequesting: true, + isSuccessful: false, + error: null + } + } + } + + case 'FETCH_CONTRACT_LIST_SUCCESS': { + const payload: { name: string, alias: string, file: string }[] = action.payload + + return { + ...state, + contracts: { + ...state.contracts, + contractList: payload, + isSuccessful: true, + isRequesting: false, + error: null + } + } + } + + case 'FETCH_CONTRACT_LIST_FAILED': { + const payload: string = action.payload + + return { + ...state, + contracts: { + ...state.contracts, + isRequesting: false, + isSuccessful: false, + error: payload + } + } + } + + case 'SET_LOAD_TYPE': { + const payload: 'abi' | 'sol' | 'other' = action.payload + + return { + ...state, + contracts: { + ...state.contracts, + loadType: payload + } + } + } + + case 'SET_CURRENT_FILE': { + const payload: string = action.payload + + return { + ...state, + contracts: { + ...state.contracts, + currentFile: payload + } + } + } + default: return state } 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 73859a4835..00cc462573 100644 --- a/libs/remix-ui/run-tab/src/lib/run-tab.tsx +++ b/libs/remix-ui/run-tab/src/lib/run-tab.tsx @@ -9,7 +9,7 @@ import { RecorderUI } from './components/recorderCardUI' import { SettingsUI } from './components/settingsUI' import { Modal, RunTabProps } from './types' import { runTabInitialState, runTabReducer } from './reducers/runTab' -import { initRunTab, setAccount, setUnit, setGasFee, setExecutionContext, setWeb3Endpoint, clearPopUp, createNewBlockchainAccount, setPassphrasePrompt, setMatchPassphrasePrompt, signMessageWithAddress } from './actions' +import { initRunTab, setAccount, setUnit, setGasFee, setExecutionContext, setWeb3Endpoint, clearPopUp, createNewBlockchainAccount, setPassphrasePrompt, setMatchPassphrasePrompt, signMessageWithAddress, getSelectedContract } from './actions' import './css/run-tab.css' export function RunTabUI (props: RunTabProps) { @@ -134,7 +134,12 @@ export function RunTabUI (props: RunTabProps) { tooltip={toast} signMessageWithAddress={signMessageWithAddress} /> - +
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 8162ac3cea..99778ced10 100644 --- a/libs/remix-ui/run-tab/src/lib/types/index.ts +++ b/libs/remix-ui/run-tab/src/lib/types/index.ts @@ -100,7 +100,34 @@ export interface ValueProps { } export interface ContractDropdownProps { - exEnvironment: string + exEnvironment: string, + contracts: { + contractList: { + name: string, + alias: string, + file: string + }[], + loadType: 'abi' | 'sol' | 'other', + currentFile: string, + isRequesting: boolean, + isSuccessful: boolean, + error: string + }, + getSelectedContract: (contractName: string, compilerAtributeName: string) => { + name: string, + contract: string, + compiler: any, + abi: any, + bytecodeObject: any, + bytecodeLinkReferences: any, + object: any, + deployedBytecode: any, + getConstructorInterface: () => any, + getConstructorInputs: () => any, + isOverSizeLimit: () => boolean, + metadata: any + }, + modal: (title: string, message: string | JSX.Element, okLabel: string, okFn: () => void, cancelLabel?: string, cancelFn?: () => void) => void } export interface RecorderProps { @@ -121,3 +148,19 @@ export interface Modal { cancelLabel: string cancelFn: () => void } + +export interface ContractGUIProps { + title?: string, + funcABI: { + name: string, + type: string, + inputs: { name: string, type: string }[], + stateMutability: string, + payable: boolean + }, + inputs: any, + clickCallBack: (inputs: { name: string, type: string }[], input: string) => void, + widthClass?: string, + evmBC: any, + lookupOnly: boolean +}