diff --git a/apps/remix-ide/src/blockchain/blockchain.js b/apps/remix-ide/src/blockchain/blockchain.js index 346d8ded69..fef939a3d4 100644 --- a/apps/remix-ide/src/blockchain/blockchain.js +++ b/apps/remix-ide/src/blockchain/blockchain.js @@ -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 diff --git a/libs/remix-core-plugin/src/lib/openzeppelin-proxy.ts b/libs/remix-core-plugin/src/lib/openzeppelin-proxy.ts index 9f9e8780d5..af0c7bebaa 100644 --- a/libs/remix-core-plugin/src/lib/openzeppelin-proxy.ts +++ b/libs/remix-core-plugin/src/lib/openzeppelin-proxy.ts @@ -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) } } diff --git a/libs/remix-core-plugin/src/types/contract.ts b/libs/remix-core-plugin/src/types/contract.ts index 92c32653b5..bd0c9075d0 100644 --- a/libs/remix-core-plugin/src/types/contract.ts +++ b/libs/remix-core-plugin/src/types/contract.ts @@ -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 }[] +} diff --git a/libs/remix-ui/helper/src/lib/helper-components.tsx b/libs/remix-ui/helper/src/lib/helper-components.tsx index 206fbec926..aa2928f691 100644 --- a/libs/remix-ui/helper/src/lib/helper-components.tsx +++ b/libs/remix-ui/helper/src/lib/helper-components.tsx @@ -89,3 +89,9 @@ export const cancelProxyMsg = () => ( Proxy deployment cancelled. ) + +export const cancelUpgradeMsg = () => ( +
+ Upgrade with proxy cancelled. +
+) diff --git a/libs/remix-ui/run-tab/src/lib/actions/deploy.ts b/libs/remix-ui/run-tab/src/lib/actions/deploy.ts index 25847a562b..b43065468b 100644 --- a/libs/remix-ui/run-tab/src/lib/actions/deploy.ts +++ b/libs/remix-ui/run-tab/src/lib/actions/deploy.ts @@ -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) }, diff --git a/libs/remix-ui/run-tab/src/lib/actions/events.ts b/libs/remix-ui/run-tab/src/lib/actions/events.ts index 13eacd809d..447da599fc 100644 --- a/libs/remix-ui/run-tab/src/lib/actions/events.ts +++ b/libs/remix-ui/run-tab/src/lib/actions/events.ts @@ -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 diff --git a/libs/remix-ui/run-tab/src/lib/actions/payload.ts b/libs/remix-ui/run-tab/src/lib/actions/payload.ts index f48461a459..bb0fc91a41 100644 --- a/libs/remix-ui/run-tab/src/lib/actions/payload.ts +++ b/libs/remix-ui/run-tab/src/lib/actions/payload.ts @@ -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 diff --git a/libs/remix-ui/run-tab/src/lib/components/contractDropdownUI.tsx b/libs/remix-ui/run-tab/src/lib/components/contractDropdownUI.tsx index fc8196aea0..da13ce63d6 100644 --- a/libs/remix-ui/run-tab/src/lib/components/contractDropdownUI.tsx +++ b/libs/remix-ui/run-tab/src/lib/components/contractDropdownUI.tsx @@ -27,7 +27,7 @@ export function ContractDropdownUI (props: ContractDropdownProps) { const [constructorInterface, setConstructorInterface] = useState(null) const [constructorInputs, setConstructorInputs] = useState(null) const contractsRef = useRef(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) {
- Upgrade Contract + Upgrade With Proxy
@@ -343,12 +345,12 @@ export function ContractGUI (props: ContractGUIProps) { { - !useLastProxy ? + !useLastProxy ?
: - { proxyAddress ? shortenAddress(proxyAddress) : 'No proxy address available' } + { proxyAddress || 'No proxy address available' } } diff --git a/libs/remix-ui/run-tab/src/lib/reducers/runTab.ts b/libs/remix-ui/run-tab/src/lib/reducers/runTab.ts index fd690e7f64..5299b748ab 100644 --- a/libs/remix-ui/run-tab/src/lib/reducers/runTab.ts +++ b/libs/remix-ui/run-tab/src/lib/reducers/runTab.ts @@ -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 - } } } } diff --git a/libs/remix-ui/run-tab/src/lib/types/index.ts b/libs/remix-ui/run-tab/src/lib/types/index.ts index 27b5085833..0595ef69dc 100644 --- a/libs/remix-ui/run-tab/src/lib/types/index.ts +++ b/libs/remix-ui/run-tab/src/lib/types/index.ts @@ -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 {