Skip to content

Commit

Permalink
CLI backmerge (#603)
Browse files Browse the repository at this point in the history
* Fix typos in comments (#596)
* Show inherited functions in Debug (when deploying with `hardhat`) (#564)
  • Loading branch information
FilipHarald committed Nov 15, 2023
1 parent 0a2e9af commit 0135237
Show file tree
Hide file tree
Showing 12 changed files with 158 additions and 36 deletions.
6 changes: 6 additions & 0 deletions .changeset/curly-fireants-jog.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
"create-eth": patch
---

- Fix typos in comments (#596)
- Show inherited functions in Debug (when deploying with hardhat) (#564)
Original file line number Diff line number Diff line change
@@ -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<ContractName> }) => {
if (!deployedContractData) {
Expand All @@ -9,20 +9,33 @@ 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</>;
}

return (
<>
{functionsToDisplay.map(fn => (
<ReadOnlyFunctionForm contractAddress={deployedContractData.address} abiFunction={fn} key={fn.name} />
{functionsToDisplay.map(({ fn, inheritedFrom }) => (
<ReadOnlyFunctionForm
contractAddress={deployedContractData.address}
abiFunction={fn}
key={fn.name}
inheritedFrom={inheritedFrom}
/>
))}
</>
);
Expand Down
Original file line number Diff line number Diff line change
@@ -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,
Expand All @@ -15,24 +15,33 @@ 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</>;
}

return (
<>
{functionsToDisplay.map(fn => (
{functionsToDisplay.map(({ fn, inheritedFrom }) => (
<DisplayVariable
abiFunction={fn}
contractAddress={deployedContractData.address}
key={fn.name}
refreshDisplayVariables={refreshDisplayVariables}
inheritedFrom={inheritedFrom}
/>
))}
</>
Expand Down
Original file line number Diff line number Diff line change
@@ -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,
Expand All @@ -15,23 +15,32 @@ 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</>;
}

return (
<>
{functionsToDisplay.map((fn, idx) => (
{functionsToDisplay.map(({ fn, inheritedFrom }, idx) => (
<WriteOnlyFunctionForm
key={`${fn.name}-${idx}}`}
abiFunction={fn}
onChange={onChange}
contractAddress={deployedContractData.address}
inheritedFrom={inheritedFrom}
/>
))}
</>
Expand Down
Original file line number Diff line number Diff line change
@@ -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";
Expand All @@ -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,
Expand All @@ -35,7 +42,7 @@ export const DisplayVariable = ({ contractAddress, abiFunction, refreshDisplayVa

return (
<div className="space-y-1 pb-2">
<div className="flex items-center gap-2">
<div className="flex items-center">
<h3 className="font-medium text-lg mb-0 break-all">{abiFunction.name}</h3>
<button className="btn btn-ghost btn-xs" onClick={async () => await refetch()}>
{isFetching ? (
Expand All @@ -44,6 +51,7 @@ export const DisplayVariable = ({ contractAddress, abiFunction, refreshDisplayVa
<ArrowPathIcon className="h-3 w-3 cursor-pointer" aria-hidden="true" />
)}
</button>
<InheritanceTooltip inheritedFrom={inheritedFrom} />
</div>
<div className="text-gray-500 font-medium flex flex-col items-start">
<div>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import { InformationCircleIcon } from "@heroicons/react/20/solid";

export const InheritanceTooltip = ({ inheritedFrom }: { inheritedFrom?: string }) => (
<>
{inheritedFrom && (
<span
className="tooltip tooltip-top tooltip-accent px-2 md:break-normal"
data-tip={`Inherited from: ${inheritedFrom}`}
>
<InformationCircleIcon className="h-4 w-4" aria-hidden="true" />
</span>
)}
</>
);
Original file line number Diff line number Diff line change
@@ -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";
Expand All @@ -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<Record<string, any>>(() => getInitialFormState(abiFunction));
const [result, setResult] = useState<unknown>();

Expand Down Expand Up @@ -49,7 +51,10 @@ export const ReadOnlyFunctionForm = ({ contractAddress, abiFunction }: TReadOnly

return (
<div className="flex flex-col gap-3 py-5 first:pt-0 last:pb-1">
<p className="font-medium my-0 break-words">{abiFunction.name}</p>
<p className="font-medium my-0 break-words">
{abiFunction.name}
<InheritanceTooltip inheritedFrom={inheritedFrom} />
</p>
{inputElements}
<div className="flex justify-between gap-2 flex-wrap">
<div className="flex-grow w-4/5">
Expand Down
Original file line number Diff line number Diff line change
@@ -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";
Expand All @@ -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<Record<string, any>>(() => getInitialFormState(abiFunction));
const [txValue, setTxValue] = useState<string | bigint>("");
const { chain } = useNetwork();
Expand Down Expand Up @@ -80,7 +87,10 @@ export const WriteOnlyFunctionForm = ({ abiFunction, onChange, contractAddress }
return (
<div className="py-5 space-y-3 first:pt-0 last:pb-1">
<div className={`flex gap-3 ${zeroInputs ? "flex-row justify-between items-center" : "flex-col"}`}>
<p className="font-medium my-0 break-words">{abiFunction.name}</p>
<p className="font-medium my-0 break-words">
{abiFunction.name}
<InheritanceTooltip inheritedFrom={inheritedFrom} />
</p>
{inputs}
{abiFunction.stateMutability === "payable" ? (
<IntegerInput
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,7 @@ export type TBurnerSigner = {
*/
generateNewBurner: () => void;
/**
* explictly save burner to storage
* explicitly save burner to storage
*/
saveBurner: () => void;
};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
*/
Expand Down
13 changes: 9 additions & 4 deletions templates/base/packages/nextjs/utils/scaffold-eth/contract.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,12 +36,17 @@ const deepMergeContracts = <D extends Record<PropertyKey, any>, 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;
};
};

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,9 @@ const generatedContractComment = `
*/
`;

const DEPLOYMENTS_DIR = "./deployments";
const ARTIFACTS_DIR = "./artifacts";

function getDirectories(path: string) {
return fs
.readdirSync(path, { withFileTypes: true })
Expand All @@ -31,7 +34,46 @@ function getContractNames(path: string) {
.map(dirent => dirent.name.split(".")[0]);
}

const DEPLOYMENTS_DIR = "./deployments";
function getActualSourcesForContract(sources: Record<string, any>, 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<string, any>, contractName: string) {
const actualSources = getActualSourcesForContract(sources, contractName);
const inheritedFunctions = {} as Record<string, any>;

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)) {
Expand All @@ -42,10 +84,11 @@ function getContractDataFromDeployments() {
const chainId = fs.readFileSync(`${DEPLOYMENTS_DIR}/${chainName}/.chainId`).toString();
const contracts = {} as Record<string, any>;
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;
}
Expand Down

0 comments on commit 0135237

Please sign in to comment.