Merge pull request #2260 from ethereum/proxy

Support Proxy
pull/2566/head
David Disu 3 years ago committed by GitHub
commit e5a113bdb0
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 4
      .eslintrc.json
  2. 16
      apps/remix-ide-e2e/src/commands/createContract.ts
  3. 10
      apps/remix-ide-e2e/src/tests/debugger.test.ts
  4. 8
      apps/remix-ide-e2e/src/tests/libraryDeployment.test.ts
  5. 6
      apps/remix-ide-e2e/src/tests/recorder.test.ts
  6. 2
      apps/remix-ide-e2e/src/tests/signingMessage.test.ts
  7. 12
      apps/remix-ide-e2e/src/tests/specialFunctions.test.ts
  8. 2
      apps/remix-ide-e2e/src/tests/terminal.test.ts
  9. 4
      apps/remix-ide-e2e/src/tests/transactionExecution.test.ts
  10. 2
      apps/remix-ide-e2e/src/types/index.d.ts
  11. 10
      apps/remix-ide/src/app/udapp/run-tab.js
  12. 152
      apps/remix-ide/src/blockchain/blockchain.js
  13. 91
      libs/remix-core-plugin/src/lib/constants/uups.ts
  14. 61
      libs/remix-core-plugin/src/lib/openzeppelin-proxy.ts
  15. 135
      libs/remix-core-plugin/src/types/contract.ts
  16. 56
      libs/remix-lib/src/execution/txFormat.ts
  17. 10
      libs/remix-lib/test/txFormat.ts
  18. 10
      libs/remix-ui/helper/src/lib/helper-components.tsx
  19. 135
      libs/remix-ui/run-tab/src/lib/actions/account.ts
  20. 96
      libs/remix-ui/run-tab/src/lib/actions/actions.ts
  21. 300
      libs/remix-ui/run-tab/src/lib/actions/deploy.ts
  22. 153
      libs/remix-ui/run-tab/src/lib/actions/events.ts
  23. 746
      libs/remix-ui/run-tab/src/lib/actions/index.ts
  24. 111
      libs/remix-ui/run-tab/src/lib/actions/payload.ts
  25. 72
      libs/remix-ui/run-tab/src/lib/actions/recorder.ts
  26. 48
      libs/remix-ui/run-tab/src/lib/components/contractDropdownUI.tsx
  27. 139
      libs/remix-ui/run-tab/src/lib/components/contractGUI.tsx
  28. 33
      libs/remix-ui/run-tab/src/lib/components/deployButton.tsx
  29. 20
      libs/remix-ui/run-tab/src/lib/components/deployInput.tsx
  30. 1
      libs/remix-ui/run-tab/src/lib/components/instanceContainerUI.tsx
  31. 1
      libs/remix-ui/run-tab/src/lib/components/mainnet.tsx
  32. 30
      libs/remix-ui/run-tab/src/lib/components/multiDeployInput.tsx
  33. 2
      libs/remix-ui/run-tab/src/lib/components/recorderCardUI.tsx
  34. 9
      libs/remix-ui/run-tab/src/lib/components/universalDappUI.tsx
  35. 47
      libs/remix-ui/run-tab/src/lib/constants/index.ts
  36. 19
      libs/remix-ui/run-tab/src/lib/css/run-tab.css
  37. 148
      libs/remix-ui/run-tab/src/lib/reducers/runTab.ts
  38. 103
      libs/remix-ui/run-tab/src/lib/run-tab.tsx
  39. 105
      libs/remix-ui/run-tab/src/lib/types/index.ts
  40. 48113
      package-lock.json
  41. 1
      package.json

@ -29,7 +29,9 @@
"@typescript-eslint/no-this-alias": "off",
"@typescript-eslint/no-empty-function": "off",
"eslint-disable-next-line no-empty": "off",
"no-empty": "off"
"no-empty": "off",
"jsx-a11y/anchor-is-valid": "off",
"@typescript-eslint/no-inferrable-types": "off"
}
},
{

@ -2,7 +2,7 @@ import { NightwatchBrowser } from 'nightwatch'
import EventEmitter from 'events'
class CreateContract extends EventEmitter {
command (this: NightwatchBrowser, inputParams: string): NightwatchBrowser {
command (this: NightwatchBrowser, inputParams: string[]): NightwatchBrowser {
this.api.perform((done) => {
createContract(this.api, inputParams, () => {
done()
@ -13,11 +13,19 @@ class CreateContract extends EventEmitter {
}
}
function createContract (browser: NightwatchBrowser, inputParams: string, callback: VoidFunction) {
if (inputParams) {
browser.setValue('.udapp_contractActionsContainerSingle > input', inputParams, function () {
function createContract (browser: NightwatchBrowser, inputParams: string[], callback: VoidFunction) {
if (inputParams.length === 1) {
browser.setValue('.udapp_contractActionsContainerSingle > input', inputParams[0], function () {
browser.click('.udapp_contractActionsContainerSingle > button').pause(500).perform(function () { callback() })
})
} else if (inputParams.length > 1) {
browser.perform((done) => {
for (let i = 0; i < inputParams.length; i++) {
browser.setValue(`div.udapp_multiArg:nth-child(${i + 1}) > input`, inputParams[i])
}
done()
})
.click('div.udapp_multiArg > button').pause(500).perform(function () { callback() })
} else {
browser
.click('.udapp_contractActionsContainerSingle > button')

@ -86,7 +86,7 @@ module.exports = {
.clickLaunchIcon('udapp')
.waitForElementPresent('*[title="Deploy - transact (not payable)"]', 35000)
.selectContract('ERC20')
.createContract('"tokenName", "symbol"')
.createContract(["tokenName", "symbol"])
.debugTransaction(0)
.pause(2000)
.waitForElementVisible('#stepdetail')
@ -115,7 +115,7 @@ module.exports = {
.testContracts('withABIEncoderV2.sol', sources[2]['withABIEncoderV2.sol'], ['test'])
.clickLaunchIcon('udapp')
.selectContract('test')
.createContract('')
.createContract([])
.clearConsole()
.clickInstance(0)
.clickFunction('test1 - transact (not payable)', { types: 'bytes userData', values: '0x000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000015b38da6a701c568545dcfcb03fcb875f56beddc4' })
@ -148,7 +148,7 @@ module.exports = {
.testContracts('locals.sol', sources[3]['locals.sol'], ['testLocals'])
.clickLaunchIcon('udapp')
.waitForElementPresent('*[title="Deploy - transact (not payable)"]', 40000)
.createContract('')
.createContract([])
.pause(2000)
.clearConsole()
.clickInstance(0)
@ -173,7 +173,7 @@ module.exports = {
.pause(2000)
.testContracts('withGeneratedSources.sol', sources[4]['withGeneratedSources.sol'], ['A'])
.clickLaunchIcon('udapp')
.createContract('')
.createContract([])
.clearConsole()
.clickInstance(0)
.clickFunction('f - transact (not payable)', { types: 'uint256[] ', values: '[]' })
@ -235,7 +235,7 @@ module.exports = {
.testContracts('reverted.sol', sources[6]['reverted.sol'], ['A', 'B', 'C'])
.clickLaunchIcon('udapp')
.selectContract('A')
.createContract('')
.createContract([])
.pause(500)
.clickInstance(0)
.clickFunction('callA - transact (not payable)')

@ -22,7 +22,7 @@ module.exports = {
browser.verifyContracts(['test'])
.clickLaunchIcon('udapp')
.selectContract('test')
.createContract('')
.createContract([])
.getAddressAtPosition(0, (address) => {
console.log('testAutoDeployLib ' + address)
addressRef = address
@ -46,7 +46,7 @@ module.exports = {
.verifyContracts(['test'])
.clickLaunchIcon('udapp')
.selectContract('lib') // deploy lib
.createContract('')
.createContract([])
.perform((done) => {
browser.getAddressAtPosition(0, (address) => {
console.log(address)
@ -74,7 +74,7 @@ function checkDeployShouldFail (browser: NightwatchBrowser, callback: VoidFuncti
.openFile('Untitled5.sol')
.clickLaunchIcon('udapp')
.selectContract('test') // deploy lib
.createContract('')
.createContract([])
.pause(2000)
.getText('div[class^="terminal"]', (value) => {
console.log('value: ', value)
@ -98,7 +98,7 @@ function checkDeployShouldSucceed (browser: NightwatchBrowser, address: string,
.openFile('Untitled5.sol')
.clickLaunchIcon('udapp')
.selectContract('test') // deploy lib
.createContract('')
.createContract([])
.getAddressAtPosition(1, (address) => {
addressRef = address
})

@ -39,7 +39,7 @@ module.exports = {
'Save scenario': function (browser: NightwatchBrowser) {
browser.testContracts('testRecorder.sol', sources[0]['testRecorder.sol'], ['testRecorder'])
.clickLaunchIcon('udapp')
.createContract('12')
.createContract(['12'])
.clickInstance(0)
.clickFunction('set - transact (not payable)', { types: 'uint256 _p', values: '34' })
.click('i.savetransaction')
@ -72,11 +72,11 @@ module.exports = {
.clickLaunchIcon('udapp')
.selectContract('t1est')
.pause(1000)
.createContract('')
.createContract([])
.clickInstance(0)
.selectContract('t2est')
.pause(1000)
.createContract('')
.createContract([])
.click('i.savetransaction')
.waitForElementVisible('[data-id="udappNotify-modal-footer-ok-react"]')
.execute(function () {

@ -33,7 +33,7 @@ module.exports = {
.clickLaunchIcon('udapp')
.pause(5000)
.selectContract('ECVerify')
.createContract('')
.createContract([])
.clickInstance(0)
.perform((done) => {
browser.getAddressAtPosition(0, (address) => {

@ -24,7 +24,7 @@ module.exports = {
.clickLaunchIcon('udapp')
.selectAccount('0xCA35b7d915458EF540aDe6068dFe2F44E8fa733c') // this account will be used for this test suite
.selectContract('CheckSpecials')
.createContract('') // deploy
.createContract([]) // deploy
.clickInstance(0)
.perform((done) => {
browser.getAddressAtPosition(0, (address) => {
@ -92,7 +92,7 @@ module.exports = {
.testContracts('receiveOnly.sol', sources[1]['receiveOnly.sol'], ['CheckSpecials'])
.clickLaunchIcon('udapp')
.selectContract('CheckSpecials')
.createContract('')
.createContract([])
.clickInstance(0)
.perform((done) => {
browser.getAddressAtPosition(0, (address) => {
@ -122,7 +122,7 @@ module.exports = {
.testContracts('fallbackOnlyPayable.sol', sources[2]['fallbackOnlyPayable.sol'], ['CheckSpecials'])
.clickLaunchIcon('udapp')
.selectContract('CheckSpecials')
.createContract('')
.createContract([])
.clickInstance(0)
.perform((done) => {
browser.getAddressAtPosition(0, (address) => {
@ -153,7 +153,7 @@ module.exports = {
.testContracts('fallbackOnlyNotPayable.sol', sources[3]['fallbackOnlyNotPayable.sol'], ['CheckSpecials'])
.clickLaunchIcon('udapp')
.selectContract('CheckSpecials')
.createContract('')
.createContract([])
.clickInstance(0)
.perform((done) => {
browser.getAddressAtPosition(0, (address) => {
@ -174,7 +174,7 @@ module.exports = {
.clearValue('#value')
.setValue('#value', '0')
.pause(2000)
.createContract('')
.createContract([])
.pause(1000)
.clickInstance(0).pause(1000)
.perform((done) => {
@ -208,7 +208,7 @@ module.exports = {
.waitForElementVisible('#value')
.clearValue('#value')
.setValue('#value', '0').pause(2000)
.createContract('')
.createContract([])
.clickInstance(0)
.pause(1000)
.perform((done) => {

@ -172,7 +172,7 @@ module.exports = {
.clickLaunchIcon('udapp')
.click('*[data-id="deployAndRunClearInstances"]')
.selectContract('OwnerTest')
.createContract('')
.createContract([])
.pause(1000)
.journalChildIncludes('constructor', { shouldHaveOnlyOneOccurence: true })
.pause(5000)

@ -202,10 +202,10 @@ module.exports = {
.addFile('Storage.sol', sources[6]['Storage.sol'])
.addFile('Owner.sol', sources[6]['Owner.sol'])
.clickLaunchIcon('udapp')
.createContract('42, 24')
.createContract(['42', '24'])
.openFile('Storage.sol')
.clickLaunchIcon('udapp')
.createContract('102') // this creation will fail if the component hasn't been properly reset.
.createContract(['102']) // this creation will fail if the component hasn't been properly reset.
.clickInstance(1)
.clickFunction('store - transact (not payable)', { types: 'uint256 num', values: '24' })
.testFunction('last', // we check if the contract is actually reachable.

@ -34,7 +34,7 @@ declare module 'nightwatch' {
getModalBody(callback: (value: string, cb: VoidFunction) => void): NightwatchBrowser,
modalFooterCancelClick(id?: string): NightwatchBrowser,
selectContract(contractName: string): NightwatchBrowser,
createContract(inputParams: string): NightwatchBrowser,
createContract(inputParams: string[]): NightwatchBrowser,
getAddressAtPosition(index: number, cb: (pos: string) => void): NightwatchBrowser,
testConstantFunction(address: string, fnFullName: string, expectedInput: NightwatchTestConstantFunctionExpectedInput | null, expectedOutput: string): NightwatchBrowser,
getEditorValue(callback: (content: string) => void): NightwatchBrowser,

@ -1,6 +1,7 @@
import React from 'react' // eslint-disable-line
import { RunTabUI } from '@remix-ui/run-tab'
import { ViewPlugin } from '@remixproject/engine-web'
import { addressToString } from '@remix-ui/helper'
import * as packageJson from '../../../../../package.json'
const EventManager = require('../../lib/events')
@ -18,7 +19,7 @@ const profile = {
version: packageJson.version,
permission: true,
events: ['newTransaction'],
methods: ['createVMAccount', 'sendTransaction', 'getAccounts', 'pendingTransactionsCount', 'getSettings', 'setEnvironmentMode', 'clearAllInstances', 'addInstance']
methods: ['createVMAccount', 'sendTransaction', 'getAccounts', 'pendingTransactionsCount', 'getSettings', 'setEnvironmentMode', 'clearAllInstances', 'addInstance', 'resolveContractAndAddInstance']
}
export class RunTab extends ViewPlugin {
@ -163,4 +164,11 @@ export class RunTab extends ViewPlugin {
readFile (fileName) {
return this.call('fileManager', 'readFile', fileName)
}
resolveContractAndAddInstance (contractObject, address) {
const data = this.compilersArtefacts.getCompilerAbstract(contractObject.contract.file)
this.compilersArtefacts.addResolvedContract(addressToString(address), data)
this.addInstance(address, contractObject.abi, contractObject.name)
}
}

@ -3,7 +3,6 @@ import React from 'react' // eslint-disable-line
import Web3 from 'web3'
import { Plugin } from '@remixproject/engine'
import { toBuffer, addHexPrefix } from 'ethereumjs-util'
import { waterfall } from 'async'
import { EventEmitter } from 'events'
import { format } from 'util'
import { ExecutionContext } from './execution-context'
@ -12,6 +11,8 @@ import InjectedProvider from './providers/injected.js'
import NodeProvider from './providers/node.js'
import { execution, EventManager, helpers } from '@remix-project/remix-lib'
import { etherScanLink } from './helper'
import { logBuilder, confirmProxyMsg } from "@remix-ui/helper"
import { cancelProxyMsg } from '@remix-ui/helper'
const { txFormat, txExecution, typeConversion, txListener: Txlistener, TxRunner, TxRunnerWeb3, txHelper } = execution
const { txResultHelper: resultToRemixTx } = helpers
const packageJson = require('../../../../package.json')
@ -113,7 +114,9 @@ export class Blockchain extends Plugin {
const { continueCb, promptCb, statusCb, finalCb } = callbacks
const constructor = selectedContract.getConstructorInterface()
txFormat.buildData(selectedContract.name, selectedContract.object, compilerContracts, true, constructor, args, (error, data) => {
if (error) return statusCb(`creation of ${selectedContract.name} errored: ${error.message ? error.message : error}`)
if (error) {
return statusCb(`creation of ${selectedContract.name} errored: ${error.message ? error.message : error}`)
}
statusCb(`creation of ${selectedContract.name} pending...`)
this.createContract(selectedContract, data, continueCb, promptCb, confirmationCb, finalCb)
@ -127,13 +130,73 @@ export class Blockchain extends Plugin {
const { continueCb, promptCb, statusCb, finalCb } = callbacks
const constructor = selectedContract.getConstructorInterface()
txFormat.encodeConstructorCallAndLinkLibraries(selectedContract.object, args, constructor, contractMetadata.linkReferences, selectedContract.bytecodeLinkReferences, (error, data) => {
if (error) return statusCb(`creation of ${selectedContract.name} errored: ${error.message ? error.message : error}`)
if (error) {
return statusCb(`creation of ${selectedContract.name} errored: ${error.message ? error.message : error}`)
}
statusCb(`creation of ${selectedContract.name} pending...`)
this.createContract(selectedContract, data, continueCb, promptCb, confirmationCb, finalCb)
})
}
async deployProxy (proxyData, implementationContractObject) {
const proxyModal = {
id: 'confirmProxyDeployment',
title: 'ERC1967',
message: `Confirm you want to deploy an ERC1967 proxy contract that is connected to your implementation.
For more info on ERC1967, see https://docs.openzeppelin.com/contracts/4.x/api/proxy#ERC1967Proxy`,
modalType: 'modal',
okLabel: 'OK',
cancelLabel: 'Cancel',
okFn: () => {
this.runProxyTx(proxyData, implementationContractObject)
},
cancelFn: () => {
this.call('notification', 'toast', cancelProxyMsg())
},
hideFn: () => null
}
this.call('notification', 'modal', proxyModal)
}
async runProxyTx (proxyData, implementationContractObject) {
const args = { useCall: false, data: proxyData }
const confirmationCb = (network, tx, gasEstimation, continueTxExecution, cancelCb) => {
// continue using original authorization given by user
continueTxExecution(null)
}
const continueCb = (error, continueTxExecution, cancelCb) => { continueTxExecution() }
const promptCb = (okCb, cancelCb) => { okCb() }
const finalCb = (error, txResult, address, returnValue) => {
if (error) {
const log = logBuilder(error)
return this.call('terminal', 'logHtml', log)
}
return this.call('udapp', 'resolveContractAndAddInstance', implementationContractObject, address)
}
this.runTx(args, confirmationCb, continueCb, promptCb, finalCb)
}
async getEncodedFunctionHex (args, funABI) {
return new Promise((resolve, reject) => {
txFormat.encodeFunctionCall(args, funABI, (error, data) => {
if (error) return reject(error)
resolve(data.dataHex)
})
})
}
async getEncodedParams (args, funABI) {
return new Promise((resolve, reject) => {
txFormat.encodeParams(args, funABI, (error, encodedParams) => {
if (error) return reject(error)
return resolve(encodedParams.dataHex)
})
})
}
createContract (selectedContract, data, continueCb, promptCb, confirmationCb, finalCb) {
if (data) {
data.contractName = selectedContract.name
@ -433,55 +496,71 @@ export class Blockchain extends Plugin {
})
}
runTx (args, confirmationCb, continueCb, promptCb, cb) {
waterfall([
(next) => { // getGasLimit
async runTx (args, confirmationCb, continueCb, promptCb, cb) {
const getGasLimit = () => {
return new Promise((resolve, reject) => {
if (this.transactionContextAPI.getGasLimit) {
return this.transactionContextAPI.getGasLimit(next)
return this.transactionContextAPI.getGasLimit((err, value) => {
if (err) return reject(err)
return resolve(value)
})
}
next(null, 3000000)
},
(gasLimit, next) => { // queryValue
return resolve(3000000)
})
}
const queryValue = () => {
return new Promise((resolve, reject) => {
if (args.value) {
return next(null, args.value, gasLimit)
return resolve(args.value)
}
if (args.useCall || !this.transactionContextAPI.getValue) {
return next(null, 0, gasLimit)
return resolve(0)
}
this.transactionContextAPI.getValue(function (err, value) {
next(err, value, gasLimit)
this.transactionContextAPI.getValue((err, value) => {
if (err) return reject(err)
return resolve(value)
})
},
(value, gasLimit, next) => { // getAccount
})
}
const getAccount = () => {
return new Promise((resolve, reject) => {
if (args.from) {
return next(null, args.from, value, gasLimit)
return resolve(args.from)
}
if (this.transactionContextAPI.getAddress) {
return this.transactionContextAPI.getAddress(function (err, address) {
next(err, address, value, gasLimit)
if (err) return reject(err)
return resolve(address)
})
}
this.getAccounts(function (err, accounts) {
if (err) return reject(err)
const address = accounts[0]
if (err) return next(err)
if (!address) return next('No accounts available')
if (!address) return reject('No accounts available')
if (this.executionContext.isVM() && !this.providers.vm.RemixSimulatorProvider.Accounts.accounts[address]) {
return next('Invalid account selected')
return reject('Invalid account selected')
}
next(null, address, value, gasLimit)
return resolve(address)
})
},
(fromAddress, value, gasLimit, next) => { // runTransaction
})
}
const runTransaction = async () => {
// eslint-disable-next-line no-async-promise-executor
return new Promise(async (resolve, reject) => {
const fromAddress = await getAccount()
const value = await queryValue()
const gasLimit = await getGasLimit()
const tx = { to: args.to, data: args.data.dataHex, useCall: args.useCall, from: fromAddress, value: value, gasLimit: gasLimit, timestamp: args.data.timestamp }
const payLoad = { funAbi: args.data.funAbi, funArgs: args.data.funArgs, contractBytecode: args.data.contractBytecode, contractName: args.data.contractName, contractABI: args.data.contractABI, linkReferences: args.data.linkReferences }
if (!tx.timestamp) tx.timestamp = Date.now()
if (!tx.timestamp) tx.timestamp = Date.now()
const timestamp = tx.timestamp
this.event.trigger('initiatingTransaction', [timestamp, tx, payLoad])
this.txRunner.rawRun(tx, confirmationCb, continueCb, promptCb,
async (error, result) => {
if (error) return next(error)
if (error) return reject(error)
const isVM = this.executionContext.isVM()
if (isVM && tx.useCall) {
@ -500,16 +579,15 @@ export class Blockchain extends Plugin {
try { error = 'error: ' + JSON.stringify(error) } catch (e) { console.log(e) }
}
}
next(error, result, tx)
return resolve({ result, tx })
}
)
})
}
],
async (error, txResult, tx) => {
if (error) {
return cb(error)
}
try {
const transaction = await runTransaction()
const txResult = transaction.result
const tx = transaction.tx
/*
value of txResult is inconsistent:
- transact to contract:
@ -517,12 +595,12 @@ export class Blockchain extends Plugin {
- call to contract:
{"result":"0x0000000000000000000000000000000000000000000000000000000000000000","transactionHash":"0x5236a76152054a8aad0c7135bcc151f03bccb773be88fbf4823184e47fc76247"}
*/
const isVM = this.executionContext.isVM()
let execResult
let returnValue = null
if (isVM) {
const hhlogs = await this.web3().eth.getHHLogsForTx(txResult.transactionHash)
if (hhlogs && hhlogs.length) {
let finalLogs = '<b>console.log:</b>\n'
for (const log of hhlogs) {
@ -562,7 +640,9 @@ export class Blockchain extends Plugin {
address = txResult.receipt.contractAddress
}
cb(error, txResult, address, returnValue)
})
cb(null, txResult, address, returnValue)
} catch (error) {
cb(error)
}
}
}

File diff suppressed because one or more lines are too long

@ -1,26 +1,77 @@
import { Plugin } from '@remixproject/engine';
import { ContractData } from '../types/contract';
import { ContractABI, ContractAST, DeployOption } from '../types/contract';
import { UUPS, UUPSABI, UUPSBytecode, UUPSfunAbi } from './constants/uups';
const proxyProfile = {
name: 'openzeppelin-proxy',
displayName: 'openzeppelin-proxy',
description: 'openzeppelin-proxy',
methods: ['isConcerned', 'execute']
methods: ['isConcerned', 'execute', 'getDeployOptions']
};
export class OpenZeppelinProxy extends Plugin {
blockchain: any
kind: 'UUPS' | 'Transparent'
constructor(blockchain) {
super(proxyProfile)
this.blockchain = blockchain
}
async isConcerned(contractData: ContractData): Promise<boolean> {
async isConcerned(ast: ContractAST = {} as ContractAST): Promise<boolean> {
// check in the AST if it's an upgradable contract
if (ast.nodes && ast.nodes.find(node => node.absolutePath && node.absolutePath.includes(UUPS))) {
this.kind = 'UUPS'
return true
}
//
// else if transparent contract run check true/false
//
return false
}
async execute(contractData: ContractData, contractMetadata: any, compiledContracts: any) {
async getDeployOptions (contracts: ContractABI): Promise<{ [name: string]: DeployOption }> {
const inputs = {}
if (this.kind === 'UUPS') {
Object.keys(contracts).map(name => {
const abi = contracts[name].abi
const initializeInput = abi.find(node => node.name === 'initialize')
if (initializeInput) {
inputs[name] = {
inputs: initializeInput,
initializeInputs: this.blockchain.getInputs(initializeInput)
}
}
})
}
return inputs
}
async execute(implAddress: string, args: string | string [] = '', initializeABI, implementationContractObject): Promise<void> {
// deploy the proxy, or use an existing one
if (!initializeABI) throw new Error('Cannot deploy proxy: Missing initialize ABI')
args = args === '' ? [] : args
const _data = await this.blockchain.getEncodedFunctionHex(args || [], initializeABI)
if (this.kind === 'UUPS') this.deployUUPSProxy(implAddress, _data, implementationContractObject)
}
async deployUUPSProxy (implAddress: string, _data: string, implementationContractObject): Promise<void> {
const args = [implAddress, _data]
const constructorData = await this.blockchain.getEncodedParams(args, UUPSfunAbi)
const proxyName = 'ERC1967Proxy'
const data = {
contractABI: UUPSABI,
contractByteCode: UUPSBytecode,
contractName: proxyName,
funAbi: UUPSfunAbi,
funArgs: args,
linkReferences: {},
dataHex: UUPSBytecode + constructorData.replace('0x', '')
}
// re-use implementation contract's ABI for UI display in udapp and change name to proxy name.
implementationContractObject.name = proxyName
this.blockchain.deployProxy(data, implementationContractObject)
}
}

@ -3,8 +3,8 @@ export interface FuncABI {
type: string,
inputs: { name: string, type: string }[],
stateMutability: string,
payable: boolean,
constant: any
payable?: boolean,
constant?: any
}
export interface ContractData {
@ -21,3 +21,134 @@ export interface ContractData {
isOverSizeLimit: () => boolean,
metadata: any
}
export interface ContractAST {
id: number,
absolutePath: string,
exportedSymbols: {
[key: string]: number[]
},
license: string,
nodeType: string,
src: string,
nodes: {
id: number,
literals: string[],
nodeType: string,
src: string,
absolutePath?: string,
file?: string,
nameLocation?: string,
scope?: number,
srcUnit?: number,
unitAlias?: string,
symbolAliases?: any[],
abstract?: boolean,
baseContracts?: any[],
contractDependencies?: any[],
contractKind?: string,
fullyImplemented?: boolean,
linearizedBaseContracts?: number[],
name?: string,
usedErrors?: any[]
}[]
}
export interface ContractABI {
[key: string]: {
abi: ({
inputs: never[];
stateMutability: string;
type: string;
anonymous?: undefined;
name?: undefined;
outputs?: undefined;
} | {
anonymous: boolean;
inputs: {
indexed: boolean;
internalType: string;
name: string;
type: string;
}[];
name: string;
type: string;
stateMutability?: undefined;
outputs?: undefined;
} | {
inputs: {
internalType: string;
name: string;
type: string;
}[];
name: string;
outputs: {
internalType: string;
name: string;
type: string;
}[];
stateMutability: string;
type: string;
anonymous?: undefined;
})[];
devdoc: {
kind: string;
methods: {
[key: string]: {
[key: string]: string
}
};
version: number;
};
evm: any
metadata: string;
storageLayout: {
storage: {
astId: number;
contract: string;
label: string;
offset: number;
slot: string;
type: string;
}[];
types: {
[key: string]: {
base: string;
encoding: string;
label: string;
numberOfBytes: string;
members?: {
astId: number;
contract: string;
label: string;
offset: number;
slot: string;
type: string;
}[];
};
};
};
userdoc: {
kind: string;
methods: any;
version: number;
};
};
}
export type DeployOption = {
initializeInputs: string,
inputs: {
inputs: {
internalType?: string,
name: string,
type: string
}[],
name: "initialize",
outputs?: any[],
stateMutability: string,
type: string,
payable?: boolean,
constant?: any
}
}

@ -38,27 +38,41 @@ export function encodeData (funABI, values, contractbyteCode) {
export function encodeParams (params, funAbi, callback) {
let data: Buffer | string = ''
let dataHex = ''
let funArgs
if (params.indexOf('raw:0x') === 0) {
let funArgs = []
if (Array.isArray(params)) {
funArgs = params
if (funArgs.length > 0) {
try {
data = encodeParamsHelper(funAbi, funArgs)
dataHex = data.toString()
} catch (e) {
return callback('Error encoding arguments: ' + e)
}
}
if (data.slice(0, 9) === 'undefined') {
dataHex = data.slice(9)
}
if (data.slice(0, 2) === '0x') {
dataHex = data.slice(2)
}
} else if (params.indexOf('raw:0x') === 0) {
// in that case we consider that the input is already encoded and *does not* contain the method signature
dataHex = params.replace('raw:0x', '')
data = Buffer.from(dataHex, 'hex')
} else {
try {
params = params.replace(/(^|,\s+|,)(\d+)(\s+,|,|$)/g, '$1"$2"$3') // replace non quoted number by quoted number
params = params.replace(/(^|,\s+|,)(0[xX][0-9a-fA-F]+)(\s+,|,|$)/g, '$1"$2"$3') // replace non quoted hex string by quoted hex string
funArgs = JSON.parse('[' + params + ']')
funArgs = parseFunctionParams(params)
} catch (e) {
return callback('Error encoding arguments: ' + e)
}
if (funArgs.length > 0) {
try {
if (funArgs.length > 0) {
data = encodeParamsHelper(funAbi, funArgs)
dataHex = data.toString()
}
} catch (e) {
return callback('Error encoding arguments: ' + e)
}
}
if (data.slice(0, 9) === 'undefined') {
dataHex = data.slice(9)
}
@ -396,7 +410,7 @@ export function decodeResponse (response, fnabi) {
}
export function parseFunctionParams (params) {
let args = []
const args = []
// Check if parameter string starts with array or string
let startIndex = isArrayOrStringStart(params, 0) ? -1 : 0
for (let i = 0; i < params.length; i++) {
@ -431,29 +445,27 @@ export function parseFunctionParams (params) {
if (bracketCount !== 0 && j === params.length - 1) {
throw new Error('invalid tuple params')
}
if (bracketCount === 0) break
}
// If bracketCount = 0, it means complete array/nested array parsed, push it to the arguments list
args.push(JSON.parse(params.substring(i, j)))
args.push(parseFunctionParams(params.substring(i + 1, j)))
i = j - 1
} else if (params.charAt(i) === ',') {
} else if (params.charAt(i) === ',' || i === params.length - 1) { // , or end of string
// if startIndex >= 0, it means a parameter was being parsed, it can be first or other parameter
if (startIndex >= 0) {
args.push(params.substring(startIndex, i))
let param = params.substring(startIndex, i === params.length - 1 ? undefined : i)
const trimmed = param.trim()
if (param.startsWith('0x')) param = `${param}`
if (/[0-9]/g.test(trimmed)) param = `${trimmed}`
if (typeof param === 'string') {
if (trimmed === 'true') param = true
if (trimmed === 'false') param = false
}
args.push(param)
}
// Register start index of a parameter to parse
startIndex = isArrayOrStringStart(params, i + 1) ? -1 : i + 1
} else if (startIndex >= 0 && i === params.length - 1) {
// If start index is registered and string is completed (To handle last parameter)
args.push(params.substring(startIndex, params.length))
}
}
args = args.map(e => {
if (!Array.isArray(e)) {
return e.trim()
} else {
return e
}
})
return args
}

@ -35,7 +35,7 @@ tape('ContractParameters - (TxFormat.buildData) - format input parameters', func
function testWithInput (st, params, expected) {
txFormat.buildData('uintContractTest', context.contract, context.output.contracts, true, context.contract.abi[0], params, (error, data) => {
if (error) { return st.fails(error) }
if (error) { return st.fail(error) }
console.log(data)
if (!data.dataHex.endsWith(expected)) {
st.fail(`result of buildData ${data.dataHex} should end with ${expected} . `)
@ -62,7 +62,7 @@ tape('ContractStringParameters - (TxFormat.buildData) - format string input para
function testWithStringInput (st, params, expected) {
txFormat.buildData('stringContractTest', context.contract, context.output.contracts, true, context.contract.abi[0], params, (error, data) => {
if (error) { return st.fails(error) }
if (error) { return st.fail(error) }
console.log(data)
if (!data.dataHex.endsWith(expected)) {
st.fail(`result of buildData ${data.dataHex} should end with ${expected} . `)
@ -88,7 +88,7 @@ tape('ContractArrayParameters - (TxFormat.buildData) - format array input parame
function testWithArrayInput (st, params, expected) {
txFormat.buildData('arrayContractTest', context.contract, context.output.contracts, true, context.contract.abi[0], params, (error, data) => {
if (error) { return st.fails(error) }
if (error) { return st.fail(error) }
console.log(data)
if (!data.dataHex.endsWith(expected)) {
st.fail(`result of buildData ${data.dataHex} should end with ${expected} . `)
@ -113,7 +113,7 @@ tape('ContractNestedArrayParameters - (TxFormat.buildData) - format nested array
function testWithNestedArrayInput (st, params, expected) {
txFormat.buildData('nestedArrayContractTest', context.contract, context.output.contracts, true, context.contract.abi[4], params, (error, data) => {
if (error) {
return st.fails(error)
return st.fail(error)
}
console.log(data)
if (!data.dataHex.endsWith(expected)) {
@ -185,7 +185,7 @@ function testLinkLibrary (st, fakeDeployedContracts, callbackDeployLibraries) {
const deployMsg = ['creation of library test.sol:lib1 pending...',
'creation of library test.sol:lib2_lib2_lib2_lib2_lib2_lib2_lib2_lib2_lib2_lib2_lib2_lib2_lib2_lib2_lib2_lib2_lib2_lib2_lib2_lib2_lib2_lib2_lib2_lib2_lib2_lib2_lib2 pending...']
txFormat.buildData('testContractLinkLibrary', context.contract, context.output.contracts, true, context.contract.abi[0], '', (error, data) => {
if (error) { return st.fails(error) }
if (error) { return st.fail(error) }
console.log(data)
const linkedbyteCode = data.dataHex
let libReference = context.contract.evm.bytecode.linkReferences['test.sol']['lib1']

@ -84,3 +84,13 @@ export const recursivePasteToastMsg = () => (
File(s) to paste is an ancestor of the destination folder
</div>
)
export const logBuilder = (msg: string) => {
return <pre>{msg}</pre>
}
export const cancelProxyMsg = () => (
<div>
<b>Proxy deployment cancelled.</b>
</div>
)

@ -0,0 +1,135 @@
import { shortenAddress, web3Dialog } from "@remix-ui/helper"
import { RunTab } from "../types/run-tab"
import { clearInstances, setAccount, setExecEnv } from "./actions"
import { displayNotification, displayPopUp, fetchAccountsListFailed, fetchAccountsListRequest, fetchAccountsListSuccess, setExternalEndpoint, setMatchPassphrase, setPassphrase } from "./payload"
export const updateAccountBalances = (plugin: RunTab, dispatch: React.Dispatch<any>) => {
const accounts = plugin.REACT_API.accounts.loadedAccounts
Object.keys(accounts).map((value) => {
plugin.blockchain.getBalanceInEther(value, (err, balance) => {
if (err) return
const updated = shortenAddress(value, balance)
accounts[value] = updated
})
})
dispatch(fetchAccountsListSuccess(accounts))
}
export const fillAccountsList = async (plugin: RunTab, dispatch: React.Dispatch<any>) => {
try {
dispatch(fetchAccountsListRequest())
const promise = plugin.blockchain.getAccounts()
promise.then(async (accounts: string[]) => {
const loadedAccounts = {}
if (!accounts) accounts = []
// allSettled is undefined..
// so the current promise (all) will finish when:
// - all the promises resolve
// - at least one reject
// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise/all
await (Promise as any).all(accounts.map((account) => {
return new Promise((resolve, reject) => {
plugin.blockchain.getBalanceInEther(account, (err, balance) => {
if (err) return reject(err)
const updated = shortenAddress(account, balance)
loadedAccounts[account] = updated
resolve(account)
})
})
}))
const provider = plugin.blockchain.getProvider()
if (provider === 'injected') {
const selectedAddress = plugin.blockchain.getInjectedWeb3Address()
if (!(Object.keys(loadedAccounts).includes(selectedAddress))) setAccount(dispatch, null)
}
dispatch(fetchAccountsListSuccess(loadedAccounts))
}).catch((e) => {
dispatch(fetchAccountsListFailed(e.message))
})
} catch (e) {
dispatch(displayPopUp(`Cannot get account list: ${e}`))
}
}
export const setFinalContext = (plugin: RunTab, dispatch: React.Dispatch<any>) => {
// set the final context. Cause it is possible that this is not the one we've originaly selected
const value = _getProviderDropdownValue(plugin)
setExecEnv(dispatch, value)
clearInstances(dispatch)
}
const _getProviderDropdownValue = (plugin: RunTab): string => {
const provider = plugin.blockchain.getProvider()
const fork = plugin.blockchain.getCurrentFork()
return provider === 'vm' ? provider + '-' + fork : provider
}
export const setExecutionContext = (plugin: RunTab, dispatch: React.Dispatch<any>, executionContext: { context: string, fork: string }) => {
const displayContent = web3Dialog(plugin.REACT_API.externalEndpoint, (endpoint: string) => {
dispatch(setExternalEndpoint(endpoint))
})
plugin.blockchain.changeExecutionContext(executionContext, () => {
plugin.call('notification', 'modal', {
id: 'envNotification',
title: 'External node request',
message: displayContent,
okLabel: 'OK',
cancelLabel: 'Cancel',
okFn: () => {
plugin.blockchain.setProviderFromEndpoint(plugin.REACT_API.externalEndpoint, executionContext, (alertMsg) => {
if (alertMsg) plugin.call('notification', 'toast', alertMsg)
setFinalContext(plugin, dispatch)
})
},
cancelFn: () => {
setFinalContext(plugin, dispatch)
}
})
}, (alertMsg) => {
plugin.call('notification', 'toast', alertMsg)
}, () => { setFinalContext(plugin, dispatch) })
}
export const createNewBlockchainAccount = async (plugin: RunTab, dispatch: React.Dispatch<any>, cbMessage: JSX.Element) => {
plugin.blockchain.newAccount(
'',
(cb) => {
dispatch(displayNotification('Enter Passphrase', cbMessage, 'OK', 'Cancel', async () => {
if (plugin.REACT_API.passphrase === plugin.REACT_API.matchPassphrase) {
cb(plugin.REACT_API.passphrase)
} else {
dispatch(displayNotification('Error', 'Passphase does not match', 'OK', null))
}
setPassphrase('')
setMatchPassphrase('')
}, () => {}))
},
async (error, address) => {
if (error) {
return dispatch(displayPopUp('Cannot create an account: ' + error))
}
dispatch(displayPopUp(`account ${address} created`))
await fillAccountsList(plugin, dispatch)
}
)
}
export const signMessageWithAddress = (plugin: RunTab, dispatch: React.Dispatch<any>, account: string, message: string, modalContent: (hash: string, data: string) => JSX.Element, passphrase?: string) => {
plugin.blockchain.signMessage(message, account, passphrase, (err, msgHash, signedData) => {
if (err) {
return displayPopUp(err)
}
dispatch(displayNotification('Signed Message', modalContent(msgHash, signedData), 'OK', null, () => {}, null))
})
}

@ -0,0 +1,96 @@
import { ContractData } from "@remix-project/core-plugin"
import { addNewInstance, addProvider, clearAllInstances, clearRecorderCount, hidePopUp, removeExistingInstance, removeProvider, setBaseFeePerGas, setConfirmSettings, setCurrentContract, setExecutionEnvironment, setExternalEndpoint, setGasLimit, setGasPrice, setGasPriceStatus, setMatchPassphrase, setMaxFee, setMaxPriorityFee, setNetworkName, setPassphrase, setPathToScenario, setSelectedAccount, setSendUnit, setSendValue, setTxFeeContent } from "./payload"
export const setAccount = (dispatch: React.Dispatch<any>, account: string) => {
dispatch(setSelectedAccount(account))
}
export const setUnit = (dispatch: React.Dispatch<any>, unit: 'ether' | 'finney' | 'gwei' | 'wei') => {
dispatch(setSendUnit(unit))
}
export const setGasFee = (dispatch: React.Dispatch<any>, value: number) => {
dispatch(setGasLimit(value))
}
export const setExecEnv = (dispatch: React.Dispatch<any>, env: string) => {
dispatch(setExecutionEnvironment(env))
}
export const setNetworkNameFromProvider = (dispatch: React.Dispatch<any>, networkName: string) => {
dispatch(setNetworkName(networkName))
}
export const addExternalProvider = (dispatch: React.Dispatch<any>, network) => {
dispatch(addProvider(network))
}
export const removeExternalProvider = (dispatch: React.Dispatch<any>, name) => {
dispatch(removeProvider(name))
}
export const clearPopUp = async (dispatch: React.Dispatch<any>) => {
dispatch(hidePopUp())
}
export const setPassphrasePrompt = (dispatch: React.Dispatch<any>, passphrase: string) => {
dispatch(setPassphrase(passphrase))
}
export const setMatchPassphrasePrompt = (dispatch: React.Dispatch<any>, passphrase: string) => {
dispatch(setMatchPassphrase(passphrase))
}
export const updateGasPriceStatus = (dispatch: React.Dispatch<any>, status: boolean) => {
dispatch(setGasPriceStatus(status))
}
export const updateConfirmSettings = (dispatch: React.Dispatch<any>, confirmation: boolean) => {
dispatch(setConfirmSettings(confirmation))
}
export const updateMaxFee = (dispatch: React.Dispatch<any>, fee: string) => {
dispatch(setMaxFee(fee))
}
export const updateMaxPriorityFee = (dispatch: React.Dispatch<any>, fee: string) => {
dispatch(setMaxPriorityFee(fee))
}
export const updateBaseFeePerGas = (dispatch: React.Dispatch<any>, baseFee: string) => {
dispatch(setBaseFeePerGas(baseFee))
}
export const updateGasPrice = (dispatch: React.Dispatch<any>, price: string) => {
dispatch(setGasPrice(price))
}
export const updateTxFeeContent = (dispatch: React.Dispatch<any>, content: string) => {
dispatch(setTxFeeContent(content))
}
export const addInstance = (dispatch: React.Dispatch<any>, instance: { contractData?: ContractData, address: string, name: string, abi?: any, decodedResponse?: Record<number, any> }) => {
instance.decodedResponse = {}
dispatch(addNewInstance(instance))
}
export const removeInstance = (dispatch: React.Dispatch<any>, index: number) => {
dispatch(removeExistingInstance(index))
}
export const clearInstances = (dispatch: React.Dispatch<any>) => {
dispatch(clearAllInstances())
dispatch(clearRecorderCount())
}
export const setSelectedContract = (dispatch: React.Dispatch<any>, contractName: string) => {
dispatch(setCurrentContract(contractName))
}
export const updateScenarioPath = (dispatch: React.Dispatch<any>, path: string) => {
dispatch(setPathToScenario(path))
}
export const setSendTransactionValue = (dispatch: React.Dispatch<any>, value: string) => {
dispatch(setSendValue(value))
}

@ -0,0 +1,300 @@
import { ContractData, FuncABI } from "@remix-project/core-plugin"
import { RunTab } from "../types/run-tab"
import { CompilerAbstract as CompilerAbstractType } from '@remix-project/remix-solidity-ts'
import * as remixLib from '@remix-project/remix-lib'
import { DeployMode, MainnetPrompt } from "../types"
import { displayNotification, displayPopUp, setDecodedResponse } from "./payload"
import { addInstance } from "./actions"
import { addressToString, logBuilder } from "@remix-ui/helper"
declare global {
interface Window {
_paq: any
}
}
const _paq = window._paq = window._paq || [] //eslint-disable-line
const txHelper = remixLib.execution.txHelper
const txFormat = remixLib.execution.txFormat
const loadContractFromAddress = (plugin: RunTab, address, confirmCb, cb) => {
if (/.(.abi)$/.exec(plugin.config.get('currentFile'))) {
confirmCb(() => {
let abi
try {
abi = JSON.parse(plugin.editor.currentContent())
} catch (e) {
return cb('Failed to parse the current file as JSON ABI.')
}
_paq.push(['trackEvent', 'udapp', 'AtAddressLoadWithABI'])
cb(null, 'abi', abi)
})
} else {
_paq.push(['trackEvent', 'udapp', 'AtAddressLoadWithArtifacts'])
cb(null, 'instance')
}
}
export const getSelectedContract = (contractName: string, compiler: CompilerAbstractType): ContractData => {
if (!contractName) return null
// const compiler = plugin.compilersArtefacts[compilerAtributeName]
if (!compiler) return null
const contract = compiler.getContract(contractName)
return {
name: contractName,
contract: contract,
compiler: compiler,
abi: contract.object.abi,
bytecodeObject: contract.object.evm.bytecode.object,
bytecodeLinkReferences: contract.object.evm.bytecode.linkReferences,
object: contract.object,
deployedBytecode: contract.object.evm.deployedBytecode,
getConstructorInterface: () => {
return txHelper.getConstructorInterface(contract.object.abi)
},
getConstructorInputs: () => {
const constructorInteface = txHelper.getConstructorInterface(contract.object.abi)
return txHelper.inputParametersDeclarationToString(constructorInteface.inputs)
},
isOverSizeLimit: () => {
const deployedBytecode = contract.object.evm.deployedBytecode
return (deployedBytecode && deployedBytecode.object.length / 2 > 24576)
},
metadata: contract.object.metadata
}
}
const getCompilerContracts = (plugin: RunTab) => {
return plugin.compilersArtefacts.__last.getData().contracts
}
export const terminalLogger = (plugin: RunTab, view: JSX.Element) => {
plugin.call('terminal', 'logHtml', view)
}
export const confirmationHandler = (plugin: RunTab, dispatch: React.Dispatch<any>, confirmDialogContent: MainnetPrompt, network, tx, gasEstimation, continueTxExecution, cancelCb) => {
if (network.name !== 'Main') {
return continueTxExecution(null)
}
const amount = plugin.blockchain.fromWei(tx.value, true, 'ether')
const content = confirmDialogContent(tx, network, amount, gasEstimation, plugin.blockchain.determineGasFees(tx), plugin.blockchain.determineGasPrice.bind(plugin.blockchain))
dispatch(displayNotification('Confirm transaction', content, 'Confirm', 'Cancel', () => {
plugin.blockchain.config.setUnpersistedProperty('doNotShowTransactionConfirmationAgain', plugin.REACT_API.confirmSettings)
// TODO: check if this is check is still valid given the refactor
if (!plugin.REACT_API.gasPriceStatus) {
cancelCb('Given transaction fee is not correct')
} else {
continueTxExecution({ maxFee: plugin.REACT_API.maxFee, maxPriorityFee: plugin.REACT_API.maxPriorityFee, baseFeePerGas: plugin.REACT_API.baseFeePerGas, gasPrice: plugin.REACT_API.gasPrice })
}
}, () => {
return cancelCb('Transaction canceled by user.')
}))
}
const getConfirmationCb = (plugin: RunTab, dispatch: React.Dispatch<any>, confirmDialogContent: MainnetPrompt) => {
// this code is the same as in recorder.js. TODO need to be refactored out
return (network, tx, gasEstimation, continueTxExecution, cancelCb) => {
confirmationHandler(plugin, dispatch, confirmDialogContent, network, tx, gasEstimation, continueTxExecution, cancelCb)
}
}
export const continueHandler = (dispatch: React.Dispatch<any>, gasEstimationPrompt: (msg: string) => JSX.Element, error, continueTxExecution, cancelCb) => {
if (error) {
const msg = typeof error !== 'string' ? error.message : error
dispatch(displayNotification('Gas estimation failed', gasEstimationPrompt(msg), 'Send Transaction', 'Cancel Transaction', () => {
continueTxExecution()
}, () => {
cancelCb()
}))
} else {
continueTxExecution()
}
}
export const promptHandler = (dispatch: React.Dispatch<any>, passphrasePrompt, okCb, cancelCb) => {
dispatch(displayNotification('Passphrase requested', passphrasePrompt('Personal mode is enabled. Please provide passphrase of account'), 'OK', 'Cancel', okCb, cancelCb))
}
export const createInstance = async (
plugin: RunTab,
dispatch: React.Dispatch<any>,
selectedContract: ContractData,
gasEstimationPrompt: (msg: string) => JSX.Element,
passphrasePrompt: (msg: string) => JSX.Element,
publishToStorage: (storage: 'ipfs' | 'swarm',
contract: ContractData) => void,
mainnetPrompt: MainnetPrompt,
isOverSizePrompt: () => JSX.Element,
args,
deployMode: DeployMode[]) => {
const isProxyDeployment = (deployMode || []).find(mode => mode === 'Deploy with Proxy')
const statusCb = (msg: string) => {
const log = logBuilder(msg)
return terminalLogger(plugin, log)
}
const finalCb = (error, contractObject, address) => {
if (error) {
const log = logBuilder(error)
return terminalLogger(plugin, log)
}
addInstance(dispatch, { contractData: contractObject, address, name: contractObject.name })
const data = plugin.compilersArtefacts.getCompilerAbstract(contractObject.contract.file)
plugin.compilersArtefacts.addResolvedContract(addressToString(address), data)
if (plugin.REACT_API.ipfsChecked) {
_paq.push(['trackEvent', 'udapp', 'DeployAndPublish', plugin.REACT_API.networkName])
publishToStorage('ipfs', selectedContract)
} else {
_paq.push(['trackEvent', 'udapp', 'DeployOnly', plugin.REACT_API.networkName])
}
if (isProxyDeployment) {
const initABI = contractObject.abi.find(abi => abi.name === 'initialize')
plugin.call('openzeppelin-proxy', 'execute', addressToString(address), args, initABI, contractObject)
}
}
let contractMetadata
try {
contractMetadata = await plugin.call('compilerMetadata', 'deployMetadataOf', selectedContract.name, selectedContract.contract.file)
} catch (error) {
return statusCb(`creation of ${selectedContract.name} errored: ${error.message ? error.message : error}`)
}
const compilerContracts = getCompilerContracts(plugin)
const confirmationCb = getConfirmationCb(plugin, dispatch, mainnetPrompt)
if (selectedContract.isOverSizeLimit()) {
return dispatch(displayNotification('Contract code size over limit', isOverSizePrompt(), 'Force Send', 'Cancel', () => {
deployContract(plugin, selectedContract, !isProxyDeployment ? args : '', contractMetadata, compilerContracts, {
continueCb: (error, continueTxExecution, cancelCb) => {
continueHandler(dispatch, gasEstimationPrompt, error, continueTxExecution, cancelCb)
},
promptCb: (okCb, cancelCb) => {
promptHandler(dispatch, passphrasePrompt, okCb, cancelCb)
},
statusCb,
finalCb
}, confirmationCb)
}, () => {
const log = logBuilder(`creation of ${selectedContract.name} canceled by user.`)
return terminalLogger(plugin, log)
}))
}
deployContract(plugin, selectedContract, !isProxyDeployment ? args : '', contractMetadata, compilerContracts, {
continueCb: (error, continueTxExecution, cancelCb) => {
continueHandler(dispatch, gasEstimationPrompt, error, continueTxExecution, cancelCb)
},
promptCb: (okCb, cancelCb) => {
promptHandler(dispatch, passphrasePrompt, okCb, cancelCb)
},
statusCb,
finalCb
}, confirmationCb)
}
const deployContract = (plugin: RunTab, selectedContract, args, contractMetadata, compilerContracts, callbacks, confirmationCb) => {
_paq.push(['trackEvent', 'udapp', 'DeployContractTo', plugin.REACT_API.networkName])
const { statusCb } = callbacks
if (!contractMetadata || (contractMetadata && contractMetadata.autoDeployLib)) {
return plugin.blockchain.deployContractAndLibraries(selectedContract, args, contractMetadata, compilerContracts, callbacks, confirmationCb)
}
if (Object.keys(selectedContract.bytecodeLinkReferences).length) statusCb(`linking ${JSON.stringify(selectedContract.bytecodeLinkReferences, null, '\t')} using ${JSON.stringify(contractMetadata.linkReferences, null, '\t')}`)
plugin.blockchain.deployContractWithLibrary(selectedContract, args, contractMetadata, compilerContracts, callbacks, confirmationCb)
}
export const loadAddress = (plugin: RunTab, dispatch: React.Dispatch<any>, contract: ContractData, address: string) => {
loadContractFromAddress(plugin, address,
(cb) => {
dispatch(displayNotification('At Address', `Do you really want to interact with ${address} using the current ABI definition?`, 'OK', 'Cancel', cb, null))
},
(error, loadType, abi) => {
if (error) {
return dispatch(displayNotification('Alert', error, 'OK', null))
}
if (loadType === 'abi') {
return addInstance(dispatch, { abi, address, name: '<at address>' })
} else if (loadType === 'instance') {
if (!contract) return dispatch(displayPopUp('No compiled contracts found.'))
const currentFile = plugin.REACT_API.contracts.currentFile
const compiler = plugin.REACT_API.contracts.contractList[currentFile].find(item => item.alias === contract.name)
const contractData = getSelectedContract(contract.name, compiler.compiler)
return addInstance(dispatch, { contractData, address, name: contract.name })
}
}
)
}
export const getContext = (plugin: RunTab) => {
return plugin.blockchain.context()
}
export const runTransactions = (
plugin: RunTab,
dispatch: React.Dispatch<any>,
instanceIndex: number,
lookupOnly: boolean,
funcABI: FuncABI,
inputsValues: string,
contractName: string,
contractABI, contract,
address,
logMsg:string,
mainnetPrompt: MainnetPrompt,
gasEstimationPrompt: (msg: string) => JSX.Element,
passphrasePrompt: (msg: string) => JSX.Element,
funcIndex?: number) => {
let callinfo = ''
if (lookupOnly) callinfo = 'call'
else if (funcABI.type === 'fallback' || funcABI.type === 'receive') callinfo = 'lowLevelInteracions'
else callinfo = 'transact'
_paq.push(['trackEvent', 'udapp', callinfo, plugin.blockchain.getCurrentNetworkStatus().network.name])
const params = funcABI.type !== 'fallback' ? inputsValues : ''
plugin.blockchain.runOrCallContractMethod(
contractName,
contractABI,
funcABI,
contract,
inputsValues,
address,
params,
lookupOnly,
logMsg,
(msg) => {
const log = logBuilder(msg)
return terminalLogger(plugin, log)
},
(returnValue) => {
const response = txFormat.decodeResponse(returnValue, funcABI)
dispatch(setDecodedResponse(instanceIndex, response, funcIndex))
},
(network, tx, gasEstimation, continueTxExecution, cancelCb) => {
confirmationHandler(plugin, dispatch, mainnetPrompt, network, tx, gasEstimation, continueTxExecution, cancelCb)
},
(error, continueTxExecution, cancelCb) => {
continueHandler(dispatch, gasEstimationPrompt, error, continueTxExecution, cancelCb)
},
(okCb, cancelCb) => {
promptHandler(dispatch, passphrasePrompt, okCb, cancelCb)
}
)
}
export const getFuncABIInputs = (plugin: RunTab, funcABI: FuncABI) => {
return plugin.blockchain.getInputs(funcABI)
}

@ -0,0 +1,153 @@
import { envChangeNotification } from "@remix-ui/helper"
import { RunTab } from "../types/run-tab"
import { setExecutionContext, setFinalContext, updateAccountBalances } from "./account"
import { addExternalProvider, addInstance, removeExternalProvider, setNetworkNameFromProvider } from "./actions"
import { clearAllInstances, clearRecorderCount, fetchContractListSuccess, resetUdapp, setCurrentContract, setCurrentFile, setDeployOptions, setLoadType, setRecorderCount, setSendValue } from "./payload"
import { CompilerAbstract } from '@remix-project/remix-solidity'
import * as ethJSUtil from 'ethereumjs-util'
import Web3 from 'web3'
export const setupEvents = (plugin: RunTab, dispatch: React.Dispatch<any>) => {
plugin.blockchain.events.on('newTransaction', (tx, receipt) => {
plugin.emit('newTransaction', tx, receipt)
})
plugin.blockchain.event.register('transactionExecuted', (error, from, to, data, lookupOnly, txResult) => {
if (!lookupOnly) dispatch(setSendValue('0'))
if (error) return
updateAccountBalances(plugin, dispatch)
})
plugin.blockchain.event.register('contextChanged', (context, silent) => {
setFinalContext(plugin, dispatch)
})
plugin.blockchain.event.register('networkStatus', ({ error, network }) => {
if (error) {
const netUI = 'can\'t detect network '
setNetworkNameFromProvider(dispatch, netUI)
return
}
const networkProvider = plugin.networkModule.getNetworkProvider.bind(plugin.networkModule)
const netUI = (networkProvider() !== 'vm') ? `${network.name} (${network.id || '-'}) network` : 'VM'
setNetworkNameFromProvider(dispatch, netUI)
})
plugin.blockchain.event.register('addProvider', provider => addExternalProvider(dispatch, provider))
plugin.blockchain.event.register('removeProvider', name => removeExternalProvider(dispatch, name))
plugin.on('solidity', 'compilationFinished', (file, source, languageVersion, data, input, version) => broadcastCompilationResult(plugin, dispatch, file, source, languageVersion, data, input))
plugin.on('vyper', 'compilationFinished', (file, source, languageVersion, data) => broadcastCompilationResult(plugin, dispatch, file, source, languageVersion, data))
plugin.on('lexon', 'compilationFinished', (file, source, languageVersion, data) => broadcastCompilationResult(plugin, dispatch, file, source, languageVersion, data))
plugin.on('yulp', 'compilationFinished', (file, source, languageVersion, data) => broadcastCompilationResult(plugin, dispatch, file, source, languageVersion, data))
plugin.on('nahmii-compiler', 'compilationFinished', (file, source, languageVersion, data) => broadcastCompilationResult(plugin, dispatch, file, source, languageVersion, data))
plugin.on('udapp', 'setEnvironmentModeReducer', (env: { context: string, fork: string }, from: string) => {
plugin.call('notification', 'toast', envChangeNotification(env, from))
setExecutionContext(plugin, dispatch, env)
})
plugin.on('udapp', 'clearAllInstancesReducer', () => {
dispatch(clearAllInstances())
})
plugin.on('udapp', 'addInstanceReducer', (address, abi, name) => {
addInstance(dispatch, { abi, address, name })
})
plugin.on('filePanel', 'setWorkspace', () => {
dispatch(resetUdapp())
resetAndInit(plugin)
})
plugin.fileManager.events.on('currentFileChanged', (currentFile: string) => {
if (/.(.abi)$/.exec(currentFile)) {
dispatch(setLoadType('abi'))
} else if (/.(.sol)$/.exec(currentFile) ||
/.(.vy)$/.exec(currentFile) || // vyper
/.(.lex)$/.exec(currentFile) || // lexon
/.(.contract)$/.exec(currentFile)) {
dispatch(setLoadType('sol'))
} else {
dispatch(setLoadType('other'))
}
dispatch(setCurrentFile(currentFile))
})
plugin.recorder.event.register('recorderCountChange', (count) => {
dispatch(setRecorderCount(count))
})
plugin.event.register('cleared', () => {
dispatch(clearRecorderCount())
})
}
const broadcastCompilationResult = async (plugin: RunTab, dispatch: React.Dispatch<any>, file, source, languageVersion, data, input?) => {
// TODO check whether the tab is configured
const compiler = new CompilerAbstract(languageVersion, data, source, input)
plugin.compilersArtefacts[languageVersion] = compiler
plugin.compilersArtefacts.__last = compiler
const contracts = getCompiledContracts(compiler).map((contract) => {
return { name: languageVersion, alias: contract.name, file: contract.file, compiler }
})
const index = contracts.findIndex(contract => contract.alias === plugin.REACT_API.contracts.currentContract)
if ((index < 0) && (contracts.length > 0)) dispatch(setCurrentContract(contracts[0].alias))
const isUpgradeable = await plugin.call('openzeppelin-proxy', 'isConcerned', data.sources[file] ? data.sources[file].ast : {})
if (isUpgradeable) {
const options = await plugin.call('openzeppelin-proxy', 'getDeployOptions', data.contracts[file])
dispatch(setDeployOptions({ options: [{ title: 'Deploy with Proxy', active: false }], initializeOptions: options }))
}
else dispatch(setDeployOptions({} as any))
dispatch(fetchContractListSuccess({ [file]: contracts }))
dispatch(setCurrentFile(file))
// TODO: set current contract
}
const getCompiledContracts = (compiler) => {
const contracts = []
compiler.visitContracts((contract) => {
contracts.push(contract)
})
return contracts
}
export const resetAndInit = (plugin: RunTab) => {
plugin.blockchain.resetAndInit(plugin.config, {
getAddress: (cb) => {
cb(null, plugin.REACT_API.accounts.selectedAccount)
},
getValue: (cb) => {
try {
const number = plugin.REACT_API.sendValue
const unit = plugin.REACT_API.sendUnit
cb(null, Web3.utils.toWei(number, unit))
} catch (e) {
cb(e)
}
},
getGasLimit: (cb) => {
try {
const gasLimit = '0x' + new ethJSUtil.BN(plugin.REACT_API.gasLimit, 10).toString(16)
cb(null, gasLimit)
} catch (e) {
cb(e.message)
}
}
})
}

@ -1,17 +1,15 @@
// eslint-disable-next-line no-unused-vars
import React from 'react'
import * as ethJSUtil from 'ethereumjs-util'
import Web3 from 'web3'
import { addressToString, createNonClashingNameAsync, envChangeNotification, extractNameFromKey, shortenAddress, web3Dialog } from '@remix-ui/helper'
import { addNewInstance, addProvider, clearAllInstances, clearRecorderCount, displayNotification, displayPopUp, fetchAccountsListFailed, fetchAccountsListRequest, fetchAccountsListSuccess, fetchContractListSuccess, hidePopUp, removeExistingInstance, removeProvider, resetUdapp, setBaseFeePerGas, setConfirmSettings, setCurrentFile, setDecodedResponse, setEnvToasterContent, setExecutionEnvironment, setExternalEndpoint, setGasLimit, setGasPrice, setGasPriceStatus, setLoadType, setMatchPassphrase, setMaxFee, setMaxPriorityFee, setNetworkName, setPassphrase, setPathToScenario, setRecorderCount, setSelectedAccount, setSendUnit, setSendValue, setTxFeeContent } from './payload'
import { RunTab } from '../types/run-tab'
import { CompilerAbstract } from '@remix-project/remix-solidity'
import * as remixLib from '@remix-project/remix-lib'
import { MainnetPrompt } from '../types'
import { ContractData, FuncABI, } from '@remix-project/core-plugin'
import { resetAndInit, setupEvents } from './events'
import { createNewBlockchainAccount, fillAccountsList, setExecutionContext, signMessageWithAddress } from './account'
import { clearInstances, clearPopUp, removeInstance, setAccount, setGasFee, setMatchPassphrasePrompt, setNetworkNameFromProvider, setPassphrasePrompt, setSelectedContract, setSendTransactionValue, setUnit, updateBaseFeePerGas, updateConfirmSettings, updateGasPrice, updateGasPriceStatus, updateMaxFee, updateMaxPriorityFee, updateScenarioPath, updateTxFeeContent } from './actions'
import { createInstance, getContext, getFuncABIInputs, getSelectedContract, loadAddress, runTransactions } from './deploy'
import { CompilerAbstract as CompilerAbstractType } from '@remix-project/remix-solidity-ts'
import { ContractData, FuncABI } from "@remix-project/core-plugin"
import { DeployMode, MainnetPrompt } from '../types'
import { runCurrentScenario, storeScenario } from './recorder'
const txFormat = remixLib.execution.txFormat
declare global {
interface Window {
_paq: any
@ -19,709 +17,45 @@ declare global {
}
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
resetAndInit()
setupEvents()
resetAndInit(plugin)
setupEvents(plugin, dispatch)
setInterval(() => {
fillAccountsList()
fillAccountsList(plugin, dispatch)
}, 1000)
}
const setupEvents = () => {
plugin.blockchain.events.on('newTransaction', (tx, receipt) => {
plugin.emit('newTransaction', tx, receipt)
})
plugin.blockchain.event.register('transactionExecuted', (error, from, to, data, lookupOnly, txResult) => {
if (!lookupOnly) dispatch(setSendValue('0'))
if (error) return
updateAccountBalances()
})
plugin.blockchain.event.register('contextChanged', (context, silent) => {
setFinalContext()
})
plugin.blockchain.event.register('networkStatus', ({ error, network }) => {
if (error) {
const netUI = 'can\'t detect network '
setNetworkNameFromProvider(netUI)
return
}
const networkProvider = plugin.networkModule.getNetworkProvider.bind(plugin.networkModule)
const netUI = (networkProvider() !== 'vm') ? `${network.name} (${network.id || '-'}) network` : 'VM'
setNetworkNameFromProvider(netUI)
})
plugin.blockchain.event.register('addProvider', provider => addExternalProvider(provider))
plugin.blockchain.event.register('removeProvider', name => removeExternalProvider(name))
plugin.on('solidity', 'compilationFinished', (file, source, languageVersion, data, input, version) => broadcastCompilationResult(file, source, languageVersion, data, input))
plugin.on('vyper', 'compilationFinished', (file, source, languageVersion, data) => broadcastCompilationResult(file, source, languageVersion, data))
plugin.on('lexon', 'compilationFinished', (file, source, languageVersion, data) => broadcastCompilationResult(file, source, languageVersion, data))
plugin.on('yulp', 'compilationFinished', (file, source, languageVersion, data) => broadcastCompilationResult(file, source, languageVersion, data))
plugin.on('nahmii-compiler', 'compilationFinished', (file, source, languageVersion, data) => broadcastCompilationResult(file, source, languageVersion, data))
plugin.on('udapp', 'setEnvironmentModeReducer', (env: { context: string, fork: string }, from: string) => {
plugin.call('notification', 'toast', envChangeNotification(env, from))
setExecutionContext(env)
})
plugin.on('udapp', 'clearAllInstancesReducer', () => {
dispatch(clearAllInstances())
})
plugin.on('udapp', 'addInstanceReducer', (address, abi, name) => {
addInstance({ abi, address, name })
})
plugin.on('filePanel', 'setWorkspace', () => {
dispatch(resetUdapp())
resetAndInit()
})
plugin.fileManager.events.on('currentFileChanged', (currentFile: string) => {
if (/.(.abi)$/.exec(currentFile)) {
dispatch(setLoadType('abi'))
} else if (/.(.sol)$/.exec(currentFile) ||
/.(.vy)$/.exec(currentFile) || // vyper
/.(.lex)$/.exec(currentFile) || // lexon
/.(.contract)$/.exec(currentFile)) {
dispatch(setLoadType('sol'))
} else {
dispatch(setLoadType('other'))
}
dispatch(setCurrentFile(currentFile))
})
plugin.recorder.event.register('recorderCountChange', (count) => {
dispatch(setRecorderCount(count))
})
plugin.event.register('cleared', () => {
dispatch(clearRecorderCount())
})
}
const updateAccountBalances = () => {
const accounts = plugin.REACT_API.accounts.loadedAccounts
Object.keys(accounts).map((value) => {
plugin.blockchain.getBalanceInEther(value, (err, balance) => {
if (err) return
const updated = shortenAddress(value, balance)
accounts[value] = updated
})
})
dispatch(fetchAccountsListSuccess(accounts))
}
const fillAccountsList = async () => {
try {
dispatch(fetchAccountsListRequest())
const promise = plugin.blockchain.getAccounts()
promise.then(async (accounts: string[]) => {
const loadedAccounts = {}
if (!accounts) accounts = []
// allSettled is undefined..
// so the current promise (all) will finish when:
// - all the promises resolve
// - at least one reject
// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise/all
await (Promise as any).all(accounts.map((account) => {
return new Promise((resolve, reject) => {
plugin.blockchain.getBalanceInEther(account, (err, balance) => {
if (err) return reject(err)
const updated = shortenAddress(account, balance)
loadedAccounts[account] = updated
resolve(account)
})
})
}))
const provider = plugin.blockchain.getProvider()
if (provider === 'injected') {
const selectedAddress = plugin.blockchain.getInjectedWeb3Address()
if (!(Object.keys(loadedAccounts).includes(selectedAddress))) setAccount(null)
}
dispatch(fetchAccountsListSuccess(loadedAccounts))
}).catch((e) => {
dispatch(fetchAccountsListFailed(e.message))
})
} catch (e) {
dispatch(displayPopUp(`Cannot get account list: ${e}`))
}
}
export const setAccount = (account: string) => {
dispatch(setSelectedAccount(account))
}
export const setUnit = (unit: 'ether' | 'finney' | 'gwei' | 'wei') => {
dispatch(setSendUnit(unit))
}
export const setGasFee = (value: number) => {
dispatch(setGasLimit(value))
}
const setFinalContext = () => {
// set the final context. Cause it is possible that this is not the one we've originaly selected
const value = _getProviderDropdownValue()
setExecEnv(value)
clearInstances()
}
const _getProviderDropdownValue = (): string => {
const provider = plugin.blockchain.getProvider()
const fork = plugin.blockchain.getCurrentFork()
return provider === 'vm' ? provider + '-' + fork : provider
}
const setExecEnv = (env: string) => {
dispatch(setExecutionEnvironment(env))
}
export const setNetworkNameFromProvider = (networkName: string) => {
dispatch(setNetworkName(networkName))
}
const addExternalProvider = (network) => {
dispatch(addProvider(network))
}
const removeExternalProvider = (name) => {
dispatch(removeProvider(name))
}
export const setExecutionContext = (executionContext: { context: string, fork: string }) => {
const displayContent = web3Dialog(plugin.REACT_API.externalEndpoint, setWeb3Endpoint)
plugin.blockchain.changeExecutionContext(executionContext, () => {
plugin.call('notification', 'modal', {
id: 'envNotification',
title: 'External node request',
message: displayContent,
okLabel: 'OK',
cancelLabel: 'Cancel',
okFn: () => {
plugin.blockchain.setProviderFromEndpoint(plugin.REACT_API.externalEndpoint, executionContext, (alertMsg) => {
if (alertMsg) plugin.call('notification', 'toast', alertMsg)
setFinalContext()
})
},
cancelFn: () => {
setFinalContext()
}
})
}, (alertMsg) => {
plugin.call('notification', 'toast', alertMsg)
}, () => { setFinalContext() })
}
export const setWeb3Endpoint = (endpoint: string) => {
dispatch(setExternalEndpoint(endpoint))
}
export const clearPopUp = async () => {
dispatch(hidePopUp())
}
export const createNewBlockchainAccount = async (cbMessage: JSX.Element) => {
plugin.blockchain.newAccount(
'',
(cb) => {
dispatch(displayNotification('Enter Passphrase', cbMessage, 'OK', 'Cancel', async () => {
if (plugin.REACT_API.passphrase === plugin.REACT_API.matchPassphrase) {
cb(plugin.REACT_API.passphrase)
} else {
dispatch(displayNotification('Error', 'Passphase does not match', 'OK', null))
}
setPassphrase('')
setMatchPassphrase('')
}, () => {}))
},
async (error, address) => {
if (error) {
return dispatch(displayPopUp('Cannot create an account: ' + error))
}
dispatch(displayPopUp(`account ${address} created`))
await fillAccountsList()
}
)
}
export const setPassphrasePrompt = (passphrase: string) => {
dispatch(setPassphrase(passphrase))
}
export const setMatchPassphrasePrompt = (passphrase: string) => {
dispatch(setMatchPassphrase(passphrase))
}
export const signMessageWithAddress = (account: string, message: string, modalContent: (hash: string, data: string) => JSX.Element, passphrase?: string) => {
plugin.blockchain.signMessage(message, account, passphrase, (err, msgHash, signedData) => {
if (err) {
return displayPopUp(err)
}
dispatch(displayNotification('Signed Message', modalContent(msgHash, signedData), 'OK', null, () => {}, null))
})
}
const broadcastCompilationResult = (file, source, languageVersion, data, input?) => {
// TODO check whether the tab is configured
const compiler = new CompilerAbstract(languageVersion, data, source, input)
plugin.compilersArtefacts[languageVersion] = compiler
plugin.compilersArtefacts.__last = compiler
const contracts = getCompiledContracts(compiler).map((contract) => {
return { name: languageVersion, alias: contract.name, file: contract.file, compiler }
})
dispatch(fetchContractListSuccess({ [file]: contracts }))
dispatch(setCurrentFile(file))
}
const loadContractFromAddress = (address, confirmCb, cb) => {
if (/.(.abi)$/.exec(plugin.config.get('currentFile'))) {
confirmCb(() => {
let abi
try {
abi = JSON.parse(plugin.editor.currentContent())
} catch (e) {
return cb('Failed to parse the current file as JSON ABI.')
}
_paq.push(['trackEvent', 'udapp', 'AtAddressLoadWithABI'])
cb(null, 'abi', abi)
})
} else {
_paq.push(['trackEvent', 'udapp', 'AtAddressLoadWithArtifacts'])
cb(null, 'instance')
}
}
const getCompiledContracts = (compiler) => {
const contracts = []
compiler.visitContracts((contract) => {
contracts.push(contract)
})
return contracts
}
export const getSelectedContract = (contractName: string, compiler: CompilerAbstractType): ContractData => {
if (!contractName) return null
// const compiler = plugin.compilersArtefacts[compilerAtributeName]
if (!compiler) return null
const contract = compiler.getContract(contractName)
return {
name: contractName,
contract: contract,
compiler: compiler,
abi: contract.object.abi,
bytecodeObject: contract.object.evm.bytecode.object,
bytecodeLinkReferences: contract.object.evm.bytecode.linkReferences,
object: contract.object,
deployedBytecode: contract.object.evm.deployedBytecode,
getConstructorInterface: () => {
return txHelper.getConstructorInterface(contract.object.abi)
},
getConstructorInputs: () => {
const constructorInteface = txHelper.getConstructorInterface(contract.object.abi)
return txHelper.inputParametersDeclarationToString(constructorInteface.inputs)
},
isOverSizeLimit: () => {
const deployedBytecode = contract.object.evm.deployedBytecode
return (deployedBytecode && deployedBytecode.object.length / 2 > 24576)
},
metadata: contract.object.metadata
}
}
const getCompilerContracts = () => {
return plugin.compilersArtefacts.__last.getData().contracts
}
const terminalLogger = (view: JSX.Element) => {
plugin.call('terminal', 'logHtml', view)
}
const confirmationHandler = (confirmDialogContent: MainnetPrompt, network, tx, gasEstimation, continueTxExecution, cancelCb) => {
if (network.name !== 'Main') {
return continueTxExecution(null)
}
const amount = plugin.blockchain.fromWei(tx.value, true, 'ether')
const content = confirmDialogContent(tx, network, amount, gasEstimation, plugin.blockchain.determineGasFees(tx), plugin.blockchain.determineGasPrice.bind(plugin.blockchain))
dispatch(displayNotification('Confirm transaction', content, 'Confirm', 'Cancel', () => {
plugin.blockchain.config.setUnpersistedProperty('doNotShowTransactionConfirmationAgain', plugin.REACT_API.confirmSettings)
// TODO: check if this is check is still valid given the refactor
if (!plugin.REACT_API.gasPriceStatus) {
cancelCb('Given transaction fee is not correct')
} else {
continueTxExecution({ maxFee: plugin.REACT_API.maxFee, maxPriorityFee: plugin.REACT_API.maxPriorityFee, baseFeePerGas: plugin.REACT_API.baseFeePerGas, gasPrice: plugin.REACT_API.gasPrice })
}
}, () => {
return cancelCb('Transaction canceled by user.')
}))
}
const getConfirmationCb = (confirmDialogContent: MainnetPrompt) => {
// this code is the same as in recorder.js. TODO need to be refactored out
return (network, tx, gasEstimation, continueTxExecution, cancelCb) => {
confirmationHandler(confirmDialogContent, network, tx, gasEstimation, continueTxExecution, cancelCb)
}
}
const continueHandler = (gasEstimationPrompt: (msg: string) => JSX.Element, error, continueTxExecution, cancelCb) => {
if (error) {
const msg = typeof error !== 'string' ? error.message : error
dispatch(displayNotification('Gas estimation failed', gasEstimationPrompt(msg), 'Send Transaction', 'Cancel Transaction', () => {
continueTxExecution()
}, () => {
cancelCb()
}))
} else {
continueTxExecution()
}
}
const promptHandler = (passphrasePrompt, okCb, cancelCb) => {
dispatch(displayNotification('Passphrase requested', passphrasePrompt('Personal mode is enabled. Please provide passphrase of account'), 'OK', 'Cancel', okCb, cancelCb))
}
export const createInstance = async (
selectedContract: ContractData,
gasEstimationPrompt: (msg: string) => JSX.Element,
passphrasePrompt: (msg: string) => JSX.Element,
logBuilder: (msg: string) => JSX.Element,
publishToStorage: (storage: 'ipfs' | 'swarm',
contract: ContractData) => void,
mainnetPrompt: MainnetPrompt,
isOverSizePrompt: () => JSX.Element,
args) => {
const statusCb = (msg: string) => {
const log = logBuilder(msg)
return terminalLogger(log)
}
const finalCb = (error, contractObject, address) => {
if (error) {
const log = logBuilder(error)
return terminalLogger(log)
}
addInstance({ contractData: contractObject, address, name: contractObject.name })
const data = plugin.compilersArtefacts.getCompilerAbstract(contractObject.contract.file)
plugin.compilersArtefacts.addResolvedContract(addressToString(address), data)
if (plugin.REACT_API.ipfsChecked) {
_paq.push(['trackEvent', 'udapp', 'DeployAndPublish', plugin.REACT_API.networkName])
publishToStorage('ipfs', selectedContract)
} else {
_paq.push(['trackEvent', 'udapp', 'DeployOnly', plugin.REACT_API.networkName])
}
}
let contractMetadata
try {
contractMetadata = await plugin.call('compilerMetadata', 'deployMetadataOf', selectedContract.name, selectedContract.contract.file)
} catch (error) {
return statusCb(`creation of ${selectedContract.name} errored: ${error.message ? error.message : error}`)
}
const compilerContracts = getCompilerContracts()
const confirmationCb = getConfirmationCb(mainnetPrompt)
if (selectedContract.isOverSizeLimit()) {
return dispatch(displayNotification('Contract code size over limit', isOverSizePrompt(), 'Force Send', 'Cancel', () => {
deployContract(selectedContract, args, contractMetadata, compilerContracts, {
continueCb: (error, continueTxExecution, cancelCb) => {
continueHandler(gasEstimationPrompt, error, continueTxExecution, cancelCb)
},
promptCb: (okCb, cancelCb) => {
promptHandler(passphrasePrompt, okCb, cancelCb)
},
statusCb,
finalCb
}, confirmationCb)
}, () => {
const log = logBuilder(`creation of ${selectedContract.name} canceled by user.`)
return terminalLogger(log)
}))
}
deployContract(selectedContract, args, contractMetadata, compilerContracts, {
continueCb: (error, continueTxExecution, cancelCb) => {
continueHandler(gasEstimationPrompt, error, continueTxExecution, cancelCb)
},
promptCb: (okCb, cancelCb) => {
promptHandler(passphrasePrompt, okCb, cancelCb)
},
statusCb,
finalCb
}, confirmationCb)
}
const deployContract = (selectedContract, args, contractMetadata, compilerContracts, callbacks, confirmationCb) => {
_paq.push(['trackEvent', 'udapp', 'DeployContractTo', plugin.REACT_API.networkName])
const { statusCb } = callbacks
if (!contractMetadata || (contractMetadata && contractMetadata.autoDeployLib)) {
return plugin.blockchain.deployContractAndLibraries(selectedContract, args, contractMetadata, compilerContracts, callbacks, confirmationCb)
}
if (Object.keys(selectedContract.bytecodeLinkReferences).length) statusCb(`linking ${JSON.stringify(selectedContract.bytecodeLinkReferences, null, '\t')} using ${JSON.stringify(contractMetadata.linkReferences, null, '\t')}`)
plugin.blockchain.deployContractWithLibrary(selectedContract, args, contractMetadata, compilerContracts, callbacks, confirmationCb)
}
export const updateGasPriceStatus = (status: boolean) => {
dispatch(setGasPriceStatus(status))
}
export const updateConfirmSettings = (confirmation: boolean) => {
dispatch(setConfirmSettings(confirmation))
}
export const updateMaxFee = (fee: string) => {
dispatch(setMaxFee(fee))
}
export const updateMaxPriorityFee = (fee: string) => {
dispatch(setMaxPriorityFee(fee))
}
export const updateBaseFeePerGas = (baseFee: string) => {
dispatch(setBaseFeePerGas(baseFee))
}
export const updateGasPrice = (price: string) => {
dispatch(setGasPrice(price))
}
export const updateTxFeeContent = (content: string) => {
dispatch(setTxFeeContent(content))
}
export const addInstance = (instance: { contractData?: ContractData, address: string, name: string, abi?: any, decodedResponse?: Record<number, any> }) => {
instance.decodedResponse = {}
dispatch(addNewInstance(instance))
}
export const removeInstance = (index: number) => {
dispatch(removeExistingInstance(index))
}
export const clearInstances = () => {
dispatch(clearAllInstances())
dispatch(clearRecorderCount())
}
export const loadAddress = (contract: ContractData, address: string) => {
loadContractFromAddress(address,
(cb) => {
dispatch(displayNotification('At Address', `Do you really want to interact with ${address} using the current ABI definition?`, 'OK', 'Cancel', cb, null))
},
(error, loadType, abi) => {
if (error) {
return dispatch(displayNotification('Alert', error, 'OK', null))
}
if (loadType === 'abi') {
return addInstance({ abi, address, name: '<at address>' })
} else if (loadType === 'instance') {
if (!contract) return dispatch(displayPopUp('No compiled contracts found.'))
const currentFile = plugin.REACT_API.contracts.currentFile
const compiler = plugin.REACT_API.contracts.contractList[currentFile].find(item => item.alias === contract.name)
const contractData = getSelectedContract(contract.name, compiler.compiler)
return addInstance({ contractData, address, name: contract.name })
}
}
)
}
export const getContext = () => {
return plugin.blockchain.context()
}
export const runTransactions = (
instanceIndex: number,
lookupOnly: boolean,
funcABI: FuncABI,
inputsValues: string,
contractName: string,
contractABI, contract,
address,
logMsg:string,
logBuilder: (msg: string) => JSX.Element,
mainnetPrompt: MainnetPrompt,
gasEstimationPrompt: (msg: string) => JSX.Element,
passphrasePrompt: (msg: string) => JSX.Element,
funcIndex?: number) => {
let callinfo = ''
if (lookupOnly) callinfo = 'call'
else if (funcABI.type === 'fallback' || funcABI.type === 'receive') callinfo = 'lowLevelInteracions'
else callinfo = 'transact'
_paq.push(['trackEvent', 'udapp', callinfo, plugin.blockchain.getCurrentNetworkStatus().network.name])
const params = funcABI.type !== 'fallback' ? inputsValues : ''
plugin.blockchain.runOrCallContractMethod(
contractName,
contractABI,
funcABI,
contract,
inputsValues,
address,
params,
lookupOnly,
logMsg,
(msg) => {
const log = logBuilder(msg)
return terminalLogger(log)
},
(returnValue) => {
const response = txFormat.decodeResponse(returnValue, funcABI)
dispatch(setDecodedResponse(instanceIndex, response, funcIndex))
},
(network, tx, gasEstimation, continueTxExecution, cancelCb) => {
confirmationHandler(mainnetPrompt, network, tx, gasEstimation, continueTxExecution, cancelCb)
},
(error, continueTxExecution, cancelCb) => {
continueHandler(gasEstimationPrompt, error, continueTxExecution, cancelCb)
},
(okCb, cancelCb) => {
promptHandler(passphrasePrompt, okCb, cancelCb)
}
)
}
const saveScenario = async (newPath: string, provider, promptCb, cb) => {
const txJSON = JSON.stringify(plugin.recorder.getAll(), null, 2)
promptCb(async () => {
try {
await provider.set(newPath, txJSON)
await plugin.fileManager.open(newPath)
} catch (error) {
if (error) return cb('Failed to create file. ' + newPath + ' ' + error)
}
})
}
export const storeScenario = async (prompt: (msg: string, defaultValue: string) => JSX.Element) => {
const path = plugin.fileManager.currentPath()
const fileProvider = await plugin.fileManager.fileProviderOf(path)
if (!fileProvider) return displayNotification('Alert', 'Invalid File Provider', 'OK', null)
const newPath = await createNonClashingNameAsync(path + '/' + plugin.REACT_API.recorder.pathToScenario, plugin.fileManager)
const newName = extractNameFromKey(newPath)
saveScenario(newPath, fileProvider,
(cb) => {
dispatch(displayNotification('Save transactions as scenario', prompt('Transactions will be saved in a file under ' + path, newName), 'OK', 'Cancel', cb, null))
},
(error) => {
if (error) return dispatch(displayNotification('Alert', error, 'OK', null))
}
)
}
const runScenario = (file: string, gasEstimationPrompt: (msg: string) => JSX.Element, passphrasePrompt: (msg: string) => JSX.Element, confirmDialogContent: MainnetPrompt, logBuilder: (msg: string) => JSX.Element) => {
if (!file) return dispatch(displayNotification('Alert', 'Unable to run scenerio, no specified scenario file', 'OK', null))
plugin.fileManager.readFile(file).then((json) => {
// TODO: there is still a UI dependency to remove here, it's still too coupled at this point to remove easily
plugin.recorder.runScenario(
json,
(error, continueTxExecution, cancelCb) => {
continueHandler(gasEstimationPrompt, error, continueTxExecution, cancelCb)
}, (okCb, cancelCb) => {
promptHandler(passphrasePrompt, okCb, cancelCb)
}, (msg) => {
dispatch(displayNotification('Alert', msg, 'OK', null))
}, (network, tx, gasEstimation, continueTxExecution, cancelCb) => {
confirmationHandler(confirmDialogContent, network, tx, gasEstimation, continueTxExecution, cancelCb)
}, (msg: string) => {
const log = logBuilder(msg)
return terminalLogger(log)
}, (error, abi, address, contractName) => {
if (error) {
return dispatch(displayNotification('Alert', error, 'OK', null))
}
addInstance({ name: contractName, address, abi })
})
}).catch((error) => dispatch(displayNotification('Alert', error, 'OK', null)))
}
export const runCurrentScenario = (gasEstimationPrompt: (msg: string) => JSX.Element, passphrasePrompt: (msg: string) => JSX.Element, confirmDialogContent: MainnetPrompt, logBuilder: (msg: string) => JSX.Element) => {
const file = plugin.config.get('currentFile')
if (!file) return dispatch(displayNotification('Alert', 'A scenario file has to be selected', 'Ok', null))
runScenario(file, gasEstimationPrompt, passphrasePrompt, confirmDialogContent, logBuilder)
}
export const updateScenarioPath = (path: string) => {
dispatch(setPathToScenario(path))
}
export const getFuncABIInputs = (funcABI: FuncABI) => {
return plugin.blockchain.getInputs(funcABI)
}
export const setSendTransactionValue = (value: string) => {
dispatch(setSendValue(value))
}
const resetAndInit = () => {
plugin.blockchain.resetAndInit(plugin.config, {
getAddress: (cb) => {
cb(null, plugin.REACT_API.accounts.selectedAccount)
},
getValue: (cb) => {
try {
const number = plugin.REACT_API.sendValue
const unit = plugin.REACT_API.sendUnit
cb(null, Web3.utils.toWei(number, unit))
} catch (e) {
cb(e)
}
},
getGasLimit: (cb) => {
try {
cb(null, '0x' + new ethJSUtil.BN(plugin.REACT_API.gasLimit, 10).toString(16))
} catch (e) {
cb(e.message)
}
}
})
}
export const setAccountAddress = (account: string) => setAccount(dispatch, account)
export const setUnitValue = (unit: 'ether' | 'finney' | 'gwei' | 'wei') => setUnit(dispatch, unit)
export const setGasFeeAmount = (value: number) => setGasFee(dispatch, value)
export const setExecutionEnvironment = (executionContext: { context: string, fork: string }) => setExecutionContext(plugin, dispatch, executionContext)
export const hideToaster = () => clearPopUp(dispatch)
export const createNewAddress = (cbMessage: JSX.Element) => createNewBlockchainAccount(plugin, dispatch, cbMessage)
export const setPassphraseModal = (passphrase: string) => setPassphrasePrompt(dispatch, passphrase)
export const setMatchPassphraseModal = (passphrase: string) => setMatchPassphrasePrompt(dispatch, passphrase)
export const signMessage = (account: string, message: string, modalContent: (hash: string, data: string) => JSX.Element, passphrase?: string) => signMessageWithAddress(plugin, dispatch, account, message, modalContent, passphrase)
export const fetchSelectedContract = (contractName: string, compiler: CompilerAbstractType) => getSelectedContract(contractName, compiler)
export const createNewInstance = async (selectedContract: ContractData, gasEstimationPrompt: (msg: string) => JSX.Element, passphrasePrompt: (msg: string) => JSX.Element, publishToStorage: (storage: 'ipfs' | 'swarm', contract: ContractData) => void, mainnetPrompt: MainnetPrompt, isOverSizePrompt: () => JSX.Element, args, deployMode: DeployMode[]) => createInstance(plugin, dispatch, selectedContract, gasEstimationPrompt, passphrasePrompt, publishToStorage, mainnetPrompt, isOverSizePrompt, args, deployMode)
export const setSendValue = (value: string) => setSendTransactionValue(dispatch, value)
export const setBaseFeePerGas = (baseFee: string) => updateBaseFeePerGas(dispatch, baseFee)
export const setConfirmSettings = (confirmation: boolean) => updateConfirmSettings(dispatch, confirmation)
export const setGasPrice = (price: string) => updateGasPrice(dispatch, price)
export const setGasPriceStatus = (status: boolean) => updateGasPriceStatus(dispatch, status)
export const setMaxFee = (fee: string) => updateMaxFee(dispatch, fee)
export const setMaxPriorityFee = (fee: string) => updateMaxPriorityFee(dispatch, fee)
export const setTxFeeContent = (content: string) => updateTxFeeContent(dispatch, content)
export const removeInstances = () => clearInstances(dispatch)
export const removeSingleInstance = (index: number) => removeInstance(dispatch, index)
export const getExecutionContext = () => getContext(plugin)
export const executeTransactions = (instanceIndex: number, lookupOnly: boolean, funcABI: FuncABI, inputsValues: string, contractName: string, contractABI, contract, address, logMsg:string, mainnetPrompt: MainnetPrompt, gasEstimationPrompt: (msg: string) => JSX.Element, passphrasePrompt: (msg: string) => JSX.Element, funcIndex?: number) => runTransactions(plugin, dispatch, instanceIndex, lookupOnly, funcABI, inputsValues, contractName, contractABI, contract, address, logMsg, mainnetPrompt, gasEstimationPrompt, passphrasePrompt, funcIndex)
export const loadFromAddress = (contract: ContractData, address: string) => loadAddress(plugin, dispatch, contract, address)
export const storeNewScenario = async (prompt: (msg: string, defaultValue: string) => JSX.Element) => storeScenario(plugin, dispatch, prompt)
export const runScenario = (gasEstimationPrompt: (msg: string) => JSX.Element, passphrasePrompt: (msg: string) => JSX.Element, confirmDialogContent: MainnetPrompt) => runCurrentScenario(plugin, dispatch, gasEstimationPrompt, passphrasePrompt, confirmDialogContent)
export const setScenarioPath = (path: string) => updateScenarioPath(dispatch, path)
export const getFuncABIValues = (funcABI: FuncABI) => getFuncABIInputs(plugin, funcABI)
export const setNetworkName = (networkName: string) => setNetworkNameFromProvider(dispatch, networkName)
export const updateSelectedContract = (contractName) => setSelectedContract(dispatch, contractName)

@ -1,249 +1,251 @@
import { ContractList } from '../reducers/runTab'
import { ContractData } from '@remix-project/core-plugin'
import { ADD_DEPLOY_OPTION, ADD_INSTANCE, ADD_PROVIDER, CLEAR_INSTANCES, CLEAR_RECORDER_COUNT, DISPLAY_NOTIFICATION, DISPLAY_POPUP_MESSAGE, FETCH_ACCOUNTS_LIST_FAILED, FETCH_ACCOUNTS_LIST_REQUEST, FETCH_ACCOUNTS_LIST_SUCCESS, FETCH_CONTRACT_LIST_FAILED, FETCH_CONTRACT_LIST_REQUEST, FETCH_CONTRACT_LIST_SUCCESS, HIDE_NOTIFICATION, HIDE_POPUP_MESSAGE, REMOVE_DEPLOY_OPTION, REMOVE_INSTANCE, REMOVE_PROVIDER, RESET_STATE, SET_BASE_FEE_PER_GAS, SET_CONFIRM_SETTINGS, SET_CURRENT_CONTRACT, SET_CURRENT_FILE, SET_DECODED_RESPONSE, SET_DEPLOY_OPTIONS, SET_EXECUTION_ENVIRONMENT, SET_EXTERNAL_WEB3_ENDPOINT, SET_GAS_LIMIT, SET_GAS_PRICE, SET_GAS_PRICE_STATUS, SET_IPFS_CHECKED_STATE, SET_LOAD_TYPE, SET_MATCH_PASSPHRASE, SET_MAX_FEE, SET_MAX_PRIORITY_FEE, SET_NETWORK_NAME, SET_PASSPHRASE, SET_PATH_TO_SCENARIO, SET_PERSONAL_MODE, SET_RECORDER_COUNT, SET_SELECTED_ACCOUNT, SET_SEND_UNIT, SET_SEND_VALUE, SET_TX_FEE_CONTENT } from '../constants'
import { DeployMode, DeployOptions } from '../types'
export const fetchAccountsListRequest = () => {
return {
type: 'FETCH_ACCOUNTS_LIST_REQUEST',
type: FETCH_ACCOUNTS_LIST_REQUEST,
payload: null
}
}
export const fetchAccountsListSuccess = (accounts: Record<string, string>) => {
return {
type: 'FETCH_ACCOUNTS_LIST_SUCCESS',
type: FETCH_ACCOUNTS_LIST_SUCCESS,
payload: accounts
}
}
export const fetchAccountsListFailed = (error: string) => {
return {
type: 'FETCH_ACCOUNTS_LIST_FAILED',
type: FETCH_ACCOUNTS_LIST_FAILED,
payload: error
}
}
export const setSendValue = (value: string) => {
return {
type: 'SET_SEND_VALUE',
type: SET_SEND_VALUE,
payload: value
}
}
export const setSelectedAccount = (account: string) => {
return {
type: 'SET_SELECTED_ACCOUNT',
type: SET_SELECTED_ACCOUNT,
payload: account
}
}
export const setSendUnit = (unit: 'ether' | 'finney' | 'gwei' | 'wei') => {
return {
type: 'SET_SEND_UNIT',
type: SET_SEND_UNIT,
payload: unit
}
}
export const setGasLimit = (gasLimit: number) => {
return {
type: 'SET_GAS_LIMIT',
type: SET_GAS_LIMIT,
payload: gasLimit
}
}
export const setExecutionEnvironment = (executionEnvironment: string) => {
return {
type: 'SET_EXECUTION_ENVIRONMENT',
type: SET_EXECUTION_ENVIRONMENT,
payload: executionEnvironment
}
}
export const setPersonalMode = (mode: boolean) => {
return {
type: 'SET_PERSONAL_MODE',
type: SET_PERSONAL_MODE,
payload: mode
}
}
export const setNetworkName = (networkName: string) => {
return {
type: 'SET_NETWORK_NAME',
type: SET_NETWORK_NAME,
payload: networkName
}
}
export const addProvider = (provider: string) => {
return {
type: 'ADD_PROVIDER',
type: ADD_PROVIDER,
payload: provider
}
}
export const removeProvider = (provider: string) => {
return {
type: 'REMOVE_PROVIDER',
type: REMOVE_PROVIDER,
payload: provider
}
}
export const displayNotification = (title: string, message: string | JSX.Element, labelOk: string, labelCancel: string, actionOk?: (...args) => void, actionCancel?: (...args) => void) => {
return {
type: 'DISPLAY_NOTIFICATION',
type: DISPLAY_NOTIFICATION,
payload: { title, message, labelOk, labelCancel, actionOk, actionCancel }
}
}
export const hideNotification = () => {
return {
type: 'HIDE_NOTIFICATION'
type: HIDE_NOTIFICATION
}
}
export const setExternalEndpoint = (endpoint: string) => {
return {
type: 'SET_EXTERNAL_WEB3_ENDPOINT',
type: SET_EXTERNAL_WEB3_ENDPOINT,
payload: endpoint
}
}
export const displayPopUp = (message: string | JSX.Element) => {
return {
type: 'DISPLAY_POPUP_MESSAGE',
type: DISPLAY_POPUP_MESSAGE,
payload: message
}
}
export const hidePopUp = () => {
return {
type: 'HIDE_POPUP_MESSAGE'
type: HIDE_POPUP_MESSAGE
}
}
export const setPassphrase = (passphrase: string) => {
return {
type: 'SET_PASSPHRASE',
type: SET_PASSPHRASE,
payload: passphrase
}
}
export const setMatchPassphrase = (passphrase: string) => {
return {
type: 'SET_MATCH_PASSPHRASE',
type: SET_MATCH_PASSPHRASE,
payload: passphrase
}
}
export const fetchContractListRequest = () => {
return {
type: 'FETCH_CONTRACT_LIST_REQUEST'
type: FETCH_CONTRACT_LIST_REQUEST
}
}
export const fetchContractListSuccess = (contracts: ContractList) => {
return {
type: 'FETCH_CONTRACT_LIST_SUCCESS',
type: FETCH_CONTRACT_LIST_SUCCESS,
payload: contracts
}
}
export const fetchContractListFailed = (error: string) => {
return {
type: 'FETCH_CONTRACT_LIST_FAILED',
type: FETCH_CONTRACT_LIST_FAILED,
payload: error
}
}
export const setLoadType = (type: 'abi' | 'sol' | 'other') => {
return {
type: 'SET_LOAD_TYPE',
type: SET_LOAD_TYPE,
payload: type
}
}
export const setCurrentFile = (file: string) => {
return {
type: 'SET_CURRENT_FILE',
type: SET_CURRENT_FILE,
payload: file
}
}
export const setIpfsCheckedState = (state: boolean) => {
return {
type: 'SET_IPFS_CHECKED_STATE',
type: SET_IPFS_CHECKED_STATE,
payload: state
}
}
export const setGasPriceStatus = (status: boolean) => {
return {
type: 'SET_GAS_PRICE_STATUS',
type: SET_GAS_PRICE_STATUS,
payload: status
}
}
export const setConfirmSettings = (confirmation: boolean) => {
return {
type: 'SET_CONFIRM_SETTINGS',
type: SET_CONFIRM_SETTINGS,
payload: confirmation
}
}
export const setMaxFee = (fee: string) => {
return {
type: 'SET_MAX_FEE',
type: SET_MAX_FEE,
payload: fee
}
}
export const setMaxPriorityFee = (fee: string) => {
return {
type: 'SET_MAX_PRIORITY_FEE',
type: SET_MAX_PRIORITY_FEE,
payload: fee
}
}
export const setBaseFeePerGas = (baseFee: string) => {
return {
type: 'SET_BASE_FEE_PER_GAS',
type: SET_BASE_FEE_PER_GAS,
payload: baseFee
}
}
export const setGasPrice = (price: string) => {
return {
type: 'SET_GAS_PRICE',
type: SET_GAS_PRICE,
payload: price
}
}
export const setTxFeeContent = (content: string) => {
return {
type: 'SET_TX_FEE_CONTENT',
type: SET_TX_FEE_CONTENT,
payload: content
}
}
export const addNewInstance = (instance: { contractData?: ContractData, address: string, name: string, abi?: any }) => {
return {
type: 'ADD_INSTANCE',
type: ADD_INSTANCE,
payload: instance
}
}
export const removeExistingInstance = (index: number) => {
return {
type: 'REMOVE_INSTANCE',
type: REMOVE_INSTANCE,
payload: index
}
}
export const clearAllInstances = () => {
return {
type: 'CLEAR_INSTANCES'
type: CLEAR_INSTANCES
}
}
export const setDecodedResponse = (instanceIndex: number, response, funcIndex?: number) => {
return {
type: 'SET_DECODED_RESPONSE',
type: SET_DECODED_RESPONSE,
payload: {
instanceIndex,
funcIndex,
@ -254,33 +256,54 @@ export const setDecodedResponse = (instanceIndex: number, response, funcIndex?:
export const setPathToScenario = (path: string) => {
return {
type: 'SET_PATH_TO_SCENARIO',
type: SET_PATH_TO_SCENARIO,
payload: path
}
}
export const setRecorderCount = (count: number) => {
return {
type: 'SET_RECORDER_COUNT',
type: SET_RECORDER_COUNT,
payload: count
}
}
export const clearRecorderCount = () => {
return {
type: 'CLEAR_RECORDER_COUNT'
type: CLEAR_RECORDER_COUNT
}
}
export const setEnvToasterContent = (content: (env: { context: string, fork: string }, from: string) => void) => {
export const resetUdapp = () => {
return {
type: 'SET_ENV_TOASTER_CONTENT',
payload: content
type: RESET_STATE
}
}
export const resetUdapp = () => {
export const addDeployOption = (deployOption: DeployOptions) => {
return {
payload: deployOption,
type: ADD_DEPLOY_OPTION
}
}
export const removeDeployOption = (title: DeployMode) => {
return {
payload: title,
type: REMOVE_DEPLOY_OPTION
}
}
export const setDeployOptions = (deployOptions: DeployOptions) => {
return {
payload: deployOptions,
type: SET_DEPLOY_OPTIONS
}
}
export const setCurrentContract = (contractName: string) => {
return {
type: 'RESET_STATE'
payload: contractName,
type: SET_CURRENT_CONTRACT
}
}

@ -0,0 +1,72 @@
import { createNonClashingNameAsync, extractNameFromKey, logBuilder } from "@remix-ui/helper"
import { MainnetPrompt } from "../types"
import { RunTab } from "../types/run-tab"
import { addInstance } from "./actions"
import { confirmationHandler, continueHandler, promptHandler, terminalLogger } from "./deploy"
import { displayNotification } from "./payload"
const saveScenario = async (plugin: RunTab, newPath: string, provider, promptCb, cb) => {
const txJSON = JSON.stringify(plugin.recorder.getAll(), null, 2)
promptCb(async () => {
try {
await provider.set(newPath, txJSON)
await plugin.fileManager.open(newPath)
} catch (error) {
if (error) return cb('Failed to create file. ' + newPath + ' ' + error)
}
})
}
export const storeScenario = async (plugin: RunTab, dispatch: React.Dispatch<any>, prompt: (msg: string, defaultValue: string) => JSX.Element) => {
const path = plugin.fileManager.currentPath()
const fileProvider = await plugin.fileManager.fileProviderOf(path)
if (!fileProvider) return displayNotification('Alert', 'Invalid File Provider', 'OK', null)
const newPath = await createNonClashingNameAsync(path + '/' + plugin.REACT_API.recorder.pathToScenario, plugin.fileManager)
const newName = extractNameFromKey(newPath)
saveScenario(plugin, newPath, fileProvider,
(cb) => {
dispatch(displayNotification('Save transactions as scenario', prompt('Transactions will be saved in a file under ' + path, newName), 'OK', 'Cancel', cb, null))
},
(error) => {
if (error) return dispatch(displayNotification('Alert', error, 'OK', null))
}
)
}
const runScenario = (plugin: RunTab, dispatch: React.Dispatch<any>, file: string, gasEstimationPrompt: (msg: string) => JSX.Element, passphrasePrompt: (msg: string) => JSX.Element, confirmDialogContent: MainnetPrompt) => {
if (!file) return dispatch(displayNotification('Alert', 'Unable to run scenerio, no specified scenario file', 'OK', null))
plugin.fileManager.readFile(file).then((json) => {
// TODO: there is still a UI dependency to remove here, it's still too coupled at this point to remove easily
plugin.recorder.runScenario(
json,
(error, continueTxExecution, cancelCb) => {
continueHandler(dispatch, gasEstimationPrompt, error, continueTxExecution, cancelCb)
}, (okCb, cancelCb) => {
promptHandler(dispatch, passphrasePrompt, okCb, cancelCb)
}, (msg) => {
dispatch(displayNotification('Alert', msg, 'OK', null))
}, (network, tx, gasEstimation, continueTxExecution, cancelCb) => {
confirmationHandler(plugin, dispatch, confirmDialogContent, network, tx, gasEstimation, continueTxExecution, cancelCb)
}, (msg: string) => {
const log = logBuilder(msg)
return terminalLogger(plugin, log)
}, (error, abi, address, contractName) => {
if (error) {
return dispatch(displayNotification('Alert', error, 'OK', null))
}
addInstance(dispatch, { name: contractName, address, abi })
})
}).catch((error) => dispatch(displayNotification('Alert', error, 'OK', null)))
}
export const runCurrentScenario = (plugin: RunTab, dispatch: React.Dispatch<any>, gasEstimationPrompt: (msg: string) => JSX.Element, passphrasePrompt: (msg: string) => JSX.Element, confirmDialogContent: MainnetPrompt) => {
const file = plugin.config.get('currentFile')
if (!file) return dispatch(displayNotification('Alert', 'A scenario file has to be selected', 'Ok', null))
runScenario(plugin, dispatch, file, gasEstimationPrompt, passphrasePrompt, confirmDialogContent)
}

@ -1,6 +1,6 @@
// eslint-disable-next-line no-use-before-define
import React, { useEffect, useRef, useState } from 'react'
import { ContractDropdownProps } from '../types'
import { ContractDropdownProps, DeployMode } from '../types'
import { ContractData, FuncABI } from '@remix-project/core-plugin'
import * as ethJSUtil from 'ethereumjs-util'
import { ContractGUI } from './contractGUI'
@ -22,13 +22,12 @@ export function ContractDropdownUI (props: ContractDropdownProps) {
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<ContractData>(null)
const [constructorInterface, setConstructorInterface] = useState<FuncABI>(null)
const [constructorInputs, setConstructorInputs] = useState(null)
const contractsRef = useRef<HTMLSelectElement>(null)
const { contractList, loadType, currentFile, compilationCount } = props.contracts
const { contractList, loadType, currentFile, currentContract, compilationCount, deployOptions } = props.contracts
useEffect(() => {
enableAtAddress(false)
@ -66,13 +65,13 @@ export function ContractDropdownUI (props: ContractDropdownProps) {
display: 'none',
content: ''
})
if (!selectedContract) enableAtAddress(false)
if (!currentContract) enableAtAddress(false)
} else {
setAbiLabel({
display: 'none',
content: ''
})
if (!selectedContract) enableAtAddress(false)
if (!currentContract) enableAtAddress(false)
}
if (currentFile) {
enableContractNames(true)
@ -85,11 +84,11 @@ export function ContractDropdownUI (props: ContractDropdownProps) {
}, [loadType, currentFile, compilationCount])
useEffect(() => {
if (selectedContract && contractList[currentFile]) {
const contract = contractList[currentFile].find(contract => contract.alias === selectedContract)
if (currentContract && contractList[currentFile]) {
const contract = contractList[currentFile].find(contract => contract.alias === currentContract)
if (contract) {
const loadedContractData = props.getSelectedContract(selectedContract, contract.compiler)
const loadedContractData = props.getSelectedContract(currentContract, contract.compiler)
if (loadedContractData) {
setLoadedContractData(loadedContractData)
@ -98,7 +97,7 @@ export function ContractDropdownUI (props: ContractDropdownProps) {
}
}
}
}, [selectedContract, compilationCount])
}, [currentContract, compilationCount])
useEffect(() => {
initSelectedContract()
@ -108,9 +107,10 @@ export function ContractDropdownUI (props: ContractDropdownProps) {
const contracts = contractList[currentFile]
if (contracts && contracts.length > 0) {
const contract = contracts.find(contract => contract.alias === selectedContract)
const contract = contracts.find(contract => contract.alias === currentContract)
if (!selectedContract || !contract) setSelectedContract(contracts[0].alias)
if (!currentContract) props.setSelectedContract(contracts[0].alias)
else if (!contract) props.setSelectedContract(currentContract)
// TODO highlight contractlist box with css.
}
}
@ -143,15 +143,15 @@ export function ContractDropdownUI (props: ContractDropdownProps) {
}
}
const clickCallback = (inputs, value) => {
createInstance(loadedContractData, value)
const clickCallback = (inputs, value, deployMode?: DeployMode[]) => {
createInstance(loadedContractData, value, deployMode)
}
const createInstance = (selectedContract, args) => {
const createInstance = (selectedContract, args, deployMode?: DeployMode[]) => {
if (selectedContract.bytecodeObject.length === 0) {
return props.modal('Alert', 'This contract may be abstract, it may not implement an abstract parent\'s methods completely or it may not invoke an inherited contract\'s constructor correctly.', 'OK', () => {})
}
props.createInstance(loadedContractData, props.gasEstimationPrompt, props.passphrasePrompt, props.logBuilder, props.publishToStorage, props.mainnetPrompt, isOverSizePrompt, args)
props.createInstance(loadedContractData, props.gasEstimationPrompt, props.passphrasePrompt, props.publishToStorage, props.mainnetPrompt, isOverSizePrompt, args, deployMode)
}
const atAddressChanged = (event) => {
@ -189,7 +189,7 @@ export function ContractDropdownUI (props: ContractDropdownProps) {
const handleContractChange = (e) => {
const value = e.target.value
setSelectedContract(value)
props.setSelectedContract(value)
}
const checkSumWarning = () => {
@ -214,19 +214,29 @@ export function ContractDropdownUI (props: ContractDropdownProps) {
<div className="udapp_container" data-id="contractDropdownContainer">
<label className="udapp_settingsLabel">Contract</label>
<div className="udapp_subcontainer">
<select ref={contractsRef} value={selectedContract} onChange={handleContractChange} className="udapp_contractNames custom-select" disabled={contractOptions.disabled} title={contractOptions.title} style={{ display: loadType === 'abi' ? 'none' : 'block' }}>
<select ref={contractsRef} value={currentContract} onChange={handleContractChange} className="udapp_contractNames custom-select" disabled={contractOptions.disabled} title={contractOptions.title} style={{ display: loadType === 'abi' ? 'none' : 'block' }}>
{ (contractList[currentFile] || []).map((contract, index) => {
return <option key={index} value={contract.alias}>{contract.alias} - {contract.file}</option>
}) }
</select>
{ (contractList[currentFile] || []).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">
{ ((contractList[currentFile] && contractList[currentFile].filter(contract => contract)) || []).length <= 0 ? 'No compiled contracts'
: loadedContractData ? <div>
<ContractGUI title='Deploy' funcABI={constructorInterface} clickCallBack={clickCallback} inputs={constructorInputs} widthClass='w-50' evmBC={loadedContractData.bytecodeObject} lookupOnly={false} />
<ContractGUI
title='Deploy'
isDeploy={true}
deployOption={deployOptions.options}
initializerOptions={deployOptions.initializeOptions ? deployOptions.initializeOptions[currentContract] : null}
funcABI={constructorInterface}
clickCallBack={clickCallback}
inputs={constructorInputs}
widthClass='w-50'
evmBC={loadedContractData.bytecodeObject}
lookupOnly={false}
/>
<div className="d-flex py-1 align-items-center custom-control custom-checkbox">
<input
id="deployAndRunPublishToIPFS"

@ -3,6 +3,8 @@ 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'
import { MultiDeployInput } from './multiDeployInput'
import { DeployInput } from './deployInput'
const txFormat = remixLib.execution.txFormat
export function ContractGUI (props: ContractGUIProps) {
@ -13,8 +15,19 @@ export function ContractGUI (props: ContractGUIProps) {
title: string,
content: string,
classList: string,
dataId: string
}>({ title: '', content: '', classList: '', dataId: '' })
dataId: string,
widthClass: string
}>({ title: '', content: '', classList: '', dataId: '', widthClass: '' })
const [selectedDeployIndex, setSelectedDeployIndex] = useState<number>(null)
const [showOptions, setShowOptions] = useState<boolean>(false)
const [hasArgs, setHasArgs] = useState<boolean>(false)
const [isMultiField, setIsMultiField] = useState<boolean>(false)
const [deployInputs, setDeployInputs] = useState<{
internalType?: string,
name: string,
type: string
}[]>([])
const [deployPlaceholder, setDeployPlaceholder] = useState<string>('')
const multiFields = useRef<Array<HTMLInputElement | null>>([])
const basicInputRef = useRef<HTMLInputElement>()
@ -28,7 +41,7 @@ export function ContractGUI (props: ContractGUIProps) {
}
setBasicInput('')
// we have the reset the fields before reseting the previous references.
basicInputRef.current.value = ''
if (basicInputRef.current) basicInputRef.current.value = ''
multiFields.current.filter((el) => el !== null && el !== undefined).forEach((el) => el.value = '')
multiFields.current = []
}, [props.title, props.funcABI])
@ -40,7 +53,8 @@ export function ContractGUI (props: ContractGUIProps) {
title: title + ' - call',
content: 'call',
classList: 'btn-info',
dataId: title + ' - call'
dataId: title + ' - call',
widthClass: props.widthClass
})
} else if (props.funcABI.stateMutability === 'payable' || props.funcABI.payable) {
// // transact. stateMutability = payable
@ -48,7 +62,8 @@ export function ContractGUI (props: ContractGUIProps) {
title: title + ' - transact (payable)',
content: 'transact',
classList: 'btn-danger',
dataId: title + ' - transact (payable)'
dataId: title + ' - transact (payable)',
widthClass: props.widthClass
})
} else {
// // transact. stateMutability = nonpayable
@ -56,13 +71,59 @@ export function ContractGUI (props: ContractGUIProps) {
title: title + ' - transact (not payable)',
content: 'transact',
classList: 'btn-warning',
dataId: title + ' - transact (not payable)'
dataId: title + ' - transact (not payable)',
widthClass: props.widthClass
})
}
}, [props.lookupOnly, props.funcABI, title])
const getContentOnCTC = () => {
const multiString = getMultiValsString()
useEffect(() => {
if (props.deployOption && props.deployOption[selectedDeployIndex]) {
if (props.deployOption[selectedDeployIndex].title === 'Deploy with Proxy') {
if (props.initializerOptions) {
setDeployInputs(props.initializerOptions.inputs.inputs)
setDeployPlaceholder(props.initializerOptions.initializeInputs)
setHasArgs(true)
if (props.initializerOptions.inputs.inputs.length > 1) setIsMultiField(true)
else setIsMultiField(false)
} else {
setDeployInputs([])
setDeployPlaceholder('')
setHasArgs(false)
setIsMultiField(false)
}
} else {
if (props.funcABI) {
setDeployInputs(props.funcABI.inputs)
setDeployPlaceholder(props.inputs)
setHasArgs(true)
if (props.funcABI.inputs.length > 1) setIsMultiField(true)
else setIsMultiField(false)
} else {
setDeployInputs([])
setDeployPlaceholder('')
setHasArgs(false)
setIsMultiField(false)
}
}
} else {
if (props.funcABI) {
setDeployInputs(props.funcABI.inputs)
setDeployPlaceholder(props.inputs)
setHasArgs(true)
if (props.funcABI.inputs.length > 1) setIsMultiField(true)
else setIsMultiField(false)
} else {
setDeployInputs([])
setDeployPlaceholder('')
setHasArgs(false)
setIsMultiField(false)
}
}
}, [selectedDeployIndex, props.funcABI, props.initializerOptions])
const getContentOnCTC = (fields: HTMLInputElement[]) => {
const multiString = getMultiValsString(fields)
// copy-to-clipboard icon is only visible for method requiring input params
if (!multiString) {
return 'cannot encode empty arguments'
@ -89,13 +150,13 @@ export function ContractGUI (props: ContractGUIProps) {
const switchMethodViewOff = () => {
setToggleContainer(false)
const multiValString = getMultiValsString()
const multiValString = getMultiValsString(multiFields.current)
if (multiValString) setBasicInput(multiValString)
}
const getMultiValsString = () => {
const valArray = multiFields.current
const getMultiValsString = (fields: HTMLInputElement[]) => {
const valArray = fields
let ret = ''
const valArrayTest = []
@ -130,7 +191,8 @@ export function ContractGUI (props: ContractGUIProps) {
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 + ']')
inputString = JSON.stringify([inputString])
const inputJSON = JSON.parse(inputString)
const multiInputs = multiFields.current
for (let k = 0; k < multiInputs.length; k++) {
@ -142,7 +204,9 @@ export function ContractGUI (props: ContractGUIProps) {
}
const handleActionClick = () => {
props.clickCallBack(props.funcABI.inputs, basicInput)
const deployMode = selectedDeployIndex !== null ? [props.deployOption[selectedDeployIndex].title] : []
props.clickCallBack(props.funcABI.inputs, basicInput, deployMode)
}
const handleBasicInput = (e) => {
@ -151,18 +215,51 @@ export function ContractGUI (props: ContractGUIProps) {
setBasicInput(value)
}
const handleExpandMultiClick = () => {
const valsString = getMultiValsString()
const handleMultiValsSubmit = (fields: HTMLInputElement[]) => {
const valsString = getMultiValsString(fields)
const deployMode = selectedDeployIndex !== null ? [props.deployOption[selectedDeployIndex].title] : []
if (valsString) {
props.clickCallBack(props.funcABI.inputs, valsString)
props.clickCallBack(props.funcABI.inputs, valsString, deployMode)
} else {
props.clickCallBack(props.funcABI.inputs, '')
props.clickCallBack(props.funcABI.inputs, '', deployMode)
}
}
const setSelectedDeploy = (index: number) => {
setSelectedDeployIndex(index !== selectedDeployIndex ? index : null)
if (basicInputRef.current) basicInputRef.current.value = ''
setBasicInput('')
}
const toggleOptions = () => {
setShowOptions(!showOptions)
}
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_contractProperty ${hasArgs ? 'udapp_hasArgs' : ''}`}>
{
props.isDeploy ? !isMultiField ?
<DeployInput
buttonOptions={buttonOptions}
funcABI={props.initializerOptions ? props.initializerOptions.inputs : props.funcABI}
inputs={deployPlaceholder}
handleBasicInput={handleBasicInput}
basicInputRef={basicInputRef}
selectedIndex={selectedDeployIndex}
setSelectedIndex={setSelectedDeploy}
handleActionClick={handleActionClick}
deployOptions={props.deployOption}
/> : <MultiDeployInput
buttonOptions={buttonOptions}
selectedIndex={selectedDeployIndex}
setSelectedIndex={setSelectedDeploy}
handleMultiValsSubmit={handleMultiValsSubmit}
inputs={deployInputs}
getMultiValsString={getMultiValsString}
deployOptions={props.deployOption}
/> :
<>
<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
@ -195,11 +292,13 @@ export function ContractGUI (props: ContractGUIProps) {
})}
</div>
<div className="udapp_group udapp_multiArg">
<CopyToClipboard tip='Encode values of input fields & copy to clipboard' icon='fa-clipboard' direction={'left'} getContent={getContentOnCTC} />
<button onClick={handleExpandMultiClick} title={buttonOptions.title} data-id={buttonOptions.dataId} className={`udapp_instanceButton ${buttonOptions.classList}`}>{ buttonOptions.content }</button>
<CopyToClipboard tip='Encode values of input fields & copy to clipboard' icon='fa-clipboard' direction={'left'} getContent={() => getContentOnCTC(multiFields.current)} />
<button onClick={() => handleMultiValsSubmit(multiFields.current)} title={buttonOptions.title} data-id={buttonOptions.dataId} className={`udapp_instanceButton ${buttonOptions.classList}`}>{ buttonOptions.content }</button>
</div>
</div>
</div>
</>
}
</div>
)
}

@ -0,0 +1,33 @@
import React, { useState } from 'react'
import { DeployButtonProps } from '../types'
import { ButtonGroup, Dropdown } from 'react-bootstrap'
export function DeployButton (props: DeployButtonProps) {
const [showOptions, setShowOptions] = useState<boolean>(false)
const toggleOptions = () => {
setShowOptions(!showOptions)
}
return (
<>
{ props.deployOptions && (props.deployOptions || []).length > 0 ?
<Dropdown as={ButtonGroup}>
<button onClick={props.handleActionClick} title={props.buttonOptions.title} className={`udapp_instanceButton ${props.buttonOptions.widthClass} btn btn-sm ${props.buttonOptions.classList}`} data-id={props.buttonOptions.dataId}>{ props.deployOptions[props.selectedIndex] ? props.deployOptions[props.selectedIndex].title : 'Deploy' }</button>
<Dropdown.Toggle split id="dropdown-split-basic" className={`btn btn-sm dropdown-toggle dropdown-toggle-split ${props.buttonOptions.classList}`} style={{ maxWidth: 25, minWidth: 0, height: 32 }} />
<Dropdown.Menu className="deploy-items border-0">
{
(props.deployOptions).map(({ title, active }, index) => <Dropdown.Item onClick={() => {
props.setSelectedIndex(index)
toggleOptions()
}} key={index}> { props.selectedIndex === index ? <span>&#10003; {title} </span> : <span className="pl-3">{title}</span> }</Dropdown.Item>)
}
</Dropdown.Menu>
</Dropdown> :
<button onClick={props.handleActionClick} title={props.buttonOptions.title} className={`udapp_instanceButton ${props.buttonOptions.widthClass} btn btn-sm ${props.buttonOptions.classList}`} data-id={props.buttonOptions.dataId}>
Deploy
</button>
}
</>
)
}

@ -0,0 +1,20 @@
import React from 'react'
import { DeployInputProps } from '../types'
import { DeployButton } from './deployButton'
export function DeployInput (props: DeployInputProps) {
return (
<div className="udapp_contractActionsContainerSingle pt-2" style={{ display: 'flex' }}>
<DeployButton buttonOptions={props.buttonOptions} selectedIndex={props.selectedIndex} setSelectedIndex={props.setSelectedIndex} handleActionClick={props.handleActionClick} deployOptions={props.deployOptions} />
<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={props.handleBasicInput}
ref={props.basicInputRef}
style={{ visibility: !props.inputs ? 'hidden' : 'visible' }}
/>
</div>
)
}

@ -30,7 +30,6 @@ export function InstanceContainerUI (props: InstanceContainerProps) {
removeInstance={props.removeInstance}
index={index}
gasEstimationPrompt={props.gasEstimationPrompt}
logBuilder={props.logBuilder}
passphrasePrompt={props.passphrasePrompt}
mainnetPrompt={props.mainnetPrompt}
runTransactions={props.runTransactions}

@ -22,7 +22,6 @@ export function MainnetPrompt (props: MainnetProps) {
}, [])
const onMaxFeeChange = (value: string) => {
console.log('value: ', value)
const maxFee = value
// @ts-ignore
if (parseInt(props.network.lastBlock.baseFeePerGas, 16) > Web3.utils.toWei(maxFee, 'Gwei')) {

@ -0,0 +1,30 @@
import React, { useRef, useState } from 'react'
import { MultiDeployInputProps } from '../types'
import { DeployButton } from './deployButton'
export function MultiDeployInput (props: MultiDeployInputProps) {
const multiFields = useRef<Array<HTMLInputElement | null>>([])
return (
<div className="udapp_contractActionsContainerMulti" style={{ display: 'flex' }}>
<div className="udapp_contractActionsContainerMultiInner text-dark">
<div className="udapp_multiHeader">
<div className="udapp_multiTitle run-instance-multi-title">Deploy</div>
</div>
<div>
{props.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 tip='Encode values of input fields & copy to clipboard' icon='fa-clipboard' direction={'left'} getContent={getContentOnCTC} /> */}
<DeployButton buttonOptions={props.buttonOptions} selectedIndex={props.selectedIndex} setSelectedIndex={props.setSelectedIndex} handleActionClick={() => { props.handleMultiValsSubmit(multiFields.current) }} deployOptions={props.deployOptions} />
</div>
</div>
</div>
)
}

@ -9,7 +9,7 @@ export function RecorderUI (props: RecorderProps) {
}
const handleClickRunButton = () => {
props.runCurrentScenario(props.gasEstimationPrompt, props.passphrasePrompt, props.mainnetPrompt, props.logBuilder)
props.runCurrentScenario(props.gasEstimationPrompt, props.passphrasePrompt, props.mainnetPrompt)
}
const toggleClass = () => {

@ -119,7 +119,6 @@ export function UniversalDappUI (props: UdappProps) {
props.instance.contractData,
address,
logMsg,
props.logBuilder,
props.mainnetPrompt,
props.gasEstimationPrompt,
props.passphrasePrompt,
@ -184,7 +183,7 @@ export function UniversalDappUI (props: UdappProps) {
const renderData = (item, parent, key: string | number, keyPath: string) => {
const data = extractDataDefault(item, parent)
const children = (data.children || []).map((child) => {
const children = (data.children || []).map((child, index) => {
return (
renderData(child.value, data, child.key, keyPath + '/' + child.key)
)
@ -237,7 +236,7 @@ export function UniversalDappUI (props: UdappProps) {
const lookupOnly = funcABI.stateMutability === 'view' || funcABI.stateMutability === 'pure' || isConstant
const inputs = props.getFuncABIInputs(funcABI)
return <>
return <div key={index}>
<ContractGUI
funcABI={funcABI}
clickCallBack={(valArray: { name: string, type: string }[], inputsValues: string) => {
@ -255,14 +254,14 @@ export function UniversalDappUI (props: UdappProps) {
const funcIndex = index.toString()
const response = props.instance.decodedResponse[key]
return key === funcIndex ? Object.keys(response || {}).map((innerkey) => {
return key === funcIndex ? Object.keys(response || {}).map((innerkey, index) => {
return renderData(props.instance.decodedResponse[key][innerkey], response, innerkey, innerkey)
}) : null
})
}
</TreeView>
</div>
</>
</div>
})
}
</div>

@ -0,0 +1,47 @@
export const FETCH_ACCOUNTS_LIST_REQUEST = 'FETCH_ACCOUNTS_LIST_REQUEST'
export const FETCH_ACCOUNTS_LIST_SUCCESS = 'FETCH_ACCOUNTS_LIST_SUCCESS'
export const FETCH_ACCOUNTS_LIST_FAILED = 'FETCH_ACCOUNTS_LIST_FAILED'
export const SET_SEND_VALUE = 'SET_SEND_VALUE'
export const SET_SELECTED_ACCOUNT = 'SET_SELECTED_ACCOUNT'
export const SET_SEND_UNIT = 'SET_SEND_UNIT'
export const SET_GAS_LIMIT = 'SET_GAS_LIMIT'
export const SET_EXECUTION_ENVIRONMENT = 'SET_EXECUTION_ENVIRONMENT'
export const SET_PERSONAL_MODE = 'SET_PERSONAL_MODE'
export const SET_NETWORK_NAME = 'SET_NETWORK_NAME'
export const FETCH_PROVIDER_LIST_REQUEST = 'FETCH_PROVIDER_LIST_REQUEST'
export const FETCH_PROVIDER_LIST_SUCCESS = 'FETCH_PROVIDER_LIST_SUCCESS'
export const FETCH_PROVIDER_LIST_FAILED = 'FETCH_PROVIDER_LIST_FAILED'
export const ADD_PROVIDER = 'ADD_PROVIDER'
export const REMOVE_PROVIDER = 'REMOVE_PROVIDER'
export const DISPLAY_NOTIFICATION = 'DISPLAY_NOTIFICATION'
export const HIDE_NOTIFICATION = 'HIDE_NOTIFICATION'
export const SET_EXTERNAL_WEB3_ENDPOINT = 'SET_EXTERNAL_WEB3_ENDPOINT'
export const DISPLAY_POPUP_MESSAGE = 'DISPLAY_POPUP_MESSAGE'
export const HIDE_POPUP_MESSAGE = 'HIDE_POPUP_MESSAGE'
export const SET_PASSPHRASE = 'SET_PASSPHRASE'
export const SET_MATCH_PASSPHRASE = 'SET_MATCH_PASSPHRASE'
export const FETCH_CONTRACT_LIST_REQUEST = 'FETCH_CONTRACT_LIST_REQUEST'
export const FETCH_CONTRACT_LIST_SUCCESS = 'FETCH_CONTRACT_LIST_SUCCESS'
export const FETCH_CONTRACT_LIST_FAILED = 'FETCH_CONTRACT_LIST_FAILED'
export const SET_LOAD_TYPE = 'SET_LOAD_TYPE'
export const SET_CURRENT_FILE = 'SET_CURRENT_FILE'
export const SET_IPFS_CHECKED_STATE = 'SET_IPFS_CHECKED_STATE'
export const SET_GAS_PRICE_STATUS = 'SET_GAS_PRICE_STATUS'
export const SET_CONFIRM_SETTINGS = 'SET_CONFIRM_SETTINGS'
export const SET_MAX_FEE = 'SET_MAX_FEE'
export const SET_MAX_PRIORITY_FEE = 'SET_MAX_PRIORITY_FEE'
export const SET_BASE_FEE_PER_GAS = 'SET_BASE_FEE_PER_GAS'
export const SET_GAS_PRICE = 'SET_GAS_PRICE'
export const SET_TX_FEE_CONTENT = 'SET_TX_FEE_CONTENT'
export const ADD_INSTANCE = 'ADD_INSTANCE'
export const REMOVE_INSTANCE = 'REMOVE_INSTANCE'
export const CLEAR_INSTANCES = 'CLEAR_INSTANCES'
export const SET_DECODED_RESPONSE = 'SET_DECODED_RESPONSE'
export const SET_PATH_TO_SCENARIO = 'SET_PATH_TO_SCENARIO'
export const SET_RECORDER_COUNT = 'SET_RECORDER_COUNT'
export const CLEAR_RECORDER_COUNT = 'CLEAR_RECORDER_COUNT'
export const RESET_STATE = 'RESET_STATE'
export const ADD_DEPLOY_OPTION = 'ADD_DEPLOY_OPTION'
export const REMOVE_DEPLOY_OPTION = 'REMOVE_DEPLOY_OPTION'
export const SET_DEPLOY_OPTIONS = 'SET_DEPLOY_OPTIONS'
export const SET_CURRENT_CONTRACT = 'SET_CURRENT_CONTRACT'

@ -401,7 +401,6 @@
}
.udapp_contractActionsContainerMultiInner {
width: 100%;
padding: 16px 8px 16px 14px;
border-radius: 3px;
margin-bottom: 8px;
}
@ -513,4 +512,22 @@
white-space: -o-pre-wrap; /* Opera 7 */
word-wrap: break-word; /* Internet Explorer 5.5+ */
}
.deploy-items {
padding: 0.25rem 0.25rem;
border-radius: .25rem;
}
.deploy-items a {
border-radius: .25rem;
text-transform: none;
text-decoration: none;
font-weight: normal;
font-size: .8rem;
padding: 0.25rem 0.25rem;
width:auto;
}
.dropdown-item.hover, .dropdown-item:hover {
color: #fff;
text-decoration: none;
background-color: #007aa6;
}

@ -1,5 +1,7 @@
import { CompilerAbstract } from '@remix-project/remix-solidity-ts'
import { ContractData } from '@remix-project/core-plugin'
import { DeployMode, DeployOption, DeployOptions } from '../types'
import { ADD_DEPLOY_OPTION, ADD_INSTANCE, ADD_PROVIDER, CLEAR_INSTANCES, CLEAR_RECORDER_COUNT, DISPLAY_NOTIFICATION, DISPLAY_POPUP_MESSAGE, FETCH_ACCOUNTS_LIST_FAILED, FETCH_ACCOUNTS_LIST_REQUEST, FETCH_ACCOUNTS_LIST_SUCCESS, FETCH_CONTRACT_LIST_FAILED, FETCH_CONTRACT_LIST_REQUEST, FETCH_CONTRACT_LIST_SUCCESS, FETCH_PROVIDER_LIST_FAILED, FETCH_PROVIDER_LIST_REQUEST, FETCH_PROVIDER_LIST_SUCCESS, HIDE_NOTIFICATION, HIDE_POPUP_MESSAGE, REMOVE_DEPLOY_OPTION, REMOVE_INSTANCE, REMOVE_PROVIDER, RESET_STATE, SET_BASE_FEE_PER_GAS, SET_CONFIRM_SETTINGS, SET_CURRENT_CONTRACT, SET_CURRENT_FILE, SET_DECODED_RESPONSE, SET_DEPLOY_OPTIONS, SET_EXECUTION_ENVIRONMENT, SET_EXTERNAL_WEB3_ENDPOINT, SET_GAS_LIMIT, SET_GAS_PRICE, SET_GAS_PRICE_STATUS, SET_IPFS_CHECKED_STATE, SET_LOAD_TYPE, SET_MATCH_PASSPHRASE, SET_MAX_FEE, SET_MAX_PRIORITY_FEE, SET_NETWORK_NAME, SET_PASSPHRASE, SET_PATH_TO_SCENARIO, SET_PERSONAL_MODE, SET_RECORDER_COUNT, SET_SELECTED_ACCOUNT, SET_SEND_UNIT, SET_SEND_VALUE, SET_TX_FEE_CONTENT } from '../constants'
interface Action {
type: string
payload: any
@ -62,8 +64,10 @@ export interface RunTabState {
compiler: CompilerAbstract
}[]
},
deployOptions: DeployOptions
loadType: 'abi' | 'sol' | 'other'
currentFile: string,
currentContract: string,
compilationCount: number,
isRequesting: boolean,
isSuccessful: boolean,
@ -154,8 +158,10 @@ export const runTabInitialState: RunTabState = {
matchPassphrase: '',
contracts: {
contractList: {},
deployOptions: {} as any,
loadType: 'other',
currentFile: '',
currentContract: '',
compilationCount: 0,
isRequesting: false,
isSuccessful: false,
@ -186,7 +192,7 @@ type AddProvider = {
export const runTabReducer = (state: RunTabState = runTabInitialState, action: Action) => {
switch (action.type) {
case 'FETCH_ACCOUNTS_LIST_REQUEST': {
case FETCH_ACCOUNTS_LIST_REQUEST: {
return {
...state,
accounts: {
@ -198,7 +204,7 @@ export const runTabReducer = (state: RunTabState = runTabInitialState, action: A
}
}
case 'FETCH_ACCOUNTS_LIST_SUCCESS': {
case FETCH_ACCOUNTS_LIST_SUCCESS: {
const payload: Record<string, string> = action.payload
return {
@ -213,7 +219,7 @@ export const runTabReducer = (state: RunTabState = runTabInitialState, action: A
}
}
case 'FETCH_ACCOUNTS_LIST_FAILED': {
case FETCH_ACCOUNTS_LIST_FAILED: {
const payload: string = action.payload
return {
@ -227,7 +233,7 @@ export const runTabReducer = (state: RunTabState = runTabInitialState, action: A
}
}
case 'SET_SEND_VALUE': {
case SET_SEND_VALUE: {
const payload: string = action.payload
return {
@ -236,7 +242,7 @@ export const runTabReducer = (state: RunTabState = runTabInitialState, action: A
}
}
case 'SET_SELECTED_ACCOUNT': {
case SET_SELECTED_ACCOUNT: {
const payload: string = action.payload
return {
@ -248,7 +254,7 @@ export const runTabReducer = (state: RunTabState = runTabInitialState, action: A
}
}
case 'SET_SEND_UNIT': {
case SET_SEND_UNIT: {
const payload: 'ether' | 'finney' | 'gwei' | 'wei' = action.payload
return {
@ -257,7 +263,7 @@ export const runTabReducer = (state: RunTabState = runTabInitialState, action: A
}
}
case 'SET_GAS_LIMIT': {
case SET_GAS_LIMIT: {
const payload: number = action.payload
return {
@ -266,7 +272,7 @@ export const runTabReducer = (state: RunTabState = runTabInitialState, action: A
}
}
case 'SET_EXECUTION_ENVIRONMENT': {
case SET_EXECUTION_ENVIRONMENT: {
const payload: string = action.payload
return {
@ -281,7 +287,7 @@ export const runTabReducer = (state: RunTabState = runTabInitialState, action: A
}
}
case 'SET_PERSONAL_MODE': {
case SET_PERSONAL_MODE: {
const payload: boolean = action.payload
return {
@ -290,7 +296,7 @@ export const runTabReducer = (state: RunTabState = runTabInitialState, action: A
}
}
case 'SET_NETWORK_NAME': {
case SET_NETWORK_NAME: {
const payload: string = action.payload
return {
@ -299,7 +305,7 @@ export const runTabReducer = (state: RunTabState = runTabInitialState, action: A
}
}
case 'FETCH_PROVIDER_LIST_REQUEST': {
case FETCH_PROVIDER_LIST_REQUEST: {
return {
...state,
providers: {
@ -311,7 +317,7 @@ export const runTabReducer = (state: RunTabState = runTabInitialState, action: A
}
}
case 'FETCH_PROVIDER_LIST_SUCCESS': {
case FETCH_PROVIDER_LIST_SUCCESS: {
const payload: { id?: string, dataId?: string, title?: string, value: string, fork?: string, content: string }[] = action.payload
return {
@ -326,7 +332,7 @@ export const runTabReducer = (state: RunTabState = runTabInitialState, action: A
}
}
case 'FETCH_PROVIDER_LIST_FAILED': {
case FETCH_PROVIDER_LIST_FAILED: {
const payload: string = action.payload
return {
@ -340,7 +346,7 @@ export const runTabReducer = (state: RunTabState = runTabInitialState, action: A
}
}
case 'ADD_PROVIDER': {
case ADD_PROVIDER: {
const payload: AddProvider = action.payload
const id = action.payload.name
state.providers.providerList.push({
@ -359,7 +365,7 @@ export const runTabReducer = (state: RunTabState = runTabInitialState, action: A
}
}
case 'REMOVE_PROVIDER': {
case REMOVE_PROVIDER: {
const id: string = action.payload
const providers = state.providers.providerList.filter((el) => el.id !== id)
return {
@ -371,7 +377,7 @@ export const runTabReducer = (state: RunTabState = runTabInitialState, action: A
}
}
case 'DISPLAY_NOTIFICATION': {
case DISPLAY_NOTIFICATION: {
const payload = action.payload as { title: string, message: string, actionOk: () => void, actionCancel: () => void, labelOk: string, labelCancel: string }
return {
@ -387,14 +393,14 @@ export const runTabReducer = (state: RunTabState = runTabInitialState, action: A
}
}
case 'HIDE_NOTIFICATION': {
case HIDE_NOTIFICATION: {
return {
...state,
notification: runTabInitialState.notification
}
}
case 'SET_EXTERNAL_WEB3_ENDPOINT': {
case SET_EXTERNAL_WEB3_ENDPOINT: {
const payload: string = action.payload
return {
@ -403,7 +409,7 @@ export const runTabReducer = (state: RunTabState = runTabInitialState, action: A
}
}
case 'DISPLAY_POPUP_MESSAGE': {
case DISPLAY_POPUP_MESSAGE: {
const payload = action.payload as string
return {
@ -412,14 +418,14 @@ export const runTabReducer = (state: RunTabState = runTabInitialState, action: A
}
}
case 'HIDE_POPUP_MESSAGE': {
case HIDE_POPUP_MESSAGE: {
return {
...state,
popup: ''
}
}
case 'SET_PASSPHRASE': {
case SET_PASSPHRASE: {
const passphrase: string = action.payload
return {
@ -428,7 +434,7 @@ export const runTabReducer = (state: RunTabState = runTabInitialState, action: A
}
}
case 'SET_MATCH_PASSPHRASE': {
case SET_MATCH_PASSPHRASE: {
const passphrase: string = action.payload
return {
@ -437,7 +443,7 @@ export const runTabReducer = (state: RunTabState = runTabInitialState, action: A
}
}
case 'FETCH_CONTRACT_LIST_REQUEST': {
case FETCH_CONTRACT_LIST_REQUEST: {
return {
...state,
contracts: {
@ -449,7 +455,7 @@ export const runTabReducer = (state: RunTabState = runTabInitialState, action: A
}
}
case 'FETCH_CONTRACT_LIST_SUCCESS': {
case FETCH_CONTRACT_LIST_SUCCESS: {
const payload: ContractList = action.payload
return {
@ -464,7 +470,7 @@ export const runTabReducer = (state: RunTabState = runTabInitialState, action: A
}
}
case 'FETCH_CONTRACT_LIST_FAILED': {
case FETCH_CONTRACT_LIST_FAILED: {
const payload: string = action.payload
return {
@ -478,7 +484,19 @@ export const runTabReducer = (state: RunTabState = runTabInitialState, action: A
}
}
case 'SET_LOAD_TYPE': {
case SET_CURRENT_CONTRACT: {
const payload: string = action.payload
return {
...state,
contracts: {
...state.contracts,
currentContract: payload
}
}
}
case SET_LOAD_TYPE: {
const payload: 'abi' | 'sol' | 'other' = action.payload
return {
@ -490,7 +508,7 @@ export const runTabReducer = (state: RunTabState = runTabInitialState, action: A
}
}
case 'SET_CURRENT_FILE': {
case SET_CURRENT_FILE: {
const payload: string = action.payload
return {
@ -503,7 +521,7 @@ export const runTabReducer = (state: RunTabState = runTabInitialState, action: A
}
}
case 'SET_IPFS_CHECKED_STATE': {
case SET_IPFS_CHECKED_STATE: {
const payload: boolean = action.payload
return {
@ -512,7 +530,7 @@ export const runTabReducer = (state: RunTabState = runTabInitialState, action: A
}
}
case 'SET_GAS_PRICE_STATUS': {
case SET_GAS_PRICE_STATUS: {
const payload: boolean = action.payload
return {
@ -521,7 +539,7 @@ export const runTabReducer = (state: RunTabState = runTabInitialState, action: A
}
}
case 'SET_CONFIRM_SETTINGS': {
case SET_CONFIRM_SETTINGS: {
const payload: boolean = action.payload
return {
@ -530,7 +548,7 @@ export const runTabReducer = (state: RunTabState = runTabInitialState, action: A
}
}
case 'SET_MAX_FEE': {
case SET_MAX_FEE: {
const payload: string = action.payload
return {
@ -539,7 +557,7 @@ export const runTabReducer = (state: RunTabState = runTabInitialState, action: A
}
}
case 'SET_MAX_PRIORITY_FEE': {
case SET_MAX_PRIORITY_FEE: {
const payload: string = action.payload
return {
@ -548,7 +566,7 @@ export const runTabReducer = (state: RunTabState = runTabInitialState, action: A
}
}
case 'SET_BASE_FEE_PER_GAS': {
case SET_BASE_FEE_PER_GAS: {
const payload: string = action.payload
return {
@ -557,7 +575,7 @@ export const runTabReducer = (state: RunTabState = runTabInitialState, action: A
}
}
case 'SET_GAS_PRICE': {
case SET_GAS_PRICE: {
const payload: string = action.payload
return {
@ -566,7 +584,7 @@ export const runTabReducer = (state: RunTabState = runTabInitialState, action: A
}
}
case 'SET_TX_FEE_CONTENT': {
case SET_TX_FEE_CONTENT: {
const payload: string = action.payload
return {
@ -575,7 +593,7 @@ export const runTabReducer = (state: RunTabState = runTabInitialState, action: A
}
}
case 'ADD_INSTANCE': {
case ADD_INSTANCE: {
const payload: { contractData: ContractData, address: string, name: string, abi?: any, decodedResponse?: Record<number, any> } = action.payload
return {
@ -587,7 +605,7 @@ export const runTabReducer = (state: RunTabState = runTabInitialState, action: A
}
}
case 'REMOVE_INSTANCE': {
case REMOVE_INSTANCE: {
const payload: number = action.payload
return {
@ -599,7 +617,7 @@ export const runTabReducer = (state: RunTabState = runTabInitialState, action: A
}
}
case 'CLEAR_INSTANCES': {
case CLEAR_INSTANCES: {
return {
...state,
instances: {
@ -609,7 +627,7 @@ export const runTabReducer = (state: RunTabState = runTabInitialState, action: A
}
}
case 'SET_DECODED_RESPONSE': {
case SET_DECODED_RESPONSE: {
const payload: { instanceIndex: number, funcIndex: number, response: any } = action.payload
return {
@ -624,7 +642,7 @@ export const runTabReducer = (state: RunTabState = runTabInitialState, action: A
}
}
case 'SET_PATH_TO_SCENARIO': {
case SET_PATH_TO_SCENARIO: {
const payload: string = action.payload
return {
@ -636,7 +654,7 @@ export const runTabReducer = (state: RunTabState = runTabInitialState, action: A
}
}
case 'SET_RECORDER_COUNT': {
case SET_RECORDER_COUNT: {
const payload: number = action.payload
return {
@ -648,7 +666,7 @@ export const runTabReducer = (state: RunTabState = runTabInitialState, action: A
}
}
case 'CLEAR_RECORDER_COUNT': {
case CLEAR_RECORDER_COUNT: {
return {
...state,
recorder: {
@ -658,13 +676,57 @@ export const runTabReducer = (state: RunTabState = runTabInitialState, action: A
}
}
case 'RESET_STATE': {
case RESET_STATE: {
return {
...runTabInitialState,
ipfsChecked: state.ipfsChecked
}
}
case ADD_DEPLOY_OPTION: {
const payload: { title: DeployMode, active: boolean } = action.payload
return {
...state,
contracts: {
...state.contracts,
deployOptions: {
...state.contracts.deployOptions,
options: [...state.contracts.deployOptions.options, payload]
}
}
}
}
case REMOVE_DEPLOY_OPTION: {
const payload: string = action.payload
const options = state.contracts.deployOptions.options.filter(val => val.title !== payload)
return {
...state,
contracts: {
...state.contracts,
deployOptions: {
...state.contracts.deployOptions,
options
}
}
}
}
case SET_DEPLOY_OPTIONS: {
const payload: DeployOptions = action.payload
return {
...state,
contracts: {
...state.contracts,
deployOptions: payload
}
}
}
default:
return state
}

@ -11,22 +11,22 @@ import { Modal, Network, RunTabProps, Tx } from './types'
import { ContractData } from '@remix-project/core-plugin'
import { runTabInitialState, runTabReducer } from './reducers/runTab'
import {
initRunTab, setAccount,
setUnit, setGasFee,
setExecutionContext, setWeb3Endpoint,
clearPopUp, createNewBlockchainAccount,
setPassphrasePrompt, setMatchPassphrasePrompt,
signMessageWithAddress, getSelectedContract,
createInstance, setSendTransactionValue,
updateBaseFeePerGas, updateConfirmSettings,
updateGasPrice, updateGasPriceStatus,
updateMaxFee, updateMaxPriorityFee,
updateTxFeeContent, clearInstances,
removeInstance, getContext,
runTransactions, loadAddress,
storeScenario, runCurrentScenario,
updateScenarioPath, getFuncABIInputs,
setNetworkNameFromProvider
initRunTab, setAccountAddress,
setUnitValue, setGasFeeAmount,
setExecutionEnvironment,
hideToaster, createNewAddress,
setPassphraseModal, setMatchPassphraseModal,
signMessage, fetchSelectedContract,
createNewInstance, setSendValue,
setBaseFeePerGas, setConfirmSettings,
setGasPrice, setGasPriceStatus,
setMaxFee, setMaxPriorityFee,
setTxFeeContent, removeInstances,
removeSingleInstance, getExecutionContext,
executeTransactions, loadFromAddress,
storeNewScenario, runScenario,
setScenarioPath, getFuncABIValues,
setNetworkName, updateSelectedContract
} from './actions'
import './css/run-tab.css'
import { PublishToStorage } from '@remix-ui/publish-to-storage'
@ -134,7 +134,7 @@ export function RunTabUI (props: RunTabProps) {
const handleToaster = () => {
setFocusToaster('')
clearPopUp()
hideToaster()
}
const toast = (toasterMsg: string) => {
@ -166,16 +166,12 @@ export function RunTabUI (props: RunTabProps) {
)
}
const logBuilder = (msg: string) => {
return <pre>{msg}</pre>
}
const passphrasePrompt = (message: string) => {
return <PassphrasePrompt message={message} setPassphrase={setPassphrasePrompt} defaultValue={runTab.passphrase} />
return <PassphrasePrompt message={message} setPassphrase={setPassphraseModal} defaultValue={runTab.passphrase} />
}
const scenarioPrompt = (message: string, defaultValue) => {
return <ScenarioPrompt message={message} setScenarioPath={updateScenarioPath} defaultValue={defaultValue} />
return <ScenarioPrompt message={message} setScenarioPath={setScenarioPath} defaultValue={defaultValue} />
}
const mainnetPrompt = (tx: Tx, network: Network, amount: string, gasEstimation: string, gasFees: (maxFee: string, cb: (txFeeText: string, priceStatus: boolean) => void) => void, determineGasPrice: (cb: (txFeeText: string, gasPriceValue: string, gasPriceStatus: boolean) => void) => void) => {
@ -186,13 +182,13 @@ export function RunTabUI (props: RunTabProps) {
amount={amount}
gasEstimation={gasEstimation}
setNewGasPrice={gasFees}
updateBaseFeePerGas={updateBaseFeePerGas}
updateConfirmSettings={updateConfirmSettings}
updateGasPrice={updateGasPrice}
updateGasPriceStatus={updateGasPriceStatus}
updateMaxFee={updateMaxFee}
updateMaxPriorityFee={updateMaxPriorityFee}
setTxFeeContent={updateTxFeeContent}
updateBaseFeePerGas={setBaseFeePerGas}
updateConfirmSettings={setConfirmSettings}
updateGasPrice={setGasPrice}
updateGasPriceStatus={setGasPriceStatus}
updateMaxFee={setMaxFee}
updateMaxPriorityFee={setMaxPriorityFee}
setTxFeeContent={setTxFeeContent}
txFeeContent={runTab.txFeeContent}
maxFee={runTab.maxFee}
maxPriorityFee={runTab.maxPriorityFee}
@ -207,67 +203,64 @@ export function RunTabUI (props: RunTabProps) {
networkName={runTab.networkName}
personalMode={runTab.personalMode}
selectExEnv={runTab.selectExEnv}
setWeb3Endpoint={setWeb3Endpoint}
accounts={runTab.accounts}
setAccount={setAccount}
setUnit={setUnit}
setAccount={setAccountAddress}
setUnit={setUnitValue}
sendValue={runTab.sendValue}
setSendValue={setSendTransactionValue}
setSendValue={setSendValue}
sendUnit={runTab.sendUnit}
gasLimit={runTab.gasLimit}
setGasFee={setGasFee}
setGasFee={setGasFeeAmount}
providers={runTab.providers}
setExecutionContext={setExecutionContext}
createNewBlockchainAccount={createNewBlockchainAccount}
setPassphrase={setPassphrasePrompt}
setMatchPassphrase={setMatchPassphrasePrompt}
setExecutionContext={setExecutionEnvironment}
createNewBlockchainAccount={createNewAddress}
setPassphrase={setPassphraseModal}
setMatchPassphrase={setMatchPassphraseModal}
modal={modal}
tooltip={toast}
signMessageWithAddress={signMessageWithAddress}
signMessageWithAddress={signMessage}
passphrase={runTab.passphrase}
/>
<ContractDropdownUI
exEnvironment={runTab.selectExEnv}
contracts={runTab.contracts}
getSelectedContract={getSelectedContract}
getSelectedContract={fetchSelectedContract}
modal={modal}
passphrase={runTab.passphrase}
setPassphrase={setPassphrasePrompt}
createInstance={createInstance}
setPassphrase={setPassphraseModal}
createInstance={createNewInstance}
ipfsCheckedState={runTab.ipfsChecked}
setIpfsCheckedState={setCheckIpfs}
publishToStorage={publishToStorage}
gasEstimationPrompt={gasEstimationPrompt}
logBuilder={logBuilder}
passphrasePrompt={passphrasePrompt}
mainnetPrompt={mainnetPrompt}
tooltip={toast}
loadAddress={loadAddress}
loadAddress={loadFromAddress}
networkName={runTab.networkName}
setNetworkName={setNetworkNameFromProvider}
setNetworkName={setNetworkName}
setSelectedContract={updateSelectedContract}
/>
<RecorderUI
gasEstimationPrompt={gasEstimationPrompt}
logBuilder={logBuilder}
passphrasePrompt={passphrasePrompt}
mainnetPrompt={mainnetPrompt}
storeScenario={storeScenario}
runCurrentScenario={runCurrentScenario}
storeScenario={storeNewScenario}
runCurrentScenario={runScenario}
scenarioPrompt={scenarioPrompt}
count={runTab.recorder.transactionCount}
/>
<InstanceContainerUI
instances={runTab.instances}
clearInstances={clearInstances}
removeInstance={removeInstance}
getContext={getContext}
clearInstances={removeInstances}
removeInstance={removeSingleInstance}
getContext={getExecutionContext}
gasEstimationPrompt={gasEstimationPrompt}
logBuilder={logBuilder}
passphrasePrompt={passphrasePrompt}
mainnetPrompt={mainnetPrompt}
runTransactions={runTransactions}
runTransactions={executeTransactions}
sendValue={runTab.sendValue}
getFuncABIInputs={getFuncABIInputs}
getFuncABIInputs={getFuncABIValues}
/>
</div>
</div>

@ -1,3 +1,4 @@
import { Ref } from 'react'
import { CompilerAbstract } from '@remix-project/remix-solidity-ts'
import { ContractData, FuncABI } from '@remix-project/core-plugin'
import { ContractList } from '../reducers/runTab'
@ -21,7 +22,6 @@ export interface SettingsProps {
sendUnit: string,
gasLimit: number,
setGasFee: (value: number) => void,
setWeb3Endpoint: (endpoint: string) => void,
personalMode: boolean,
networkName: string,
providers: {
@ -128,8 +128,10 @@ export interface ContractDropdownProps {
exEnvironment: string,
contracts: {
contractList: ContractList,
deployOptions: DeployOptions,
loadType: 'abi' | 'sol' | 'other',
currentFile: string,
currentContract: string,
compilationCount: number,
isRequesting: boolean,
isSuccessful: boolean,
@ -143,29 +145,28 @@ export interface ContractDropdownProps {
selectedContract: ContractData,
gasEstimationPrompt: (msg: string) => JSX.Element,
passphrasePrompt: (msg: string) => JSX.Element,
logBuilder: (msg: string) => JSX.Element,
publishToStorage: (storage: 'ipfs' | 'swarm',
contract: ContractData) => void,
mainnetPrompt: MainnetPrompt,
isOverSizePrompt: () => JSX.Element,
args) => void,
args,
deployMode: DeployMode[]) => void,
ipfsCheckedState: boolean,
setIpfsCheckedState: (value: boolean) => void,
publishToStorage: (storage: 'ipfs' | 'swarm', contract: ContractData) => void,
gasEstimationPrompt: (msg: string) => JSX.Element,
logBuilder: (msg: string) => JSX.Element,
passphrasePrompt: (message: string) => JSX.Element,
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) => JSX.Element,
tooltip: (toasterMsg: string | JSX.Element) => void,
loadAddress: (contract: ContractData, address: string) => void,
networkName: string,
setNetworkName: (name: string) => void
setNetworkName: (name: string) => void,
setSelectedContract: (contractName: string) => void
}
export interface RecorderProps {
storeScenario: (prompt: (msg: string, defaultValue: 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,
runCurrentScenario: (gasEstimationPrompt: (msg: string) => JSX.Element, passphrasePrompt: (msg: string) => JSX.Element, confirmDialogContent: MainnetPrompt) => void,
mainnetPrompt: MainnetPrompt,
gasEstimationPrompt: (msg: string) => JSX.Element,
passphrasePrompt: (msg: string) => JSX.Element,
@ -196,13 +197,11 @@ export interface InstanceContainerProps {
contractABI, contract,
address,
logMsg:string,
logBuilder: (msg: string) => JSX.Element,
mainnetPrompt: MainnetPrompt,
gasEstimationPrompt: (msg: string) => JSX.Element,
passphrasePrompt: (msg: string) => JSX.Element,
funcIndex?: number) => void,
gasEstimationPrompt: (msg: string) => JSX.Element,
logBuilder: (msg: string) => JSX.Element,
passphrasePrompt: (message: string) => JSX.Element,
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) => JSX.Element,
sendValue: string,
@ -220,15 +219,43 @@ export interface Modal {
cancelFn: () => void
}
export type DeployMode = 'Deploy with Proxy' | 'Upgrade Proxy'
export type DeployOption = {
initializeInputs: string,
inputs: {
inputs: {
internalType?: string,
name: string,
type: string
}[],
name: "initialize",
outputs?: any[],
stateMutability: string,
type: string,
payable?: boolean,
constant?: any
}
}
export interface DeployOptions {
initializeOptions: {
[key: string]: DeployOption
},
options: { title: DeployMode, active: boolean }[],
}
export interface ContractGUIProps {
title?: string,
funcABI: FuncABI,
inputs: any,
clickCallBack: (inputs: { name: string, type: string }[], input: string) => void,
inputs: string,
clickCallBack: (inputs: { name: string, type: string }[], input: string, deployMode?: DeployMode[]) => void,
widthClass?: string,
evmBC: any,
lookupOnly: boolean,
disabled?: boolean
disabled?: boolean,
isDeploy?: boolean,
deployOption?: { title: DeployMode, active: boolean }[],
initializerOptions?: DeployOption
}
export interface MainnetProps {
network: Network,
@ -261,7 +288,6 @@ export interface UdappProps {
removeInstance: (index: number) => void,
index: number,
gasEstimationPrompt: (msg: string) => JSX.Element,
logBuilder: (msg: string) => JSX.Element,
passphrasePrompt: (message: string) => JSX.Element,
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) => JSX.Element,
runTransactions: (
@ -273,7 +299,6 @@ export interface UdappProps {
contractABI, contract,
address,
logMsg:string,
logBuilder: (msg: string) => JSX.Element,
mainnetPrompt: MainnetPrompt,
gasEstimationPrompt: (msg: string) => JSX.Element,
passphrasePrompt: (msg: string) => JSX.Element,
@ -281,3 +306,55 @@ export interface UdappProps {
sendValue: string,
getFuncABIInputs: (funcABI: FuncABI) => string
}
export interface DeployButtonProps {
deployOptions: { title: DeployMode, active: boolean }[],
buttonOptions: {
title: string,
content: string,
classList: string,
dataId: string,
widthClass: string
},
selectedIndex: number,
setSelectedIndex: (index: number) => void,
handleActionClick: () => void
}
export interface DeployInputProps {
funcABI: FuncABI,
inputs: string,
handleBasicInput: (e) => void,
basicInputRef: Ref<HTMLInputElement>,
buttonOptions: {
title: string,
content: string,
classList: string,
dataId: string,
widthClass: string
},
selectedIndex: number,
setSelectedIndex: (index: number) => void,
handleActionClick: (fields?: HTMLInputElement[]) => void,
deployOptions: { title: DeployMode, active: boolean }[]
}
export interface MultiDeployInputProps {
deployOptions?: { title: DeployMode, active: boolean }[],
buttonOptions: {
title: string,
content: string,
classList: string,
dataId: string,
widthClass: string
},
selectedIndex: number,
setSelectedIndex: (index: number) => void,
handleMultiValsSubmit: (fields?: HTMLInputElement[]) => void,
inputs: {
internalType?: string,
name: string,
type: string
}[],
getMultiValsString: (fields: HTMLInputElement[]) => void
}

48113
package-lock.json generated

File diff suppressed because it is too large Load Diff

@ -163,6 +163,7 @@
"ansi-gray": "^0.1.1",
"async": "^2.6.2",
"axios": ">=0.26.0",
"bootstrap": "^5.1.3",
"brace": "^0.8.0",
"change-case": "^4.1.1",
"chokidar": "^2.1.8",

Loading…
Cancel
Save