From c75c5102dd59a43ca0fcceee8762ab9034caad3a Mon Sep 17 00:00:00 2001 From: Aniket Singh Date: Tue, 27 Dec 2022 12:22:42 +0530 Subject: [PATCH 1/2] state mutability fix --- package.json | 5 +++ src/extension.ts | 6 +++ src/utils/networks.ts | 95 ++++++++++++++++++++++++++++++------------- 3 files changed, 78 insertions(+), 28 deletions(-) diff --git a/package.json b/package.json index d5536b7..dcf4322 100644 --- a/package.json +++ b/package.json @@ -146,6 +146,11 @@ "command": "ethcode.rental.create", "title": "Create new ERC4907 contract", "category": "Ethcode" + }, + { + "command": "ethcode.payable.set", + "title": "Set payable value", + "category": "Ethcode" } ], "keybindings": [ diff --git a/src/extension.ts b/src/extension.ts index e4fd460..a7c58ea 100644 --- a/src/extension.ts +++ b/src/extension.ts @@ -6,6 +6,7 @@ import { callContractMethod, deployContract, displayBalance, + setPayableValue, setTransactionGas, updateSelectedNetwork, } from "./utils/networks"; @@ -112,6 +113,11 @@ export async function activate(context: vscode.ExtensionContext) { importKeyPair(context); }), + //set payable value + commands.registerCommand("ethcode.payable.set", async () => { + setPayableValue(context); + }), + // Set custom gas estimate // commands.registerCommand('ethcode.transaction.gas.set', async () => { // const gasInp: InputBoxOptions = { diff --git a/src/utils/networks.ts b/src/utils/networks.ts index 32526ab..02895cf 100644 --- a/src/utils/networks.ts +++ b/src/utils/networks.ts @@ -1,6 +1,6 @@ import { ethers } from "ethers"; import * as vscode from "vscode"; -import { window } from "vscode"; +import { window, InputBoxOptions } from "vscode"; import { CompiledJSONOutput, GasEstimateOutput, @@ -86,8 +86,7 @@ const getSelectedProvider = (context: vscode.ExtensionContext) => { // Contract function calls const displayBalance = async (context: vscode.ExtensionContext) => { - - if(getSelectedNetwork(context) === undefined) { + if (getSelectedNetwork(context) === undefined) { logger.log("No network selected. Please select a network."); return; } @@ -95,8 +94,8 @@ const displayBalance = async (context: vscode.ExtensionContext) => { const address: any = await context.workspaceState.get("account"); const nativeCurrencySymbol = getSelectedNetConf(context).nativeCurrency.symbol; - - try { + + try { getSelectedProvider(context) .getBalance(address) .then(async (value) => { @@ -170,7 +169,12 @@ const callContractMethod = async (context: vscode.ExtensionContext) => { if (contractAddres === undefined) throw new Error("Enter deployed address of selected contract."); - if (abiItem.stateMutability === "view") { + let result: any; + + if ( + abiItem.stateMutability === "view" || + abiItem.stateMutability === "pure" + ) { selectContract(context); const contract = new ethers.Contract( @@ -179,41 +183,58 @@ const callContractMethod = async (context: vscode.ExtensionContext) => { getSelectedProvider(context) ); - const result = await contract[abiItem.name as string](...params); + result = await contract[abiItem.name as string](...params); logger.success( `Calling ${compiledOutput.name} : ${abiItem.name} --> Success!` ); - logger.log(JSON.stringify(result)); - } else { - const contract = await getSignedContract(context, contractAddres); + if (result) { + logger.log(JSON.stringify(result)); + } + } - let result; + if ( + abiItem.stateMutability === "nonpayable" || + abiItem.stateMutability === "payable" + ) { + const contract = await getSignedContract(context, contractAddres); + const gasCondition = (await context.workspaceState.get("gas")) as string; - if (abiItem.stateMutability === "nonpayable") { - const gasCondition = (await context.workspaceState.get( - "gas" - )) as string; + const gasEstimate = await getGasEstimates(gasCondition, context); + const settingsGasLimit = (await getConfiguration().get( + "gasLimit" + )) as number; - const gasEstimate = await getGasEstimates(gasCondition, context); - const settingsGasLimit = (await getConfiguration().get( - "gasLimit" - )) as number; - if (gasEstimate !== undefined) { - const maxFeePerGas = (gasEstimate as EstimateGas).price; + // check for mainnets and testnets... + // execute if mainnet + if (gasEstimate !== undefined) { + const maxFeePerGas = (gasEstimate as EstimateGas).price; + // if method statemutability is payable + if (abiItem.stateMutability === "payable") { + const value = context.workspaceState.get("payableValue"); result = await contract[abiItem.name as string](...params, { + value: value, gasPrice: ethers.utils.parseUnits(maxFeePerGas.toString(), "gwei"), gasLimit: settingsGasLimit, }); } else { - result = await contract[abiItem.name as string](...params); + // if method statemutability is non-payble + result = await contract[abiItem.name as string](...params, { + gasPrice: ethers.utils.parseUnits(maxFeePerGas.toString(), "gwei"), + gasLimit: settingsGasLimit, + }); } } else { - const found: any = abiItem.inputs?.find( - (e: any) => e.type === "uint256" - ); - result = await contract[abiItem.name as string](...params, { - value: found.value, - }); + // execute if testnet + if (abiItem.stateMutability === "payable") { + // if method statemutability is payable + const value = context.workspaceState.get("payableValue"); + result = await contract[abiItem.name as string](...params, { + value: value, + }); + } else { + // if method statemutability is payable + result = await contract[abiItem.name as string](...params); + } } logger.success("Waiting for confirmation..."); @@ -337,6 +358,23 @@ const getContractFactoryWithParams = async ( return myContract; }; +const setPayableValue = async (context: vscode.ExtensionContext) => { + try { + const inputBoxOpts: InputBoxOptions = { + placeHolder: "value", + ignoreFocusOut: true, + }; + const value = await window.showInputBox(inputBoxOpts); + if (value === undefined) { + return; + } + await context.workspaceState.update("payableValue", value); + logger.log(`payable value set to: ${value} wei`); + } catch (error) { + logger.log("Error: payable value is not saved"); + } +}; + export { getConfiguration, getNetworkNames, @@ -349,4 +387,5 @@ export { deployContract, isTestingNetwork, setTransactionGas, + setPayableValue, }; From c5768a212cf97235bfa7fdf8792a79136f89ed04 Mon Sep 17 00:00:00 2001 From: Aniket Singh Date: Wed, 28 Dec 2022 16:24:12 +0530 Subject: [PATCH 2/2] contract call code optimized --- src/types/types.ts | 6 + .../contractCall/ImmutableFunctionCall.ts | 25 ++++ src/utils/contractCall/mutableFunctionCall.ts | 54 +++++++++ src/utils/networks.ts | 112 ++++++------------ 4 files changed, 119 insertions(+), 78 deletions(-) create mode 100644 src/utils/contractCall/ImmutableFunctionCall.ts create mode 100644 src/utils/contractCall/mutableFunctionCall.ts diff --git a/src/types/types.ts b/src/types/types.ts index 573a88a..4ef639e 100644 --- a/src/types/types.ts +++ b/src/types/types.ts @@ -117,6 +117,12 @@ export interface ERC4907ContractType { contract: string; ERC4907Contract: string; } + +export interface useContractType { + contractName: string; + abiItemName: string; + contractAddress: any; +} // Typeguard export function isConstructorInputValue( diff --git a/src/utils/contractCall/ImmutableFunctionCall.ts b/src/utils/contractCall/ImmutableFunctionCall.ts new file mode 100644 index 0000000..75b4bca --- /dev/null +++ b/src/utils/contractCall/ImmutableFunctionCall.ts @@ -0,0 +1,25 @@ +import { JsonFragment } from "@ethersproject/abi"; +import { ethers } from "ethers"; +import { ExtensionContext } from "vscode"; +import { useContractType } from "../../types"; +import { getSelectedProvider } from "../networks"; +import { logger } from "../../lib"; + +export const ImmutableFunctionCall = async ( + context: ExtensionContext, + params: any[], + abi: readonly JsonFragment[], + useContract: useContractType +) => { + const { abiItemName, contractName, contractAddress } = useContract; + const contract = new ethers.Contract( + contractAddress, + abi, + getSelectedProvider(context) + ); + const result = await contract[abiItemName](...params); + logger.success(`Calling ${contractName} : ${abiItemName} --> Success!`); + if (result) { + logger.log(JSON.stringify(result)); + } +}; diff --git a/src/utils/contractCall/mutableFunctionCall.ts b/src/utils/contractCall/mutableFunctionCall.ts new file mode 100644 index 0000000..65e6c50 --- /dev/null +++ b/src/utils/contractCall/mutableFunctionCall.ts @@ -0,0 +1,54 @@ +import { ethers } from "ethers"; +import { ExtensionContext } from "vscode"; +import { logger } from "../../lib"; +import { EstimateGas, useContractType } from "../../types"; +import { getGasEstimates } from "../functions"; +import { + getConfiguration, + getSelectedNetConf, + getSignedContract, +} from "../networks"; + +export const MutableFunctionCall = async ( + context: ExtensionContext, + state: string, + params: any[], + useContract: useContractType +) => { + const MAX_FEE_PER_GAS = 100; + const { abiItemName, contractAddress, contractName } = useContract; + + const contract = await getSignedContract(context, contractAddress); + + let gasCondition = (await context.workspaceState.get("gas")) as string; + + const gasEstimate: EstimateGas | undefined = await getGasEstimates( + gasCondition, + context + ); + + const maxFeePerGas = + gasEstimate !== undefined ? gasEstimate.maxFeePerGas : MAX_FEE_PER_GAS; + + const settingsGasLimit = (await getConfiguration().get("gasLimit")) as number; + + const value = + state === "payable" ? await context.workspaceState.get("payableValue") : 0; + + const result = await contract[abiItemName as string](...params, { + value: value, + gasPrice: ethers.utils.parseUnits(maxFeePerGas.toString(), "gwei"), + gasLimit: settingsGasLimit, + }); + + logger.success("Waiting for confirmation..."); + + await result.wait(); + logger.success("Transaction confirmed!"); + logger.success(`Calling ${contractName} : ${abiItemName} --> Success!`); + logger.success( + `You can see detail of this transaction here. ${ + getSelectedNetConf(context).blockScanner + }/tx/${result.hash}` + ); +}; diff --git a/src/utils/networks.ts b/src/utils/networks.ts index 02895cf..c0d9948 100644 --- a/src/utils/networks.ts +++ b/src/utils/networks.ts @@ -9,7 +9,12 @@ import { } from "../types/output"; import { logger } from "../lib"; import { extractPvtKey } from "./wallet"; -import { INetworkQP, EstimateGas, NetworkConfig } from "../types"; +import { + INetworkQP, + EstimateGas, + NetworkConfig, + useContractType, +} from "../types"; import { getConstructorInputs, getDeployedInputs, @@ -19,6 +24,8 @@ import { import { errors } from "../config/errors"; import { selectContract } from "./contracts"; +import { ImmutableFunctionCall } from "./contractCall/ImmutableFunctionCall"; +import { MutableFunctionCall } from "./contractCall/mutableFunctionCall"; const provider = ethers.providers; @@ -152,102 +159,47 @@ const callContractMethod = async (context: vscode.ExtensionContext) => { "contract" )) as CompiledJSONOutput; - if (compiledOutput == undefined) throw errors.ContractNotSelected; + if (compiledOutput === undefined) throw errors.ContractNotSelected; const abi = getAbi(compiledOutput); - if (abi == undefined) throw new Error("Abi is not defined."); + if (abi === undefined) throw new Error("Abi is not defined."); const abiItem = await getFunctionInputs(context); if (abiItem === undefined) throw new Error("Function is not defined."); - const params_ = abiItem.inputs?.map((e: any) => e.value); - const params = params_ === undefined ? [] : params_; - - logger.success(`Calling ${compiledOutput.name} : ${abiItem.name} -->`); - const contractAddres = getDeployedInputs(context).address; if (contractAddres === undefined) throw new Error("Enter deployed address of selected contract."); - let result: any; + const params_ = abiItem.inputs?.map((e: any) => e.value); + const params = params_ === undefined ? [] : params_; + + const useContract: useContractType = { + contractName: compiledOutput.name as string, + abiItemName: abiItem.name as string, + contractAddress: contractAddres, + }; + + logger.success( + `Calling ${useContract.contractName} : ${useContract.abiItemName} -->` + ); if ( abiItem.stateMutability === "view" || abiItem.stateMutability === "pure" ) { - selectContract(context); - - const contract = new ethers.Contract( - contractAddres, - abi, - getSelectedProvider(context) - ); - - result = await contract[abiItem.name as string](...params); - logger.success( - `Calling ${compiledOutput.name} : ${abiItem.name} --> Success!` - ); - if (result) { - logger.log(JSON.stringify(result)); - } + await ImmutableFunctionCall(context, params, abi, useContract); } if ( abiItem.stateMutability === "nonpayable" || abiItem.stateMutability === "payable" ) { - const contract = await getSignedContract(context, contractAddres); - const gasCondition = (await context.workspaceState.get("gas")) as string; - - const gasEstimate = await getGasEstimates(gasCondition, context); - const settingsGasLimit = (await getConfiguration().get( - "gasLimit" - )) as number; - - // check for mainnets and testnets... - // execute if mainnet - if (gasEstimate !== undefined) { - const maxFeePerGas = (gasEstimate as EstimateGas).price; - // if method statemutability is payable - if (abiItem.stateMutability === "payable") { - const value = context.workspaceState.get("payableValue"); - result = await contract[abiItem.name as string](...params, { - value: value, - gasPrice: ethers.utils.parseUnits(maxFeePerGas.toString(), "gwei"), - gasLimit: settingsGasLimit, - }); - } else { - // if method statemutability is non-payble - result = await contract[abiItem.name as string](...params, { - gasPrice: ethers.utils.parseUnits(maxFeePerGas.toString(), "gwei"), - gasLimit: settingsGasLimit, - }); - } - } else { - // execute if testnet - if (abiItem.stateMutability === "payable") { - // if method statemutability is payable - const value = context.workspaceState.get("payableValue"); - result = await contract[abiItem.name as string](...params, { - value: value, - }); - } else { - // if method statemutability is payable - result = await contract[abiItem.name as string](...params); - } - } - - logger.success("Waiting for confirmation..."); - - await result.wait(); - logger.success("Transaction confirmed!"); - logger.success( - `Calling ${compiledOutput.name} : ${abiItem.name} --> Success!` - ); - logger.success( - `You can see detail of this transaction here. ${ - getSelectedNetConf(context).blockScanner - }/tx/${result.hash}` + await MutableFunctionCall( + context, + abiItem.stateMutability, + params, + useContract ); } } catch (err: any) { @@ -368,8 +320,11 @@ const setPayableValue = async (context: vscode.ExtensionContext) => { if (value === undefined) { return; } - await context.workspaceState.update("payableValue", value); - logger.log(`payable value set to: ${value} wei`); + const valueInWei = ethers.utils.parseEther(value); + const nativeCurrencySymbol = + getSelectedNetConf(context).nativeCurrency.symbol; + await context.workspaceState.update("payableValue", valueInWei.toString()); + logger.log(`payable value set to: ${value} ${nativeCurrencySymbol}`); } catch (error) { logger.log("Error: payable value is not saved"); } @@ -388,4 +343,5 @@ export { isTestingNetwork, setTransactionGas, setPayableValue, + getSignedContract, };