Udapp recorder module

yann300-patch-36
David Disu 3 years ago committed by yann300
parent cd64c2d3e9
commit 0d8285d7fc
  1. 107
      apps/remix-ide/src/app/udapp/run-tab.js
  2. 116
      libs/remix-ui/run-tab/src/lib/actions/index.ts
  3. 22
      libs/remix-ui/run-tab/src/lib/actions/payload.ts
  4. 21
      libs/remix-ui/run-tab/src/lib/components/contractDropdownUI.tsx
  5. 2
      libs/remix-ui/run-tab/src/lib/components/instanceContainerUI.tsx
  6. 17
      libs/remix-ui/run-tab/src/lib/components/recorderCardUI.tsx
  7. 22
      libs/remix-ui/run-tab/src/lib/components/scenario.tsx
  8. 24
      libs/remix-ui/run-tab/src/lib/components/settingsUI.tsx
  9. 29
      libs/remix-ui/run-tab/src/lib/components/universalDappUI.tsx
  10. 51
      libs/remix-ui/run-tab/src/lib/reducers/runTab.ts
  11. 20
      libs/remix-ui/run-tab/src/lib/run-tab.tsx
  12. 23
      libs/remix-ui/run-tab/src/lib/types/index.ts
  13. 15
      libs/remix-ui/run-tab/src/lib/types/recorder.d.ts
  14. 2
      libs/remix-ui/run-tab/src/lib/types/run-tab.d.ts

@ -6,19 +6,10 @@ import * as packageJson from '../../../../../package.json'
const yo = require('yo-yo') const yo = require('yo-yo')
const EventManager = require('../../lib/events') const EventManager = require('../../lib/events')
const Card = require('../ui/card')
const css = require('../tabs/styles/run-tab-styles')
const SettingsUI = require('../tabs/runTab/settings.js')
const Recorder = require('../tabs/runTab/model/recorder.js') const Recorder = require('../tabs/runTab/model/recorder.js')
const RecorderUI = require('../tabs/runTab/recorder.js')
const DropdownLogic = require('../tabs/runTab/model/dropdownlogic.js')
const ContractDropdownUI = require('../tabs/runTab/contractDropdown.js')
const toaster = require('../ui/tooltip') const toaster = require('../ui/tooltip')
const _paq = window._paq = window._paq || [] const _paq = window._paq = window._paq || []
const UniversalDAppUI = require('../ui/universal-dapp-ui')
const profile = { const profile = {
name: 'udapp', name: 'udapp',
displayName: 'Deploy & run transactions', displayName: 'Deploy & run transactions',
@ -45,6 +36,7 @@ export class RunTab extends ViewPlugin {
this.compilersArtefacts = compilersArtefacts this.compilersArtefacts = compilersArtefacts
this.networkModule = networkModule this.networkModule = networkModule
this.fileProvider = fileProvider this.fileProvider = fileProvider
this.recorder = new Recorder(blockchain)
this.REACT_API = {} this.REACT_API = {}
this.setupEvents() this.setupEvents()
this.el = document.createElement('div') this.el = document.createElement('div')
@ -62,14 +54,11 @@ export class RunTab extends ViewPlugin {
getSettings () { getSettings () {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
if (!this.container) reject(new Error('UI not ready')) resolve({
else { selectedAccount: this.REACT_API.accounts.selectedAccount,
resolve({ selectedEnvMode: this.REACT_API.selectExEnv,
selectedAccount: this.settingsUI.getSelectedAccount(), networkEnvironment: this.REACT_API.networkName
selectedEnvMode: this.blockchain.getProvider(), })
networkEnvironment: this.container.querySelector('*[data-id="settingsNetworkEnv"]').textContent
})
}
}) })
} }
@ -108,92 +97,8 @@ export class RunTab extends ViewPlugin {
return this.blockchain.pendingTransactionsCount() return this.blockchain.pendingTransactionsCount()
} }
renderSettings () {
this.settingsUI = new SettingsUI(this.blockchain, this.networkModule)
this.settingsUI.event.register('clearInstance', () => {
this.event.trigger('clearInstance', [])
})
}
renderDropdown (udappUI, fileManager, compilersArtefacts, config, editor, logCallback) {
const dropdownLogic = new DropdownLogic(compilersArtefacts, config, editor, this)
this.contractDropdownUI = new ContractDropdownUI(this.blockchain, dropdownLogic, logCallback, this)
fileManager.events.on('currentFileChanged', this.contractDropdownUI.changeCurrentFile.bind(this.contractDropdownUI))
this.contractDropdownUI.event.register('clearInstance', () => {
const noInstancesText = this.noInstancesText
if (noInstancesText.parentNode) { noInstancesText.parentNode.removeChild(noInstancesText) }
})
this.contractDropdownUI.event.register('newContractABIAdded', (abi, address) => {
this.instanceContainer.appendChild(udappUI.renderInstanceFromABI(abi, address, '<at address>'))
})
this.contractDropdownUI.event.register('newContractInstanceAdded', (contractObject, address, value) => {
this.instanceContainer.appendChild(udappUI.renderInstance(contractObject, address, value))
})
}
renderRecorder (udappUI, fileManager, config, logCallback) {
this.recorderCount = yo`<span>0</span>`
const recorder = new Recorder(this.blockchain)
recorder.event.register('recorderCountChange', (count) => {
this.recorderCount.innerText = count
})
this.event.register('clearInstance', recorder.clearAll.bind(recorder))
this.recorderInterface = new RecorderUI(this.blockchain, fileManager, recorder, logCallback, config)
this.recorderInterface.event.register('newScenario', (abi, address, contractName) => {
var noInstancesText = this.noInstancesText
if (noInstancesText.parentNode) { noInstancesText.parentNode.removeChild(noInstancesText) }
this.instanceContainer.appendChild(udappUI.renderInstanceFromABI(abi, address, contractName))
})
this.recorderInterface.render()
}
renderRecorderCard () {
const collapsedView = yo`
<div class="d-flex flex-column">
<div class="ml-2 badge badge-pill badge-primary" title="The number of recorded transactions">${this.recorderCount}</div>
</div>`
const expandedView = yo`
<div class="d-flex flex-column">
<div class="${css.recorderDescription} mt-2">
All transactions (deployed contracts and function executions) in this environment can be saved and replayed in
another environment. e.g Transactions created in Javascript VM can be replayed in the Injected Web3.
</div>
<div class="${css.transactionActions}">
${this.recorderInterface.recordButton}
${this.recorderInterface.runButton}
</div>
</div>
</div>`
this.recorderCard = new Card({}, {}, { title: 'Transactions recorded', collapsedView: collapsedView })
this.recorderCard.event.register('expandCollapseCard', (arrow, body, status) => {
body.innerHTML = ''
status.innerHTML = ''
if (arrow === 'down') {
status.appendChild(collapsedView)
body.appendChild(expandedView)
} else if (arrow === 'up') {
status.appendChild(collapsedView)
}
})
}
render () { render () {
return this.el return this.el
this.udappUI = new UniversalDAppUI(this.blockchain, this.logCallback)
this.renderSettings()
this.renderDropdown(this.udappUI, this.fileManager, this.compilersArtefacts, this.config, this.editor, this.logCallback)
this.renderRecorder(this.udappUI, this.fileManager, this.config, this.logCallback)
this.renderRecorderCard()
return this.renderContainer()
} }
renderComponent () { renderComponent () {

@ -2,8 +2,8 @@
import React from 'react' import React from 'react'
import * as ethJSUtil from 'ethereumjs-util' import * as ethJSUtil from 'ethereumjs-util'
import Web3 from 'web3' import Web3 from 'web3'
import { addressToString, shortenAddress } from '@remix-ui/helper' import { addressToString, createNonClashingNameAsync, shortenAddress } from '@remix-ui/helper'
import { addNewInstance, addProvider, clearAllInstances, displayNotification, displayPopUp, fetchAccountsListFailed, fetchAccountsListRequest, fetchAccountsListSuccess, fetchContractListSuccess, hidePopUp, removeExistingInstance, removeProvider, setBaseFeePerGas, setConfirmSettings, setCurrentFile, setDecodedResponse, setExecutionEnvironment, setExternalEndpoint, setGasLimit, setGasPrice, setGasPriceStatus, setIpfsCheckedState, setLoadType, setMatchPassphrase, setMaxFee, setMaxPriorityFee, setNetworkName, setPassphrase, setSelectedAccount, setSendUnit, setSendValue, setTxFeeContent } from './payload' import { addNewInstance, addProvider, clearAllInstances, clearRecorderCount, displayNotification, displayPopUp, fetchAccountsListFailed, fetchAccountsListRequest, fetchAccountsListSuccess, fetchContractListSuccess, hidePopUp, removeExistingInstance, removeProvider, setBaseFeePerGas, setConfirmSettings, setCurrentFile, setDecodedResponse, setExecutionEnvironment, setExternalEndpoint, setGasLimit, setGasPrice, setGasPriceStatus, setIpfsCheckedState, setLoadType, setMatchPassphrase, setMaxFee, setMaxPriorityFee, setNetworkName, setPassphrase, setPathToScenario, setRecorderCount, setSelectedAccount, setSendUnit, setSendValue, setTxFeeContent } from './payload'
import { RunTab } from '../types/run-tab' import { RunTab } from '../types/run-tab'
import { CompilerAbstract } from '@remix-project/remix-solidity' import { CompilerAbstract } from '@remix-project/remix-solidity'
import * as remixLib from '@remix-project/remix-lib' import * as remixLib from '@remix-project/remix-lib'
@ -92,21 +92,16 @@ const setupEvents = () => {
plugin.on('manager', 'pluginDeactivated', removePluginProvider.bind(plugin)) plugin.on('manager', 'pluginDeactivated', removePluginProvider.bind(plugin))
plugin.on('solidity', 'compilationFinished', (file, source, languageVersion, data) => plugin.on('solidity', 'compilationFinished', (file, source, languageVersion, data) => broadcastCompilationResult(file, source, languageVersion, data))
broadcastCompilationResult(file, source, languageVersion, data)
) plugin.on('vyper', '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('lexon', 'compilationFinished', (file, source, languageVersion, data) => plugin.on('yulp', 'compilationFinished', (file, source, languageVersion, data) => broadcastCompilationResult(file, source, languageVersion, data))
broadcastCompilationResult(file, source, languageVersion, data)
) plugin.on('optimism-compiler', '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) => { plugin.fileManager.events.on('currentFileChanged', (currentFile: string) => {
if (/.(.abi)$/.exec(currentFile)) { if (/.(.abi)$/.exec(currentFile)) {
dispatch(setLoadType('abi')) dispatch(setLoadType('abi'))
@ -119,6 +114,14 @@ const setupEvents = () => {
dispatch(setLoadType('other')) dispatch(setLoadType('other'))
} }
}) })
plugin.recorder.event.register('recorderCountChange', (count) => {
dispatch(setRecorderCount(count))
})
plugin.event.register('cleared', () => {
dispatch(clearRecorderCount())
})
} }
const updateAccountBalances = () => { const updateAccountBalances = () => {
@ -540,7 +543,7 @@ export const updateTxFeeContent = (content: string) => {
dispatch(setTxFeeContent(content)) dispatch(setTxFeeContent(content))
} }
const addInstance = (instance: { contractData: ContractData, address: string, name: string }) => { const addInstance = (instance: { contractData?: ContractData, address: string, name: string, abi?: any, decodedResponse?: any }) => {
dispatch(addNewInstance(instance)) dispatch(addNewInstance(instance))
} }
@ -550,6 +553,7 @@ export const removeInstance = (index: number) => {
export const clearInstances = () => { export const clearInstances = () => {
dispatch(clearAllInstances()) dispatch(clearAllInstances())
dispatch(clearRecorderCount())
} }
export const loadAddress = (contract: ContractData, address: string) => { export const loadAddress = (contract: ContractData, address: string) => {
@ -595,10 +599,9 @@ export const runTransactions = (
if (lookupOnly) callinfo = 'call' if (lookupOnly) callinfo = 'call'
else if (funcABI.type === 'fallback' || funcABI.type === 'receive') callinfo = 'lowLevelInteracions' else if (funcABI.type === 'fallback' || funcABI.type === 'receive') callinfo = 'lowLevelInteracions'
else callinfo = 'transact' else callinfo = 'transact'
_paq.push(['trackEvent', 'udapp', callinfo, plugin.blockchain.getCurrentNetworkStatus().network.name]) _paq.push(['trackEvent', 'udapp', callinfo, plugin.blockchain.getCurrentNetworkStatus().network.name])
const params = funcABI.type !== 'fallback' ? inputsValues : ''
const params = funcABI.type !== 'fallback' ? inputsValues : ''
plugin.blockchain.runOrCallContractMethod( plugin.blockchain.runOrCallContractMethod(
contractName, contractName,
contractABI, contractABI,
@ -630,3 +633,76 @@ export const runTransactions = (
} }
) )
} }
const saveScenario = (promptCb, cb) => {
const txJSON = JSON.stringify(plugin.recorder.getAll(), null, 2)
const path = plugin.fileManager.currentPath()
promptCb(path, async () => {
const fileProvider = plugin.fileManager.fileProviderOf(path)
if (!fileProvider) return
const newFile = path + '/' + plugin.REACT_API.recorder.pathToScenario
try {
console.log('newFile: ', newFile)
const newPath = await createNonClashingNameAsync(newFile, plugin.fileManager)
console.log('newPath: ', newPath)
// eslint-disable-next-line standard/no-callback-literal
if (!fileProvider.set(newPath, txJSON)) return cb('Failed to create file ' + newFile)
plugin.fileManager.open(newFile)
} catch (error) {
// eslint-disable-next-line standard/no-callback-literal
if (error) return cb('Failed to create file. ' + newFile + ' ' + error)
}
})
}
export const storeScenario = (prompt: (msg: string) => JSX.Element) => {
saveScenario(
(path, cb) => {
dispatch(displayNotification('Save transactions as scenario', prompt('Transactions will be saved in a file under ' + path), '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))
}

@ -220,7 +220,7 @@ export const setTxFeeContent = (content: string) => {
} }
} }
export const addNewInstance = (instance: { contractData: ContractData, address: string, name: string }) => { export const addNewInstance = (instance: { contractData?: ContractData, address: string, name: string, abi?: any }) => {
return { return {
type: 'ADD_INSTANCE', type: 'ADD_INSTANCE',
payload: instance payload: instance
@ -249,3 +249,23 @@ export const setDecodedResponse = (index: number, decodedResponse) => {
} }
} }
} }
export const setPathToScenario = (path: string) => {
return {
type: 'SET_PATH_TO_SCENARIO',
payload: path
}
}
export const setRecorderCount = (count: number) => {
return {
type: 'SET_RECORDER_COUNT',
payload: count
}
}
export const clearRecorderCount = () => {
return {
type: 'CLEAR_RECORDER_COUNT'
}
}

@ -78,7 +78,6 @@ export function ContractDropdownUI (props: ContractDropdownProps) {
useEffect(() => { useEffect(() => {
if (selectedContract) { if (selectedContract) {
console.log('contractList: ', contractList)
const contract = contractList.find(contract => contract.alias === selectedContract) const contract = contractList.find(contract => contract.alias === selectedContract)
setLoadedContractData(props.getSelectedContract(selectedContract, contract.name)) setLoadedContractData(props.getSelectedContract(selectedContract, contract.name))
@ -124,26 +123,6 @@ export function ContractDropdownUI (props: ContractDropdownProps) {
props.createInstance(loadedContractData, props.gasEstimationPrompt, props.passphrasePrompt, props.logBuilder, props.publishToStorage, props.mainnetPrompt, isOverSizePrompt, args) props.createInstance(loadedContractData, props.gasEstimationPrompt, props.passphrasePrompt, props.logBuilder, props.publishToStorage, props.mainnetPrompt, isOverSizePrompt, args)
} }
// listenToContextChange () {
// this.blockchain.event.register('networkStatus', ({ error, network }) => {
// if (error) {
// console.log('can\'t detect network')
// return
// }
// this.exEnvironment = this.blockchain.getProvider()
// this.networkName = network.name
// const savedConfig = window.localStorage.getItem(`ipfs/${this.exEnvironment}/${this.networkName}`)
// // check if an already selected option exist else use default workflow
// if (savedConfig !== null) {
// this.setCheckedState(savedConfig)
// } else {
// this.setCheckedState(this.networkName === 'Main')
// }
// })
// }
const atAddressChanged = (event) => { const atAddressChanged = (event) => {
const value = event.target.value const value = event.target.value

@ -24,6 +24,7 @@ export function InstanceContainerUI (props: InstanceContainerProps) {
{ instanceList.length > 0 { instanceList.length > 0
? <div> { props.instances.instanceList.map((instance, index) => { ? <div> { props.instances.instanceList.map((instance, index) => {
return <UniversalDappUI return <UniversalDappUI
key={index}
instance={instance} instance={instance}
context={props.getContext()} context={props.getContext()}
removeInstance={props.removeInstance} removeInstance={props.removeInstance}
@ -33,7 +34,6 @@ export function InstanceContainerUI (props: InstanceContainerProps) {
passphrasePrompt={props.passphrasePrompt} passphrasePrompt={props.passphrasePrompt}
mainnetPrompt={props.mainnetPrompt} mainnetPrompt={props.mainnetPrompt}
runTransactions={props.runTransactions} runTransactions={props.runTransactions}
decodedResponse={instance.decodedResponse || {}}
sendValue={props.sendValue} sendValue={props.sendValue}
/> />
}) } }) }

@ -23,28 +23,17 @@ export function RecorderUI (props: RecorderProps) {
} }
const triggerRecordButton = () => { const triggerRecordButton = () => {
// dispatch saveScenario() props.storeScenario(props.scenarioPrompt)
// this.saveScenario(
// (path, cb) => {
// modalDialogCustom.prompt('Save transactions as scenario', 'Transactions will be saved in a file under ' + path, 'scenario.json', cb)
// },
// (error) => {
// if (error) return modalDialogCustom.alert(error)
// }
// )
} }
const handleClickRunButton = () => { const handleClickRunButton = () => {
// dispatchRunButtonClickHandler props.runCurrentScenario(props.gasEstimationPrompt, props.passphrasePrompt, props.mainnetPrompt, props.logBuilder)
// const file = this.config.get('currentFile')
// if (!file) return modalDialogCustom.alert('A scenario file has to be selected')
// this.runScenario(file)
} }
return ( return (
<div className="udapp_cardContainer list-group-item border-0"> <div className="udapp_cardContainer list-group-item border-0">
<TreeView> <TreeView>
<TreeViewItem label={card('Transactions recorded', 0)} showIcon={false} labelClass="ml-n1"> <TreeViewItem label={card('Transactions recorded', props.count)} showIcon={false} labelClass="ml-n1">
<div className="d-flex flex-column"> <div className="d-flex flex-column">
<div className="udapp_recorderDescription mt-2"> <div className="udapp_recorderDescription mt-2">
All transactions (deployed contracts and function executions) in this environment can be saved and replayed in All transactions (deployed contracts and function executions) in this environment can be saved and replayed in

@ -0,0 +1,22 @@
// eslint-disable-next-line no-use-before-define
import React from 'react'
interface ScenarioProps {
message: string,
setScenarioPath: (path: string) => void,
defaultValue?: string
}
export function ScenarioPrompt (props: ScenarioProps) {
const handleScenarioPath = (e) => {
props.setScenarioPath(e.target.value)
}
return (
<div> { props.message }
<div>
<input id="prompt_text" type="text" name='prompt_text' className="form-control" style={{ width: '100%' }} onInput={handleScenarioPath} data-id='modalDialogCustomPromptText' defaultValue={props.defaultValue} />
</div>
</div>
)
}

@ -8,32 +8,8 @@ import { GasPriceUI } from './gasPrice'
import { ValueUI } from './value' import { ValueUI } from './value'
export function SettingsUI (props: SettingsProps) { export function SettingsUI (props: SettingsProps) {
// constructor () {
// this.blockchain = blockchain
// this.event = new EventManager()
// this._components = {}
// this._components = {
// registry: globalRegistry,
// networkModule: networkModule
// }
// this._components.registry = globalRegistry
// this._deps = {
// config: this._components.registry.get('config').api
// }
// this._deps.config.events.on('settings/personal-mode_changed', this.onPersonalChange.bind(this)) // this._deps.config.events.on('settings/personal-mode_changed', this.onPersonalChange.bind(this))
// /**
// * generate a value used by the env dropdown list.
// * @return {String} - can return 'vm-berlin, 'vm-london', 'injected' or 'web3'
// */
// _getProviderDropdownValue () {
// const provider = this.blockchain.getProvider()
// const fork = this.blockchain.getCurrentFork()
// return provider === 'vm' ? provider + '-' + fork : provider
// }
return ( return (
<div className="udapp_settings"> <div className="udapp_settings">
<EnvironmentUI setWeb3Endpoint={props.setWeb3Endpoint} selectedEnv={props.selectExEnv} providers={props.providers} setExecutionContext={props.setExecutionContext} externalEndpoint={props.externalEndpoint} /> <EnvironmentUI setWeb3Endpoint={props.setWeb3Endpoint} selectedEnv={props.selectExEnv} providers={props.providers} setExecutionContext={props.setExecutionContext} externalEndpoint={props.externalEndpoint} />

@ -18,16 +18,18 @@ export function UniversalDappUI (props: UdappProps) {
const [expandPath, setExpandPath] = useState<string[]>([]) const [expandPath, setExpandPath] = useState<string[]>([])
const [llIError, setLlIError] = useState<string>('') const [llIError, setLlIError] = useState<string>('')
const [calldataValue, setCalldataValue] = useState<string>('') const [calldataValue, setCalldataValue] = useState<string>('')
const [inputs, setInputs] = useState<string>(null)
const [evmBC, setEvmBC] = useState(null)
useEffect(() => { useEffect(() => {
if (!props.abi) { if (!props.instance.abi) {
const abi = txHelper.sortAbiFunction(props.instance.contractData.abi) const abi = txHelper.sortAbiFunction(props.instance.contractData.abi)
setContractABI(abi) setContractABI(abi)
} else { } else {
setContractABI(props.abi) setContractABI(props.instance.abi)
} }
}, [props.abi]) }, [props.instance.abi])
useEffect(() => { useEffect(() => {
if (props.instance.address) { if (props.instance.address) {
@ -39,6 +41,13 @@ export function UniversalDappUI (props: UdappProps) {
} }
}, [props.instance.address]) }, [props.instance.address])
useEffect(() => {
if (props.instance.contractData) {
setInputs(props.instance.contractData.getConstructorInputs())
setEvmBC(props.instance.contractData.bytecodeObject)
}
}, [props.instance.contractData])
const sendData = () => { const sendData = () => {
setLlIError('') setLlIError('')
const fallback = txHelper.getFallbackInterface(contractABI) const fallback = txHelper.getFallbackInterface(contractABI)
@ -227,14 +236,22 @@ export function UniversalDappUI (props: UdappProps) {
const isConstant = funcABI.constant !== undefined ? funcABI.constant : false const isConstant = funcABI.constant !== undefined ? funcABI.constant : false
const lookupOnly = funcABI.stateMutability === 'view' || funcABI.stateMutability === 'pure' || isConstant const lookupOnly = funcABI.stateMutability === 'view' || funcABI.stateMutability === 'pure' || isConstant
return <ContractGUI funcABI={funcABI} clickCallBack={(valArray: { name: string, type: string }[], inputsValues: string) => runTransaction(lookupOnly, funcABI, valArray, inputsValues)} inputs={props.instance.contractData.getConstructorInputs()} evmBC={props.instance.contractData.bytecodeObject} lookupOnly={lookupOnly} /> return <ContractGUI
funcABI={funcABI}
clickCallBack={(valArray: { name: string, type: string }[], inputsValues: string) => {
runTransaction(lookupOnly, funcABI, valArray, inputsValues)
}}
inputs={inputs}
evmBC={evmBC}
lookupOnly={lookupOnly}
/>
}) })
} }
<div className="udapp_value"> <div className="udapp_value">
<TreeView id="treeView"> <TreeView id="treeView">
{ {
Object.keys(props.decodedResponse).map((innerkey) => { Object.keys(props.instance.decodedResponse || {}).map((innerkey) => {
return renderData(props.decodedResponse[innerkey], props.decodedResponse, innerkey, innerkey) return renderData(props.instance.decodedResponse[innerkey], props.instance.decodedResponse, innerkey, innerkey)
}) })
} }
</TreeView> </TreeView>

@ -66,12 +66,17 @@ export interface RunTabState {
gasPrice: string, gasPrice: string,
instances: { instances: {
instanceList: { instanceList: {
contractData: ContractData, contractData?: ContractData,
address: string, address: string,
name: string, name: string,
decodedResponse?: any decodedResponse?: any,
abi?: any
}[], }[],
error: string error: string
},
recorder: {
pathToScenario: string,
transactionCount: number
} }
} }
@ -153,6 +158,10 @@ export const runTabInitialState: RunTabState = {
instances: { instances: {
instanceList: [], instanceList: [],
error: null error: null
},
recorder: {
pathToScenario: 'scenario.json',
transactionCount: 0
} }
} }
@ -534,7 +543,7 @@ export const runTabReducer = (state: RunTabState = runTabInitialState, action: A
} }
case 'ADD_INSTANCE': { case 'ADD_INSTANCE': {
const payload: { contractData: ContractData, address: string, name: string } = action.payload const payload: { contractData: ContractData, address: string, name: string, abi?: any, decodedResponse?: any } = action.payload
return { return {
...state, ...state,
@ -552,7 +561,7 @@ export const runTabReducer = (state: RunTabState = runTabInitialState, action: A
...state, ...state,
instances: { instances: {
...state.instances, ...state.instances,
instanceList: state.instances.instanceList.filter((instance, index) => index !== payload) instanceList: state.instances.instanceList.filter((_, index) => index !== payload)
} }
} }
} }
@ -582,6 +591,40 @@ export const runTabReducer = (state: RunTabState = runTabInitialState, action: A
} }
} }
case 'SET_PATH_TO_SCENARIO': {
const payload: string = action.payload
return {
...state,
recorder: {
...state.recorder,
pathToScenario: payload
}
}
}
case 'SET_RECORDER_COUNT': {
const payload: number = action.payload
return {
...state,
recorder: {
...state.recorder,
transactionCount: payload
}
}
}
case 'CLEAR_RECORDER_COUNT': {
return {
...state,
recorder: {
...state.recorder,
transactionCount: 0
}
}
}
default: default:
return state return state
} }

@ -22,12 +22,15 @@ import {
updateMaxFee, updateMaxPriorityFee, updateMaxFee, updateMaxPriorityFee,
updateTxFeeContent, clearInstances, updateTxFeeContent, clearInstances,
removeInstance, getContext, removeInstance, getContext,
runTransactions, loadAddress runTransactions, loadAddress,
storeScenario, runCurrentScenario,
updateScenarioPath
} from './actions' } from './actions'
import './css/run-tab.css' import './css/run-tab.css'
import { PublishToStorage } from '@remix-ui/publish-to-storage' import { PublishToStorage } from '@remix-ui/publish-to-storage'
import { PassphrasePrompt } from './components/passphrase' import { PassphrasePrompt } from './components/passphrase'
import { MainnetPrompt } from './components/mainnet' import { MainnetPrompt } from './components/mainnet'
import { ScenarioPrompt } from './components/scenario'
export function RunTabUI (props: RunTabProps) { export function RunTabUI (props: RunTabProps) {
const { plugin } = props const { plugin } = props
@ -161,6 +164,10 @@ export function RunTabUI (props: RunTabProps) {
return <PassphrasePrompt message={message} setPassphrase={setPassphrasePrompt} defaultValue={runTab.passphrase} /> return <PassphrasePrompt message={message} setPassphrase={setPassphrasePrompt} defaultValue={runTab.passphrase} />
} }
const scenarioPrompt = (message: string) => {
return <ScenarioPrompt message={message} setScenarioPath={updateScenarioPath} defaultValue={runTab.recorder.pathToScenario} />
}
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) => { 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) => {
return <MainnetPrompt return <MainnetPrompt
init={determineGasPrice} init={determineGasPrice}
@ -227,7 +234,16 @@ export function RunTabUI (props: RunTabProps) {
tooltip={toast} tooltip={toast}
loadAddress={loadAddress} loadAddress={loadAddress}
/> />
<RecorderUI /> <RecorderUI
gasEstimationPrompt={gasEstimationPrompt}
logBuilder={logBuilder}
passphrasePrompt={passphrasePrompt}
mainnetPrompt={mainnetPrompt}
storeScenario={storeScenario}
runCurrentScenario={runCurrentScenario}
scenarioPrompt={scenarioPrompt}
count={runTab.recorder.transactionCount}
/>
<InstanceContainerUI <InstanceContainerUI
instances={runTab.instances} instances={runTab.instances}
clearInstances={clearInstances} clearInstances={clearInstances}

@ -1,4 +1,3 @@
import { type } from 'os'
import { RunTab } from './run-tab' import { RunTab } from './run-tab'
export interface RunTabProps { export interface RunTabProps {
plugin: RunTab plugin: RunTab
@ -186,16 +185,24 @@ export interface ContractDropdownProps {
} }
export interface RecorderProps { export interface RecorderProps {
storeScenario: (prompt: (msg: string) => JSX.Element) => void,
runCurrentScenario: (gasEstimationPrompt: (msg: string) => JSX.Element, passphrasePrompt: (msg: string) => JSX.Element, confirmDialogContent: MainnetPrompt, logBuilder: (msg: string) => JSX.Element) => void,
logBuilder: (msg: string) => JSX.Element,
mainnetPrompt: MainnetPrompt,
gasEstimationPrompt: (msg: string) => JSX.Element,
passphrasePrompt: (msg: string) => JSX.Element,
scenarioPrompt: (msg: string) => JSX.Element,
count: number
} }
export interface InstanceContainerProps { export interface InstanceContainerProps {
instances: { instances: {
instanceList: { instanceList: {
contractData: ContractData, contractData?: ContractData,
address: string, address: string,
name: string, name: string,
decodedResponse?: any decodedResponse?: any,
abi?: any
}[], }[],
error: string error: string
}, },
@ -263,12 +270,13 @@ export interface MainnetProps {
export interface UdappProps { export interface UdappProps {
instance: { instance: {
contractData: ContractData, contractData?: ContractData,
address: string, address: string,
name: string name: string,
decodedResponse?: any,
abi?: any
}, },
context: 'memory' | 'blockchain', context: 'memory' | 'blockchain',
abi?: FuncABI[],
removeInstance: (index: number) => void, removeInstance: (index: number) => void,
index: number, index: number,
gasEstimationPrompt: (msg: string) => JSX.Element, gasEstimationPrompt: (msg: string) => JSX.Element,
@ -288,6 +296,5 @@ export interface UdappProps {
mainnetPrompt: MainnetPrompt, mainnetPrompt: MainnetPrompt,
gasEstimationPrompt: (msg: string) => JSX.Element, gasEstimationPrompt: (msg: string) => JSX.Element,
passphrasePrompt: (msg: string) => JSX.Element) => void, passphrasePrompt: (msg: string) => JSX.Element) => void,
decodedResponse: any,
sendValue: string sendValue: string
} }

@ -0,0 +1,15 @@
export class Recorder {
constructor(blockchain: Blockchain);
event: any;
data: { _listen: boolean, _replay: boolean, journal: any[], _createdContracts: any, _createdContractsReverse: any, _usedAccounts: any, _abis: any, _contractABIReferences: any, _linkReferences: any };
setListen: (listen) => void;
extractTimestamp: (value) => any;
resolveAddress: (record, accounts, options) => any;
append: (timestamp, record) => any;
getAll: () => void;
clearAll: () => void;
run: (records, accounts, options, abis, linkReferences, confirmationCb, continueCb, promptCb, alertCb, logCallBack, newContractFn) => void
runScenario: (json, continueCb, promptCb, alertCb, confirmationCb, logCallBack, cb) => void
}
import { Blockchain } from "./blockchain";

@ -34,8 +34,10 @@ export class RunTab extends ViewPlugin {
udappUI: any; udappUI: any;
renderComponent(): void; renderComponent(): void;
onReady(api: any): void; onReady(api: any): void;
recorder: Recorder;
} }
import { ViewPlugin } from "@remixproject/engine-web/lib/view"; import { ViewPlugin } from "@remixproject/engine-web/lib/view";
import { Blockchain } from "./blockchain"; import { Blockchain } from "./blockchain";
import { RunTabState } from "../reducers/runTab"; import { RunTabState } from "../reducers/runTab";
import { Recorder } from "./recorder";

Loading…
Cancel
Save