Fix toggle multiparam

yann300-patch-36
David Disu 3 years ago committed by yann300
parent 594d9bbc16
commit 99201aa514
  1. 2
      libs/remix-ui/clipboard/src/lib/copy-to-clipboard/copy-to-clipboard.tsx
  2. 144
      libs/remix-ui/run-tab/src/lib/actions/index.ts
  3. 34
      libs/remix-ui/run-tab/src/lib/actions/payload.ts
  4. 395
      libs/remix-ui/run-tab/src/lib/components/contractDropdownUI.tsx
  5. 195
      libs/remix-ui/run-tab/src/lib/components/contractGUI.tsx
  6. 51
      libs/remix-ui/run-tab/src/lib/components/settingsUI.tsx
  7. 280
      libs/remix-ui/run-tab/src/lib/css/run-tab.css
  8. 89
      libs/remix-ui/run-tab/src/lib/reducers/runTab.ts
  9. 9
      libs/remix-ui/run-tab/src/lib/run-tab.tsx
  10. 45
      libs/remix-ui/run-tab/src/lib/types/index.ts

@ -36,7 +36,7 @@ export const CopyToClipboard = (props: ICopyToClipboard) => {
}
const reset = () => {
setTimeout(() => setMessage('Copy'), 500)
setTimeout(() => setMessage(tip), 500)
}
return (

@ -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<any>
export const initRunTab = (udapp: RunTab) => async (reducerDispatch: React.Dispatch<any>) => {
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
}

@ -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
}
}

@ -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<string>('')
@ -13,9 +14,31 @@ export function ContractDropdownUI (props: ContractDropdownProps) {
content: ''
})
const [ipfsCheckedState, setIpfsCheckedState] = useState<boolean>(false)
const [loadType] = useState<string>('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<string>('')
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<string>('')
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`<option value="${contract.name}" compiler="${compilerFullName}">${contract.name} - ${contract.file}</option>`)
// })
// }
// 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`<div>Gas estimation errored with the following message (see below).
// The transaction execution will likely fail. Do you want to force sending? <br>
// ${msg}
// </div>`,
// {
// 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`<div>Contract creation initialization returns data with length of more than 24576 bytes. The deployment will likely fails. <br>
// More info: <a href="https://github.com/ethereum/EIPs/blob/master/EIPS/eip-170.md" target="_blank">eip-170</a>
// </div>`,
// {
// 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 (
<div className="udapp_container" data-id="contractDropdownContainer">
<label className="udapp_settingsLabel">Contract</label>
<div className="udapp_subcontainer">
<select ref={contracts} className="udapp_contractNames custom-select" disabled title="Please compile *.sol file to deploy or access a contract"></select>
<i title="No contract compiled yet or compilation failed. Please check the compile tab for more information." className="m-2 ml-3 fas fa-times-circle udapp_errorIcon" ></i>
<select value={selectedContract} onChange={handleContractChange} className="udapp_contractNames custom-select" disabled={contractOptions.disabled} title={contractOptions.title} style={{ display: loadType === 'abi' ? 'none' : 'block' }}>
{ contractList.map((contract, index) => {
return <option key={index} value={contract.alias}>{contract.alias} - {contract.file}</option>
}) }
</select>
{ (contractList.length <= 0) && <i style={{ display: compFails }} title="No contract compiled yet or compilation failed. Please check the compile tab for more information." className="m-2 ml-3 fas fa-times-circle udapp_errorIcon" ></i> }
<span className="py-1" style={{ display: abiLabel.display }}>{ abiLabel.content }</span>
</div>
<div>
<div className="udapp_deployDropdown">
<div className="d-flex py-1 align-items-center custom-control custom-checkbox">
<input
id="deployAndRunPublishToIPFS"
data-id="contractDropdownIpfsCheckbox"
className="form-check-input custom-control-input"
type="checkbox"
onChange={handleCheckedIPFS}
checked={ipfsCheckedState}
/>
<label
htmlFor="deployAndRunPublishToIPFS"
data-id="contractDropdownIpfsCheckboxLabel"
className="m-0 form-check-label custom-control-label udapp_checkboxAlign"
title="Publishing the source code and metadata to IPFS facilitates source code verification using Sourcify and will greatly foster contract adoption (auditing, debugging, calling it, etc...)"
>
Publish to IPFS
</label>
</div>
{ contractList.length <= 0 ? 'No compiled contracts'
: loadedContractData ? <div>
<ContractGUI title='Deploy' funcABI={loadedContractData.getConstructorInterface()} clickCallBack={clickCallback} inputs={loadedContractData.getConstructorInputs()} widthClass='w-50' evmBC={loadedContractData.bytecodeObject} lookupOnly={false} />
<div className="d-flex py-1 align-items-center custom-control custom-checkbox">
<input
id="deployAndRunPublishToIPFS"
data-id="contractDropdownIpfsCheckbox"
className="form-check-input custom-control-input"
type="checkbox"
onChange={handleCheckedIPFS}
checked={ipfsCheckedState}
/>
<label
htmlFor="deployAndRunPublishToIPFS"
data-id="contractDropdownIpfsCheckboxLabel"
className="m-0 form-check-label custom-control-label udapp_checkboxAlign"
title="Publishing the source code and metadata to IPFS facilitates source code verification using Sourcify and will greatly foster contract adoption (auditing, debugging, calling it, etc...)"
>
Publish to IPFS
</label>
</div>
</div> : ''
}
</div>
<div className="udapp_orLabel mt-2">or</div>
<div className="udapp_orLabel mt-2" style={{ display: loadType === 'abi' ? 'none' : 'block' }}>or</div>
<div className="udapp_button udapp_atAddressSect">
<button className="udapp_atAddress btn btn-sm btn-info" id="runAndDeployAtAdressButton" onClick={loadFromAddress}>At Address</button>
<input ref={atAddressButtonInput} className="udapp_input udapp_ataddressinput ataddressinput form-control" placeholder="Load contract from Address" title="address of contract" onInput={atAddressChanged} />
<button className="udapp_atAddress btn btn-sm btn-info" id="runAndDeployAtAdressButton" disabled={atAddressOptions.disabled} onClick={loadFromAddress}>At Address</button>
<input
className="udapp_input udapp_ataddressinput ataddressinput form-control"
placeholder="Load contract from Address"
title="address of contract"
onChange={atAddressChanged}
disabled={atAddressOptions.disabled}
/>
</div>
</div>
</div>

@ -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<string>('')
const [basicInput, setBasicInput] = useState<string>('')
const [toggleContainer, setToggleContainer] = useState<boolean>(false)
const [buttonOptions, setButtonOptions] = useState<{
title: string,
content: string,
classList: string,
dataId: string
}>({ title: '', content: '', classList: '', dataId: '' })
const multiFields = useRef<Array<HTMLInputElement | null>>([])
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 (
<div className={`udapp_contractProperty ${(props.funcABI.inputs && props.funcABI.inputs.length > 0) || (props.funcABI.type === 'fallback') || (props.funcABI.type === 'receive') ? 'udapp_hasArgs' : ''}`}>
<div className="udapp_contractActionsContainerSingle pt-2" style={{ display: toggleContainer ? 'none' : 'flex' }}>
<button onClick={handleActionClick} title={buttonOptions.title} className={`udapp_instanceButton ${props.widthClass} btn btn-sm ${buttonOptions.classList}`} data-id={buttonOptions.dataId}>{title}</button>
<input
className="form-control"
data-id={props.funcABI.type === 'fallback' || props.funcABI.type === 'receive' ? `'(${props.funcABI.type}')` : 'multiParamManagerBasicInputField'}
placeholder={props.inputs}
title={props.funcABI.type === 'fallback' || props.funcABI.type === 'receive' ? `'(${props.funcABI.type}')` : props.inputs}
onChange={handleBasicInput}
style={{ visibility: !((props.funcABI.inputs && props.funcABI.inputs.length > 0) || (props.funcABI.type === 'fallback') || (props.funcABI.type === 'receive')) ? 'hidden' : 'visible' }}>
</input>
<i
className="fas fa-angle-down udapp_methCaret"
onClick={switchMethodViewOn}
title={title}
style={{ visibility: !(props.funcABI.inputs && props.funcABI.inputs.length > 0) ? 'hidden' : 'visible' }}></i>
</div>
<div className="udapp_contractActionsContainerMulti" style={{ display: toggleContainer ? 'flex' : 'none' }}>
<div className="udapp_contractActionsContainerMultiInner text-dark">
<div onClick={switchMethodViewOff} className="udapp_multiHeader">
<div className="udapp_multiTitle run-instance-multi-title">{title}</div>
<i className='fas fa-angle-up udapp_methCaret'></i>
</div>
<div>
{props.funcABI.inputs.map((inp, index) => {
return (
<div className="udapp_multiArg" key={index}>
<label htmlFor={inp.name}> {inp.name}: </label>
<input ref={el => { multiFields.current[index] = el }} className="form-control" placeholder={inp.type} title={inp.name} data-id={`multiParamManagerInput${inp.name}`} />
</div>)
})}
</div>
<div className="udapp_group udapp_multiArg">
<CopyToClipboard content={clipboardContent()} tip='Encode values of input fields & copy to clipboard' icon='fa-clipboard' direction={'left'} />
<button onClick={handleExpandMultiClick} title={buttonOptions.title} data-id={buttonOptions.dataId} className={`udapp_instanceButton ${buttonOptions.classList}`}>{ buttonOptions.content }</button>
</div>
</div>
</div>
</div>
)
}

@ -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 = '<path/to/local/folder/for/test/chain>'
// return yo`
// <div class="">
// Note: To use Geth & https://remix.ethereum.org, configure it to allow requests from Remix:(see <a href="https://geth.ethereum.org/docs/rpc/server" target="_blank">Geth Docs on rpc server</a>)
// <div class="border p-1">geth --http --http.corsdomain https://remix.ethereum.org</div>
// <br>
// To run Remix & a local Geth test node, use this command: (see <a href="https://geth.ethereum.org/getting-started/dev-mode" target="_blank">Geth Docs on Dev mode</a>)
// <div class="border p-1">geth --http --http.corsdomain="${window.origin}" --http.api web3,eth,debug,personal,net --vmdebug --datadir ${thePath} --dev console</div>
// <br>
// <br>
// <b>WARNING:</b> It is not safe to use the --http.corsdomain flag with a wildcard: <b>--http.corsdomain *</b>
// <br>
// <br>For more info: <a href="https://remix-ide.readthedocs.io/en/latest/run.html#more-about-web3-provider" target="_blank">Remix Docs on Web3 Provider</a>
// <br>
// <br>
// Web3 Provider Endpoint
// </div>
// `
// }
// /**
// * 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 (
<div className="udapp_settings">
<EnvironmentUI setWeb3Endpoint={props.setWeb3Endpoint} selectedEnv={props.selectExEnv} providers={props.providers} setExecutionContext={props.setExecutionContext} externalEndpoint={props.externalEndpoint} />

@ -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%
}

@ -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
}

@ -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}
/>
<ContractDropdownUI exEnvironment={runTab.selectExEnv} />
<ContractDropdownUI
exEnvironment={runTab.selectExEnv}
contracts={runTab.contracts}
getSelectedContract={getSelectedContract}
modal={modal}
/>
<RecorderUI />
<InstanceContainerUI />
</div>

@ -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
}

Loading…
Cancel
Save