Add upgrade modal and update deploy options based on currentFile

pull/2609/head
David Disu 2 years ago committed by Aniket
parent 00f14b61e8
commit 7f89e1be69
  1. 24
      apps/remix-ide/src/blockchain/blockchain.js
  2. 13
      libs/remix-core-plugin/src/lib/openzeppelin-proxy.ts
  3. 32
      libs/remix-core-plugin/src/types/contract.ts
  4. 6
      libs/remix-ui/helper/src/lib/helper-components.tsx
  5. 2
      libs/remix-ui/run-tab/src/lib/actions/deploy.ts
  6. 9
      libs/remix-ui/run-tab/src/lib/actions/events.ts
  7. 8
      libs/remix-ui/run-tab/src/lib/actions/payload.ts
  8. 9
      libs/remix-ui/run-tab/src/lib/components/contractDropdownUI.tsx
  9. 10
      libs/remix-ui/run-tab/src/lib/components/contractGUI.tsx
  10. 30
      libs/remix-ui/run-tab/src/lib/reducers/runTab.ts
  11. 10
      libs/remix-ui/run-tab/src/lib/types/index.ts

@ -11,8 +11,7 @@ import InjectedProvider from './providers/injected.js'
import NodeProvider from './providers/node.js'
import { execution, EventManager, helpers } from '@remix-project/remix-lib'
import { etherScanLink } from './helper'
import { logBuilder, confirmProxyMsg } from "@remix-ui/helper"
import { cancelProxyMsg } from '@remix-ui/helper'
import { logBuilder, cancelUpgradeMsg, cancelProxyMsg } from "@remix-ui/helper"
const { txFormat, txExecution, typeConversion, txListener: Txlistener, TxRunner, TxRunnerWeb3, txHelper } = execution
const { txResultHelper: resultToRemixTx } = helpers
const packageJson = require('../../../../package.json')
@ -183,7 +182,26 @@ export class Blockchain extends Plugin {
this.runTx(args, confirmationCb, continueCb, promptCb, finalCb)
}
async upgradeProxy(proxyAddress, data, newImplementationContractObject) {
async upgradeProxy(proxyAddress, newImplAddress, data, newImplementationContractObject) {
const upgradeModal = {
id: 'confirmProxyDeployment',
title: 'ERC1967',
message: `Confirm you want to upgrade your contract to new implementation ${newImplAddress}.`,
modalType: 'modal',
okLabel: 'OK',
cancelLabel: 'Cancel',
okFn: () => {
this.runUpgradeTx(proxyAddress, data, newImplementationContractObject)
},
cancelFn: () => {
this.call('notification', 'toast', cancelUpgradeMsg())
},
hideFn: () => null
}
this.call('notification', 'modal', upgradeModal)
}
async runUpgradeTx (proxyAddress, data, newImplementationContractObject) {
const args = { useCall: false, data, to: proxyAddress }
const confirmationCb = (network, tx, gasEstimation, continueTxExecution, cancelCb) => {
// continue using original authorization given by user

@ -1,12 +1,12 @@
import { Plugin } from '@remixproject/engine';
import { ContractABI, ContractAST, DeployOption } from '../types/contract';
import { ContractABI, ContractAST, DeployOptions } from '../types/contract';
import { UUPS, UUPSABI, UUPSBytecode, UUPSfunAbi, UUPSupgradeAbi } from './constants/uups';
const proxyProfile = {
name: 'openzeppelin-proxy',
displayName: 'openzeppelin-proxy',
description: 'openzeppelin-proxy',
methods: ['isConcerned', 'executeUUPSProxy', 'executeUUPSContractUpgrade', 'getDeployOptions', 'getUpgradeOptions']
methods: ['isConcerned', 'executeUUPSProxy', 'executeUUPSContractUpgrade', 'getProxyOptions', 'getUpgradeOptions']
};
export class OpenZeppelinProxy extends Plugin {
blockchain: any
@ -28,7 +28,7 @@ export class OpenZeppelinProxy extends Plugin {
return false
}
async getDeployOptions (contracts: ContractABI): Promise<{ [name: string]: DeployOption }> {
async getProxyOptions (contracts: ContractABI): Promise<{ [name: string]: DeployOptions }> {
const inputs = {}
if (this.kind === 'UUPS') {
@ -36,8 +36,9 @@ export class OpenZeppelinProxy extends Plugin {
const abi = contracts[name].abi
const initializeInput = abi.find(node => node.name === 'initialize')
if (initializeInput) {
inputs[name] = {
inputs[name] = {
options: [{ title: 'Deploy with Proxy', active: false }, { title: 'Upgrade Contract', active: false }],
initializeOptions: {
inputs: initializeInput,
initializeInputs: this.blockchain.getInputs(initializeInput)
}
@ -95,6 +96,6 @@ export class OpenZeppelinProxy extends Plugin {
}
// re-use implementation contract's ABI for UI display in udapp and change name to proxy name.
newImplementationContractObject.name = proxyName
this.blockchain.upgradeProxy(proxyAddress, data, newImplementationContractObject)
this.blockchain.upgradeProxy(proxyAddress, newImplAddress, data, newImplementationContractObject)
}
}

@ -136,19 +136,25 @@ export interface ContractABI {
};
}
export type DeployMode = 'Deploy with Proxy' | 'Upgrade Contract'
export type DeployOption = {
initializeInputs: string,
initializeInputs: string,
inputs: {
inputs: {
inputs: {
internalType?: string,
name: string,
type: string
}[],
name: "initialize",
outputs?: any[],
stateMutability: string,
type: string,
payable?: boolean,
constant?: any
}
internalType?: string,
name: string,
type: string
}[],
name: "initialize",
outputs?: any[],
stateMutability: string,
type: string,
payable?: boolean,
constant?: any
}
}
export interface DeployOptions {
initializeOptions: DeployOption,
options: { title: DeployMode, active: boolean }[]
}

@ -89,3 +89,9 @@ export const cancelProxyMsg = () => (
<b>Proxy deployment cancelled.</b>
</div>
)
export const cancelUpgradeMsg = () => (
<div>
<b>Upgrade with proxy cancelled.</b>
</div>
)

@ -179,7 +179,7 @@ export const createInstance = async (
if (selectedContract.isOverSizeLimit()) {
return dispatch(displayNotification('Contract code size over limit', isOverSizePrompt(), 'Force Send', 'Cancel', () => {
deployContract(plugin, selectedContract, !isProxyDeployment ? args : '', contractMetadata, compilerContracts, {
deployContract(plugin, selectedContract, !isProxyDeployment && !isContractUpgrade ? args : '', contractMetadata, compilerContracts, {
continueCb: (error, continueTxExecution, cancelCb) => {
continueHandler(dispatch, gasEstimationPrompt, error, continueTxExecution, cancelCb)
},

@ -2,7 +2,7 @@ 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, setProxyEnvAddress, setRecorderCount, setSendValue } from "./payload"
import { addDeployOption, clearAllInstances, clearRecorderCount, fetchContractListSuccess, resetUdapp, setCurrentContract, setCurrentFile, setLoadType, setProxyEnvAddress, setRecorderCount, setSendValue } from "./payload"
import { CompilerAbstract } from '@remix-project/remix-solidity'
import * as ethJSUtil from 'ethereumjs-util'
import Web3 from 'web3'
@ -108,11 +108,12 @@ const broadcastCompilationResult = async (plugin: RunTab, dispatch: React.Dispat
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])
const options = await plugin.call('openzeppelin-proxy', 'getProxyOptions', data.contracts[file])
dispatch(setDeployOptions({ options: [{ title: 'Deploy with Proxy', active: false }, { title: 'Upgrade Contract', active: false }], initializeOptions: options }))
dispatch(addDeployOption({ [file]: options }))
} else {
dispatch(addDeployOption({ [file]: {} }))
}
else dispatch(setDeployOptions({} as any))
dispatch(fetchContractListSuccess({ [file]: contracts }))
dispatch(setCurrentFile(file))
// TODO: set current contract

@ -280,21 +280,21 @@ export const resetUdapp = () => {
}
}
export const addDeployOption = (deployOption: DeployOptions) => {
export const addDeployOption = (deployOption: { [file: string]: { [name: string]: DeployOptions } }) => {
return {
payload: deployOption,
type: ADD_DEPLOY_OPTION
}
}
export const removeDeployOption = (title: DeployMode) => {
export const removeDeployOption = (file: string) => {
return {
payload: title,
payload: file,
type: REMOVE_DEPLOY_OPTION
}
}
export const setDeployOptions = (deployOptions: DeployOptions) => {
export const setDeployOptions = (deployOptions: { [file: string]: { [name: string]: DeployOptions } }) => {
return {
payload: deployOptions,
type: SET_DEPLOY_OPTIONS

@ -27,7 +27,7 @@ export function ContractDropdownUI (props: ContractDropdownProps) {
const [constructorInterface, setConstructorInterface] = useState<FuncABI>(null)
const [constructorInputs, setConstructorInputs] = useState(null)
const contractsRef = useRef<HTMLSelectElement>(null)
const { contractList, loadType, currentFile, currentContract, compilationCount, deployOptions } = props.contracts
const { contractList, loadType, currentFile, currentContract, compilationCount, deployOptions, proxyKey } = props.contracts
useEffect(() => {
enableAtAddress(false)
@ -155,6 +155,7 @@ export function ContractDropdownUI (props: ContractDropdownProps) {
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', () => {})
}
if ((selectedContract.name !== currentContract) && (selectedContract.name === 'ERC1967Proxy')) selectedContract.name = currentContract
props.createInstance(loadedContractData, props.gasEstimationPrompt, props.passphrasePrompt, props.publishToStorage, props.mainnetPrompt, isOverSizePrompt, args, deployMode)
}
@ -232,15 +233,15 @@ export function ContractDropdownUI (props: ContractDropdownProps) {
<ContractGUI
title='Deploy'
isDeploy={true}
deployOption={deployOptions.options}
initializerOptions={deployOptions.initializeOptions ? deployOptions.initializeOptions[currentContract] : null}
deployOption={deployOptions[currentFile] && deployOptions[currentFile][currentContract] ? deployOptions[currentFile][currentContract].options : null}
initializerOptions={deployOptions[currentFile] && deployOptions[currentFile][currentContract] ? deployOptions[currentFile][currentContract].initializeOptions : null}
funcABI={constructorInterface}
clickCallBack={clickCallback}
inputs={constructorInputs}
widthClass='w-50'
evmBC={loadedContractData.bytecodeObject}
lookupOnly={false}
savedProxyAddress={deployOptions.proxyKey}
savedProxyAddress={proxyKey}
/>
<div className="d-flex py-1 align-items-center custom-control custom-checkbox">
<input

@ -3,7 +3,6 @@ import React, { useEffect, useRef, useState } from 'react'
import * as remixLib from '@remix-project/remix-lib'
import { ContractGUIProps } from '../types'
import { CopyToClipboard } from '@remix-ui/clipboard'
import { shortenAddress } from '@remix-ui/helper'
const txFormat = remixLib.execution.txFormat
export function ContractGUI (props: ContractGUIProps) {
@ -196,7 +195,10 @@ export function ContractGUI (props: ContractGUIProps) {
const value = e.target.checked
setToggleUpgradeImp(value)
if (value) setToggleDeployProxy(false)
if (value) {
setToggleDeployProxy(false)
if (useLastProxy) setProxyAddress(props.savedProxyAddress)
}
setDeployState({ deploy: false, upgrade: value })
}
@ -314,7 +316,7 @@ export function ContractGUI (props: ContractGUIProps) {
className="m-0 form-check-label custom-control-label udapp_checkboxAlign"
title="The implemetation address will be updated to a new address in the proxy contract."
>
Upgrade Contract
Upgrade With Proxy
</label>
</div>
<span onClick={handleToggleUpgradeImp}>
@ -348,7 +350,7 @@ export function ContractGUI (props: ContractGUIProps) {
<label className='mt-2 text-left d-block'>Proxy Address: </label>
<input style={{ height: 32 }} className="form-control udapp_input" placeholder='proxy address' title='Enter previously deployed proxy address on the selected network' onChange={handleSetProxyAddress} />
</div> :
<span className='text-capitalize'>{ proxyAddress ? shortenAddress(proxyAddress) : 'No proxy address available' }</span>
<span className='text-capitalize' style={{ fontSize: '.8em' }}>{ proxyAddress || 'No proxy address available' }</span>
}
</div>
</div>

@ -1,8 +1,7 @@
import { CompilerAbstract } from '@remix-project/remix-solidity-ts'
import { ContractData } from '@remix-project/core-plugin'
import { DeployMode, 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, SET_PROXY_ENV_ADDRESS } from '../constants'
import Web3 from 'web3'
import { DeployOptions } from '../types'
import { 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_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, SET_PROXY_ENV_ADDRESS, ADD_DEPLOY_OPTION, REMOVE_DEPLOY_OPTION } from '../constants'
declare const window: any
interface Action {
@ -67,7 +66,8 @@ export interface RunTabState {
compiler: CompilerAbstract
}[]
},
deployOptions: DeployOptions
deployOptions: { [file: string]: { [name: string]: DeployOptions } },
proxyKey: string,
loadType: 'abi' | 'sol' | 'other'
currentFile: string,
currentContract: string,
@ -155,6 +155,7 @@ export const runTabInitialState: RunTabState = {
contracts: {
contractList: {},
deployOptions: {} as any,
proxyKey: '',
loadType: 'other',
currentFile: '',
currentContract: '',
@ -680,39 +681,33 @@ export const runTabReducer = (state: RunTabState = runTabInitialState, action: A
}
case ADD_DEPLOY_OPTION: {
const payload: { title: DeployMode, active: boolean } = action.payload
const payload: { [file: string]: { [name: string]: DeployOptions } } = action.payload
return {
...state,
contracts: {
...state.contracts,
deployOptions: {
...state.contracts.deployOptions,
options: [...state.contracts.deployOptions.options, payload]
}
deployOptions: {...state.contracts.deployOptions, ...payload }
}
}
}
case REMOVE_DEPLOY_OPTION: {
const payload: string = action.payload
const options = state.contracts.deployOptions.options.filter(val => val.title !== payload)
const options = state.contracts.deployOptions
delete options[payload]
return {
...state,
contracts: {
...state.contracts,
deployOptions: {
...state.contracts.deployOptions,
options
}
deployOptions: options
}
}
}
case SET_DEPLOY_OPTIONS: {
const payload: DeployOptions = action.payload
const payload: { [file: string]: { [name: string]: DeployOptions } } = action.payload
return {
...state,
@ -730,10 +725,7 @@ export const runTabReducer = (state: RunTabState = runTabInitialState, action: A
...state,
contracts: {
...state.contracts,
deployOptions: {
...state.contracts.deployOptions,
proxyKey: payload
}
}
}
}

@ -128,7 +128,8 @@ export interface ContractDropdownProps {
exEnvironment: string,
contracts: {
contractList: ContractList,
deployOptions: DeployOptions,
deployOptions: { [file: string]: { [name: string]: DeployOptions } },
proxyKey: string,
loadType: 'abi' | 'sol' | 'other',
currentFile: string,
currentContract: string,
@ -238,11 +239,8 @@ export type DeployOption = {
}
}
export interface DeployOptions {
initializeOptions: {
[key: string]: DeployOption
},
options: { title: DeployMode, active: boolean }[],
proxyKey?: string
initializeOptions: DeployOption,
options: { title: DeployMode, active: boolean }[]
}
export interface ContractGUIProps {

Loading…
Cancel
Save