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-this-alias": "off",
"@typescript-eslint/no-empty-function": "off", "@typescript-eslint/no-empty-function": "off",
"eslint-disable-next-line no-empty": "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' import EventEmitter from 'events'
class CreateContract extends EventEmitter { class CreateContract extends EventEmitter {
command (this: NightwatchBrowser, inputParams: string): NightwatchBrowser { command (this: NightwatchBrowser, inputParams: string[]): NightwatchBrowser {
this.api.perform((done) => { this.api.perform((done) => {
createContract(this.api, inputParams, () => { createContract(this.api, inputParams, () => {
done() done()
@ -13,11 +13,19 @@ class CreateContract extends EventEmitter {
} }
} }
function createContract (browser: NightwatchBrowser, inputParams: string, callback: VoidFunction) { function createContract (browser: NightwatchBrowser, inputParams: string[], callback: VoidFunction) {
if (inputParams) { if (inputParams.length === 1) {
browser.setValue('.udapp_contractActionsContainerSingle > input', inputParams, function () { browser.setValue('.udapp_contractActionsContainerSingle > input', inputParams[0], function () {
browser.click('.udapp_contractActionsContainerSingle > button').pause(500).perform(function () { callback() }) 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 { } else {
browser browser
.click('.udapp_contractActionsContainerSingle > button') .click('.udapp_contractActionsContainerSingle > button')

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

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

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

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

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

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

@ -202,10 +202,10 @@ module.exports = {
.addFile('Storage.sol', sources[6]['Storage.sol']) .addFile('Storage.sol', sources[6]['Storage.sol'])
.addFile('Owner.sol', sources[6]['Owner.sol']) .addFile('Owner.sol', sources[6]['Owner.sol'])
.clickLaunchIcon('udapp') .clickLaunchIcon('udapp')
.createContract('42, 24') .createContract(['42', '24'])
.openFile('Storage.sol') .openFile('Storage.sol')
.clickLaunchIcon('udapp') .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) .clickInstance(1)
.clickFunction('store - transact (not payable)', { types: 'uint256 num', values: '24' }) .clickFunction('store - transact (not payable)', { types: 'uint256 num', values: '24' })
.testFunction('last', // we check if the contract is actually reachable. .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, getModalBody(callback: (value: string, cb: VoidFunction) => void): NightwatchBrowser,
modalFooterCancelClick(id?: string): NightwatchBrowser, modalFooterCancelClick(id?: string): NightwatchBrowser,
selectContract(contractName: string): NightwatchBrowser, selectContract(contractName: string): NightwatchBrowser,
createContract(inputParams: string): NightwatchBrowser, createContract(inputParams: string[]): NightwatchBrowser,
getAddressAtPosition(index: number, cb: (pos: string) => void): NightwatchBrowser, getAddressAtPosition(index: number, cb: (pos: string) => void): NightwatchBrowser,
testConstantFunction(address: string, fnFullName: string, expectedInput: NightwatchTestConstantFunctionExpectedInput | null, expectedOutput: string): NightwatchBrowser, testConstantFunction(address: string, fnFullName: string, expectedInput: NightwatchTestConstantFunctionExpectedInput | null, expectedOutput: string): NightwatchBrowser,
getEditorValue(callback: (content: string) => void): NightwatchBrowser, getEditorValue(callback: (content: string) => void): NightwatchBrowser,

@ -1,6 +1,7 @@
import React from 'react' // eslint-disable-line import React from 'react' // eslint-disable-line
import { RunTabUI } from '@remix-ui/run-tab' import { RunTabUI } from '@remix-ui/run-tab'
import { ViewPlugin } from '@remixproject/engine-web' import { ViewPlugin } from '@remixproject/engine-web'
import { addressToString } from '@remix-ui/helper'
import * as packageJson from '../../../../../package.json' import * as packageJson from '../../../../../package.json'
const EventManager = require('../../lib/events') const EventManager = require('../../lib/events')
@ -18,7 +19,7 @@ const profile = {
version: packageJson.version, version: packageJson.version,
permission: true, permission: true,
events: ['newTransaction'], 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 { export class RunTab extends ViewPlugin {
@ -163,4 +164,11 @@ export class RunTab extends ViewPlugin {
readFile (fileName) { readFile (fileName) {
return this.call('fileManager', '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 Web3 from 'web3'
import { Plugin } from '@remixproject/engine' import { Plugin } from '@remixproject/engine'
import { toBuffer, addHexPrefix } from 'ethereumjs-util' import { toBuffer, addHexPrefix } from 'ethereumjs-util'
import { waterfall } from 'async'
import { EventEmitter } from 'events' import { EventEmitter } from 'events'
import { format } from 'util' import { format } from 'util'
import { ExecutionContext } from './execution-context' import { ExecutionContext } from './execution-context'
@ -12,6 +11,8 @@ import InjectedProvider from './providers/injected.js'
import NodeProvider from './providers/node.js' import NodeProvider from './providers/node.js'
import { execution, EventManager, helpers } from '@remix-project/remix-lib' import { execution, EventManager, helpers } from '@remix-project/remix-lib'
import { etherScanLink } from './helper' 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 { txFormat, txExecution, typeConversion, txListener: Txlistener, TxRunner, TxRunnerWeb3, txHelper } = execution
const { txResultHelper: resultToRemixTx } = helpers const { txResultHelper: resultToRemixTx } = helpers
const packageJson = require('../../../../package.json') const packageJson = require('../../../../package.json')
@ -113,7 +114,9 @@ export class Blockchain extends Plugin {
const { continueCb, promptCb, statusCb, finalCb } = callbacks const { continueCb, promptCb, statusCb, finalCb } = callbacks
const constructor = selectedContract.getConstructorInterface() const constructor = selectedContract.getConstructorInterface()
txFormat.buildData(selectedContract.name, selectedContract.object, compilerContracts, true, constructor, args, (error, data) => { 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...`) statusCb(`creation of ${selectedContract.name} pending...`)
this.createContract(selectedContract, data, continueCb, promptCb, confirmationCb, finalCb) this.createContract(selectedContract, data, continueCb, promptCb, confirmationCb, finalCb)
@ -127,13 +130,73 @@ export class Blockchain extends Plugin {
const { continueCb, promptCb, statusCb, finalCb } = callbacks const { continueCb, promptCb, statusCb, finalCb } = callbacks
const constructor = selectedContract.getConstructorInterface() const constructor = selectedContract.getConstructorInterface()
txFormat.encodeConstructorCallAndLinkLibraries(selectedContract.object, args, constructor, contractMetadata.linkReferences, selectedContract.bytecodeLinkReferences, (error, data) => { 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...`) statusCb(`creation of ${selectedContract.name} pending...`)
this.createContract(selectedContract, data, continueCb, promptCb, confirmationCb, finalCb) 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) { createContract (selectedContract, data, continueCb, promptCb, confirmationCb, finalCb) {
if (data) { if (data) {
data.contractName = selectedContract.name data.contractName = selectedContract.name
@ -433,55 +496,71 @@ export class Blockchain extends Plugin {
}) })
} }
runTx (args, confirmationCb, continueCb, promptCb, cb) { async runTx (args, confirmationCb, continueCb, promptCb, cb) {
waterfall([ const getGasLimit = () => {
(next) => { // getGasLimit return new Promise((resolve, reject) => {
if (this.transactionContextAPI.getGasLimit) { 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) return resolve(3000000)
}, })
(gasLimit, next) => { // queryValue }
const queryValue = () => {
return new Promise((resolve, reject) => {
if (args.value) { if (args.value) {
return next(null, args.value, gasLimit) return resolve(args.value)
} }
if (args.useCall || !this.transactionContextAPI.getValue) { if (args.useCall || !this.transactionContextAPI.getValue) {
return next(null, 0, gasLimit) return resolve(0)
} }
this.transactionContextAPI.getValue(function (err, value) { this.transactionContextAPI.getValue((err, value) => {
next(err, value, gasLimit) if (err) return reject(err)
return resolve(value)
}) })
}, })
(value, gasLimit, next) => { // getAccount }
const getAccount = () => {
return new Promise((resolve, reject) => {
if (args.from) { if (args.from) {
return next(null, args.from, value, gasLimit) return resolve(args.from)
} }
if (this.transactionContextAPI.getAddress) { if (this.transactionContextAPI.getAddress) {
return this.transactionContextAPI.getAddress(function (err, address) { 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) { this.getAccounts(function (err, accounts) {
if (err) return reject(err)
const address = accounts[0] const address = accounts[0]
if (err) return next(err) if (!address) return reject('No accounts available')
if (!address) return next('No accounts available')
if (this.executionContext.isVM() && !this.providers.vm.RemixSimulatorProvider.Accounts.accounts[address]) { 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 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 } 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 const timestamp = tx.timestamp
this.event.trigger('initiatingTransaction', [timestamp, tx, payLoad]) this.event.trigger('initiatingTransaction', [timestamp, tx, payLoad])
this.txRunner.rawRun(tx, confirmationCb, continueCb, promptCb, this.txRunner.rawRun(tx, confirmationCb, continueCb, promptCb,
async (error, result) => { async (error, result) => {
if (error) return next(error) if (error) return reject(error)
const isVM = this.executionContext.isVM() const isVM = this.executionContext.isVM()
if (isVM && tx.useCall) { if (isVM && tx.useCall) {
@ -500,16 +579,15 @@ export class Blockchain extends Plugin {
try { error = 'error: ' + JSON.stringify(error) } catch (e) { console.log(e) } try { error = 'error: ' + JSON.stringify(error) } catch (e) { console.log(e) }
} }
} }
next(error, result, tx) return resolve({ result, tx })
} }
) )
})
} }
], try {
async (error, txResult, tx) => { const transaction = await runTransaction()
if (error) { const txResult = transaction.result
return cb(error) const tx = transaction.tx
}
/* /*
value of txResult is inconsistent: value of txResult is inconsistent:
- transact to contract: - transact to contract:
@ -517,12 +595,12 @@ export class Blockchain extends Plugin {
- call to contract: - call to contract:
{"result":"0x0000000000000000000000000000000000000000000000000000000000000000","transactionHash":"0x5236a76152054a8aad0c7135bcc151f03bccb773be88fbf4823184e47fc76247"} {"result":"0x0000000000000000000000000000000000000000000000000000000000000000","transactionHash":"0x5236a76152054a8aad0c7135bcc151f03bccb773be88fbf4823184e47fc76247"}
*/ */
const isVM = this.executionContext.isVM() const isVM = this.executionContext.isVM()
let execResult let execResult
let returnValue = null let returnValue = null
if (isVM) { if (isVM) {
const hhlogs = await this.web3().eth.getHHLogsForTx(txResult.transactionHash) const hhlogs = await this.web3().eth.getHHLogsForTx(txResult.transactionHash)
if (hhlogs && hhlogs.length) { if (hhlogs && hhlogs.length) {
let finalLogs = '<b>console.log:</b>\n' let finalLogs = '<b>console.log:</b>\n'
for (const log of hhlogs) { for (const log of hhlogs) {
@ -562,7 +640,9 @@ export class Blockchain extends Plugin {
address = txResult.receipt.contractAddress 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 { 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 = { const proxyProfile = {
name: 'openzeppelin-proxy', name: 'openzeppelin-proxy',
displayName: 'openzeppelin-proxy', displayName: 'openzeppelin-proxy',
description: 'openzeppelin-proxy', description: 'openzeppelin-proxy',
methods: ['isConcerned', 'execute'] methods: ['isConcerned', 'execute', 'getDeployOptions']
}; };
export class OpenZeppelinProxy extends Plugin { export class OpenZeppelinProxy extends Plugin {
blockchain: any blockchain: any
kind: 'UUPS' | 'Transparent'
constructor(blockchain) { constructor(blockchain) {
super(proxyProfile) super(proxyProfile)
this.blockchain = blockchain 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 // 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 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 // 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, type: string,
inputs: { name: string, type: string }[], inputs: { name: string, type: string }[],
stateMutability: string, stateMutability: string,
payable: boolean, payable?: boolean,
constant: any constant?: any
} }
export interface ContractData { export interface ContractData {
@ -21,3 +21,134 @@ export interface ContractData {
isOverSizeLimit: () => boolean, isOverSizeLimit: () => boolean,
metadata: any 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) { export function encodeParams (params, funAbi, callback) {
let data: Buffer | string = '' let data: Buffer | string = ''
let dataHex = '' let dataHex = ''
let funArgs let funArgs = []
if (params.indexOf('raw:0x') === 0) { 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 // in that case we consider that the input is already encoded and *does not* contain the method signature
dataHex = params.replace('raw:0x', '') dataHex = params.replace('raw:0x', '')
data = Buffer.from(dataHex, 'hex') data = Buffer.from(dataHex, 'hex')
} else { } else {
try { try {
params = params.replace(/(^|,\s+|,)(\d+)(\s+,|,|$)/g, '$1"$2"$3') // replace non quoted number by quoted number funArgs = parseFunctionParams(params)
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 + ']')
} catch (e) { } catch (e) {
return callback('Error encoding arguments: ' + e) return callback('Error encoding arguments: ' + e)
} }
if (funArgs.length > 0) {
try { try {
if (funArgs.length > 0) {
data = encodeParamsHelper(funAbi, funArgs) data = encodeParamsHelper(funAbi, funArgs)
dataHex = data.toString() dataHex = data.toString()
}
} catch (e) { } catch (e) {
return callback('Error encoding arguments: ' + e) return callback('Error encoding arguments: ' + e)
} }
}
if (data.slice(0, 9) === 'undefined') { if (data.slice(0, 9) === 'undefined') {
dataHex = data.slice(9) dataHex = data.slice(9)
} }
@ -396,7 +410,7 @@ export function decodeResponse (response, fnabi) {
} }
export function parseFunctionParams (params) { export function parseFunctionParams (params) {
let args = [] const args = []
// Check if parameter string starts with array or string // Check if parameter string starts with array or string
let startIndex = isArrayOrStringStart(params, 0) ? -1 : 0 let startIndex = isArrayOrStringStart(params, 0) ? -1 : 0
for (let i = 0; i < params.length; i++) { for (let i = 0; i < params.length; i++) {
@ -431,29 +445,27 @@ export function parseFunctionParams (params) {
if (bracketCount !== 0 && j === params.length - 1) { if (bracketCount !== 0 && j === params.length - 1) {
throw new Error('invalid tuple params') 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(parseFunctionParams(params.substring(i + 1, j)))
args.push(JSON.parse(params.substring(i, j)))
i = j - 1 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, it means a parameter was being parsed, it can be first or other parameter
if (startIndex >= 0) { 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 // Register start index of a parameter to parse
startIndex = isArrayOrStringStart(params, i + 1) ? -1 : i + 1 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 return args
} }

@ -35,7 +35,7 @@ tape('ContractParameters - (TxFormat.buildData) - format input parameters', func
function testWithInput (st, params, expected) { function testWithInput (st, params, expected) {
txFormat.buildData('uintContractTest', context.contract, context.output.contracts, true, context.contract.abi[0], params, (error, data) => { 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) console.log(data)
if (!data.dataHex.endsWith(expected)) { if (!data.dataHex.endsWith(expected)) {
st.fail(`result of buildData ${data.dataHex} should end with ${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) { function testWithStringInput (st, params, expected) {
txFormat.buildData('stringContractTest', context.contract, context.output.contracts, true, context.contract.abi[0], params, (error, data) => { 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) console.log(data)
if (!data.dataHex.endsWith(expected)) { if (!data.dataHex.endsWith(expected)) {
st.fail(`result of buildData ${data.dataHex} should end with ${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) { function testWithArrayInput (st, params, expected) {
txFormat.buildData('arrayContractTest', context.contract, context.output.contracts, true, context.contract.abi[0], params, (error, data) => { 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) console.log(data)
if (!data.dataHex.endsWith(expected)) { if (!data.dataHex.endsWith(expected)) {
st.fail(`result of buildData ${data.dataHex} should end with ${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) { function testWithNestedArrayInput (st, params, expected) {
txFormat.buildData('nestedArrayContractTest', context.contract, context.output.contracts, true, context.contract.abi[4], params, (error, data) => { txFormat.buildData('nestedArrayContractTest', context.contract, context.output.contracts, true, context.contract.abi[4], params, (error, data) => {
if (error) { if (error) {
return st.fails(error) return st.fail(error)
} }
console.log(data) console.log(data)
if (!data.dataHex.endsWith(expected)) { if (!data.dataHex.endsWith(expected)) {
@ -185,7 +185,7 @@ function testLinkLibrary (st, fakeDeployedContracts, callbackDeployLibraries) {
const deployMsg = ['creation of library test.sol:lib1 pending...', 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...'] '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) => { 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) console.log(data)
const linkedbyteCode = data.dataHex const linkedbyteCode = data.dataHex
let libReference = context.contract.evm.bytecode.linkReferences['test.sol']['lib1'] 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 File(s) to paste is an ancestor of the destination folder
</div> </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 // eslint-disable-next-line no-unused-vars
import React from 'react' 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 { RunTab } from '../types/run-tab'
import { CompilerAbstract } from '@remix-project/remix-solidity' import { resetAndInit, setupEvents } from './events'
import * as remixLib from '@remix-project/remix-lib' import { createNewBlockchainAccount, fillAccountsList, setExecutionContext, signMessageWithAddress } from './account'
import { MainnetPrompt } from '../types' import { clearInstances, clearPopUp, removeInstance, setAccount, setGasFee, setMatchPassphrasePrompt, setNetworkNameFromProvider, setPassphrasePrompt, setSelectedContract, setSendTransactionValue, setUnit, updateBaseFeePerGas, updateConfirmSettings, updateGasPrice, updateGasPriceStatus, updateMaxFee, updateMaxPriorityFee, updateScenarioPath, updateTxFeeContent } from './actions'
import { ContractData, FuncABI, } from '@remix-project/core-plugin' import { createInstance, getContext, getFuncABIInputs, getSelectedContract, loadAddress, runTransactions } from './deploy'
import { CompilerAbstract as CompilerAbstractType } from '@remix-project/remix-solidity-ts' 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 { declare global {
interface Window { interface Window {
_paq: any _paq: any
@ -19,709 +17,45 @@ declare global {
} }
const _paq = window._paq = window._paq || [] //eslint-disable-line const _paq = window._paq = window._paq || [] //eslint-disable-line
const txHelper = remixLib.execution.txHelper
let plugin: RunTab, dispatch: React.Dispatch<any> let plugin: RunTab, dispatch: React.Dispatch<any>
export const initRunTab = (udapp: RunTab) => async (reducerDispatch: React.Dispatch<any>) => { export const initRunTab = (udapp: RunTab) => async (reducerDispatch: React.Dispatch<any>) => {
plugin = udapp plugin = udapp
dispatch = reducerDispatch dispatch = reducerDispatch
resetAndInit() resetAndInit(plugin)
setupEvents() setupEvents(plugin, dispatch)
setInterval(() => { setInterval(() => {
fillAccountsList() fillAccountsList(plugin, dispatch)
}, 1000) }, 1000)
} }
const setupEvents = () => { export const setAccountAddress = (account: string) => setAccount(dispatch, account)
plugin.blockchain.events.on('newTransaction', (tx, receipt) => { export const setUnitValue = (unit: 'ether' | 'finney' | 'gwei' | 'wei') => setUnit(dispatch, unit)
plugin.emit('newTransaction', tx, receipt) 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)
plugin.blockchain.event.register('transactionExecuted', (error, from, to, data, lookupOnly, txResult) => { export const createNewAddress = (cbMessage: JSX.Element) => createNewBlockchainAccount(plugin, dispatch, cbMessage)
if (!lookupOnly) dispatch(setSendValue('0')) export const setPassphraseModal = (passphrase: string) => setPassphrasePrompt(dispatch, passphrase)
if (error) return export const setMatchPassphraseModal = (passphrase: string) => setMatchPassphrasePrompt(dispatch, passphrase)
updateAccountBalances() 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)
plugin.blockchain.event.register('contextChanged', (context, silent) => { export const setSendValue = (value: string) => setSendTransactionValue(dispatch, value)
setFinalContext() export const setBaseFeePerGas = (baseFee: string) => updateBaseFeePerGas(dispatch, baseFee)
}) export const setConfirmSettings = (confirmation: boolean) => updateConfirmSettings(dispatch, confirmation)
export const setGasPrice = (price: string) => updateGasPrice(dispatch, price)
plugin.blockchain.event.register('networkStatus', ({ error, network }) => { export const setGasPriceStatus = (status: boolean) => updateGasPriceStatus(dispatch, status)
if (error) { export const setMaxFee = (fee: string) => updateMaxFee(dispatch, fee)
const netUI = 'can\'t detect network ' export const setMaxPriorityFee = (fee: string) => updateMaxPriorityFee(dispatch, fee)
setNetworkNameFromProvider(netUI) export const setTxFeeContent = (content: string) => updateTxFeeContent(dispatch, content)
export const removeInstances = () => clearInstances(dispatch)
return export const removeSingleInstance = (index: number) => removeInstance(dispatch, index)
} export const getExecutionContext = () => getContext(plugin)
const networkProvider = plugin.networkModule.getNetworkProvider.bind(plugin.networkModule) 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)
const netUI = (networkProvider() !== 'vm') ? `${network.name} (${network.id || '-'}) network` : 'VM' 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)
setNetworkNameFromProvider(netUI) 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)
plugin.blockchain.event.register('addProvider', provider => addExternalProvider(provider)) export const setNetworkName = (networkName: string) => setNetworkNameFromProvider(dispatch, networkName)
export const updateSelectedContract = (contractName) => setSelectedContract(dispatch, contractName)
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)
}
}
})
}

@ -1,249 +1,251 @@
import { ContractList } from '../reducers/runTab' import { ContractList } from '../reducers/runTab'
import { ContractData } from '@remix-project/core-plugin' 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 = () => { export const fetchAccountsListRequest = () => {
return { return {
type: 'FETCH_ACCOUNTS_LIST_REQUEST', type: FETCH_ACCOUNTS_LIST_REQUEST,
payload: null payload: null
} }
} }
export const fetchAccountsListSuccess = (accounts: Record<string, string>) => { export const fetchAccountsListSuccess = (accounts: Record<string, string>) => {
return { return {
type: 'FETCH_ACCOUNTS_LIST_SUCCESS', type: FETCH_ACCOUNTS_LIST_SUCCESS,
payload: accounts payload: accounts
} }
} }
export const fetchAccountsListFailed = (error: string) => { export const fetchAccountsListFailed = (error: string) => {
return { return {
type: 'FETCH_ACCOUNTS_LIST_FAILED', type: FETCH_ACCOUNTS_LIST_FAILED,
payload: error payload: error
} }
} }
export const setSendValue = (value: string) => { export const setSendValue = (value: string) => {
return { return {
type: 'SET_SEND_VALUE', type: SET_SEND_VALUE,
payload: value payload: value
} }
} }
export const setSelectedAccount = (account: string) => { export const setSelectedAccount = (account: string) => {
return { return {
type: 'SET_SELECTED_ACCOUNT', type: SET_SELECTED_ACCOUNT,
payload: account payload: account
} }
} }
export const setSendUnit = (unit: 'ether' | 'finney' | 'gwei' | 'wei') => { export const setSendUnit = (unit: 'ether' | 'finney' | 'gwei' | 'wei') => {
return { return {
type: 'SET_SEND_UNIT', type: SET_SEND_UNIT,
payload: unit payload: unit
} }
} }
export const setGasLimit = (gasLimit: number) => { export const setGasLimit = (gasLimit: number) => {
return { return {
type: 'SET_GAS_LIMIT', type: SET_GAS_LIMIT,
payload: gasLimit payload: gasLimit
} }
} }
export const setExecutionEnvironment = (executionEnvironment: string) => { export const setExecutionEnvironment = (executionEnvironment: string) => {
return { return {
type: 'SET_EXECUTION_ENVIRONMENT', type: SET_EXECUTION_ENVIRONMENT,
payload: executionEnvironment payload: executionEnvironment
} }
} }
export const setPersonalMode = (mode: boolean) => { export const setPersonalMode = (mode: boolean) => {
return { return {
type: 'SET_PERSONAL_MODE', type: SET_PERSONAL_MODE,
payload: mode payload: mode
} }
} }
export const setNetworkName = (networkName: string) => { export const setNetworkName = (networkName: string) => {
return { return {
type: 'SET_NETWORK_NAME', type: SET_NETWORK_NAME,
payload: networkName payload: networkName
} }
} }
export const addProvider = (provider: string) => { export const addProvider = (provider: string) => {
return { return {
type: 'ADD_PROVIDER', type: ADD_PROVIDER,
payload: provider payload: provider
} }
} }
export const removeProvider = (provider: string) => { export const removeProvider = (provider: string) => {
return { return {
type: 'REMOVE_PROVIDER', type: REMOVE_PROVIDER,
payload: provider payload: provider
} }
} }
export const displayNotification = (title: string, message: string | JSX.Element, labelOk: string, labelCancel: string, actionOk?: (...args) => void, actionCancel?: (...args) => void) => { export const displayNotification = (title: string, message: string | JSX.Element, labelOk: string, labelCancel: string, actionOk?: (...args) => void, actionCancel?: (...args) => void) => {
return { return {
type: 'DISPLAY_NOTIFICATION', type: DISPLAY_NOTIFICATION,
payload: { title, message, labelOk, labelCancel, actionOk, actionCancel } payload: { title, message, labelOk, labelCancel, actionOk, actionCancel }
} }
} }
export const hideNotification = () => { export const hideNotification = () => {
return { return {
type: 'HIDE_NOTIFICATION' type: HIDE_NOTIFICATION
} }
} }
export const setExternalEndpoint = (endpoint: string) => { export const setExternalEndpoint = (endpoint: string) => {
return { return {
type: 'SET_EXTERNAL_WEB3_ENDPOINT', type: SET_EXTERNAL_WEB3_ENDPOINT,
payload: endpoint payload: endpoint
} }
} }
export const displayPopUp = (message: string | JSX.Element) => { export const displayPopUp = (message: string | JSX.Element) => {
return { return {
type: 'DISPLAY_POPUP_MESSAGE', type: DISPLAY_POPUP_MESSAGE,
payload: message payload: message
} }
} }
export const hidePopUp = () => { export const hidePopUp = () => {
return { return {
type: 'HIDE_POPUP_MESSAGE' type: HIDE_POPUP_MESSAGE
} }
} }
export const setPassphrase = (passphrase: string) => { export const setPassphrase = (passphrase: string) => {
return { return {
type: 'SET_PASSPHRASE', type: SET_PASSPHRASE,
payload: passphrase payload: passphrase
} }
} }
export const setMatchPassphrase = (passphrase: string) => { export const setMatchPassphrase = (passphrase: string) => {
return { return {
type: 'SET_MATCH_PASSPHRASE', type: SET_MATCH_PASSPHRASE,
payload: passphrase payload: passphrase
} }
} }
export const fetchContractListRequest = () => { export const fetchContractListRequest = () => {
return { return {
type: 'FETCH_CONTRACT_LIST_REQUEST' type: FETCH_CONTRACT_LIST_REQUEST
} }
} }
export const fetchContractListSuccess = (contracts: ContractList) => { export const fetchContractListSuccess = (contracts: ContractList) => {
return { return {
type: 'FETCH_CONTRACT_LIST_SUCCESS', type: FETCH_CONTRACT_LIST_SUCCESS,
payload: contracts payload: contracts
} }
} }
export const fetchContractListFailed = (error: string) => { export const fetchContractListFailed = (error: string) => {
return { return {
type: 'FETCH_CONTRACT_LIST_FAILED', type: FETCH_CONTRACT_LIST_FAILED,
payload: error payload: error
} }
} }
export const setLoadType = (type: 'abi' | 'sol' | 'other') => { export const setLoadType = (type: 'abi' | 'sol' | 'other') => {
return { return {
type: 'SET_LOAD_TYPE', type: SET_LOAD_TYPE,
payload: type payload: type
} }
} }
export const setCurrentFile = (file: string) => { export const setCurrentFile = (file: string) => {
return { return {
type: 'SET_CURRENT_FILE', type: SET_CURRENT_FILE,
payload: file payload: file
} }
} }
export const setIpfsCheckedState = (state: boolean) => { export const setIpfsCheckedState = (state: boolean) => {
return { return {
type: 'SET_IPFS_CHECKED_STATE', type: SET_IPFS_CHECKED_STATE,
payload: state payload: state
} }
} }
export const setGasPriceStatus = (status: boolean) => { export const setGasPriceStatus = (status: boolean) => {
return { return {
type: 'SET_GAS_PRICE_STATUS', type: SET_GAS_PRICE_STATUS,
payload: status payload: status
} }
} }
export const setConfirmSettings = (confirmation: boolean) => { export const setConfirmSettings = (confirmation: boolean) => {
return { return {
type: 'SET_CONFIRM_SETTINGS', type: SET_CONFIRM_SETTINGS,
payload: confirmation payload: confirmation
} }
} }
export const setMaxFee = (fee: string) => { export const setMaxFee = (fee: string) => {
return { return {
type: 'SET_MAX_FEE', type: SET_MAX_FEE,
payload: fee payload: fee
} }
} }
export const setMaxPriorityFee = (fee: string) => { export const setMaxPriorityFee = (fee: string) => {
return { return {
type: 'SET_MAX_PRIORITY_FEE', type: SET_MAX_PRIORITY_FEE,
payload: fee payload: fee
} }
} }
export const setBaseFeePerGas = (baseFee: string) => { export const setBaseFeePerGas = (baseFee: string) => {
return { return {
type: 'SET_BASE_FEE_PER_GAS', type: SET_BASE_FEE_PER_GAS,
payload: baseFee payload: baseFee
} }
} }
export const setGasPrice = (price: string) => { export const setGasPrice = (price: string) => {
return { return {
type: 'SET_GAS_PRICE', type: SET_GAS_PRICE,
payload: price payload: price
} }
} }
export const setTxFeeContent = (content: string) => { export const setTxFeeContent = (content: string) => {
return { return {
type: 'SET_TX_FEE_CONTENT', type: SET_TX_FEE_CONTENT,
payload: content payload: content
} }
} }
export const addNewInstance = (instance: { contractData?: ContractData, address: string, name: string, abi?: any }) => { export const addNewInstance = (instance: { contractData?: ContractData, address: string, name: string, abi?: any }) => {
return { return {
type: 'ADD_INSTANCE', type: ADD_INSTANCE,
payload: instance payload: instance
} }
} }
export const removeExistingInstance = (index: number) => { export const removeExistingInstance = (index: number) => {
return { return {
type: 'REMOVE_INSTANCE', type: REMOVE_INSTANCE,
payload: index payload: index
} }
} }
export const clearAllInstances = () => { export const clearAllInstances = () => {
return { return {
type: 'CLEAR_INSTANCES' type: CLEAR_INSTANCES
} }
} }
export const setDecodedResponse = (instanceIndex: number, response, funcIndex?: number) => { export const setDecodedResponse = (instanceIndex: number, response, funcIndex?: number) => {
return { return {
type: 'SET_DECODED_RESPONSE', type: SET_DECODED_RESPONSE,
payload: { payload: {
instanceIndex, instanceIndex,
funcIndex, funcIndex,
@ -254,33 +256,54 @@ export const setDecodedResponse = (instanceIndex: number, response, funcIndex?:
export const setPathToScenario = (path: string) => { export const setPathToScenario = (path: string) => {
return { return {
type: 'SET_PATH_TO_SCENARIO', type: SET_PATH_TO_SCENARIO,
payload: path payload: path
} }
} }
export const setRecorderCount = (count: number) => { export const setRecorderCount = (count: number) => {
return { return {
type: 'SET_RECORDER_COUNT', type: SET_RECORDER_COUNT,
payload: count payload: count
} }
} }
export const clearRecorderCount = () => { export const clearRecorderCount = () => {
return { 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 { return {
type: 'SET_ENV_TOASTER_CONTENT', type: RESET_STATE
payload: content
} }
} }
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 { 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 // eslint-disable-next-line no-use-before-define
import React, { useEffect, useRef, useState } from 'react' 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 { ContractData, FuncABI } from '@remix-project/core-plugin'
import * as ethJSUtil from 'ethereumjs-util' import * as ethJSUtil from 'ethereumjs-util'
import { ContractGUI } from './contractGUI' import { ContractGUI } from './contractGUI'
@ -22,13 +22,12 @@ export function ContractDropdownUI (props: ContractDropdownProps) {
title: 'Please compile *.sol file to deploy or access a contract', title: 'Please compile *.sol file to deploy or access a contract',
disabled: true disabled: true
}) })
const [selectedContract, setSelectedContract] = useState<string>('')
const [compFails, setCompFails] = useState<'none' | 'block'>('none') const [compFails, setCompFails] = useState<'none' | 'block'>('none')
const [loadedContractData, setLoadedContractData] = useState<ContractData>(null) const [loadedContractData, setLoadedContractData] = useState<ContractData>(null)
const [constructorInterface, setConstructorInterface] = useState<FuncABI>(null) const [constructorInterface, setConstructorInterface] = useState<FuncABI>(null)
const [constructorInputs, setConstructorInputs] = useState(null) const [constructorInputs, setConstructorInputs] = useState(null)
const contractsRef = useRef<HTMLSelectElement>(null) const contractsRef = useRef<HTMLSelectElement>(null)
const { contractList, loadType, currentFile, compilationCount } = props.contracts const { contractList, loadType, currentFile, currentContract, compilationCount, deployOptions } = props.contracts
useEffect(() => { useEffect(() => {
enableAtAddress(false) enableAtAddress(false)
@ -66,13 +65,13 @@ export function ContractDropdownUI (props: ContractDropdownProps) {
display: 'none', display: 'none',
content: '' content: ''
}) })
if (!selectedContract) enableAtAddress(false) if (!currentContract) enableAtAddress(false)
} else { } else {
setAbiLabel({ setAbiLabel({
display: 'none', display: 'none',
content: '' content: ''
}) })
if (!selectedContract) enableAtAddress(false) if (!currentContract) enableAtAddress(false)
} }
if (currentFile) { if (currentFile) {
enableContractNames(true) enableContractNames(true)
@ -85,11 +84,11 @@ export function ContractDropdownUI (props: ContractDropdownProps) {
}, [loadType, currentFile, compilationCount]) }, [loadType, currentFile, compilationCount])
useEffect(() => { useEffect(() => {
if (selectedContract && contractList[currentFile]) { if (currentContract && contractList[currentFile]) {
const contract = contractList[currentFile].find(contract => contract.alias === selectedContract) const contract = contractList[currentFile].find(contract => contract.alias === currentContract)
if (contract) { if (contract) {
const loadedContractData = props.getSelectedContract(selectedContract, contract.compiler) const loadedContractData = props.getSelectedContract(currentContract, contract.compiler)
if (loadedContractData) { if (loadedContractData) {
setLoadedContractData(loadedContractData) setLoadedContractData(loadedContractData)
@ -98,7 +97,7 @@ export function ContractDropdownUI (props: ContractDropdownProps) {
} }
} }
} }
}, [selectedContract, compilationCount]) }, [currentContract, compilationCount])
useEffect(() => { useEffect(() => {
initSelectedContract() initSelectedContract()
@ -108,9 +107,10 @@ export function ContractDropdownUI (props: ContractDropdownProps) {
const contracts = contractList[currentFile] const contracts = contractList[currentFile]
if (contracts && contracts.length > 0) { 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. // TODO highlight contractlist box with css.
} }
} }
@ -143,15 +143,15 @@ export function ContractDropdownUI (props: ContractDropdownProps) {
} }
} }
const clickCallback = (inputs, value) => { const clickCallback = (inputs, value, deployMode?: DeployMode[]) => {
createInstance(loadedContractData, value) createInstance(loadedContractData, value, deployMode)
} }
const createInstance = (selectedContract, args) => { const createInstance = (selectedContract, args, deployMode?: DeployMode[]) => {
if (selectedContract.bytecodeObject.length === 0) { 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', () => {}) 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) => { const atAddressChanged = (event) => {
@ -189,7 +189,7 @@ export function ContractDropdownUI (props: ContractDropdownProps) {
const handleContractChange = (e) => { const handleContractChange = (e) => {
const value = e.target.value const value = e.target.value
setSelectedContract(value) props.setSelectedContract(value)
} }
const checkSumWarning = () => { const checkSumWarning = () => {
@ -214,19 +214,29 @@ export function ContractDropdownUI (props: ContractDropdownProps) {
<div className="udapp_container" data-id="contractDropdownContainer"> <div className="udapp_container" data-id="contractDropdownContainer">
<label className="udapp_settingsLabel">Contract</label> <label className="udapp_settingsLabel">Contract</label>
<div className="udapp_subcontainer"> <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) => { { (contractList[currentFile] || []).map((contract, index) => {
return <option key={index} value={contract.alias}>{contract.alias} - {contract.file}</option> return <option key={index} value={contract.alias}>{contract.alias} - {contract.file}</option>
}) } }) }
</select> </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> <span className="py-1" style={{ display: abiLabel.display }}>{ abiLabel.content }</span>
</div> </div>
<div> <div>
<div className="udapp_deployDropdown"> <div className="udapp_deployDropdown">
{ ((contractList[currentFile] && contractList[currentFile].filter(contract => contract)) || []).length <= 0 ? 'No compiled contracts' { ((contractList[currentFile] && contractList[currentFile].filter(contract => contract)) || []).length <= 0 ? 'No compiled contracts'
: loadedContractData ? <div> : 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"> <div className="d-flex py-1 align-items-center custom-control custom-checkbox">
<input <input
id="deployAndRunPublishToIPFS" id="deployAndRunPublishToIPFS"

@ -3,6 +3,8 @@ import React, { useEffect, useRef, useState } from 'react'
import * as remixLib from '@remix-project/remix-lib' import * as remixLib from '@remix-project/remix-lib'
import { ContractGUIProps } from '../types' import { ContractGUIProps } from '../types'
import { CopyToClipboard } from '@remix-ui/clipboard' import { CopyToClipboard } from '@remix-ui/clipboard'
import { MultiDeployInput } from './multiDeployInput'
import { DeployInput } from './deployInput'
const txFormat = remixLib.execution.txFormat const txFormat = remixLib.execution.txFormat
export function ContractGUI (props: ContractGUIProps) { export function ContractGUI (props: ContractGUIProps) {
@ -13,8 +15,19 @@ export function ContractGUI (props: ContractGUIProps) {
title: string, title: string,
content: string, content: string,
classList: string, classList: string,
dataId: string dataId: string,
}>({ title: '', content: '', classList: '', dataId: '' }) 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 multiFields = useRef<Array<HTMLInputElement | null>>([])
const basicInputRef = useRef<HTMLInputElement>() const basicInputRef = useRef<HTMLInputElement>()
@ -28,7 +41,7 @@ export function ContractGUI (props: ContractGUIProps) {
} }
setBasicInput('') setBasicInput('')
// we have the reset the fields before reseting the previous references. // 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.filter((el) => el !== null && el !== undefined).forEach((el) => el.value = '')
multiFields.current = [] multiFields.current = []
}, [props.title, props.funcABI]) }, [props.title, props.funcABI])
@ -40,7 +53,8 @@ export function ContractGUI (props: ContractGUIProps) {
title: title + ' - call', title: title + ' - call',
content: 'call', content: 'call',
classList: 'btn-info', classList: 'btn-info',
dataId: title + ' - call' dataId: title + ' - call',
widthClass: props.widthClass
}) })
} else if (props.funcABI.stateMutability === 'payable' || props.funcABI.payable) { } else if (props.funcABI.stateMutability === 'payable' || props.funcABI.payable) {
// // transact. stateMutability = payable // // transact. stateMutability = payable
@ -48,7 +62,8 @@ export function ContractGUI (props: ContractGUIProps) {
title: title + ' - transact (payable)', title: title + ' - transact (payable)',
content: 'transact', content: 'transact',
classList: 'btn-danger', classList: 'btn-danger',
dataId: title + ' - transact (payable)' dataId: title + ' - transact (payable)',
widthClass: props.widthClass
}) })
} else { } else {
// // transact. stateMutability = nonpayable // // transact. stateMutability = nonpayable
@ -56,13 +71,59 @@ export function ContractGUI (props: ContractGUIProps) {
title: title + ' - transact (not payable)', title: title + ' - transact (not payable)',
content: 'transact', content: 'transact',
classList: 'btn-warning', classList: 'btn-warning',
dataId: title + ' - transact (not payable)' dataId: title + ' - transact (not payable)',
widthClass: props.widthClass
}) })
} }
}, [props.lookupOnly, props.funcABI, title]) }, [props.lookupOnly, props.funcABI, title])
const getContentOnCTC = () => { useEffect(() => {
const multiString = getMultiValsString() 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 // copy-to-clipboard icon is only visible for method requiring input params
if (!multiString) { if (!multiString) {
return 'cannot encode empty arguments' return 'cannot encode empty arguments'
@ -89,13 +150,13 @@ export function ContractGUI (props: ContractGUIProps) {
const switchMethodViewOff = () => { const switchMethodViewOff = () => {
setToggleContainer(false) setToggleContainer(false)
const multiValString = getMultiValsString() const multiValString = getMultiValsString(multiFields.current)
if (multiValString) setBasicInput(multiValString) if (multiValString) setBasicInput(multiValString)
} }
const getMultiValsString = () => { const getMultiValsString = (fields: HTMLInputElement[]) => {
const valArray = multiFields.current const valArray = fields
let ret = '' let ret = ''
const valArrayTest = [] const valArrayTest = []
@ -130,7 +191,8 @@ export function ContractGUI (props: ContractGUIProps) {
if (inputString) { if (inputString) {
inputString = inputString.replace(/(^|,\s+|,)(\d+)(\s+,|,|$)/g, '$1"$2"$3') // replace non quoted number by quoted number 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 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 const multiInputs = multiFields.current
for (let k = 0; k < multiInputs.length; k++) { for (let k = 0; k < multiInputs.length; k++) {
@ -142,7 +204,9 @@ export function ContractGUI (props: ContractGUIProps) {
} }
const handleActionClick = () => { 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) => { const handleBasicInput = (e) => {
@ -151,18 +215,51 @@ export function ContractGUI (props: ContractGUIProps) {
setBasicInput(value) setBasicInput(value)
} }
const handleExpandMultiClick = () => { const handleMultiValsSubmit = (fields: HTMLInputElement[]) => {
const valsString = getMultiValsString() const valsString = getMultiValsString(fields)
const deployMode = selectedDeployIndex !== null ? [props.deployOption[selectedDeployIndex].title] : []
if (valsString) { if (valsString) {
props.clickCallBack(props.funcABI.inputs, valsString) props.clickCallBack(props.funcABI.inputs, valsString, deployMode)
} else { } 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 ( 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' }}> <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> <button onClick={handleActionClick} title={buttonOptions.title} className={`udapp_instanceButton ${props.widthClass} btn btn-sm ${buttonOptions.classList}`} data-id={buttonOptions.dataId}>{title}</button>
<input <input
@ -195,11 +292,13 @@ export function ContractGUI (props: ContractGUIProps) {
})} })}
</div> </div>
<div className="udapp_group udapp_multiArg"> <div className="udapp_group udapp_multiArg">
<CopyToClipboard tip='Encode values of input fields & copy to clipboard' icon='fa-clipboard' direction={'left'} getContent={getContentOnCTC} /> <CopyToClipboard tip='Encode values of input fields & copy to clipboard' icon='fa-clipboard' direction={'left'} getContent={() => getContentOnCTC(multiFields.current)} />
<button onClick={handleExpandMultiClick} title={buttonOptions.title} data-id={buttonOptions.dataId} className={`udapp_instanceButton ${buttonOptions.classList}`}>{ buttonOptions.content }</button> <button onClick={() => handleMultiValsSubmit(multiFields.current)} title={buttonOptions.title} data-id={buttonOptions.dataId} className={`udapp_instanceButton ${buttonOptions.classList}`}>{ buttonOptions.content }</button>
</div> </div>
</div> </div>
</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} removeInstance={props.removeInstance}
index={index} index={index}
gasEstimationPrompt={props.gasEstimationPrompt} gasEstimationPrompt={props.gasEstimationPrompt}
logBuilder={props.logBuilder}
passphrasePrompt={props.passphrasePrompt} passphrasePrompt={props.passphrasePrompt}
mainnetPrompt={props.mainnetPrompt} mainnetPrompt={props.mainnetPrompt}
runTransactions={props.runTransactions} runTransactions={props.runTransactions}

@ -22,7 +22,6 @@ export function MainnetPrompt (props: MainnetProps) {
}, []) }, [])
const onMaxFeeChange = (value: string) => { const onMaxFeeChange = (value: string) => {
console.log('value: ', value)
const maxFee = value const maxFee = value
// @ts-ignore // @ts-ignore
if (parseInt(props.network.lastBlock.baseFeePerGas, 16) > Web3.utils.toWei(maxFee, 'Gwei')) { 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 = () => { const handleClickRunButton = () => {
props.runCurrentScenario(props.gasEstimationPrompt, props.passphrasePrompt, props.mainnetPrompt, props.logBuilder) props.runCurrentScenario(props.gasEstimationPrompt, props.passphrasePrompt, props.mainnetPrompt)
} }
const toggleClass = () => { const toggleClass = () => {

@ -119,7 +119,6 @@ export function UniversalDappUI (props: UdappProps) {
props.instance.contractData, props.instance.contractData,
address, address,
logMsg, logMsg,
props.logBuilder,
props.mainnetPrompt, props.mainnetPrompt,
props.gasEstimationPrompt, props.gasEstimationPrompt,
props.passphrasePrompt, props.passphrasePrompt,
@ -184,7 +183,7 @@ export function UniversalDappUI (props: UdappProps) {
const renderData = (item, parent, key: string | number, keyPath: string) => { const renderData = (item, parent, key: string | number, keyPath: string) => {
const data = extractDataDefault(item, parent) const data = extractDataDefault(item, parent)
const children = (data.children || []).map((child) => { const children = (data.children || []).map((child, index) => {
return ( return (
renderData(child.value, data, child.key, keyPath + '/' + child.key) 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 lookupOnly = funcABI.stateMutability === 'view' || funcABI.stateMutability === 'pure' || isConstant
const inputs = props.getFuncABIInputs(funcABI) const inputs = props.getFuncABIInputs(funcABI)
return <> return <div key={index}>
<ContractGUI <ContractGUI
funcABI={funcABI} funcABI={funcABI}
clickCallBack={(valArray: { name: string, type: string }[], inputsValues: string) => { clickCallBack={(valArray: { name: string, type: string }[], inputsValues: string) => {
@ -255,14 +254,14 @@ export function UniversalDappUI (props: UdappProps) {
const funcIndex = index.toString() const funcIndex = index.toString()
const response = props.instance.decodedResponse[key] 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) return renderData(props.instance.decodedResponse[key][innerkey], response, innerkey, innerkey)
}) : null }) : null
}) })
} }
</TreeView> </TreeView>
</div> </div>
</> </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 { .udapp_contractActionsContainerMultiInner {
width: 100%; width: 100%;
padding: 16px 8px 16px 14px;
border-radius: 3px; border-radius: 3px;
margin-bottom: 8px; margin-bottom: 8px;
} }
@ -513,4 +512,22 @@
white-space: -o-pre-wrap; /* Opera 7 */ white-space: -o-pre-wrap; /* Opera 7 */
word-wrap: break-word; /* Internet Explorer 5.5+ */ 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 { CompilerAbstract } from '@remix-project/remix-solidity-ts'
import { ContractData } from '@remix-project/core-plugin' 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 { interface Action {
type: string type: string
payload: any payload: any
@ -62,8 +64,10 @@ export interface RunTabState {
compiler: CompilerAbstract compiler: CompilerAbstract
}[] }[]
}, },
deployOptions: DeployOptions
loadType: 'abi' | 'sol' | 'other' loadType: 'abi' | 'sol' | 'other'
currentFile: string, currentFile: string,
currentContract: string,
compilationCount: number, compilationCount: number,
isRequesting: boolean, isRequesting: boolean,
isSuccessful: boolean, isSuccessful: boolean,
@ -154,8 +158,10 @@ export const runTabInitialState: RunTabState = {
matchPassphrase: '', matchPassphrase: '',
contracts: { contracts: {
contractList: {}, contractList: {},
deployOptions: {} as any,
loadType: 'other', loadType: 'other',
currentFile: '', currentFile: '',
currentContract: '',
compilationCount: 0, compilationCount: 0,
isRequesting: false, isRequesting: false,
isSuccessful: false, isSuccessful: false,
@ -186,7 +192,7 @@ type AddProvider = {
export const runTabReducer = (state: RunTabState = runTabInitialState, action: Action) => { export const runTabReducer = (state: RunTabState = runTabInitialState, action: Action) => {
switch (action.type) { switch (action.type) {
case 'FETCH_ACCOUNTS_LIST_REQUEST': { case FETCH_ACCOUNTS_LIST_REQUEST: {
return { return {
...state, ...state,
accounts: { 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 const payload: Record<string, string> = action.payload
return { 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 const payload: string = action.payload
return { 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 const payload: string = action.payload
return { 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 const payload: string = action.payload
return { 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 const payload: 'ether' | 'finney' | 'gwei' | 'wei' = action.payload
return { 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 const payload: number = action.payload
return { 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 const payload: string = action.payload
return { 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 const payload: boolean = action.payload
return { 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 const payload: string = action.payload
return { return {
@ -299,7 +305,7 @@ export const runTabReducer = (state: RunTabState = runTabInitialState, action: A
} }
} }
case 'FETCH_PROVIDER_LIST_REQUEST': { case FETCH_PROVIDER_LIST_REQUEST: {
return { return {
...state, ...state,
providers: { 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 const payload: { id?: string, dataId?: string, title?: string, value: string, fork?: string, content: string }[] = action.payload
return { 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 const payload: string = action.payload
return { 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 payload: AddProvider = action.payload
const id = action.payload.name const id = action.payload.name
state.providers.providerList.push({ 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 id: string = action.payload
const providers = state.providers.providerList.filter((el) => el.id !== id) const providers = state.providers.providerList.filter((el) => el.id !== id)
return { 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 } const payload = action.payload as { title: string, message: string, actionOk: () => void, actionCancel: () => void, labelOk: string, labelCancel: string }
return { return {
@ -387,14 +393,14 @@ export const runTabReducer = (state: RunTabState = runTabInitialState, action: A
} }
} }
case 'HIDE_NOTIFICATION': { case HIDE_NOTIFICATION: {
return { return {
...state, ...state,
notification: runTabInitialState.notification notification: runTabInitialState.notification
} }
} }
case 'SET_EXTERNAL_WEB3_ENDPOINT': { case SET_EXTERNAL_WEB3_ENDPOINT: {
const payload: string = action.payload const payload: string = action.payload
return { 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 const payload = action.payload as string
return { return {
@ -412,14 +418,14 @@ export const runTabReducer = (state: RunTabState = runTabInitialState, action: A
} }
} }
case 'HIDE_POPUP_MESSAGE': { case HIDE_POPUP_MESSAGE: {
return { return {
...state, ...state,
popup: '' popup: ''
} }
} }
case 'SET_PASSPHRASE': { case SET_PASSPHRASE: {
const passphrase: string = action.payload const passphrase: string = action.payload
return { 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 const passphrase: string = action.payload
return { return {
@ -437,7 +443,7 @@ export const runTabReducer = (state: RunTabState = runTabInitialState, action: A
} }
} }
case 'FETCH_CONTRACT_LIST_REQUEST': { case FETCH_CONTRACT_LIST_REQUEST: {
return { return {
...state, ...state,
contracts: { 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 const payload: ContractList = action.payload
return { 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 const payload: string = action.payload
return { 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 const payload: 'abi' | 'sol' | 'other' = action.payload
return { 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 const payload: string = action.payload
return { 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 const payload: boolean = action.payload
return { 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 const payload: boolean = action.payload
return { 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 const payload: boolean = action.payload
return { 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 const payload: string = action.payload
return { 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 const payload: string = action.payload
return { 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 const payload: string = action.payload
return { 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 const payload: string = action.payload
return { 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 const payload: string = action.payload
return { 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 const payload: { contractData: ContractData, address: string, name: string, abi?: any, decodedResponse?: Record<number, any> } = action.payload
return { return {
@ -587,7 +605,7 @@ export const runTabReducer = (state: RunTabState = runTabInitialState, action: A
} }
} }
case 'REMOVE_INSTANCE': { case REMOVE_INSTANCE: {
const payload: number = action.payload const payload: number = action.payload
return { return {
@ -599,7 +617,7 @@ export const runTabReducer = (state: RunTabState = runTabInitialState, action: A
} }
} }
case 'CLEAR_INSTANCES': { case CLEAR_INSTANCES: {
return { return {
...state, ...state,
instances: { 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 const payload: { instanceIndex: number, funcIndex: number, response: any } = action.payload
return { 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 const payload: string = action.payload
return { 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 const payload: number = action.payload
return { return {
@ -648,7 +666,7 @@ export const runTabReducer = (state: RunTabState = runTabInitialState, action: A
} }
} }
case 'CLEAR_RECORDER_COUNT': { case CLEAR_RECORDER_COUNT: {
return { return {
...state, ...state,
recorder: { recorder: {
@ -658,13 +676,57 @@ export const runTabReducer = (state: RunTabState = runTabInitialState, action: A
} }
} }
case 'RESET_STATE': { case RESET_STATE: {
return { return {
...runTabInitialState, ...runTabInitialState,
ipfsChecked: state.ipfsChecked 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: default:
return state return state
} }

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

@ -1,3 +1,4 @@
import { Ref } from 'react'
import { CompilerAbstract } from '@remix-project/remix-solidity-ts' import { CompilerAbstract } from '@remix-project/remix-solidity-ts'
import { ContractData, FuncABI } from '@remix-project/core-plugin' import { ContractData, FuncABI } from '@remix-project/core-plugin'
import { ContractList } from '../reducers/runTab' import { ContractList } from '../reducers/runTab'
@ -21,7 +22,6 @@ export interface SettingsProps {
sendUnit: string, sendUnit: string,
gasLimit: number, gasLimit: number,
setGasFee: (value: number) => void, setGasFee: (value: number) => void,
setWeb3Endpoint: (endpoint: string) => void,
personalMode: boolean, personalMode: boolean,
networkName: string, networkName: string,
providers: { providers: {
@ -128,8 +128,10 @@ export interface ContractDropdownProps {
exEnvironment: string, exEnvironment: string,
contracts: { contracts: {
contractList: ContractList, contractList: ContractList,
deployOptions: DeployOptions,
loadType: 'abi' | 'sol' | 'other', loadType: 'abi' | 'sol' | 'other',
currentFile: string, currentFile: string,
currentContract: string,
compilationCount: number, compilationCount: number,
isRequesting: boolean, isRequesting: boolean,
isSuccessful: boolean, isSuccessful: boolean,
@ -143,29 +145,28 @@ export interface ContractDropdownProps {
selectedContract: ContractData, selectedContract: ContractData,
gasEstimationPrompt: (msg: string) => JSX.Element, gasEstimationPrompt: (msg: string) => JSX.Element,
passphrasePrompt: (msg: string) => JSX.Element, passphrasePrompt: (msg: string) => JSX.Element,
logBuilder: (msg: string) => JSX.Element,
publishToStorage: (storage: 'ipfs' | 'swarm', publishToStorage: (storage: 'ipfs' | 'swarm',
contract: ContractData) => void, contract: ContractData) => void,
mainnetPrompt: MainnetPrompt, mainnetPrompt: MainnetPrompt,
isOverSizePrompt: () => JSX.Element, isOverSizePrompt: () => JSX.Element,
args) => void, args,
deployMode: DeployMode[]) => void,
ipfsCheckedState: boolean, ipfsCheckedState: boolean,
setIpfsCheckedState: (value: boolean) => void, setIpfsCheckedState: (value: boolean) => void,
publishToStorage: (storage: 'ipfs' | 'swarm', contract: ContractData) => void, publishToStorage: (storage: 'ipfs' | 'swarm', contract: ContractData) => void,
gasEstimationPrompt: (msg: string) => JSX.Element, gasEstimationPrompt: (msg: string) => JSX.Element,
logBuilder: (msg: string) => JSX.Element,
passphrasePrompt: (message: 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, 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, tooltip: (toasterMsg: string | JSX.Element) => void,
loadAddress: (contract: ContractData, address: string) => void, loadAddress: (contract: ContractData, address: string) => void,
networkName: string, networkName: string,
setNetworkName: (name: string) => void setNetworkName: (name: string) => void,
setSelectedContract: (contractName: string) => void
} }
export interface RecorderProps { export interface RecorderProps {
storeScenario: (prompt: (msg: string, defaultValue: string) => JSX.Element) => void, 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, runCurrentScenario: (gasEstimationPrompt: (msg: string) => JSX.Element, passphrasePrompt: (msg: string) => JSX.Element, confirmDialogContent: MainnetPrompt) => void,
logBuilder: (msg: string) => JSX.Element,
mainnetPrompt: MainnetPrompt, mainnetPrompt: MainnetPrompt,
gasEstimationPrompt: (msg: string) => JSX.Element, gasEstimationPrompt: (msg: string) => JSX.Element,
passphrasePrompt: (msg: string) => JSX.Element, passphrasePrompt: (msg: string) => JSX.Element,
@ -196,13 +197,11 @@ export interface InstanceContainerProps {
contractABI, contract, contractABI, contract,
address, address,
logMsg:string, logMsg:string,
logBuilder: (msg: string) => JSX.Element,
mainnetPrompt: MainnetPrompt, mainnetPrompt: MainnetPrompt,
gasEstimationPrompt: (msg: string) => JSX.Element, gasEstimationPrompt: (msg: string) => JSX.Element,
passphrasePrompt: (msg: string) => JSX.Element, passphrasePrompt: (msg: string) => JSX.Element,
funcIndex?: number) => void, funcIndex?: number) => void,
gasEstimationPrompt: (msg: string) => JSX.Element, gasEstimationPrompt: (msg: string) => JSX.Element,
logBuilder: (msg: string) => JSX.Element,
passphrasePrompt: (message: 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, 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, sendValue: string,
@ -220,15 +219,43 @@ export interface Modal {
cancelFn: () => void 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 { export interface ContractGUIProps {
title?: string, title?: string,
funcABI: FuncABI, funcABI: FuncABI,
inputs: any, inputs: string,
clickCallBack: (inputs: { name: string, type: string }[], input: string) => void, clickCallBack: (inputs: { name: string, type: string }[], input: string, deployMode?: DeployMode[]) => void,
widthClass?: string, widthClass?: string,
evmBC: any, evmBC: any,
lookupOnly: boolean, lookupOnly: boolean,
disabled?: boolean disabled?: boolean,
isDeploy?: boolean,
deployOption?: { title: DeployMode, active: boolean }[],
initializerOptions?: DeployOption
} }
export interface MainnetProps { export interface MainnetProps {
network: Network, network: Network,
@ -261,7 +288,6 @@ export interface UdappProps {
removeInstance: (index: number) => void, removeInstance: (index: number) => void,
index: number, index: number,
gasEstimationPrompt: (msg: string) => JSX.Element, gasEstimationPrompt: (msg: string) => JSX.Element,
logBuilder: (msg: string) => JSX.Element,
passphrasePrompt: (message: 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, 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: ( runTransactions: (
@ -273,7 +299,6 @@ export interface UdappProps {
contractABI, contract, contractABI, contract,
address, address,
logMsg:string, logMsg:string,
logBuilder: (msg: string) => JSX.Element,
mainnetPrompt: MainnetPrompt, mainnetPrompt: MainnetPrompt,
gasEstimationPrompt: (msg: string) => JSX.Element, gasEstimationPrompt: (msg: string) => JSX.Element,
passphrasePrompt: (msg: string) => JSX.Element, passphrasePrompt: (msg: string) => JSX.Element,
@ -281,3 +306,55 @@ export interface UdappProps {
sendValue: string, sendValue: string,
getFuncABIInputs: (funcABI: FuncABI) => 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", "ansi-gray": "^0.1.1",
"async": "^2.6.2", "async": "^2.6.2",
"axios": ">=0.26.0", "axios": ">=0.26.0",
"bootstrap": "^5.1.3",
"brace": "^0.8.0", "brace": "^0.8.0",
"change-case": "^4.1.1", "change-case": "^4.1.1",
"chokidar": "^2.1.8", "chokidar": "^2.1.8",

Loading…
Cancel
Save