diff --git a/.changeset/curly-fireants-jog.md b/.changeset/curly-fireants-jog.md new file mode 100644 index 000000000..873e2096d --- /dev/null +++ b/.changeset/curly-fireants-jog.md @@ -0,0 +1,6 @@ +--- +"create-eth": patch +--- + +- Fix typos in comments (#596) +- Show inherited functions in Debug (when deploying with hardhat) (#564) diff --git a/templates/base/packages/nextjs/components/scaffold-eth/Contract/ContractReadMethods.tsx b/templates/base/packages/nextjs/components/scaffold-eth/Contract/ContractReadMethods.tsx index eaf11a8fd..13eae1c36 100644 --- a/templates/base/packages/nextjs/components/scaffold-eth/Contract/ContractReadMethods.tsx +++ b/templates/base/packages/nextjs/components/scaffold-eth/Contract/ContractReadMethods.tsx @@ -1,6 +1,6 @@ import { ReadOnlyFunctionForm } from "./ReadOnlyFunctionForm"; import { Abi, AbiFunction } from "abitype"; -import { Contract, ContractName } from "~~/utils/scaffold-eth/contract"; +import { Contract, ContractName, GenericContract, InheritedFunctions } from "~~/utils/scaffold-eth/contract"; export const ContractReadMethods = ({ deployedContractData }: { deployedContractData: Contract }) => { if (!deployedContractData) { @@ -9,11 +9,19 @@ export const ContractReadMethods = ({ deployedContractData }: { deployedContract const functionsToDisplay = ( ((deployedContractData.abi || []) as Abi).filter(part => part.type === "function") as AbiFunction[] - ).filter(fn => { - const isQueryableWithParams = - (fn.stateMutability === "view" || fn.stateMutability === "pure") && fn.inputs.length > 0; - return isQueryableWithParams; - }); + ) + .filter(fn => { + const isQueryableWithParams = + (fn.stateMutability === "view" || fn.stateMutability === "pure") && fn.inputs.length > 0; + return isQueryableWithParams; + }) + .map(fn => { + return { + fn, + inheritedFrom: ((deployedContractData as GenericContract)?.inheritedFunctions as InheritedFunctions)?.[fn.name], + }; + }) + .sort((a, b) => (b.inheritedFrom ? b.inheritedFrom.localeCompare(a.inheritedFrom) : 1)); if (!functionsToDisplay.length) { return <>No read methods; @@ -21,8 +29,13 @@ export const ContractReadMethods = ({ deployedContractData }: { deployedContract return ( <> - {functionsToDisplay.map(fn => ( - + {functionsToDisplay.map(({ fn, inheritedFrom }) => ( + ))} ); diff --git a/templates/base/packages/nextjs/components/scaffold-eth/Contract/ContractVariables.tsx b/templates/base/packages/nextjs/components/scaffold-eth/Contract/ContractVariables.tsx index 804569927..0e625cc0c 100644 --- a/templates/base/packages/nextjs/components/scaffold-eth/Contract/ContractVariables.tsx +++ b/templates/base/packages/nextjs/components/scaffold-eth/Contract/ContractVariables.tsx @@ -1,6 +1,6 @@ import { DisplayVariable } from "./DisplayVariable"; import { Abi, AbiFunction } from "abitype"; -import { Contract, ContractName } from "~~/utils/scaffold-eth/contract"; +import { Contract, ContractName, GenericContract, InheritedFunctions } from "~~/utils/scaffold-eth/contract"; export const ContractVariables = ({ refreshDisplayVariables, @@ -15,11 +15,19 @@ export const ContractVariables = ({ const functionsToDisplay = ( (deployedContractData.abi as Abi).filter(part => part.type === "function") as AbiFunction[] - ).filter(fn => { - const isQueryableWithNoParams = - (fn.stateMutability === "view" || fn.stateMutability === "pure") && fn.inputs.length === 0; - return isQueryableWithNoParams; - }); + ) + .filter(fn => { + const isQueryableWithNoParams = + (fn.stateMutability === "view" || fn.stateMutability === "pure") && fn.inputs.length === 0; + return isQueryableWithNoParams; + }) + .map(fn => { + return { + fn, + inheritedFrom: ((deployedContractData as GenericContract)?.inheritedFunctions as InheritedFunctions)?.[fn.name], + }; + }) + .sort((a, b) => (b.inheritedFrom ? b.inheritedFrom.localeCompare(a.inheritedFrom) : 1)); if (!functionsToDisplay.length) { return <>No contract variables; @@ -27,12 +35,13 @@ export const ContractVariables = ({ return ( <> - {functionsToDisplay.map(fn => ( + {functionsToDisplay.map(({ fn, inheritedFrom }) => ( ))} diff --git a/templates/base/packages/nextjs/components/scaffold-eth/Contract/ContractWriteMethods.tsx b/templates/base/packages/nextjs/components/scaffold-eth/Contract/ContractWriteMethods.tsx index c13fe8c22..e42baf373 100644 --- a/templates/base/packages/nextjs/components/scaffold-eth/Contract/ContractWriteMethods.tsx +++ b/templates/base/packages/nextjs/components/scaffold-eth/Contract/ContractWriteMethods.tsx @@ -1,6 +1,6 @@ import { WriteOnlyFunctionForm } from "./WriteOnlyFunctionForm"; import { Abi, AbiFunction } from "abitype"; -import { Contract, ContractName } from "~~/utils/scaffold-eth/contract"; +import { Contract, ContractName, GenericContract, InheritedFunctions } from "~~/utils/scaffold-eth/contract"; export const ContractWriteMethods = ({ onChange, @@ -15,10 +15,18 @@ export const ContractWriteMethods = ({ const functionsToDisplay = ( (deployedContractData.abi as Abi).filter(part => part.type === "function") as AbiFunction[] - ).filter(fn => { - const isWriteableFunction = fn.stateMutability !== "view" && fn.stateMutability !== "pure"; - return isWriteableFunction; - }); + ) + .filter(fn => { + const isWriteableFunction = fn.stateMutability !== "view" && fn.stateMutability !== "pure"; + return isWriteableFunction; + }) + .map(fn => { + return { + fn, + inheritedFrom: ((deployedContractData as GenericContract)?.inheritedFunctions as InheritedFunctions)?.[fn.name], + }; + }) + .sort((a, b) => (b.inheritedFrom ? b.inheritedFrom.localeCompare(a.inheritedFrom) : 1)); if (!functionsToDisplay.length) { return <>No write methods; @@ -26,12 +34,13 @@ export const ContractWriteMethods = ({ return ( <> - {functionsToDisplay.map((fn, idx) => ( + {functionsToDisplay.map(({ fn, inheritedFrom }, idx) => ( ))} diff --git a/templates/base/packages/nextjs/components/scaffold-eth/Contract/DisplayVariable.tsx b/templates/base/packages/nextjs/components/scaffold-eth/Contract/DisplayVariable.tsx index 69bd74567..0604a9de4 100644 --- a/templates/base/packages/nextjs/components/scaffold-eth/Contract/DisplayVariable.tsx +++ b/templates/base/packages/nextjs/components/scaffold-eth/Contract/DisplayVariable.tsx @@ -1,4 +1,5 @@ import { useEffect } from "react"; +import { InheritanceTooltip } from "./InheritanceTooltip"; import { Abi, AbiFunction } from "abitype"; import { Address } from "viem"; import { useContractRead } from "wagmi"; @@ -11,9 +12,15 @@ type DisplayVariableProps = { contractAddress: Address; abiFunction: AbiFunction; refreshDisplayVariables: boolean; + inheritedFrom?: string; }; -export const DisplayVariable = ({ contractAddress, abiFunction, refreshDisplayVariables }: DisplayVariableProps) => { +export const DisplayVariable = ({ + contractAddress, + abiFunction, + refreshDisplayVariables, + inheritedFrom, +}: DisplayVariableProps) => { const { data: result, isFetching, @@ -35,7 +42,7 @@ export const DisplayVariable = ({ contractAddress, abiFunction, refreshDisplayVa return (
-
+

{abiFunction.name}

+
diff --git a/templates/base/packages/nextjs/components/scaffold-eth/Contract/InheritanceTooltip.tsx b/templates/base/packages/nextjs/components/scaffold-eth/Contract/InheritanceTooltip.tsx new file mode 100644 index 000000000..9825520ec --- /dev/null +++ b/templates/base/packages/nextjs/components/scaffold-eth/Contract/InheritanceTooltip.tsx @@ -0,0 +1,14 @@ +import { InformationCircleIcon } from "@heroicons/react/20/solid"; + +export const InheritanceTooltip = ({ inheritedFrom }: { inheritedFrom?: string }) => ( + <> + {inheritedFrom && ( + + + )} + +); diff --git a/templates/base/packages/nextjs/components/scaffold-eth/Contract/ReadOnlyFunctionForm.tsx b/templates/base/packages/nextjs/components/scaffold-eth/Contract/ReadOnlyFunctionForm.tsx index 6572fd1c8..bc8cfbc3c 100644 --- a/templates/base/packages/nextjs/components/scaffold-eth/Contract/ReadOnlyFunctionForm.tsx +++ b/templates/base/packages/nextjs/components/scaffold-eth/Contract/ReadOnlyFunctionForm.tsx @@ -1,4 +1,5 @@ import { useState } from "react"; +import { InheritanceTooltip } from "./InheritanceTooltip"; import { Abi, AbiFunction } from "abitype"; import { Address } from "viem"; import { useContractRead } from "wagmi"; @@ -14,9 +15,10 @@ import { notification } from "~~/utils/scaffold-eth"; type TReadOnlyFunctionFormProps = { contractAddress: Address; abiFunction: AbiFunction; + inheritedFrom?: string; }; -export const ReadOnlyFunctionForm = ({ contractAddress, abiFunction }: TReadOnlyFunctionFormProps) => { +export const ReadOnlyFunctionForm = ({ contractAddress, abiFunction, inheritedFrom }: TReadOnlyFunctionFormProps) => { const [form, setForm] = useState>(() => getInitialFormState(abiFunction)); const [result, setResult] = useState(); @@ -49,7 +51,10 @@ export const ReadOnlyFunctionForm = ({ contractAddress, abiFunction }: TReadOnly return (
-

{abiFunction.name}

+

+ {abiFunction.name} + +

{inputElements}
diff --git a/templates/base/packages/nextjs/components/scaffold-eth/Contract/WriteOnlyFunctionForm.tsx b/templates/base/packages/nextjs/components/scaffold-eth/Contract/WriteOnlyFunctionForm.tsx index 5d2e9e24f..01080885b 100644 --- a/templates/base/packages/nextjs/components/scaffold-eth/Contract/WriteOnlyFunctionForm.tsx +++ b/templates/base/packages/nextjs/components/scaffold-eth/Contract/WriteOnlyFunctionForm.tsx @@ -1,4 +1,5 @@ import { useEffect, useState } from "react"; +import { InheritanceTooltip } from "./InheritanceTooltip"; import { Abi, AbiFunction } from "abitype"; import { Address, TransactionReceipt } from "viem"; import { useContractWrite, useNetwork, useWaitForTransaction } from "wagmi"; @@ -18,9 +19,15 @@ type WriteOnlyFunctionFormProps = { abiFunction: AbiFunction; onChange: () => void; contractAddress: Address; + inheritedFrom?: string; }; -export const WriteOnlyFunctionForm = ({ abiFunction, onChange, contractAddress }: WriteOnlyFunctionFormProps) => { +export const WriteOnlyFunctionForm = ({ + abiFunction, + onChange, + contractAddress, + inheritedFrom, +}: WriteOnlyFunctionFormProps) => { const [form, setForm] = useState>(() => getInitialFormState(abiFunction)); const [txValue, setTxValue] = useState(""); const { chain } = useNetwork(); @@ -80,7 +87,10 @@ export const WriteOnlyFunctionForm = ({ abiFunction, onChange, contractAddress } return (
-

{abiFunction.name}

+

+ {abiFunction.name} + +

{inputs} {abiFunction.stateMutability === "payable" ? ( void; /** - * explictly save burner to storage + * explicitly save burner to storage */ saveBurner: () => void; }; diff --git a/templates/base/packages/nextjs/hooks/scaffold-eth/useTransactor.tsx b/templates/base/packages/nextjs/hooks/scaffold-eth/useTransactor.tsx index 76de9e7b2..634120ec1 100644 --- a/templates/base/packages/nextjs/hooks/scaffold-eth/useTransactor.tsx +++ b/templates/base/packages/nextjs/hooks/scaffold-eth/useTransactor.tsx @@ -29,7 +29,7 @@ const TxnNotification = ({ message, blockExplorerLink }: { message: string; bloc }; /** - * @description Runs Transaction passed in to returned funtion showing UI feedback. + * @description Runs Transaction passed in to returned function showing UI feedback. * @param _walletClient * @returns function that takes a transaction and returns a promise of the transaction hash */ diff --git a/templates/base/packages/nextjs/utils/scaffold-eth/contract.ts b/templates/base/packages/nextjs/utils/scaffold-eth/contract.ts index 02a3bf799..0695ec5f8 100644 --- a/templates/base/packages/nextjs/utils/scaffold-eth/contract.ts +++ b/templates/base/packages/nextjs/utils/scaffold-eth/contract.ts @@ -36,12 +36,17 @@ const deepMergeContracts = , S extends Record }; const contractsData = deepMergeContracts(deployedContractsData, externalContractsData); +export type InheritedFunctions = { readonly [key: string]: string }; + +export type GenericContract = { + address: Address; + abi: Abi; + inheritedFunctions?: InheritedFunctions; +}; + export type GenericContractsDeclaration = { [chainId: number]: { - [contractName: string]: { - address: Address; - abi: Abi; - }; + [contractName: string]: GenericContract; }; }; diff --git a/templates/extensions/hardhat/packages/hardhat/deploy/99_generateTsAbis.ts b/templates/extensions/hardhat/packages/hardhat/deploy/99_generateTsAbis.ts index 90b558905..9bae1eb89 100644 --- a/templates/extensions/hardhat/packages/hardhat/deploy/99_generateTsAbis.ts +++ b/templates/extensions/hardhat/packages/hardhat/deploy/99_generateTsAbis.ts @@ -17,6 +17,9 @@ const generatedContractComment = ` */ `; +const DEPLOYMENTS_DIR = "./deployments"; +const ARTIFACTS_DIR = "./artifacts"; + function getDirectories(path: string) { return fs .readdirSync(path, { withFileTypes: true }) @@ -31,7 +34,46 @@ function getContractNames(path: string) { .map(dirent => dirent.name.split(".")[0]); } -const DEPLOYMENTS_DIR = "./deployments"; +function getActualSourcesForContract(sources: Record, contractName: string) { + for (const sourcePath of Object.keys(sources)) { + const sourceName = sourcePath.split("/").pop()?.split(".sol")[0]; + if (sourceName === contractName) { + const contractContent = sources[sourcePath].content as string; + const regex = /contract\s+(\w+)\s+is\s+([^{}]+)\{/; + const match = contractContent.match(regex); + + if (match) { + const inheritancePart = match[2]; + // Split the inherited contracts by commas to get the list of inherited contracts + const inheritedContracts = inheritancePart.split(",").map(contract => `${contract.trim()}.sol`); + + return inheritedContracts; + } + return []; + } + } + return []; +} + +function getInheritedFunctions(sources: Record, contractName: string) { + const actualSources = getActualSourcesForContract(sources, contractName); + const inheritedFunctions = {} as Record; + + for (const sourceContractName of actualSources) { + const sourcePath = Object.keys(sources).find(key => key.includes(sourceContractName)); + if (sourcePath) { + const sourceName = sourcePath?.split("/").pop()?.split(".sol")[0]; + const { abi } = JSON.parse(fs.readFileSync(`${ARTIFACTS_DIR}/${sourcePath}/${sourceName}.json`).toString()); + for (const functionAbi of abi) { + if (functionAbi.type === "function") { + inheritedFunctions[functionAbi.name] = sourcePath; + } + } + } + } + + return inheritedFunctions; +} function getContractDataFromDeployments() { if (!fs.existsSync(DEPLOYMENTS_DIR)) { @@ -42,10 +84,11 @@ function getContractDataFromDeployments() { const chainId = fs.readFileSync(`${DEPLOYMENTS_DIR}/${chainName}/.chainId`).toString(); const contracts = {} as Record; for (const contractName of getContractNames(`${DEPLOYMENTS_DIR}/${chainName}`)) { - const { abi, address } = JSON.parse( + const { abi, address, metadata } = JSON.parse( fs.readFileSync(`${DEPLOYMENTS_DIR}/${chainName}/${contractName}.json`).toString(), ); - contracts[contractName] = { address, abi }; + const inheritedFunctions = getInheritedFunctions(JSON.parse(metadata).sources, contractName); + contracts[contractName] = { address, abi, inheritedFunctions }; } output[chainId] = contracts; }