From 9a43e87db302ec599fd1e97d8b77e2e68831017f Mon Sep 17 00:00:00 2001 From: Karolis Ramanauskas Date: Thu, 17 Oct 2024 21:43:25 +0300 Subject: [PATCH 01/10] fix(explorer): various fixes (#3299) --- .changeset/soft-bears-rest.md | 11 +++ .../src/app/(explorer)/[chainName]/layout.tsx | 19 ++++++ .../src/app/(explorer)/[chainName]/page.tsx | 4 +- .../[chainName]/worlds/WorldsForm.tsx | 2 +- .../worlds/[worldAddress]/interact/Form.tsx | 68 +++++++++---------- .../worlds/[worldAddress]/layout.tsx | 23 ++++++- .../observe/TransactionTableRow.tsx | 10 +-- .../worlds/[worldAddress]/page.tsx | 6 +- .../(explorer)/[chainName]/worlds/page.tsx | 15 ++-- .../app/(explorer)/queries/useTablesQuery.ts | 1 + .../src/app/(explorer)/utils/timeAgo.ts | 4 -- packages/explorer/src/common.ts | 12 +++- .../explorer/src/components/Navigation.tsx | 2 +- 13 files changed, 111 insertions(+), 66 deletions(-) create mode 100644 .changeset/soft-bears-rest.md create mode 100644 packages/explorer/src/app/(explorer)/[chainName]/layout.tsx diff --git a/.changeset/soft-bears-rest.md b/.changeset/soft-bears-rest.md new file mode 100644 index 0000000000..766f6fbc91 --- /dev/null +++ b/.changeset/soft-bears-rest.md @@ -0,0 +1,11 @@ +--- +"@latticexyz/explorer": patch +--- + +- Not found page if invalid chain name. +- Only show selector for worlds if options exist. +- Remove "future time" from transactions table. +- Improved layout for Interact tab. +- Wrap long args in transactions table. +- New tables polling. +- Add logs (regression). diff --git a/packages/explorer/src/app/(explorer)/[chainName]/layout.tsx b/packages/explorer/src/app/(explorer)/[chainName]/layout.tsx new file mode 100644 index 0000000000..816c9affda --- /dev/null +++ b/packages/explorer/src/app/(explorer)/[chainName]/layout.tsx @@ -0,0 +1,19 @@ +"use client"; + +import { notFound } from "next/navigation"; +import { isValidChainName } from "../../../common"; + +type Props = { + params: { + chainName: string; + }; + children: React.ReactNode; +}; + +export default function ChainLayout({ params: { chainName }, children }: Props) { + if (!isValidChainName(chainName)) { + return notFound(); + } + + return children; +} diff --git a/packages/explorer/src/app/(explorer)/[chainName]/page.tsx b/packages/explorer/src/app/(explorer)/[chainName]/page.tsx index e6c8fd70b9..4e8538b59d 100644 --- a/packages/explorer/src/app/(explorer)/[chainName]/page.tsx +++ b/packages/explorer/src/app/(explorer)/[chainName]/page.tsx @@ -6,6 +6,6 @@ type Props = { }; }; -export default async function ChainPage({ params }: Props) { - return redirect(`/${params.chainName}/worlds`); +export default async function ChainPage({ params: { chainName } }: Props) { + return redirect(`/${chainName}/worlds`); } diff --git a/packages/explorer/src/app/(explorer)/[chainName]/worlds/WorldsForm.tsx b/packages/explorer/src/app/(explorer)/[chainName]/worlds/WorldsForm.tsx index a17b413515..7fe546dd6a 100644 --- a/packages/explorer/src/app/(explorer)/[chainName]/worlds/WorldsForm.tsx +++ b/packages/explorer/src/app/(explorer)/[chainName]/worlds/WorldsForm.tsx @@ -84,7 +84,7 @@ export function WorldsForm({ worlds }: { worlds: Address[] }) {
- {open ? ( + {open && worlds.length > 0 ? (
{worlds?.map((world) => { diff --git a/packages/explorer/src/app/(explorer)/[chainName]/worlds/[worldAddress]/interact/Form.tsx b/packages/explorer/src/app/(explorer)/[chainName]/worlds/[worldAddress]/interact/Form.tsx index e6eb68ff84..dd9ba09c07 100644 --- a/packages/explorer/src/app/(explorer)/[chainName]/worlds/[worldAddress]/interact/Form.tsx +++ b/packages/explorer/src/app/(explorer)/[chainName]/worlds/[worldAddress]/interact/Form.tsx @@ -22,26 +22,22 @@ export function Form() { ); return ( -
-
-
-

Jump to:

+ <> +
+
+
+

Jump to:

+ { + setFilterValue(evt.target.value); + }} + /> +
- { - setFilterValue(evt.target.value); - }} - /> - -
    +
      {!isFetched && Array.from({ length: 10 }).map((_, index) => { return ( @@ -77,25 +73,25 @@ export function Form() { })}
-
-
- {!isFetched && ( - <> - - - - - - - - - )} +
+ {!isFetched && ( + <> + + + + + + + + + )} - {filteredFunctions?.map((abi) => { - return ; - })} + {filteredFunctions?.map((abi) => { + return ; + })} +
-
+ ); } diff --git a/packages/explorer/src/app/(explorer)/[chainName]/worlds/[worldAddress]/layout.tsx b/packages/explorer/src/app/(explorer)/[chainName]/worlds/[worldAddress]/layout.tsx index 2c8b5a4811..ffa329b67d 100644 --- a/packages/explorer/src/app/(explorer)/[chainName]/worlds/[worldAddress]/layout.tsx +++ b/packages/explorer/src/app/(explorer)/[chainName]/worlds/[worldAddress]/layout.tsx @@ -1,15 +1,32 @@ "use client"; +import { notFound } from "next/navigation"; +import { Address } from "viem"; +import { isValidChainName } from "../../../../../common"; import { Navigation } from "../../../../../components/Navigation"; import { Providers } from "./Providers"; import { TransactionsWatcher } from "./observe/TransactionsWatcher"; -export default function WorldLayout({ children }: { children: React.ReactNode }) { +type Props = { + params: { + chainName: string; + worldAddress: Address; + }; + children: React.ReactNode; +}; + +export default function WorldLayout({ params: { chainName }, children }: Props) { + if (!isValidChainName(chainName)) { + return notFound(); + } + return ( - +
+ + {children} +
- {children}
); } diff --git a/packages/explorer/src/app/(explorer)/[chainName]/worlds/[worldAddress]/observe/TransactionTableRow.tsx b/packages/explorer/src/app/(explorer)/[chainName]/worlds/[worldAddress]/observe/TransactionTableRow.tsx index d828a5436f..d34d1f640d 100644 --- a/packages/explorer/src/app/(explorer)/[chainName]/worlds/[worldAddress]/observe/TransactionTableRow.tsx +++ b/packages/explorer/src/app/(explorer)/[chainName]/worlds/[worldAddress]/observe/TransactionTableRow.tsx @@ -82,11 +82,11 @@ export function TransactionTableRow({ row }: { row: Row })

Inputs

{Array.isArray(data.functionData?.args) && data.functionData?.args.length > 0 ? ( -
+
{data.functionData?.args?.map((arg, idx) => (
arg {idx + 1}: - + {typeof arg === "object" && arg !== null ? JSON.stringify(arg, null, 2) : String(arg)}
@@ -113,8 +113,8 @@ export function TransactionTableRow({ row }: { row: Row }) <>
-

Logs

- {Array.isArray(logs) && logs.length > 10 ? ( +

Logs

+ {Array.isArray(logs) && logs.length > 0 ? (
    {logs.map((log, idx) => { @@ -128,7 +128,7 @@ export function TransactionTableRow({ row }: { row: Row }) {Object.entries(args).map(([key, value]) => (
  • {key}: - {value as never} + {value as never}
  • ))}
diff --git a/packages/explorer/src/app/(explorer)/[chainName]/worlds/[worldAddress]/page.tsx b/packages/explorer/src/app/(explorer)/[chainName]/worlds/[worldAddress]/page.tsx index 47b435ef54..a3b0aefcec 100644 --- a/packages/explorer/src/app/(explorer)/[chainName]/worlds/[worldAddress]/page.tsx +++ b/packages/explorer/src/app/(explorer)/[chainName]/worlds/[worldAddress]/page.tsx @@ -1,13 +1,13 @@ import { redirect } from "next/navigation"; +import { supportedChainName } from "../../../../../common"; type Props = { params: { - chainName: string; + chainName: supportedChainName; worldAddress: string; }; }; -export default async function WorldPage({ params }: Props) { - const { chainName, worldAddress } = params; +export default async function WorldPage({ params: { chainName, worldAddress } }: Props) { return redirect(`/${chainName}/worlds/${worldAddress}/explore`); } diff --git a/packages/explorer/src/app/(explorer)/[chainName]/worlds/page.tsx b/packages/explorer/src/app/(explorer)/[chainName]/worlds/page.tsx index a8ab09a6aa..04fcbedb5c 100644 --- a/packages/explorer/src/app/(explorer)/[chainName]/worlds/page.tsx +++ b/packages/explorer/src/app/(explorer)/[chainName]/worlds/page.tsx @@ -1,7 +1,7 @@ import { headers } from "next/headers"; import { redirect } from "next/navigation"; import { Address } from "viem"; -import { supportedChains, validateChainName } from "../../../../common"; +import { supportedChainName, supportedChains } from "../../../../common"; import { indexerForChainId } from "../../utils/indexerForChainId"; import { WorldsForm } from "./WorldsForm"; @@ -13,9 +13,7 @@ type ApiResponse = { }[]; }; -async function fetchWorlds(chainName: string): Promise { - validateChainName(chainName); - +async function fetchWorlds(chainName: supportedChainName): Promise { const chain = supportedChains[chainName]; const indexer = indexerForChainId(chain.id); let worldsApiUrl: string | null = null; @@ -49,15 +47,14 @@ async function fetchWorlds(chainName: string): Promise { type Props = { params: { - chainName: string; + chainName: supportedChainName; }; }; -export default async function WorldsPage({ params }: Props) { - const worlds = await fetchWorlds(params.chainName); +export default async function WorldsPage({ params: { chainName } }: Props) { + const worlds = await fetchWorlds(chainName); if (worlds.length === 1) { - return redirect(`/${params.chainName}/worlds/${worlds[0]}`); + return redirect(`/${chainName}/worlds/${worlds[0]}`); } - return ; } diff --git a/packages/explorer/src/app/(explorer)/queries/useTablesQuery.ts b/packages/explorer/src/app/(explorer)/queries/useTablesQuery.ts index 197826c86c..8220d89803 100644 --- a/packages/explorer/src/app/(explorer)/queries/useTablesQuery.ts +++ b/packages/explorer/src/app/(explorer)/queries/useTablesQuery.ts @@ -52,5 +52,6 @@ export function useTablesQuery() { }) .sort(({ namespace }) => (internalNamespaces.includes(namespace) ? 1 : -1)); }, + refetchInterval: 5000, }); } diff --git a/packages/explorer/src/app/(explorer)/utils/timeAgo.ts b/packages/explorer/src/app/(explorer)/utils/timeAgo.ts index dc9f2bcac4..cd885031d6 100644 --- a/packages/explorer/src/app/(explorer)/utils/timeAgo.ts +++ b/packages/explorer/src/app/(explorer)/utils/timeAgo.ts @@ -11,10 +11,6 @@ export function timeAgo(timestamp: bigint) { const currentTimestampSeconds = Math.floor(Date.now() / 1000); const diff = currentTimestampSeconds - Number(timestamp); - if (diff < 0) { - return "in the future"; - } - for (const unit of units) { if (diff >= unit.limit) { const unitsAgo = Math.floor(diff / unit.inSeconds); diff --git a/packages/explorer/src/common.ts b/packages/explorer/src/common.ts index 06eebb04eb..da18b9bc32 100644 --- a/packages/explorer/src/common.ts +++ b/packages/explorer/src/common.ts @@ -13,14 +13,22 @@ export const chainIdToName = Object.fromEntries( Object.entries(supportedChains).map(([chainName, chain]) => [chain.id, chainName]), ) as Record; +export function isValidChainId(chainId: unknown): chainId is supportedChainId { + return typeof chainId === "number" && chainId in chainIdToName; +} + +export function isValidChainName(name: unknown): name is supportedChainName { + return typeof name === "string" && name in supportedChains; +} + export function validateChainId(chainId: unknown): asserts chainId is supportedChainId { - if (!(typeof chainId === "number" && chainId in chainIdToName)) { + if (!isValidChainId(chainId)) { throw new Error(`Invalid chain ID. Supported chains are: ${Object.keys(chainIdToName).join(", ")}.`); } } export function validateChainName(name: unknown): asserts name is supportedChainName { - if (!(typeof name === "string" && name in supportedChains)) { + if (!isValidChainName(name)) { throw new Error(`Invalid chain name. Supported chains are: ${Object.keys(supportedChains).join(", ")}.`); } } diff --git a/packages/explorer/src/components/Navigation.tsx b/packages/explorer/src/components/Navigation.tsx index 388ddad946..95921087fe 100644 --- a/packages/explorer/src/components/Navigation.tsx +++ b/packages/explorer/src/components/Navigation.tsx @@ -30,7 +30,7 @@ function NavigationLink({ href, children }: { href: string; children: React.Reac export function Navigation() { const { data, isFetched } = useWorldAbiQuery(); return ( -
+
Explore From d4c10c18ad853bed21c55fe92e2ba09c2382316d Mon Sep 17 00:00:00 2001 From: Karolis Ramanauskas Date: Thu, 17 Oct 2024 22:02:00 +0300 Subject: [PATCH 02/10] feat(explorer): show ABI errors in interact page (#3303) --- .changeset/good-eyes-nail.md | 5 +++ .../[worldAddress]/interact/FunctionField.tsx | 33 ++++++++++--------- .../interact/{Form.tsx => InteractForm.tsx} | 20 ++++++----- .../worlds/[worldAddress]/interact/page.tsx | 4 +-- .../interact/useContractMutation.ts | 13 ++++---- 5 files changed, 44 insertions(+), 31 deletions(-) create mode 100644 .changeset/good-eyes-nail.md rename packages/explorer/src/app/(explorer)/[chainName]/worlds/[worldAddress]/interact/{Form.tsx => InteractForm.tsx} (86%) diff --git a/.changeset/good-eyes-nail.md b/.changeset/good-eyes-nail.md new file mode 100644 index 0000000000..fd33f62862 --- /dev/null +++ b/.changeset/good-eyes-nail.md @@ -0,0 +1,5 @@ +--- +"@latticexyz/explorer": patch +--- + +Interact tab now displays decoded ABI errors for failed transactions. diff --git a/packages/explorer/src/app/(explorer)/[chainName]/worlds/[worldAddress]/interact/FunctionField.tsx b/packages/explorer/src/app/(explorer)/[chainName]/worlds/[worldAddress]/interact/FunctionField.tsx index 8d54c757fa..5c99e7522a 100644 --- a/packages/explorer/src/app/(explorer)/[chainName]/worlds/[worldAddress]/interact/FunctionField.tsx +++ b/packages/explorer/src/app/(explorer)/[chainName]/worlds/[worldAddress]/interact/FunctionField.tsx @@ -1,7 +1,7 @@ "use client"; import { Coins, Eye, Send } from "lucide-react"; -import { AbiFunction } from "viem"; +import { Abi, AbiFunction } from "viem"; import { useAccount } from "wagmi"; import { z } from "zod"; import { useState } from "react"; @@ -20,7 +20,8 @@ export enum FunctionType { } type Props = { - abi: AbiFunction; + worldAbi: Abi; + functionAbi: AbiFunction; }; const formSchema = z.object({ @@ -28,12 +29,14 @@ const formSchema = z.object({ value: z.string().optional(), }); -export function FunctionField({ abi }: Props) { +export function FunctionField({ worldAbi, functionAbi }: Props) { const operationType: FunctionType = - abi.stateMutability === "view" || abi.stateMutability === "pure" ? FunctionType.READ : FunctionType.WRITE; + functionAbi.stateMutability === "view" || functionAbi.stateMutability === "pure" + ? FunctionType.READ + : FunctionType.WRITE; const [result, setResult] = useState(null); const { openConnectModal } = useConnectModal(); - const mutation = useContractMutation({ abi, operationType }); + const mutation = useContractMutation({ worldAbi, functionAbi, operationType }); const account = useAccount(); const form = useForm>({ @@ -58,23 +61,23 @@ export function FunctionField({ abi }: Props) { } } - const inputsLabel = abi?.inputs.map((input) => input.type).join(", "); + const inputsLabel = functionAbi?.inputs.map((input) => input.type).join(", "); return (
- +

- {abi?.name} + {functionAbi?.name} {inputsLabel && ` (${inputsLabel})`} - {abi.stateMutability === "payable" && } - {(abi.stateMutability === "view" || abi.stateMutability === "pure") && ( + {functionAbi.stateMutability === "payable" && } + {(functionAbi.stateMutability === "view" || functionAbi.stateMutability === "pure") && ( )} - {abi.stateMutability === "nonpayable" && } + {functionAbi.stateMutability === "nonpayable" && }

- {abi?.inputs.map((input, index) => ( + {functionAbi?.inputs.map((input, index) => ( ))} - {abi.stateMutability === "payable" && ( + {functionAbi.stateMutability === "payable" && ( - {(abi.stateMutability === "view" || abi.stateMutability === "pure") && "Read"} - {(abi.stateMutability === "payable" || abi.stateMutability === "nonpayable") && "Write"} + {(functionAbi.stateMutability === "view" || functionAbi.stateMutability === "pure") && "Read"} + {(functionAbi.stateMutability === "payable" || functionAbi.stateMutability === "nonpayable") && "Write"} {result &&
{result}
} diff --git a/packages/explorer/src/app/(explorer)/[chainName]/worlds/[worldAddress]/interact/Form.tsx b/packages/explorer/src/app/(explorer)/[chainName]/worlds/[worldAddress]/interact/InteractForm.tsx similarity index 86% rename from packages/explorer/src/app/(explorer)/[chainName]/worlds/[worldAddress]/interact/Form.tsx rename to packages/explorer/src/app/(explorer)/[chainName]/worlds/[worldAddress]/interact/InteractForm.tsx index dd9ba09c07..763a6e04d3 100644 --- a/packages/explorer/src/app/(explorer)/[chainName]/worlds/[worldAddress]/interact/Form.tsx +++ b/packages/explorer/src/app/(explorer)/[chainName]/worlds/[worldAddress]/interact/InteractForm.tsx @@ -3,7 +3,7 @@ import { Coins, Eye, Send } from "lucide-react"; import { useQueryState } from "nuqs"; import { AbiFunction } from "viem"; -import { useDeferredValue } from "react"; +import { useDeferredValue, useMemo } from "react"; import { Input } from "../../../../../../components/ui/Input"; import { Separator } from "../../../../../../components/ui/Separator"; import { Skeleton } from "../../../../../../components/ui/Skeleton"; @@ -12,14 +12,17 @@ import { useHashState } from "../../../../hooks/useHashState"; import { useWorldAbiQuery } from "../../../../queries/useWorldAbiQuery"; import { FunctionField } from "./FunctionField"; -export function Form() { +export function InteractForm() { const [hash] = useHashState(); const { data, isFetched } = useWorldAbiQuery(); const [filterValue, setFilterValue] = useQueryState("function", { defaultValue: "" }); const deferredFilterValue = useDeferredValue(filterValue); - const filteredFunctions = data?.abi?.filter( - (item) => item.type === "function" && item.name.toLowerCase().includes(deferredFilterValue.toLowerCase()), - ); + const filteredFunctions = useMemo(() => { + if (!data?.abi) return []; + return data.abi.filter( + (item) => item.type === "function" && item.name.toLowerCase().includes(deferredFilterValue.toLowerCase()), + ); + }, [data?.abi, deferredFilterValue]); return ( <> @@ -87,9 +90,10 @@ export function Form() { )} - {filteredFunctions?.map((abi) => { - return ; - })} + {data?.abi && + filteredFunctions.map((abi) => ( + + ))}
diff --git a/packages/explorer/src/app/(explorer)/[chainName]/worlds/[worldAddress]/interact/page.tsx b/packages/explorer/src/app/(explorer)/[chainName]/worlds/[worldAddress]/interact/page.tsx index 01dc779332..9e2234950a 100644 --- a/packages/explorer/src/app/(explorer)/[chainName]/worlds/[worldAddress]/interact/page.tsx +++ b/packages/explorer/src/app/(explorer)/[chainName]/worlds/[worldAddress]/interact/page.tsx @@ -1,10 +1,10 @@ import { Metadata } from "next"; -import { Form } from "./Form"; +import { InteractForm } from "./InteractForm"; export const metadata: Metadata = { title: "Interact", }; export default async function InteractPage() { - return ; + return ; } diff --git a/packages/explorer/src/app/(explorer)/[chainName]/worlds/[worldAddress]/interact/useContractMutation.ts b/packages/explorer/src/app/(explorer)/[chainName]/worlds/[worldAddress]/interact/useContractMutation.ts index d38ddb16c8..3289e8cd6b 100644 --- a/packages/explorer/src/app/(explorer)/[chainName]/worlds/[worldAddress]/interact/useContractMutation.ts +++ b/packages/explorer/src/app/(explorer)/[chainName]/worlds/[worldAddress]/interact/useContractMutation.ts @@ -8,11 +8,12 @@ import { useChain } from "../../../../hooks/useChain"; import { FunctionType } from "./FunctionField"; type UseContractMutationProps = { - abi: AbiFunction; + worldAbi: Abi; + functionAbi: AbiFunction; operationType: FunctionType; }; -export function useContractMutation({ abi, operationType }: UseContractMutationProps) { +export function useContractMutation({ worldAbi, functionAbi, operationType }: UseContractMutationProps) { const { worldAddress } = useParams(); const { id: chainId } = useChain(); const queryClient = useQueryClient(); @@ -23,9 +24,9 @@ export function useContractMutation({ abi, operationType }: UseContractMutationP mutationFn: async ({ inputs, value }: { inputs: unknown[]; value?: string }) => { if (operationType === FunctionType.READ) { const result = await readContract(wagmiConfig, { - abi: [abi] as Abi, + abi: worldAbi, address: worldAddress as Hex, - functionName: abi.name, + functionName: functionAbi.name, args: inputs, chainId, }); @@ -33,9 +34,9 @@ export function useContractMutation({ abi, operationType }: UseContractMutationP return { result }; } else { const txHash = await writeContract(wagmiConfig, { - abi: [abi] as Abi, + abi: worldAbi, address: worldAddress as Hex, - functionName: abi.name, + functionName: functionAbi.name, args: inputs, ...(value && { value: BigInt(value) }), chainId, From 3a80bed31b97d439025f68b8e4ded27354e102f1 Mon Sep 17 00:00:00 2001 From: Karolis Ramanauskas Date: Fri, 18 Oct 2024 10:30:00 +0300 Subject: [PATCH 03/10] feat(explorer): sql editor (#3276) Co-authored-by: Kevin Ingersoll --- .changeset/itchy-countries-worry.md | 5 + packages/explorer/next.config.mjs | 1 + packages/explorer/package.json | 4 + .../[worldAddress]/explore/Explorer.tsx | 2 +- .../[worldAddress]/explore/SQLEditor.tsx | 88 +++++--- .../worlds/[worldAddress]/explore/consts.ts | 41 ++++ .../explore/useMonacoErrorMarker.ts | 23 ++ .../explore/useMonacoSuggestions.ts | 60 ++++++ .../explore/useQueryAutocomplete.ts | 21 ++ .../explore/useQueryValidator.ts | 72 +++++++ pnpm-lock.yaml | 200 ++++++++++++------ 11 files changed, 430 insertions(+), 87 deletions(-) create mode 100644 .changeset/itchy-countries-worry.md create mode 100644 packages/explorer/src/app/(explorer)/[chainName]/worlds/[worldAddress]/explore/consts.ts create mode 100644 packages/explorer/src/app/(explorer)/[chainName]/worlds/[worldAddress]/explore/useMonacoErrorMarker.ts create mode 100644 packages/explorer/src/app/(explorer)/[chainName]/worlds/[worldAddress]/explore/useMonacoSuggestions.ts create mode 100644 packages/explorer/src/app/(explorer)/[chainName]/worlds/[worldAddress]/explore/useQueryAutocomplete.ts create mode 100644 packages/explorer/src/app/(explorer)/[chainName]/worlds/[worldAddress]/explore/useQueryValidator.ts diff --git a/.changeset/itchy-countries-worry.md b/.changeset/itchy-countries-worry.md new file mode 100644 index 0000000000..9374512b23 --- /dev/null +++ b/.changeset/itchy-countries-worry.md @@ -0,0 +1,5 @@ +--- +"@latticexyz/explorer": patch +--- + +Explore page now has a full-featured SQL editor with syntax highlighting, autocomplete, and query validation. diff --git a/packages/explorer/next.config.mjs b/packages/explorer/next.config.mjs index 7811d5e71d..a4f4406e34 100644 --- a/packages/explorer/next.config.mjs +++ b/packages/explorer/next.config.mjs @@ -6,6 +6,7 @@ export default function config() { output: "standalone", webpack: (config) => { config.externals.push("pino-pretty", "lokijs", "encoding"); + config.resolve.fallback = { fs: false }; return config; }, redirects: async () => { diff --git a/packages/explorer/package.json b/packages/explorer/package.json index e24b7e79fb..7198ccf340 100644 --- a/packages/explorer/package.json +++ b/packages/explorer/package.json @@ -44,6 +44,7 @@ "@latticexyz/store-indexer": "workspace:*", "@latticexyz/store-sync": "workspace:*", "@latticexyz/world": "workspace:*", + "@monaco-editor/react": "^4.6.0", "@radix-ui/react-checkbox": "^1.1.1", "@radix-ui/react-dialog": "^1.1.1", "@radix-ui/react-label": "^2.1.0", @@ -61,13 +62,16 @@ "cmdk": "1.0.0", "debug": "^4.3.4", "lucide-react": "^0.408.0", + "monaco-editor": "^0.52.0", "next": "14.2.5", + "node-sql-parser": "^5.3.3", "nuqs": "^1.19.2", "query-string": "^9.1.0", "react": "^18", "react-dom": "^18", "react-hook-form": "^7.52.1", "sonner": "^1.5.0", + "sql-autocomplete": "^1.1.1", "tailwind-merge": "^1.12.0", "tsup": "^6.7.0", "viem": "catalog:", diff --git a/packages/explorer/src/app/(explorer)/[chainName]/worlds/[worldAddress]/explore/Explorer.tsx b/packages/explorer/src/app/(explorer)/[chainName]/worlds/[worldAddress]/explore/Explorer.tsx index e408a7d9e0..f7d19fbf86 100644 --- a/packages/explorer/src/app/(explorer)/[chainName]/worlds/[worldAddress]/explore/Explorer.tsx +++ b/packages/explorer/src/app/(explorer)/[chainName]/worlds/[worldAddress]/explore/Explorer.tsx @@ -38,7 +38,7 @@ export function Explorer() { return ( <> - {indexer.type !== "sqlite" && } + {indexer.type !== "sqlite" && } diff --git a/packages/explorer/src/app/(explorer)/[chainName]/worlds/[worldAddress]/explore/SQLEditor.tsx b/packages/explorer/src/app/(explorer)/[chainName]/worlds/[worldAddress]/explore/SQLEditor.tsx index edffb85f7c..22b2a993f1 100644 --- a/packages/explorer/src/app/(explorer)/[chainName]/worlds/[worldAddress]/explore/SQLEditor.tsx +++ b/packages/explorer/src/app/(explorer)/[chainName]/worlds/[worldAddress]/explore/SQLEditor.tsx @@ -1,46 +1,84 @@ +"use client"; + import { PlayIcon } from "lucide-react"; import { useQueryState } from "nuqs"; -import { useEffect } from "react"; +import { useEffect, useState } from "react"; import { useForm } from "react-hook-form"; +import { Table } from "@latticexyz/config"; +import Editor from "@monaco-editor/react"; import { Button } from "../../../../../../components/ui/Button"; -import { Form, FormControl, FormField, FormItem } from "../../../../../../components/ui/Form"; -import { Input } from "../../../../../../components/ui/Input"; +import { Form, FormField } from "../../../../../../components/ui/Form"; +import { cn } from "../../../../../../utils"; +import { monacoOptions } from "./consts"; +import { useMonacoSuggestions } from "./useMonacoSuggestions"; +import { useQueryValidator } from "./useQueryValidator"; + +type Props = { + table?: Table; +}; + +export function SQLEditor({ table }: Props) { + const [isFocused, setIsFocused] = useState(false); + const [query, setQuery] = useQueryState("query", { defaultValue: "" }); + const validateQuery = useQueryValidator(table); + useMonacoSuggestions(table); -export function SQLEditor() { - const [query, setQuery] = useQueryState("query"); const form = useForm({ defaultValues: { - query: query || "", + query, }, }); const handleSubmit = form.handleSubmit((data) => { - setQuery(data.query); + if (validateQuery(data.query)) { + setQuery(data.query); + } }); useEffect(() => { - form.reset({ query: query || "" }); + form.reset({ query }); }, [query, form]); return ( - -
- ( - - - - - - )} - /> - - -
+ + ( + field.onChange(value)} + onMount={(editor) => { + editor.onDidFocusEditorText(() => { + setIsFocused(true); + }); + + editor.onDidBlurEditorText(() => { + setIsFocused(false); + }); + }} + loading={null} + /> + )} + /> + + ); diff --git a/packages/explorer/src/app/(explorer)/[chainName]/worlds/[worldAddress]/explore/consts.ts b/packages/explorer/src/app/(explorer)/[chainName]/worlds/[worldAddress]/explore/consts.ts new file mode 100644 index 0000000000..cca942027f --- /dev/null +++ b/packages/explorer/src/app/(explorer)/[chainName]/worlds/[worldAddress]/explore/consts.ts @@ -0,0 +1,41 @@ +import { editor } from "monaco-editor/esm/vs/editor/editor.api"; + +export const monacoOptions: editor.IStandaloneEditorConstructionOptions = { + fontSize: 14, + fontWeight: "normal", + wordWrap: "off", + lineNumbers: "off", + lineNumbersMinChars: 0, + overviewRulerLanes: 0, + overviewRulerBorder: false, + hideCursorInOverviewRuler: true, + lineDecorationsWidth: 0, + glyphMargin: false, + folding: false, + scrollBeyondLastColumn: 0, + scrollbar: { + horizontal: "hidden", + vertical: "hidden", + alwaysConsumeMouseWheel: false, + handleMouseWheel: false, + }, + find: { + addExtraSpaceOnTop: false, + autoFindInSelection: "never", + seedSearchStringFromSelection: "never", + }, + minimap: { enabled: false }, + wordBasedSuggestions: "off", + links: false, + occurrencesHighlight: "off", + cursorStyle: "line-thin", + renderLineHighlight: "none", + contextmenu: false, + roundedSelection: false, + hover: { + delay: 100, + }, + acceptSuggestionOnEnter: "on", + automaticLayout: true, + fixedOverflowWidgets: true, +}; diff --git a/packages/explorer/src/app/(explorer)/[chainName]/worlds/[worldAddress]/explore/useMonacoErrorMarker.ts b/packages/explorer/src/app/(explorer)/[chainName]/worlds/[worldAddress]/explore/useMonacoErrorMarker.ts new file mode 100644 index 0000000000..27134fc3dd --- /dev/null +++ b/packages/explorer/src/app/(explorer)/[chainName]/worlds/[worldAddress]/explore/useMonacoErrorMarker.ts @@ -0,0 +1,23 @@ +import { useCallback } from "react"; +import { useMonaco } from "@monaco-editor/react"; + +export function useMonacoErrorMarker() { + const monaco = useMonaco(); + return useCallback( + ({ message, startColumn, endColumn }: { message: string; startColumn: number; endColumn: number }) => { + if (monaco) { + monaco.editor.setModelMarkers(monaco.editor.getModels()[0], "sql", [ + { + severity: monaco.MarkerSeverity.Error, + message, + startLineNumber: 1, + startColumn, + endLineNumber: 1, + endColumn, + }, + ]); + } + }, + [monaco], + ); +} diff --git a/packages/explorer/src/app/(explorer)/[chainName]/worlds/[worldAddress]/explore/useMonacoSuggestions.ts b/packages/explorer/src/app/(explorer)/[chainName]/worlds/[worldAddress]/explore/useMonacoSuggestions.ts new file mode 100644 index 0000000000..d4c736b8d4 --- /dev/null +++ b/packages/explorer/src/app/(explorer)/[chainName]/worlds/[worldAddress]/explore/useMonacoSuggestions.ts @@ -0,0 +1,60 @@ +import { useEffect } from "react"; +import { Table } from "@latticexyz/config"; +import { useMonaco } from "@monaco-editor/react"; +import { useQueryAutocomplete } from "./useQueryAutocomplete"; + +const monacoSuggestionsMap = { + KEYWORD: "Keyword", + TABLE: "Field", + COLUMN: "Field", +} as const; + +export function useMonacoSuggestions(table?: Table) { + const monaco = useMonaco(); + const queryAutocomplete = useQueryAutocomplete(table); + + useEffect(() => { + if (!monaco) return; + + const provider = monaco.languages.registerCompletionItemProvider("sql", { + triggerCharacters: [" ", ".", ","], + + provideCompletionItems: (model, position) => { + if (!queryAutocomplete) { + return { suggestions: [] }; + } + + const textUntilPosition = model.getValueInRange({ + startLineNumber: 1, + startColumn: 1, + endLineNumber: position.lineNumber, + endColumn: position.column, + }); + + const word = model.getWordUntilPosition(position); + const range = { + startLineNumber: position.lineNumber, + startColumn: word.startColumn, + endLineNumber: position.lineNumber, + endColumn: word.endColumn, + }; + + const suggestions = queryAutocomplete + .autocomplete(textUntilPosition) + .map(({ value, optionType }) => ({ + label: value, + kind: monaco.languages.CompletionItemKind[monacoSuggestionsMap[optionType]], + insertText: value, + range, + // move keyword optionType to the top of suggestions list + sortText: optionType !== "KEYWORD" ? "0" : "1", + })) + .filter(({ label }) => !!label); + + return { suggestions }; + }, + }); + + return () => provider.dispose(); + }, [monaco, queryAutocomplete]); +} diff --git a/packages/explorer/src/app/(explorer)/[chainName]/worlds/[worldAddress]/explore/useQueryAutocomplete.ts b/packages/explorer/src/app/(explorer)/[chainName]/worlds/[worldAddress]/explore/useQueryAutocomplete.ts new file mode 100644 index 0000000000..14027d594b --- /dev/null +++ b/packages/explorer/src/app/(explorer)/[chainName]/worlds/[worldAddress]/explore/useQueryAutocomplete.ts @@ -0,0 +1,21 @@ +import { useParams } from "next/navigation"; +import { SQLAutocomplete, SQLDialect } from "sql-autocomplete"; +import { Address } from "viem"; +import { useMemo } from "react"; +import { Table } from "@latticexyz/config"; +import { useChain } from "../../../../hooks/useChain"; +import { constructTableName } from "../../../../utils/constructTableName"; + +export function useQueryAutocomplete(table?: Table) { + const { id: chainId } = useChain(); + const { worldAddress } = useParams<{ worldAddress: Address }>(); + + return useMemo(() => { + if (!table || !worldAddress || !chainId) return null; + + const tableName = constructTableName(table, worldAddress as Address, chainId); + const columnNames = Object.keys(table.schema); + + return new SQLAutocomplete(SQLDialect.PLpgSQL, [tableName], columnNames); + }, [table, worldAddress, chainId]); +} diff --git a/packages/explorer/src/app/(explorer)/[chainName]/worlds/[worldAddress]/explore/useQueryValidator.ts b/packages/explorer/src/app/(explorer)/[chainName]/worlds/[worldAddress]/explore/useQueryValidator.ts new file mode 100644 index 0000000000..3603419860 --- /dev/null +++ b/packages/explorer/src/app/(explorer)/[chainName]/worlds/[worldAddress]/explore/useQueryValidator.ts @@ -0,0 +1,72 @@ +import { useParams } from "next/navigation"; +import { Parser } from "node-sql-parser"; +import { Address } from "viem"; +import { useCallback } from "react"; +import { Table } from "@latticexyz/config"; +import { useMonaco } from "@monaco-editor/react"; +import { useChain } from "../../../../hooks/useChain"; +import { constructTableName } from "../../../../utils/constructTableName"; +import { useMonacoErrorMarker } from "./useMonacoErrorMarker"; + +const sqlParser = new Parser(); + +export function useQueryValidator(table?: Table) { + const monaco = useMonaco(); + const { worldAddress } = useParams(); + const { id: chainId } = useChain(); + const setErrorMarker = useMonacoErrorMarker(); + + return useCallback( + (value: string) => { + if (!monaco || !table) return true; + + try { + const ast = sqlParser.astify(value); + if ("columns" in ast && Array.isArray(ast.columns)) { + for (const column of ast.columns) { + const columnName = column.expr.column; + if (!Object.keys(table.schema).includes(columnName)) { + setErrorMarker({ + message: `Column '${columnName}' does not exist in the table schema.`, + startColumn: value.indexOf(columnName) + 1, + endColumn: value.indexOf(columnName) + columnName.length + 1, + }); + return false; + } + } + } + + if ("from" in ast && Array.isArray(ast.from)) { + for (const tableInfo of ast.from) { + if ("table" in tableInfo) { + const selectedTableName = tableInfo.table; + const tableName = constructTableName(table, worldAddress as Address, chainId); + + if (selectedTableName !== tableName) { + setErrorMarker({ + message: `Only '${tableName}' is available for this query.`, + startColumn: value.indexOf(selectedTableName) + 1, + endColumn: value.indexOf(selectedTableName) + selectedTableName.length + 1, + }); + return false; + } + } + } + } + + monaco.editor.setModelMarkers(monaco.editor.getModels()[0], "sql", []); + return true; + } catch (error) { + if (error instanceof Error) { + setErrorMarker({ + message: error.message, + startColumn: 1, + endColumn: value.length + 1, + }); + } + return false; + } + }, + [monaco, table, setErrorMarker, worldAddress, chainId], + ); +} diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 0608188316..4702c78d44 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -6,18 +6,6 @@ settings: catalogs: default: - '@ark/attest': - specifier: 0.12.1 - version: 0.12.1 - '@ark/util': - specifier: 0.2.2 - version: 0.2.2 - abitype: - specifier: 1.0.6 - version: 1.0.6 - arktype: - specifier: 2.0.0-beta.6 - version: 2.0.0-beta.6 viem: specifier: 2.21.19 version: 2.21.19 @@ -495,6 +483,9 @@ importers: '@latticexyz/world': specifier: workspace:* version: link:../world + '@monaco-editor/react': + specifier: ^4.6.0 + version: 4.6.0(monaco-editor@0.52.0)(react-dom@18.2.0(react@18.2.0))(react@18.2.0) '@radix-ui/react-checkbox': specifier: ^1.1.1 version: 1.1.1(@types/react-dom@18.2.7)(@types/react@18.2.22)(react-dom@18.2.0(react@18.2.0))(react@18.2.0) @@ -546,9 +537,15 @@ importers: lucide-react: specifier: ^0.408.0 version: 0.408.0(react@18.2.0) + monaco-editor: + specifier: ^0.52.0 + version: 0.52.0 next: specifier: 14.2.5 version: 14.2.5(@babel/core@7.25.2)(@opentelemetry/api@1.8.0)(react-dom@18.2.0(react@18.2.0))(react@18.2.0) + node-sql-parser: + specifier: ^5.3.3 + version: 5.3.3 nuqs: specifier: ^1.19.2 version: 1.19.2(next@14.2.5(@babel/core@7.25.2)(@opentelemetry/api@1.8.0)(react-dom@18.2.0(react@18.2.0))(react@18.2.0)) @@ -567,6 +564,9 @@ importers: sonner: specifier: ^1.5.0 version: 1.5.0(react-dom@18.2.0(react@18.2.0))(react@18.2.0) + sql-autocomplete: + specifier: ^1.1.1 + version: 1.1.1 tailwind-merge: specifier: ^1.12.0 version: 1.12.0 @@ -821,10 +821,10 @@ importers: version: 8.3.4 jest: specifier: ^29.3.1 - version: 29.5.0(@types/node@18.15.11) + version: 29.5.0(@types/node@20.12.12) ts-jest: specifier: ^29.0.5 - version: 29.0.5(@babel/core@7.21.4)(@jest/types@29.6.3)(babel-jest@29.5.0(@babel/core@7.21.4))(jest@29.5.0(@types/node@18.15.11))(typescript@5.4.2) + version: 29.0.5(@babel/core@7.25.2)(@jest/types@29.6.3)(babel-jest@29.5.0(@babel/core@7.25.2))(jest@29.5.0(@types/node@20.12.12))(typescript@5.4.2) tsup: specifier: ^6.7.0 version: 6.7.0(postcss@8.4.47)(typescript@5.4.2) @@ -1200,10 +1200,10 @@ importers: version: 27.4.1 jest: specifier: ^29.3.1 - version: 29.5.0(@types/node@20.12.12) + version: 29.5.0(@types/node@18.15.11) ts-jest: specifier: ^29.0.5 - version: 29.0.5(@babel/core@7.25.2)(@jest/types@29.6.3)(babel-jest@29.5.0(@babel/core@7.25.2))(jest@29.5.0(@types/node@20.12.12))(typescript@5.4.2) + version: 29.0.5(@babel/core@7.21.4)(@jest/types@29.6.3)(babel-jest@29.5.0(@babel/core@7.21.4))(jest@29.5.0(@types/node@18.15.11))(typescript@5.4.2) tsup: specifier: ^6.7.0 version: 6.7.0(postcss@8.4.47)(typescript@5.4.2) @@ -3484,6 +3484,18 @@ packages: resolution: {integrity: sha512-g2REf+xSt0OZfMoNNdC4+/Yy8eP3KUqvIArel54XRFKPoXbHI6+YjFfrLtfykWBjffOp7DTfIc3Kvk5TLfuiyg==} engines: {node: '>=16.0.0'} + '@monaco-editor/loader@1.4.0': + resolution: {integrity: sha512-00ioBig0x642hytVspPl7DbQyaSWRaolYie/UFNjoTdvoKPzo6xrXLhTk9ixgIKcLH5b5vDOjVNiGyY+uDCUlg==} + peerDependencies: + monaco-editor: '>= 0.21.0 < 1' + + '@monaco-editor/react@4.6.0': + resolution: {integrity: sha512-RFkU9/i7cN2bsq/iTkurMWOEErmYcY6JiQI3Jn+WeR/FGISH8JbHERjpS9oRuSOPvDMJI0Z8nJeKkbOs9sBYQw==} + peerDependencies: + monaco-editor: '>= 0.25.0 < 1' + react: ^16.8.0 || ^17.0.0 || ^18.0.0 + react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0 + '@motionone/animation@10.18.0': resolution: {integrity: sha512-9z2p5GFGCm0gBsZbi8rVMOAJCtw1WqBTIPw3ozk06gDvZInBPIsQcHgYogEJ4yuHJ+akuW8g1SEIOpTOvYs8hw==} @@ -5206,6 +5218,9 @@ packages: '@types/openurl@1.0.0': resolution: {integrity: sha512-fUHH4T8FmEl3NBtGbUYYzMo1Ev47uVCVEGVjVNjorOMzgjls6zH82yr/zqkkcEOHY2HUC5PZ8dRFwGed/NR7wQ==} + '@types/pegjs@0.10.6': + resolution: {integrity: sha512-eLYXDbZWXh2uxf+w8sXS8d6KSoXTswfps6fvCUuVAGN8eRpfe7h9eSRydxiSJvo9Bf+GzifsDOr9TMQlmJdmkw==} + '@types/prettier@2.7.2': resolution: {integrity: sha512-KufADq8uQqo1pYKVIYzfKbJfBAc0sOeXqGbFaSpv8MRmC/zXgowNZmFcbngndGk922QDmOASEXUZCaY48gs4cg==} @@ -5670,9 +5685,15 @@ packages: resolution: {integrity: sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==} engines: {node: '>=12'} + antlr4-c3@1.1.16: + resolution: {integrity: sha512-3CZQ54JPyo6lkZYDOXaKUsy5KNFucveocvRy+03R7zJQbYOUr4Z6lFeBWzkxJXLitc7yQYe7OHbB6snyp7IJvQ==} + antlr4@4.7.1: resolution: {integrity: sha512-haHyTW7Y9joE5MVs37P2lNYfU2RWBLfcRDD8OWldcdZm5TiCE91B5Xl1oWSwiDUSd4rlExpt2pu1fksYQjRBYQ==} + antlr4ts-sql@1.1.0: + resolution: {integrity: sha512-94+8/+hsPI/OLG0WRGqW88/gqz+xxrSLSz/jGqS8Xl7tYWwUj/cBB4D/QRmNezYLqEaUzMHhTeQn5hzrpC2wNQ==} + antlr4ts@0.5.0-alpha.4: resolution: {integrity: sha512-WPQDt1B74OfPv/IMS2ekXAKkTZIHl88uMetg6q3OTqgFxZ/dxDXI0EWLyZid/1Pe6hTftyg5N7gel5wNAGxXyQ==} @@ -5917,6 +5938,10 @@ packages: better-sqlite3@8.6.0: resolution: {integrity: sha512-jwAudeiTMTSyby+/SfbHDebShbmC2MCH8mU2+DXi0WJfv13ypEJm47cd3kljmy/H130CazEvkf2Li//ewcMJ1g==} + big-integer@1.6.52: + resolution: {integrity: sha512-QxD8cf2eVqJOOz63z6JIN9BzvVs/dlySa5HGSBH5xtR8dPteIRQnBxxKqkNTiT6jbDTF6jAfrd4oMcND9RGbQg==} + engines: {node: '>=0.6'} + binary-extensions@2.2.0: resolution: {integrity: sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==} engines: {node: '>=8'} @@ -7039,11 +7064,13 @@ packages: eslint@5.16.0: resolution: {integrity: sha512-S3Rz11i7c8AA5JPv7xAH+dOyq/Cu/VXHiHXBPOU1k/JAM5dXqQPt3qcrhpHSorXmrpu2g0gkIBVXAqCpzfoZIg==} engines: {node: ^6.14.0 || ^8.10.0 || >=9.10.0} + deprecated: This version is no longer supported. Please see https://eslint.org/version-support for other options. hasBin: true eslint@8.57.0: resolution: {integrity: sha512-dZ6+mexnaTIbSBZWgou51U6OmzIhYM2VcNdtiTtI7qPNZm35Akpr0f6vtw3w1Kmn5PYo+tZVfh13WrhpS6oLqQ==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + deprecated: This version is no longer supported. Please see https://eslint.org/version-support for other options. hasBin: true espree@5.0.1: @@ -8868,6 +8895,9 @@ packages: modern-ahocorasick@1.0.1: resolution: {integrity: sha512-yoe+JbhTClckZ67b2itRtistFKf8yPYelHLc7e5xAwtNAXxM6wJTUx2C7QeVSJFDzKT7bCIFyBVybPMKvmB9AA==} + monaco-editor@0.52.0: + resolution: {integrity: sha512-OeWhNpABLCeTqubfqLMXGsqf6OmPU6pHM85kF3dhy6kq5hnhuVS1p3VrEW/XhWHc71P2tHyS5JFySD8mgs1crw==} + motion@10.16.2: resolution: {integrity: sha512-p+PurYqfUdcJZvtnmAqu5fJgV2kR0uLFQuBKtLeFVTrYEVllI99tiOTSefVNYuip9ELTEkepIIDftNdze76NAQ==} @@ -9014,6 +9044,10 @@ packages: node-releases@2.0.18: resolution: {integrity: sha512-d9VeXT4SJ7ZeOqGX6R5EM022wpL+eWPooLI+5UpWn2jCT1aosUQEhQP214x33Wkwx3JQMvIm+tIoVOdodFS40g==} + node-sql-parser@5.3.3: + resolution: {integrity: sha512-KRVnneHhy5zaylFmtojHxpXqjmThFSOn7n6x1J/38gTE6NL5Wj/yY7hjbFqpYcpPwIpXM7sFDnvV3ejxmoPAJQ==} + engines: {node: '>=8'} + node-stream-zip@1.15.0: resolution: {integrity: sha512-LN4fydt9TqhZhThkZIVQnF9cwjU3qmUH9h78Mx/K7d3VvfRqqwthLwJEUOEL0QPZ0XQmNN7be5Ggit5+4dq3Bw==} engines: {node: '>=0.12.0'} @@ -10430,6 +10464,9 @@ packages: sprintf-js@1.0.3: resolution: {integrity: sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==} + sql-autocomplete@1.1.1: + resolution: {integrity: sha512-0Fz1utcPC5m6qQiFDhIgyqNk8J0DyGBtv59++uUjkjM9vOdv6ugGuM+ZcjY0qyG9ofLmT6wLHSpx+2h6jm0t6Q==} + sql.js@1.8.0: resolution: {integrity: sha512-3HD8pSkZL+5YvYUI8nlvNILs61ALqq34xgmF+BHpqxe68yZIJ1H+sIVIODvni25+CcxHUxDyrTJUL0lE/m7afw==} @@ -10451,6 +10488,9 @@ packages: resolution: {integrity: sha512-KJP1OCML99+8fhOHxwwzyWrlUuVX5GQ0ZpJTd1DFXhdkrvg1szxfHhawXUZ3g9TkXORQd4/WG68jMlQZ2p8wlg==} engines: {node: '>=6'} + state-local@1.0.7: + resolution: {integrity: sha512-HTEHMNieakEnoe33shBYcZ7NX83ACUjCu8c40iOGEZsngj9zRnkqS9j1pqQPXwobB0ZcVTk27REb7COQ0UR59w==} + statuses@1.5.0: resolution: {integrity: sha512-OpZ3zP+jT1PI7I8nemJX4AKmAX070ZkYPVWV/AaKTJl+tXCTGyVdC1a4SL8RUQYEwk/f34ZX8UTykN68FwrqAA==} engines: {node: '>= 0.6'} @@ -11927,7 +11967,7 @@ snapshots: '@smithy/util-middleware': 2.2.0 '@smithy/util-retry': 2.2.0 '@smithy/util-utf8': 2.3.0 - tslib: 2.6.2 + tslib: 2.7.0 transitivePeerDependencies: - aws-crt @@ -11939,7 +11979,7 @@ snapshots: '@smithy/smithy-client': 2.5.1 '@smithy/types': 2.12.0 fast-xml-parser: 4.2.5 - tslib: 2.6.2 + tslib: 2.7.0 '@aws-sdk/credential-provider-env@3.535.0': dependencies: @@ -11990,7 +12030,7 @@ snapshots: '@smithy/property-provider': 2.2.0 '@smithy/shared-ini-file-loader': 2.4.0 '@smithy/types': 2.12.0 - tslib: 2.6.2 + tslib: 2.7.0 transitivePeerDependencies: - aws-crt @@ -12031,20 +12071,20 @@ snapshots: '@aws-sdk/types': 3.535.0 '@smithy/protocol-http': 3.3.0 '@smithy/types': 2.12.0 - tslib: 2.6.2 + tslib: 2.7.0 '@aws-sdk/middleware-logger@3.535.0': dependencies: '@aws-sdk/types': 3.535.0 '@smithy/types': 2.12.0 - tslib: 2.6.2 + tslib: 2.7.0 '@aws-sdk/middleware-recursion-detection@3.535.0': dependencies: '@aws-sdk/types': 3.535.0 '@smithy/protocol-http': 3.3.0 '@smithy/types': 2.12.0 - tslib: 2.6.2 + tslib: 2.7.0 '@aws-sdk/middleware-user-agent@3.540.0': dependencies: @@ -12052,7 +12092,7 @@ snapshots: '@aws-sdk/util-endpoints': 3.540.0 '@smithy/protocol-http': 3.3.0 '@smithy/types': 2.12.0 - tslib: 2.6.2 + tslib: 2.7.0 '@aws-sdk/region-config-resolver@3.535.0': dependencies: @@ -12061,7 +12101,7 @@ snapshots: '@smithy/types': 2.12.0 '@smithy/util-config-provider': 2.3.0 '@smithy/util-middleware': 2.2.0 - tslib: 2.6.2 + tslib: 2.7.0 '@aws-sdk/token-providers@3.556.0(@aws-sdk/credential-provider-node@3.556.0)': dependencies: @@ -12078,14 +12118,14 @@ snapshots: '@aws-sdk/types@3.535.0': dependencies: '@smithy/types': 2.12.0 - tslib: 2.6.2 + tslib: 2.7.0 '@aws-sdk/util-endpoints@3.540.0': dependencies: '@aws-sdk/types': 3.535.0 '@smithy/types': 2.12.0 '@smithy/util-endpoints': 1.2.0 - tslib: 2.6.2 + tslib: 2.7.0 '@aws-sdk/util-locate-window@3.535.0': dependencies: @@ -12096,14 +12136,14 @@ snapshots: '@aws-sdk/types': 3.535.0 '@smithy/types': 2.12.0 bowser: 2.11.0 - tslib: 2.6.2 + tslib: 2.7.0 '@aws-sdk/util-user-agent-node@3.535.0': dependencies: '@aws-sdk/types': 3.535.0 '@smithy/node-config-provider': 2.3.0 '@smithy/types': 2.12.0 - tslib: 2.6.2 + tslib: 2.7.0 '@aws-sdk/util-utf8-browser@3.259.0': dependencies: @@ -12385,7 +12425,7 @@ snapshots: '@babel/helper-validator-identifier': 7.24.7 chalk: 2.4.2 js-tokens: 4.0.0 - picocolors: 1.0.1 + picocolors: 1.1.0 '@babel/parser@7.21.4': dependencies: @@ -14370,6 +14410,18 @@ snapshots: transitivePeerDependencies: - supports-color + '@monaco-editor/loader@1.4.0(monaco-editor@0.52.0)': + dependencies: + monaco-editor: 0.52.0 + state-local: 1.0.7 + + '@monaco-editor/react@4.6.0(monaco-editor@0.52.0)(react-dom@18.2.0(react@18.2.0))(react@18.2.0)': + dependencies: + '@monaco-editor/loader': 1.4.0(monaco-editor@0.52.0) + monaco-editor: 0.52.0 + react: 18.2.0 + react-dom: 18.2.0(react@18.2.0) + '@motionone/animation@10.18.0': dependencies: '@motionone/easing': 10.18.0 @@ -15882,7 +15934,7 @@ snapshots: '@smithy/types': 2.12.0 '@smithy/util-config-provider': 2.3.0 '@smithy/util-middleware': 2.2.0 - tslib: 2.6.2 + tslib: 2.7.0 '@smithy/core@1.4.2': dependencies: @@ -15893,7 +15945,7 @@ snapshots: '@smithy/smithy-client': 2.5.1 '@smithy/types': 2.12.0 '@smithy/util-middleware': 2.2.0 - tslib: 2.6.2 + tslib: 2.7.0 '@smithy/credential-provider-imds@2.3.0': dependencies: @@ -15909,19 +15961,19 @@ snapshots: '@smithy/querystring-builder': 2.2.0 '@smithy/types': 2.12.0 '@smithy/util-base64': 2.3.0 - tslib: 2.6.2 + tslib: 2.7.0 '@smithy/hash-node@2.2.0': dependencies: '@smithy/types': 2.12.0 '@smithy/util-buffer-from': 2.2.0 '@smithy/util-utf8': 2.3.0 - tslib: 2.6.2 + tslib: 2.7.0 '@smithy/invalid-dependency@2.2.0': dependencies: '@smithy/types': 2.12.0 - tslib: 2.6.2 + tslib: 2.7.0 '@smithy/is-array-buffer@2.2.0': dependencies: @@ -15931,7 +15983,7 @@ snapshots: dependencies: '@smithy/protocol-http': 3.3.0 '@smithy/types': 2.12.0 - tslib: 2.6.2 + tslib: 2.7.0 '@smithy/middleware-endpoint@2.5.1': dependencies: @@ -15941,7 +15993,7 @@ snapshots: '@smithy/types': 2.12.0 '@smithy/url-parser': 2.2.0 '@smithy/util-middleware': 2.2.0 - tslib: 2.6.2 + tslib: 2.7.0 '@smithy/middleware-retry@2.3.1': dependencies: @@ -15952,25 +16004,25 @@ snapshots: '@smithy/types': 2.12.0 '@smithy/util-middleware': 2.2.0 '@smithy/util-retry': 2.2.0 - tslib: 2.6.2 + tslib: 2.7.0 uuid: 9.0.1 '@smithy/middleware-serde@2.3.0': dependencies: '@smithy/types': 2.12.0 - tslib: 2.6.2 + tslib: 2.7.0 '@smithy/middleware-stack@2.2.0': dependencies: '@smithy/types': 2.12.0 - tslib: 2.6.2 + tslib: 2.7.0 '@smithy/node-config-provider@2.3.0': dependencies: '@smithy/property-provider': 2.2.0 '@smithy/shared-ini-file-loader': 2.4.0 '@smithy/types': 2.12.0 - tslib: 2.6.2 + tslib: 2.7.0 '@smithy/node-http-handler@2.5.0': dependencies: @@ -15978,7 +16030,7 @@ snapshots: '@smithy/protocol-http': 3.3.0 '@smithy/querystring-builder': 2.2.0 '@smithy/types': 2.12.0 - tslib: 2.6.2 + tslib: 2.7.0 '@smithy/property-provider@2.2.0': dependencies: @@ -15988,7 +16040,7 @@ snapshots: '@smithy/protocol-http@3.3.0': dependencies: '@smithy/types': 2.12.0 - tslib: 2.6.2 + tslib: 2.7.0 '@smithy/querystring-builder@2.2.0': dependencies: @@ -16018,7 +16070,7 @@ snapshots: '@smithy/util-middleware': 2.2.0 '@smithy/util-uri-escape': 2.2.0 '@smithy/util-utf8': 2.3.0 - tslib: 2.6.2 + tslib: 2.7.0 '@smithy/smithy-client@2.5.1': dependencies: @@ -16027,36 +16079,36 @@ snapshots: '@smithy/protocol-http': 3.3.0 '@smithy/types': 2.12.0 '@smithy/util-stream': 2.2.0 - tslib: 2.6.2 + tslib: 2.7.0 '@smithy/types@2.12.0': dependencies: - tslib: 2.6.2 + tslib: 2.7.0 '@smithy/url-parser@2.2.0': dependencies: '@smithy/querystring-parser': 2.2.0 '@smithy/types': 2.12.0 - tslib: 2.6.2 + tslib: 2.7.0 '@smithy/util-base64@2.3.0': dependencies: '@smithy/util-buffer-from': 2.2.0 '@smithy/util-utf8': 2.3.0 - tslib: 2.6.2 + tslib: 2.7.0 '@smithy/util-body-length-browser@2.2.0': dependencies: - tslib: 2.6.2 + tslib: 2.7.0 '@smithy/util-body-length-node@2.3.0': dependencies: - tslib: 2.6.2 + tslib: 2.7.0 '@smithy/util-buffer-from@2.2.0': dependencies: '@smithy/is-array-buffer': 2.2.0 - tslib: 2.6.2 + tslib: 2.7.0 '@smithy/util-config-provider@2.3.0': dependencies: @@ -16068,7 +16120,7 @@ snapshots: '@smithy/smithy-client': 2.5.1 '@smithy/types': 2.12.0 bowser: 2.11.0 - tslib: 2.6.2 + tslib: 2.7.0 '@smithy/util-defaults-mode-node@2.3.1': dependencies: @@ -16078,13 +16130,13 @@ snapshots: '@smithy/property-provider': 2.2.0 '@smithy/smithy-client': 2.5.1 '@smithy/types': 2.12.0 - tslib: 2.6.2 + tslib: 2.7.0 '@smithy/util-endpoints@1.2.0': dependencies: '@smithy/node-config-provider': 2.3.0 '@smithy/types': 2.12.0 - tslib: 2.6.2 + tslib: 2.7.0 '@smithy/util-hex-encoding@2.2.0': dependencies: @@ -16093,13 +16145,13 @@ snapshots: '@smithy/util-middleware@2.2.0': dependencies: '@smithy/types': 2.12.0 - tslib: 2.6.2 + tslib: 2.7.0 '@smithy/util-retry@2.2.0': dependencies: '@smithy/service-error-classification': 2.1.5 '@smithy/types': 2.12.0 - tslib: 2.6.2 + tslib: 2.7.0 '@smithy/util-stream@2.2.0': dependencies: @@ -16119,7 +16171,7 @@ snapshots: '@smithy/util-utf8@2.3.0': dependencies: '@smithy/util-buffer-from': 2.2.0 - tslib: 2.6.2 + tslib: 2.7.0 '@snyk/github-codeowners@1.1.0': dependencies: @@ -16477,6 +16529,8 @@ snapshots: dependencies: '@types/node': 18.15.11 + '@types/pegjs@0.10.6': {} + '@types/prettier@2.7.2': {} '@types/prop-types@15.7.5': {} @@ -17273,8 +17327,16 @@ snapshots: ansi-styles@6.2.1: {} + antlr4-c3@1.1.16: + dependencies: + antlr4ts: 0.5.0-alpha.4 + antlr4@4.7.1: {} + antlr4ts-sql@1.1.0: + dependencies: + antlr4ts: 0.5.0-alpha.4 + antlr4ts@0.5.0-alpha.4: {} any-promise@1.3.0: {} @@ -17635,6 +17697,8 @@ snapshots: bindings: 1.5.0 prebuild-install: 7.1.1 + big-integer@1.6.52: {} + binary-extensions@2.2.0: {} bindings@1.5.0: @@ -18851,7 +18915,7 @@ snapshots: debug: 4.3.7 enhanced-resolve: 5.17.1 eslint: 8.57.0 - eslint-module-utils: 2.8.1(@typescript-eslint/parser@7.1.1(eslint@8.57.0)(typescript@5.4.2))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.1(@typescript-eslint/parser@7.1.1(eslint@8.57.0)(typescript@5.4.2))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.29.1)(eslint@8.57.0))(eslint@8.57.0) + eslint-module-utils: 2.8.1(@typescript-eslint/parser@7.1.1(eslint@8.57.0)(typescript@5.4.2))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.1)(eslint@8.57.0) eslint-plugin-import: 2.29.1(@typescript-eslint/parser@7.1.1(eslint@8.57.0)(typescript@5.4.2))(eslint-import-resolver-typescript@3.6.1)(eslint@8.57.0) fast-glob: 3.3.2 get-tsconfig: 4.7.5 @@ -18863,7 +18927,7 @@ snapshots: - eslint-import-resolver-webpack - supports-color - eslint-module-utils@2.8.1(@typescript-eslint/parser@7.1.1(eslint@8.57.0)(typescript@5.4.2))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.1(@typescript-eslint/parser@7.1.1(eslint@8.57.0)(typescript@5.4.2))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.29.1)(eslint@8.57.0))(eslint@8.57.0): + eslint-module-utils@2.8.1(@typescript-eslint/parser@7.1.1(eslint@8.57.0)(typescript@5.4.2))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.1)(eslint@8.57.0): dependencies: debug: 3.2.7 optionalDependencies: @@ -18884,7 +18948,7 @@ snapshots: doctrine: 2.1.0 eslint: 8.57.0 eslint-import-resolver-node: 0.3.9 - eslint-module-utils: 2.8.1(@typescript-eslint/parser@7.1.1(eslint@8.57.0)(typescript@5.4.2))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.1(@typescript-eslint/parser@7.1.1(eslint@8.57.0)(typescript@5.4.2))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.29.1)(eslint@8.57.0))(eslint@8.57.0) + eslint-module-utils: 2.8.1(@typescript-eslint/parser@7.1.1(eslint@8.57.0)(typescript@5.4.2))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.1)(eslint@8.57.0) hasown: 2.0.2 is-core-module: 2.15.0 is-glob: 4.0.3 @@ -21466,6 +21530,8 @@ snapshots: modern-ahocorasick@1.0.1: {} + monaco-editor@0.52.0: {} + motion@10.16.2: dependencies: '@motionone/animation': 10.18.0 @@ -21600,6 +21666,11 @@ snapshots: node-releases@2.0.18: {} + node-sql-parser@5.3.3: + dependencies: + '@types/pegjs': 0.10.6 + big-integer: 1.6.52 + node-stream-zip@1.15.0: {} nopt@6.0.0: @@ -23079,6 +23150,11 @@ snapshots: sprintf-js@1.0.3: {} + sql-autocomplete@1.1.1: + dependencies: + antlr4-c3: 1.1.16 + antlr4ts-sql: 1.1.0 + sql.js@1.8.0: {} ssri@9.0.1: @@ -23097,6 +23173,8 @@ snapshots: dependencies: type-fest: 0.7.1 + state-local@1.0.7: {} + statuses@1.5.0: {} statuses@2.0.1: {} @@ -23822,13 +23900,13 @@ snapshots: dependencies: browserslist: 4.23.0 escalade: 3.1.2 - picocolors: 1.0.0 + picocolors: 1.1.0 update-browserslist-db@1.1.0(browserslist@4.23.3): dependencies: browserslist: 4.23.3 escalade: 3.1.2 - picocolors: 1.0.1 + picocolors: 1.1.0 uqr@0.1.2: {} From 1492536fd2c8d2d86390c1b8e91213012e39f9ed Mon Sep 17 00:00:00 2001 From: three9s <156545083+three9s@users.noreply.github.com> Date: Fri, 18 Oct 2024 08:03:29 -0700 Subject: [PATCH 04/10] docs(guides/emojimon): fix git commands (#3278) Co-authored-by: Kevin Ingersoll --- docs/pages/guides/emojimon/2-getting-started.mdx | 2 +- docs/pages/guides/emojimon/3-players-and-movement.mdx | 2 +- docs/pages/guides/emojimon/4-map-and-terrain.mdx | 2 +- docs/pages/guides/emojimon/5-a-wild-emojimon-appears.mdx | 2 +- docs/pages/guides/emojimon/6-advanced.mdx | 2 +- 5 files changed, 5 insertions(+), 5 deletions(-) diff --git a/docs/pages/guides/emojimon/2-getting-started.mdx b/docs/pages/guides/emojimon/2-getting-started.mdx index e2b0356617..28c35d530a 100644 --- a/docs/pages/guides/emojimon/2-getting-started.mdx +++ b/docs/pages/guides/emojimon/2-getting-started.mdx @@ -34,7 +34,7 @@ Most projects in MUD are started by running `pnpm create mud@latest { You can run this command to update all the files to this point in the game's development. ```sh copy -git reset --hard step-4 +git reset --hard origin/step-4 ``` All you have to do now is deploy to testnet! From f06169a1399beb2de56e9b34a8aad3e051ebc9d1 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Fri, 18 Oct 2024 08:19:09 -0700 Subject: [PATCH 05/10] Version Packages (#3269) Co-authored-by: github-actions[bot] --- .changeset/brown-sheep-confess.md | 5 -- .changeset/eighty-humans-divide.md | 14 ---- .changeset/five-tomatoes-guess.md | 6 -- .changeset/good-eyes-nail.md | 5 -- .changeset/hip-jokes-compare.md | 25 ------ .changeset/hip-rings-rule.md | 5 -- .changeset/hip-turkeys-cheat.md | 5 -- .changeset/hot-buckets-draw.md | 5 -- .changeset/itchy-countries-worry.md | 5 -- .changeset/lucky-bulldogs-taste.md | 5 -- .changeset/mean-squids-buy.md | 5 -- .changeset/soft-bears-rest.md | 11 --- .changeset/spotty-crabs-prove.md | 7 -- .changeset/tall-penguins-promise.md | 5 -- CHANGELOG.md | 87 +++++++++++++++++++++ docs/pages/changelog.mdx | 87 +++++++++++++++++++++ packages/abi-ts/CHANGELOG.md | 2 + packages/abi-ts/package.json | 2 +- packages/block-logs-stream/CHANGELOG.md | 17 ++++ packages/block-logs-stream/package.json | 2 +- packages/cli/CHANGELOG.md | 27 +++++++ packages/cli/package.json | 2 +- packages/common/CHANGELOG.md | 19 +++++ packages/common/package.json | 2 +- packages/config/CHANGELOG.md | 18 +++++ packages/config/package.json | 2 +- packages/create-mud/CHANGELOG.md | 12 +++ packages/create-mud/package.json | 2 +- packages/dev-tools/CHANGELOG.md | 25 ++++++ packages/dev-tools/package.json | 2 +- packages/explorer/CHANGELOG.md | 51 ++++++++++++ packages/explorer/package.json | 2 +- packages/faucet/CHANGELOG.md | 18 +++++ packages/faucet/package.json | 2 +- packages/gas-report/CHANGELOG.md | 2 + packages/gas-report/package.json | 2 +- packages/protocol-parser/CHANGELOG.md | 19 +++++ packages/protocol-parser/package.json | 2 +- packages/react/CHANGELOG.md | 8 ++ packages/react/package.json | 2 +- packages/recs/CHANGELOG.md | 8 ++ packages/recs/package.json | 2 +- packages/schema-type/CHANGELOG.md | 12 +++ packages/schema-type/package.json | 2 +- packages/solhint-config-mud/CHANGELOG.md | 2 + packages/solhint-config-mud/package.json | 2 +- packages/solhint-plugin-mud/CHANGELOG.md | 2 + packages/solhint-plugin-mud/package.json | 2 +- packages/stash/CHANGELOG.md | 18 +++++ packages/stash/package.json | 2 +- packages/store-indexer/CHANGELOG.md | 23 ++++++ packages/store-indexer/package.json | 2 +- packages/store-sync/CHANGELOG.md | 26 ++++++ packages/store-sync/package.json | 2 +- packages/store/CHANGELOG.md | 20 +++++ packages/store/package.json | 2 +- packages/utils/CHANGELOG.md | 2 + packages/utils/package.json | 2 +- packages/world-module-metadata/CHANGELOG.md | 9 +++ packages/world-module-metadata/package.json | 2 +- packages/world-modules/CHANGELOG.md | 13 +++ packages/world-modules/package.json | 2 +- packages/world/CHANGELOG.md | 21 +++++ packages/world/package.json | 2 +- 64 files changed, 572 insertions(+), 132 deletions(-) delete mode 100644 .changeset/brown-sheep-confess.md delete mode 100644 .changeset/eighty-humans-divide.md delete mode 100644 .changeset/five-tomatoes-guess.md delete mode 100644 .changeset/good-eyes-nail.md delete mode 100644 .changeset/hip-jokes-compare.md delete mode 100644 .changeset/hip-rings-rule.md delete mode 100644 .changeset/hip-turkeys-cheat.md delete mode 100644 .changeset/hot-buckets-draw.md delete mode 100644 .changeset/itchy-countries-worry.md delete mode 100644 .changeset/lucky-bulldogs-taste.md delete mode 100644 .changeset/mean-squids-buy.md delete mode 100644 .changeset/soft-bears-rest.md delete mode 100644 .changeset/spotty-crabs-prove.md delete mode 100644 .changeset/tall-penguins-promise.md diff --git a/.changeset/brown-sheep-confess.md b/.changeset/brown-sheep-confess.md deleted file mode 100644 index 55703829e7..0000000000 --- a/.changeset/brown-sheep-confess.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"@latticexyz/explorer": patch ---- - -Function filters in `Interact` tab are now included as part of the URL. diff --git a/.changeset/eighty-humans-divide.md b/.changeset/eighty-humans-divide.md deleted file mode 100644 index 40dd5e3976..0000000000 --- a/.changeset/eighty-humans-divide.md +++ /dev/null @@ -1,14 +0,0 @@ ---- -"@latticexyz/explorer": patch ---- - -Transactions in Observe tab are now populated with timing metrics when using the `observer` Viem decorator in local projects. - -You can wire up your local project to use transaction timings with: - -``` -import { observer } from "@latticexyz/explorer/observer"; - -// Extend the Viem client that is performing writes -walletClient.extend(observer()); -``` diff --git a/.changeset/five-tomatoes-guess.md b/.changeset/five-tomatoes-guess.md deleted file mode 100644 index e1fd3244d2..0000000000 --- a/.changeset/five-tomatoes-guess.md +++ /dev/null @@ -1,6 +0,0 @@ ---- -"@latticexyz/faucet": patch -"@latticexyz/store-indexer": patch ---- - -Added bin wrappers to resolve issues when installing the package locally as a dependency of another package. diff --git a/.changeset/good-eyes-nail.md b/.changeset/good-eyes-nail.md deleted file mode 100644 index fd33f62862..0000000000 --- a/.changeset/good-eyes-nail.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"@latticexyz/explorer": patch ---- - -Interact tab now displays decoded ABI errors for failed transactions. diff --git a/.changeset/hip-jokes-compare.md b/.changeset/hip-jokes-compare.md deleted file mode 100644 index a23df08dd4..0000000000 --- a/.changeset/hip-jokes-compare.md +++ /dev/null @@ -1,25 +0,0 @@ ---- -"@latticexyz/block-logs-stream": patch -"@latticexyz/cli": patch -"@latticexyz/common": patch -"@latticexyz/config": patch -"@latticexyz/dev-tools": patch -"@latticexyz/explorer": patch -"@latticexyz/faucet": patch -"@latticexyz/protocol-parser": patch -"@latticexyz/schema-type": patch -"@latticexyz/stash": patch -"@latticexyz/store-indexer": patch -"@latticexyz/store-sync": patch -"@latticexyz/store": patch -"@latticexyz/world": patch -"create-mud": patch ---- - -Bumped viem to v2.21.19. - -MUD projects using these packages should do the same to ensure no type errors due to mismatched versions: - -``` -pnpm recursive up viem@2.21.19 -``` diff --git a/.changeset/hip-rings-rule.md b/.changeset/hip-rings-rule.md deleted file mode 100644 index abb7e73306..0000000000 --- a/.changeset/hip-rings-rule.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"@latticexyz/explorer": patch ---- - -Fixed inputs display in the transactions table row. diff --git a/.changeset/hip-turkeys-cheat.md b/.changeset/hip-turkeys-cheat.md deleted file mode 100644 index fcce2b2482..0000000000 --- a/.changeset/hip-turkeys-cheat.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"@latticexyz/common": patch ---- - -Added Rhodolite devnet chain config and removed the old and now-defunct Lattice testnet chain config. diff --git a/.changeset/hot-buckets-draw.md b/.changeset/hot-buckets-draw.md deleted file mode 100644 index dbf9b59742..0000000000 --- a/.changeset/hot-buckets-draw.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"@latticexyz/explorer": patch ---- - -Display error messages for failed queries within the Explore tab's table viewer. diff --git a/.changeset/itchy-countries-worry.md b/.changeset/itchy-countries-worry.md deleted file mode 100644 index 9374512b23..0000000000 --- a/.changeset/itchy-countries-worry.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"@latticexyz/explorer": patch ---- - -Explore page now has a full-featured SQL editor with syntax highlighting, autocomplete, and query validation. diff --git a/.changeset/lucky-bulldogs-taste.md b/.changeset/lucky-bulldogs-taste.md deleted file mode 100644 index a2f01c8e94..0000000000 --- a/.changeset/lucky-bulldogs-taste.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"@latticexyz/explorer": patch ---- - -Each chain's home page now lets you find and pick a world to explore. diff --git a/.changeset/mean-squids-buy.md b/.changeset/mean-squids-buy.md deleted file mode 100644 index 243de23e00..0000000000 --- a/.changeset/mean-squids-buy.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"@latticexyz/store-sync": patch ---- - -Added experimental support for syncing state from pending logs. diff --git a/.changeset/soft-bears-rest.md b/.changeset/soft-bears-rest.md deleted file mode 100644 index 766f6fbc91..0000000000 --- a/.changeset/soft-bears-rest.md +++ /dev/null @@ -1,11 +0,0 @@ ---- -"@latticexyz/explorer": patch ---- - -- Not found page if invalid chain name. -- Only show selector for worlds if options exist. -- Remove "future time" from transactions table. -- Improved layout for Interact tab. -- Wrap long args in transactions table. -- New tables polling. -- Add logs (regression). diff --git a/.changeset/spotty-crabs-prove.md b/.changeset/spotty-crabs-prove.md deleted file mode 100644 index 6366c2191d..0000000000 --- a/.changeset/spotty-crabs-prove.md +++ /dev/null @@ -1,7 +0,0 @@ ---- -"@latticexyz/common": patch ---- - -The `transactionQueue` decorator internally keeps an updated reference for the recommended `baseFeePerGas` and `maxPriorityFeePerGas` from the connected chain to avoid having to fetch it right before sending a transaction. -However, due to the way the fee values were overridden, it wasn't possible for users to explicitly pass in custom fee values. -Now explicitly provided fee values have precedence over the internally estimated fee values. diff --git a/.changeset/tall-penguins-promise.md b/.changeset/tall-penguins-promise.md deleted file mode 100644 index 59adfb99e5..0000000000 --- a/.changeset/tall-penguins-promise.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"@latticexyz/explorer": patch ---- - -Transactions are now monitored across all tabs while the World Explorer is open. diff --git a/CHANGELOG.md b/CHANGELOG.md index 104d7a5ede..2bfb9fad8a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,90 @@ +## Version 2.2.12 + +Release date: Fri Oct 18 2024 + +### Patch changes + +**[feat(explorer): add functions filter to query state (#3268)](https://github.com/latticexyz/mud/commit/3d8db6f76f3634d532d39cf4091f22fee0a32b68)** (@latticexyz/explorer) + +Function filters in `Interact` tab are now included as part of the URL. + +**[feat(explorer): transaction timings (#3274)](https://github.com/latticexyz/mud/commit/1b0ffcf7a1a7daa2a87efe26059d6a142d257588)** (@latticexyz/explorer) + +Transactions in Observe tab are now populated with timing metrics when using the `observer` Viem decorator in local projects. + +You can wire up your local project to use transaction timings with: + +``` +import { observer } from "@latticexyz/explorer/observer"; + +// Extend the Viem client that is performing writes +walletClient.extend(observer()); +``` + +**[fix(faucet,store-indexer): add bin wrappers (#3296)](https://github.com/latticexyz/mud/commit/20f44fbf733ff876d64a544c68a3cb1a4dc307a9)** (@latticexyz/faucet, @latticexyz/store-indexer) + +Added bin wrappers to resolve issues when installing the package locally as a dependency of another package. + +**[feat(explorer): show ABI errors in interact page (#3303)](https://github.com/latticexyz/mud/commit/d4c10c18ad853bed21c55fe92e2ba09c2382316d)** (@latticexyz/explorer) + +Interact tab now displays decoded ABI errors for failed transactions. + +**[chore: bump viem (#3273)](https://github.com/latticexyz/mud/commit/ea18f270c9a43dbe489b25f11b8379ccd969c02a)** (@latticexyz/block-logs-stream, @latticexyz/cli, @latticexyz/common, @latticexyz/config, @latticexyz/dev-tools, @latticexyz/explorer, @latticexyz/faucet, @latticexyz/protocol-parser, @latticexyz/schema-type, @latticexyz/stash, @latticexyz/store-indexer, @latticexyz/store-sync, @latticexyz/store, @latticexyz/world, create-mud) + +Bumped viem to v2.21.19. + +MUD projects using these packages should do the same to ensure no type errors due to mismatched versions: + +``` +pnpm recursive up viem@2.21.19 +``` + +**[fix(explorer): display nested inputs (#3266)](https://github.com/latticexyz/mud/commit/2c9240111ae11e6727d3581453fba2b866f4b4a0)** (@latticexyz/explorer) + +Fixed inputs display in the transactions table row. + +**[feat(common): add rhodolite chain (#3295)](https://github.com/latticexyz/mud/commit/41a6e2f83ac4d48a9dccf52d933c15074b9a724e)** (@latticexyz/common) + +Added Rhodolite devnet chain config and removed the old and now-defunct Lattice testnet chain config. + +**[feat(explorer): show explore table error message (#3286)](https://github.com/latticexyz/mud/commit/af725304e133f95b0b0eb827fdf7283e54ac8342)** (@latticexyz/explorer) + +Display error messages for failed queries within the Explore tab's table viewer. + +**[feat(explorer): sql editor (#3276)](https://github.com/latticexyz/mud/commit/3a80bed31b97d439025f68b8e4ded27354e102f1)** (@latticexyz/explorer) + +Explore page now has a full-featured SQL editor with syntax highlighting, autocomplete, and query validation. + +**[feat(explorer): front page (#3255)](https://github.com/latticexyz/mud/commit/6476dec94cf32275631d49c7e8fe8fe5a0708040)** (@latticexyz/explorer) + +Each chain's home page now lets you find and pick a world to explore. + +**[feat(store-sync): add support for watching pending logs (#3287)](https://github.com/latticexyz/mud/commit/84ae33b8af3ebeb90749c6e82250869b15d17ed1)** (@latticexyz/store-sync) + +Added experimental support for syncing state from pending logs. + +**[fix(explorer): various fixes (#3299)](https://github.com/latticexyz/mud/commit/9a43e87db302ec599fd1e97d8b77e2e68831017f)** (@latticexyz/explorer) + +- Not found page if invalid chain name. +- Only show selector for worlds if options exist. +- Remove "future time" from transactions table. +- Improved layout for Interact tab. +- Wrap long args in transactions table. +- New tables polling. +- Add logs (regression). + +**[fix(common): allow overriding fees in writeContract and sendTransaction (#3288)](https://github.com/latticexyz/mud/commit/fe98442d7ee82f0d41ba10f05a4ee1bafea69d48)** (@latticexyz/common) + +The `transactionQueue` decorator internally keeps an updated reference for the recommended `baseFeePerGas` and `maxPriorityFeePerGas` from the connected chain to avoid having to fetch it right before sending a transaction. +However, due to the way the fee values were overridden, it wasn't possible for users to explicitly pass in custom fee values. +Now explicitly provided fee values have precedence over the internally estimated fee values. + +**[feat(explorer): global transactions listener (#3285)](https://github.com/latticexyz/mud/commit/4b4640913d014fb3a0a5a417b84c91b247e08ffc)** (@latticexyz/explorer) + +Transactions are now monitored across all tabs while the World Explorer is open. + +--- + ## Version 2.2.11 Release date: Mon Oct 07 2024 diff --git a/docs/pages/changelog.mdx b/docs/pages/changelog.mdx index 104d7a5ede..2bfb9fad8a 100644 --- a/docs/pages/changelog.mdx +++ b/docs/pages/changelog.mdx @@ -1,3 +1,90 @@ +## Version 2.2.12 + +Release date: Fri Oct 18 2024 + +### Patch changes + +**[feat(explorer): add functions filter to query state (#3268)](https://github.com/latticexyz/mud/commit/3d8db6f76f3634d532d39cf4091f22fee0a32b68)** (@latticexyz/explorer) + +Function filters in `Interact` tab are now included as part of the URL. + +**[feat(explorer): transaction timings (#3274)](https://github.com/latticexyz/mud/commit/1b0ffcf7a1a7daa2a87efe26059d6a142d257588)** (@latticexyz/explorer) + +Transactions in Observe tab are now populated with timing metrics when using the `observer` Viem decorator in local projects. + +You can wire up your local project to use transaction timings with: + +``` +import { observer } from "@latticexyz/explorer/observer"; + +// Extend the Viem client that is performing writes +walletClient.extend(observer()); +``` + +**[fix(faucet,store-indexer): add bin wrappers (#3296)](https://github.com/latticexyz/mud/commit/20f44fbf733ff876d64a544c68a3cb1a4dc307a9)** (@latticexyz/faucet, @latticexyz/store-indexer) + +Added bin wrappers to resolve issues when installing the package locally as a dependency of another package. + +**[feat(explorer): show ABI errors in interact page (#3303)](https://github.com/latticexyz/mud/commit/d4c10c18ad853bed21c55fe92e2ba09c2382316d)** (@latticexyz/explorer) + +Interact tab now displays decoded ABI errors for failed transactions. + +**[chore: bump viem (#3273)](https://github.com/latticexyz/mud/commit/ea18f270c9a43dbe489b25f11b8379ccd969c02a)** (@latticexyz/block-logs-stream, @latticexyz/cli, @latticexyz/common, @latticexyz/config, @latticexyz/dev-tools, @latticexyz/explorer, @latticexyz/faucet, @latticexyz/protocol-parser, @latticexyz/schema-type, @latticexyz/stash, @latticexyz/store-indexer, @latticexyz/store-sync, @latticexyz/store, @latticexyz/world, create-mud) + +Bumped viem to v2.21.19. + +MUD projects using these packages should do the same to ensure no type errors due to mismatched versions: + +``` +pnpm recursive up viem@2.21.19 +``` + +**[fix(explorer): display nested inputs (#3266)](https://github.com/latticexyz/mud/commit/2c9240111ae11e6727d3581453fba2b866f4b4a0)** (@latticexyz/explorer) + +Fixed inputs display in the transactions table row. + +**[feat(common): add rhodolite chain (#3295)](https://github.com/latticexyz/mud/commit/41a6e2f83ac4d48a9dccf52d933c15074b9a724e)** (@latticexyz/common) + +Added Rhodolite devnet chain config and removed the old and now-defunct Lattice testnet chain config. + +**[feat(explorer): show explore table error message (#3286)](https://github.com/latticexyz/mud/commit/af725304e133f95b0b0eb827fdf7283e54ac8342)** (@latticexyz/explorer) + +Display error messages for failed queries within the Explore tab's table viewer. + +**[feat(explorer): sql editor (#3276)](https://github.com/latticexyz/mud/commit/3a80bed31b97d439025f68b8e4ded27354e102f1)** (@latticexyz/explorer) + +Explore page now has a full-featured SQL editor with syntax highlighting, autocomplete, and query validation. + +**[feat(explorer): front page (#3255)](https://github.com/latticexyz/mud/commit/6476dec94cf32275631d49c7e8fe8fe5a0708040)** (@latticexyz/explorer) + +Each chain's home page now lets you find and pick a world to explore. + +**[feat(store-sync): add support for watching pending logs (#3287)](https://github.com/latticexyz/mud/commit/84ae33b8af3ebeb90749c6e82250869b15d17ed1)** (@latticexyz/store-sync) + +Added experimental support for syncing state from pending logs. + +**[fix(explorer): various fixes (#3299)](https://github.com/latticexyz/mud/commit/9a43e87db302ec599fd1e97d8b77e2e68831017f)** (@latticexyz/explorer) + +- Not found page if invalid chain name. +- Only show selector for worlds if options exist. +- Remove "future time" from transactions table. +- Improved layout for Interact tab. +- Wrap long args in transactions table. +- New tables polling. +- Add logs (regression). + +**[fix(common): allow overriding fees in writeContract and sendTransaction (#3288)](https://github.com/latticexyz/mud/commit/fe98442d7ee82f0d41ba10f05a4ee1bafea69d48)** (@latticexyz/common) + +The `transactionQueue` decorator internally keeps an updated reference for the recommended `baseFeePerGas` and `maxPriorityFeePerGas` from the connected chain to avoid having to fetch it right before sending a transaction. +However, due to the way the fee values were overridden, it wasn't possible for users to explicitly pass in custom fee values. +Now explicitly provided fee values have precedence over the internally estimated fee values. + +**[feat(explorer): global transactions listener (#3285)](https://github.com/latticexyz/mud/commit/4b4640913d014fb3a0a5a417b84c91b247e08ffc)** (@latticexyz/explorer) + +Transactions are now monitored across all tabs while the World Explorer is open. + +--- + ## Version 2.2.11 Release date: Mon Oct 07 2024 diff --git a/packages/abi-ts/CHANGELOG.md b/packages/abi-ts/CHANGELOG.md index 30252769cc..4287e12e63 100644 --- a/packages/abi-ts/CHANGELOG.md +++ b/packages/abi-ts/CHANGELOG.md @@ -1,5 +1,7 @@ # @latticexyz/abi-ts +## 2.2.12 + ## 2.2.11 ## 2.2.10 diff --git a/packages/abi-ts/package.json b/packages/abi-ts/package.json index e30fddca22..581cfeaca8 100644 --- a/packages/abi-ts/package.json +++ b/packages/abi-ts/package.json @@ -1,6 +1,6 @@ { "name": "@latticexyz/abi-ts", - "version": "2.2.11", + "version": "2.2.12", "description": "Create TypeScript type declaration files (`.d.ts`) for your ABI JSON files.", "repository": { "type": "git", diff --git a/packages/block-logs-stream/CHANGELOG.md b/packages/block-logs-stream/CHANGELOG.md index bf48d6d4a6..accaea6289 100644 --- a/packages/block-logs-stream/CHANGELOG.md +++ b/packages/block-logs-stream/CHANGELOG.md @@ -1,5 +1,22 @@ # @latticexyz/block-logs-stream +## 2.2.12 + +### Patch Changes + +- ea18f27: Bumped viem to v2.21.19. + + MUD projects using these packages should do the same to ensure no type errors due to mismatched versions: + + ``` + pnpm recursive up viem@2.21.19 + ``` + +- Updated dependencies [ea18f27] +- Updated dependencies [41a6e2f] +- Updated dependencies [fe98442] + - @latticexyz/common@2.2.12 + ## 2.2.11 ### Patch Changes diff --git a/packages/block-logs-stream/package.json b/packages/block-logs-stream/package.json index bc21d60c87..13bcd3eff5 100644 --- a/packages/block-logs-stream/package.json +++ b/packages/block-logs-stream/package.json @@ -1,6 +1,6 @@ { "name": "@latticexyz/block-logs-stream", - "version": "2.2.11", + "version": "2.2.12", "description": "Create a stream of EVM block logs for events", "repository": { "type": "git", diff --git a/packages/cli/CHANGELOG.md b/packages/cli/CHANGELOG.md index 101f6cc1bf..d6bbeaff41 100644 --- a/packages/cli/CHANGELOG.md +++ b/packages/cli/CHANGELOG.md @@ -1,5 +1,32 @@ # Change Log +## 2.2.12 + +### Patch Changes + +- ea18f27: Bumped viem to v2.21.19. + + MUD projects using these packages should do the same to ensure no type errors due to mismatched versions: + + ``` + pnpm recursive up viem@2.21.19 + ``` + +- Updated dependencies [ea18f27] +- Updated dependencies [41a6e2f] +- Updated dependencies [fe98442] + - @latticexyz/block-logs-stream@2.2.12 + - @latticexyz/common@2.2.12 + - @latticexyz/config@2.2.12 + - @latticexyz/protocol-parser@2.2.12 + - @latticexyz/schema-type@2.2.12 + - @latticexyz/store@2.2.12 + - @latticexyz/world@2.2.12 + - @latticexyz/world-module-metadata@2.2.12 + - @latticexyz/abi-ts@2.2.12 + - @latticexyz/gas-report@2.2.12 + - @latticexyz/utils@2.2.12 + ## 2.2.11 ### Patch Changes diff --git a/packages/cli/package.json b/packages/cli/package.json index c611a727ab..2a1bde5cd7 100644 --- a/packages/cli/package.json +++ b/packages/cli/package.json @@ -1,6 +1,6 @@ { "name": "@latticexyz/cli", - "version": "2.2.11", + "version": "2.2.12", "description": "Command line interface for mud", "repository": { "type": "git", diff --git a/packages/common/CHANGELOG.md b/packages/common/CHANGELOG.md index c6ae72375a..364ba1289d 100644 --- a/packages/common/CHANGELOG.md +++ b/packages/common/CHANGELOG.md @@ -1,5 +1,24 @@ # Change Log +## 2.2.12 + +### Patch Changes + +- ea18f27: Bumped viem to v2.21.19. + + MUD projects using these packages should do the same to ensure no type errors due to mismatched versions: + + ``` + pnpm recursive up viem@2.21.19 + ``` + +- 41a6e2f: Added Rhodolite devnet chain config and removed the old and now-defunct Lattice testnet chain config. +- fe98442: The `transactionQueue` decorator internally keeps an updated reference for the recommended `baseFeePerGas` and `maxPriorityFeePerGas` from the connected chain to avoid having to fetch it right before sending a transaction. + However, due to the way the fee values were overridden, it wasn't possible for users to explicitly pass in custom fee values. + Now explicitly provided fee values have precedence over the internally estimated fee values. +- Updated dependencies [ea18f27] + - @latticexyz/schema-type@2.2.12 + ## 2.2.11 ### Patch Changes diff --git a/packages/common/package.json b/packages/common/package.json index 7b6bbb4627..22814f9723 100644 --- a/packages/common/package.json +++ b/packages/common/package.json @@ -1,6 +1,6 @@ { "name": "@latticexyz/common", - "version": "2.2.11", + "version": "2.2.12", "description": "Common low level logic shared between packages", "repository": { "type": "git", diff --git a/packages/config/CHANGELOG.md b/packages/config/CHANGELOG.md index 96e26708b7..c4a8fe42c0 100644 --- a/packages/config/CHANGELOG.md +++ b/packages/config/CHANGELOG.md @@ -1,5 +1,23 @@ # Change Log +## 2.2.12 + +### Patch Changes + +- ea18f27: Bumped viem to v2.21.19. + + MUD projects using these packages should do the same to ensure no type errors due to mismatched versions: + + ``` + pnpm recursive up viem@2.21.19 + ``` + +- Updated dependencies [ea18f27] +- Updated dependencies [41a6e2f] +- Updated dependencies [fe98442] + - @latticexyz/common@2.2.12 + - @latticexyz/schema-type@2.2.12 + ## 2.2.11 ### Patch Changes diff --git a/packages/config/package.json b/packages/config/package.json index 9ac3bdb07e..327ed8f54a 100644 --- a/packages/config/package.json +++ b/packages/config/package.json @@ -1,6 +1,6 @@ { "name": "@latticexyz/config", - "version": "2.2.11", + "version": "2.2.12", "description": "Config for Store and World", "repository": { "type": "git", diff --git a/packages/create-mud/CHANGELOG.md b/packages/create-mud/CHANGELOG.md index 125bc23959..a6c38e2eeb 100644 --- a/packages/create-mud/CHANGELOG.md +++ b/packages/create-mud/CHANGELOG.md @@ -1,5 +1,17 @@ # Change Log +## 2.2.12 + +### Patch Changes + +- ea18f27: Bumped viem to v2.21.19. + + MUD projects using these packages should do the same to ensure no type errors due to mismatched versions: + + ``` + pnpm recursive up viem@2.21.19 + ``` + ## 2.2.11 ## 2.2.10 diff --git a/packages/create-mud/package.json b/packages/create-mud/package.json index a170ce0629..188ea5f9e8 100644 --- a/packages/create-mud/package.json +++ b/packages/create-mud/package.json @@ -1,6 +1,6 @@ { "name": "create-mud", - "version": "2.2.11", + "version": "2.2.12", "description": "Create a new MUD project", "license": "MIT", "author": "Lattice ", diff --git a/packages/dev-tools/CHANGELOG.md b/packages/dev-tools/CHANGELOG.md index 8018029536..a75f65245b 100644 --- a/packages/dev-tools/CHANGELOG.md +++ b/packages/dev-tools/CHANGELOG.md @@ -1,5 +1,30 @@ # @latticexyz/dev-tools +## 2.2.12 + +### Patch Changes + +- ea18f27: Bumped viem to v2.21.19. + + MUD projects using these packages should do the same to ensure no type errors due to mismatched versions: + + ``` + pnpm recursive up viem@2.21.19 + ``` + +- Updated dependencies [ea18f27] +- Updated dependencies [41a6e2f] +- Updated dependencies [84ae33b] +- Updated dependencies [fe98442] + - @latticexyz/common@2.2.12 + - @latticexyz/schema-type@2.2.12 + - @latticexyz/store-sync@2.2.12 + - @latticexyz/store@2.2.12 + - @latticexyz/world@2.2.12 + - @latticexyz/recs@2.2.12 + - @latticexyz/react@2.2.12 + - @latticexyz/utils@2.2.12 + ## 2.2.11 ### Patch Changes diff --git a/packages/dev-tools/package.json b/packages/dev-tools/package.json index a1c58d58e9..acfe2566bc 100644 --- a/packages/dev-tools/package.json +++ b/packages/dev-tools/package.json @@ -1,6 +1,6 @@ { "name": "@latticexyz/dev-tools", - "version": "2.2.11", + "version": "2.2.12", "description": "MUD developer tools", "repository": { "type": "git", diff --git a/packages/explorer/CHANGELOG.md b/packages/explorer/CHANGELOG.md index 3d5f7218a5..5e4b00a387 100644 --- a/packages/explorer/CHANGELOG.md +++ b/packages/explorer/CHANGELOG.md @@ -1,5 +1,56 @@ # @latticexyz/explorer +## 2.2.12 + +### Patch Changes + +- 3d8db6f: Function filters in `Interact` tab are now included as part of the URL. +- 1b0ffcf: Transactions in Observe tab are now populated with timing metrics when using the `observer` Viem decorator in local projects. + + You can wire up your local project to use transaction timings with: + + ``` + import { observer } from "@latticexyz/explorer/observer"; + + // Extend the Viem client that is performing writes + walletClient.extend(observer()); + ``` + +- d4c10c1: Interact tab now displays decoded ABI errors for failed transactions. +- ea18f27: Bumped viem to v2.21.19. + + MUD projects using these packages should do the same to ensure no type errors due to mismatched versions: + + ``` + pnpm recursive up viem@2.21.19 + ``` + +- 2c92401: Fixed inputs display in the transactions table row. +- af72530: Display error messages for failed queries within the Explore tab's table viewer. +- 3a80bed: Explore page now has a full-featured SQL editor with syntax highlighting, autocomplete, and query validation. +- 6476dec: Each chain's home page now lets you find and pick a world to explore. +- 9a43e87: - Not found page if invalid chain name. + - Only show selector for worlds if options exist. + - Remove "future time" from transactions table. + - Improved layout for Interact tab. + - Wrap long args in transactions table. + - New tables polling. + - Add logs (regression). +- 4b46409: Transactions are now monitored across all tabs while the World Explorer is open. +- Updated dependencies [20f44fb] +- Updated dependencies [ea18f27] +- Updated dependencies [41a6e2f] +- Updated dependencies [84ae33b] +- Updated dependencies [fe98442] + - @latticexyz/store-indexer@2.2.12 + - @latticexyz/common@2.2.12 + - @latticexyz/config@2.2.12 + - @latticexyz/protocol-parser@2.2.12 + - @latticexyz/schema-type@2.2.12 + - @latticexyz/store-sync@2.2.12 + - @latticexyz/store@2.2.12 + - @latticexyz/world@2.2.12 + ## 2.2.11 ### Patch Changes diff --git a/packages/explorer/package.json b/packages/explorer/package.json index 7198ccf340..a93d323e37 100644 --- a/packages/explorer/package.json +++ b/packages/explorer/package.json @@ -1,6 +1,6 @@ { "name": "@latticexyz/explorer", - "version": "2.2.11", + "version": "2.2.12", "description": "World Explorer is a tool for visually exploring and manipulating the state of worlds", "type": "module", "exports": { diff --git a/packages/faucet/CHANGELOG.md b/packages/faucet/CHANGELOG.md index db007bab2c..d800a5bc42 100644 --- a/packages/faucet/CHANGELOG.md +++ b/packages/faucet/CHANGELOG.md @@ -1,5 +1,23 @@ # @latticexyz/faucet +## 2.2.12 + +### Patch Changes + +- 20f44fb: Added bin wrappers to resolve issues when installing the package locally as a dependency of another package. +- ea18f27: Bumped viem to v2.21.19. + + MUD projects using these packages should do the same to ensure no type errors due to mismatched versions: + + ``` + pnpm recursive up viem@2.21.19 + ``` + +- Updated dependencies [ea18f27] +- Updated dependencies [41a6e2f] +- Updated dependencies [fe98442] + - @latticexyz/common@2.2.12 + ## 2.2.11 ### Patch Changes diff --git a/packages/faucet/package.json b/packages/faucet/package.json index 4221250252..2661c1326e 100644 --- a/packages/faucet/package.json +++ b/packages/faucet/package.json @@ -1,6 +1,6 @@ { "name": "@latticexyz/faucet", - "version": "2.2.11", + "version": "2.2.12", "description": "Faucet API for Lattice testnet", "repository": { "type": "git", diff --git a/packages/gas-report/CHANGELOG.md b/packages/gas-report/CHANGELOG.md index df75c50009..1aba762a67 100644 --- a/packages/gas-report/CHANGELOG.md +++ b/packages/gas-report/CHANGELOG.md @@ -1,5 +1,7 @@ # Change Log +## 2.2.12 + ## 2.2.11 ## 2.2.10 diff --git a/packages/gas-report/package.json b/packages/gas-report/package.json index 4fb61f7372..10e102c374 100644 --- a/packages/gas-report/package.json +++ b/packages/gas-report/package.json @@ -1,6 +1,6 @@ { "name": "@latticexyz/gas-report", - "version": "2.2.11", + "version": "2.2.12", "description": "Gas reporter for specific lines within forge tests", "repository": { "type": "git", diff --git a/packages/protocol-parser/CHANGELOG.md b/packages/protocol-parser/CHANGELOG.md index c24e43b1dc..80d195bc5d 100644 --- a/packages/protocol-parser/CHANGELOG.md +++ b/packages/protocol-parser/CHANGELOG.md @@ -1,5 +1,24 @@ # @latticexyz/protocol-parser +## 2.2.12 + +### Patch Changes + +- ea18f27: Bumped viem to v2.21.19. + + MUD projects using these packages should do the same to ensure no type errors due to mismatched versions: + + ``` + pnpm recursive up viem@2.21.19 + ``` + +- Updated dependencies [ea18f27] +- Updated dependencies [41a6e2f] +- Updated dependencies [fe98442] + - @latticexyz/common@2.2.12 + - @latticexyz/config@2.2.12 + - @latticexyz/schema-type@2.2.12 + ## 2.2.11 ### Patch Changes diff --git a/packages/protocol-parser/package.json b/packages/protocol-parser/package.json index 930c033d76..2f6bd0c543 100644 --- a/packages/protocol-parser/package.json +++ b/packages/protocol-parser/package.json @@ -1,6 +1,6 @@ { "name": "@latticexyz/protocol-parser", - "version": "2.2.11", + "version": "2.2.12", "description": "Parser utilities for the MUD protocol", "repository": { "type": "git", diff --git a/packages/react/CHANGELOG.md b/packages/react/CHANGELOG.md index d5b77cf50f..9f353f00a6 100644 --- a/packages/react/CHANGELOG.md +++ b/packages/react/CHANGELOG.md @@ -1,5 +1,13 @@ # Change Log +## 2.2.12 + +### Patch Changes + +- Updated dependencies [ea18f27] + - @latticexyz/store@2.2.12 + - @latticexyz/recs@2.2.12 + ## 2.2.11 ### Patch Changes diff --git a/packages/react/package.json b/packages/react/package.json index 3376c0ac95..ec08dafd56 100644 --- a/packages/react/package.json +++ b/packages/react/package.json @@ -1,6 +1,6 @@ { "name": "@latticexyz/react", - "version": "2.2.11", + "version": "2.2.12", "description": "React tools for MUD client.", "repository": { "type": "git", diff --git a/packages/recs/CHANGELOG.md b/packages/recs/CHANGELOG.md index 844895b130..8d9ffe71ec 100644 --- a/packages/recs/CHANGELOG.md +++ b/packages/recs/CHANGELOG.md @@ -1,5 +1,13 @@ # Change Log +## 2.2.12 + +### Patch Changes + +- Updated dependencies [ea18f27] + - @latticexyz/schema-type@2.2.12 + - @latticexyz/utils@2.2.12 + ## 2.2.11 ### Patch Changes diff --git a/packages/recs/package.json b/packages/recs/package.json index b97909bacd..478cfad424 100644 --- a/packages/recs/package.json +++ b/packages/recs/package.json @@ -1,6 +1,6 @@ { "name": "@latticexyz/recs", - "version": "2.2.11", + "version": "2.2.12", "repository": { "type": "git", "url": "https://github.com/latticexyz/mud.git", diff --git a/packages/schema-type/CHANGELOG.md b/packages/schema-type/CHANGELOG.md index 09f2dbe5e7..5e328c1433 100644 --- a/packages/schema-type/CHANGELOG.md +++ b/packages/schema-type/CHANGELOG.md @@ -1,5 +1,17 @@ # Change Log +## 2.2.12 + +### Patch Changes + +- ea18f27: Bumped viem to v2.21.19. + + MUD projects using these packages should do the same to ensure no type errors due to mismatched versions: + + ``` + pnpm recursive up viem@2.21.19 + ``` + ## 2.2.11 ## 2.2.10 diff --git a/packages/schema-type/package.json b/packages/schema-type/package.json index d69bc86d50..c7556bade5 100644 --- a/packages/schema-type/package.json +++ b/packages/schema-type/package.json @@ -1,6 +1,6 @@ { "name": "@latticexyz/schema-type", - "version": "2.2.11", + "version": "2.2.12", "description": "SchemaType enum for various languages", "repository": { "type": "git", diff --git a/packages/solhint-config-mud/CHANGELOG.md b/packages/solhint-config-mud/CHANGELOG.md index eead18e1aa..a67c171c2f 100644 --- a/packages/solhint-config-mud/CHANGELOG.md +++ b/packages/solhint-config-mud/CHANGELOG.md @@ -1,5 +1,7 @@ # Change Log +## 2.2.12 + ## 2.2.11 ## 2.2.10 diff --git a/packages/solhint-config-mud/package.json b/packages/solhint-config-mud/package.json index 19b9861dca..6fab74326f 100644 --- a/packages/solhint-config-mud/package.json +++ b/packages/solhint-config-mud/package.json @@ -1,6 +1,6 @@ { "name": "solhint-config-mud", - "version": "2.2.11", + "version": "2.2.12", "repository": { "type": "git", "url": "https://github.com/latticexyz/mud.git", diff --git a/packages/solhint-plugin-mud/CHANGELOG.md b/packages/solhint-plugin-mud/CHANGELOG.md index eead18e1aa..a67c171c2f 100644 --- a/packages/solhint-plugin-mud/CHANGELOG.md +++ b/packages/solhint-plugin-mud/CHANGELOG.md @@ -1,5 +1,7 @@ # Change Log +## 2.2.12 + ## 2.2.11 ## 2.2.10 diff --git a/packages/solhint-plugin-mud/package.json b/packages/solhint-plugin-mud/package.json index cfc9e4c9f0..efa8138373 100644 --- a/packages/solhint-plugin-mud/package.json +++ b/packages/solhint-plugin-mud/package.json @@ -1,6 +1,6 @@ { "name": "solhint-plugin-mud", - "version": "2.2.11", + "version": "2.2.12", "repository": { "type": "git", "url": "https://github.com/latticexyz/mud.git", diff --git a/packages/stash/CHANGELOG.md b/packages/stash/CHANGELOG.md index 7b87eb5342..0f2912790a 100644 --- a/packages/stash/CHANGELOG.md +++ b/packages/stash/CHANGELOG.md @@ -1,5 +1,23 @@ # @latticexyz/stash +## 2.2.12 + +### Patch Changes + +- ea18f27: Bumped viem to v2.21.19. + + MUD projects using these packages should do the same to ensure no type errors due to mismatched versions: + + ``` + pnpm recursive up viem@2.21.19 + ``` + +- Updated dependencies [ea18f27] + - @latticexyz/config@2.2.12 + - @latticexyz/protocol-parser@2.2.12 + - @latticexyz/schema-type@2.2.12 + - @latticexyz/store@2.2.12 + ## 2.2.11 ### Patch Changes diff --git a/packages/stash/package.json b/packages/stash/package.json index 393e6bdbb2..559e24c77a 100644 --- a/packages/stash/package.json +++ b/packages/stash/package.json @@ -1,6 +1,6 @@ { "name": "@latticexyz/stash", - "version": "2.2.11", + "version": "2.2.12", "description": "High performance client store and query engine for MUD", "repository": { "type": "git", diff --git a/packages/store-indexer/CHANGELOG.md b/packages/store-indexer/CHANGELOG.md index 1aa2a78dfa..b4eaec29ac 100644 --- a/packages/store-indexer/CHANGELOG.md +++ b/packages/store-indexer/CHANGELOG.md @@ -1,5 +1,28 @@ # @latticexyz/store-indexer +## 2.2.12 + +### Patch Changes + +- 20f44fb: Added bin wrappers to resolve issues when installing the package locally as a dependency of another package. +- ea18f27: Bumped viem to v2.21.19. + + MUD projects using these packages should do the same to ensure no type errors due to mismatched versions: + + ``` + pnpm recursive up viem@2.21.19 + ``` + +- Updated dependencies [ea18f27] +- Updated dependencies [41a6e2f] +- Updated dependencies [84ae33b] +- Updated dependencies [fe98442] + - @latticexyz/block-logs-stream@2.2.12 + - @latticexyz/common@2.2.12 + - @latticexyz/protocol-parser@2.2.12 + - @latticexyz/store-sync@2.2.12 + - @latticexyz/store@2.2.12 + ## 2.2.11 ### Patch Changes diff --git a/packages/store-indexer/package.json b/packages/store-indexer/package.json index 26e5dc2e02..5b257e965c 100644 --- a/packages/store-indexer/package.json +++ b/packages/store-indexer/package.json @@ -1,6 +1,6 @@ { "name": "@latticexyz/store-indexer", - "version": "2.2.11", + "version": "2.2.12", "description": "Minimal Typescript indexer for Store", "repository": { "type": "git", diff --git a/packages/store-sync/CHANGELOG.md b/packages/store-sync/CHANGELOG.md index 01a84584e4..f2e31bf724 100644 --- a/packages/store-sync/CHANGELOG.md +++ b/packages/store-sync/CHANGELOG.md @@ -1,5 +1,31 @@ # @latticexyz/store-sync +## 2.2.12 + +### Patch Changes + +- ea18f27: Bumped viem to v2.21.19. + + MUD projects using these packages should do the same to ensure no type errors due to mismatched versions: + + ``` + pnpm recursive up viem@2.21.19 + ``` + +- 84ae33b: Added experimental support for syncing state from pending logs. +- Updated dependencies [ea18f27] +- Updated dependencies [41a6e2f] +- Updated dependencies [fe98442] + - @latticexyz/block-logs-stream@2.2.12 + - @latticexyz/common@2.2.12 + - @latticexyz/config@2.2.12 + - @latticexyz/protocol-parser@2.2.12 + - @latticexyz/schema-type@2.2.12 + - @latticexyz/stash@2.2.12 + - @latticexyz/store@2.2.12 + - @latticexyz/world@2.2.12 + - @latticexyz/recs@2.2.12 + ## 2.2.11 ### Patch Changes diff --git a/packages/store-sync/package.json b/packages/store-sync/package.json index a5db979529..081a4879a3 100644 --- a/packages/store-sync/package.json +++ b/packages/store-sync/package.json @@ -1,6 +1,6 @@ { "name": "@latticexyz/store-sync", - "version": "2.2.11", + "version": "2.2.12", "description": "Utilities to sync MUD Store events with a client or cache", "repository": { "type": "git", diff --git a/packages/store/CHANGELOG.md b/packages/store/CHANGELOG.md index adeca222cf..547ce532c0 100644 --- a/packages/store/CHANGELOG.md +++ b/packages/store/CHANGELOG.md @@ -1,5 +1,25 @@ # Change Log +## 2.2.12 + +### Patch Changes + +- ea18f27: Bumped viem to v2.21.19. + + MUD projects using these packages should do the same to ensure no type errors due to mismatched versions: + + ``` + pnpm recursive up viem@2.21.19 + ``` + +- Updated dependencies [ea18f27] +- Updated dependencies [41a6e2f] +- Updated dependencies [fe98442] + - @latticexyz/common@2.2.12 + - @latticexyz/config@2.2.12 + - @latticexyz/protocol-parser@2.2.12 + - @latticexyz/schema-type@2.2.12 + ## 2.2.11 ### Patch Changes diff --git a/packages/store/package.json b/packages/store/package.json index bf487ecd3a..e02b62f5bd 100644 --- a/packages/store/package.json +++ b/packages/store/package.json @@ -1,6 +1,6 @@ { "name": "@latticexyz/store", - "version": "2.2.11", + "version": "2.2.12", "description": "Store", "repository": { "type": "git", diff --git a/packages/utils/CHANGELOG.md b/packages/utils/CHANGELOG.md index 3ef34b9e3f..6b1b58a95e 100644 --- a/packages/utils/CHANGELOG.md +++ b/packages/utils/CHANGELOG.md @@ -1,5 +1,7 @@ # Change Log +## 2.2.12 + ## 2.2.11 ## 2.2.10 diff --git a/packages/utils/package.json b/packages/utils/package.json index 612d220a3a..b8ff2bcde4 100644 --- a/packages/utils/package.json +++ b/packages/utils/package.json @@ -1,6 +1,6 @@ { "name": "@latticexyz/utils", - "version": "2.2.11", + "version": "2.2.12", "repository": { "type": "git", "url": "https://github.com/latticexyz/mud.git", diff --git a/packages/world-module-metadata/CHANGELOG.md b/packages/world-module-metadata/CHANGELOG.md index cc66c38c34..793614b714 100644 --- a/packages/world-module-metadata/CHANGELOG.md +++ b/packages/world-module-metadata/CHANGELOG.md @@ -1,5 +1,14 @@ # @latticexyz/world-module-metadata +## 2.2.12 + +### Patch Changes + +- Updated dependencies [ea18f27] + - @latticexyz/schema-type@2.2.12 + - @latticexyz/store@2.2.12 + - @latticexyz/world@2.2.12 + ## 2.2.11 ### Patch Changes diff --git a/packages/world-module-metadata/package.json b/packages/world-module-metadata/package.json index e55b2a5dae..1fdcb1d61f 100644 --- a/packages/world-module-metadata/package.json +++ b/packages/world-module-metadata/package.json @@ -1,6 +1,6 @@ { "name": "@latticexyz/world-module-metadata", - "version": "2.2.11", + "version": "2.2.12", "description": "Metadata world module", "repository": { "type": "git", diff --git a/packages/world-modules/CHANGELOG.md b/packages/world-modules/CHANGELOG.md index 247c84ce99..26d2a72f0e 100644 --- a/packages/world-modules/CHANGELOG.md +++ b/packages/world-modules/CHANGELOG.md @@ -1,5 +1,18 @@ # Change Log +## 2.2.12 + +### Patch Changes + +- Updated dependencies [ea18f27] +- Updated dependencies [41a6e2f] +- Updated dependencies [fe98442] + - @latticexyz/common@2.2.12 + - @latticexyz/config@2.2.12 + - @latticexyz/schema-type@2.2.12 + - @latticexyz/store@2.2.12 + - @latticexyz/world@2.2.12 + ## 2.2.11 ### Patch Changes diff --git a/packages/world-modules/package.json b/packages/world-modules/package.json index 523305539f..7869ba119a 100644 --- a/packages/world-modules/package.json +++ b/packages/world-modules/package.json @@ -1,6 +1,6 @@ { "name": "@latticexyz/world-modules", - "version": "2.2.11", + "version": "2.2.12", "description": "World modules", "repository": { "type": "git", diff --git a/packages/world/CHANGELOG.md b/packages/world/CHANGELOG.md index 3ddc1ce470..37d029e0d4 100644 --- a/packages/world/CHANGELOG.md +++ b/packages/world/CHANGELOG.md @@ -1,5 +1,26 @@ # Change Log +## 2.2.12 + +### Patch Changes + +- ea18f27: Bumped viem to v2.21.19. + + MUD projects using these packages should do the same to ensure no type errors due to mismatched versions: + + ``` + pnpm recursive up viem@2.21.19 + ``` + +- Updated dependencies [ea18f27] +- Updated dependencies [41a6e2f] +- Updated dependencies [fe98442] + - @latticexyz/common@2.2.12 + - @latticexyz/config@2.2.12 + - @latticexyz/protocol-parser@2.2.12 + - @latticexyz/schema-type@2.2.12 + - @latticexyz/store@2.2.12 + ## 2.2.11 ### Patch Changes diff --git a/packages/world/package.json b/packages/world/package.json index fc7b948c77..0efeba0b38 100644 --- a/packages/world/package.json +++ b/packages/world/package.json @@ -1,6 +1,6 @@ { "name": "@latticexyz/world", - "version": "2.2.11", + "version": "2.2.12", "description": "World framework", "repository": { "type": "git", From 23e6a6c97d94f85e163378148c51f51c900b3f8a Mon Sep 17 00:00:00 2001 From: V Date: Mon, 21 Oct 2024 17:53:34 -0300 Subject: [PATCH 06/10] feat(world-module-erc20): new ERC20 World Module (#3300) Co-authored-by: alvarius --- .changeset/giant-birds-argue.md | 16 + packages/world-module-erc20/.gitignore | 2 + packages/world-module-erc20/.solhint.json | 8 + packages/world-module-erc20/README.md | 131 ++++ packages/world-module-erc20/foundry.toml | 15 + packages/world-module-erc20/gas-report.json | 224 +++++++ packages/world-module-erc20/mud.config.ts | 79 +++ packages/world-module-erc20/package.json | 67 ++ packages/world-module-erc20/remappings.txt | 3 + packages/world-module-erc20/src/Constants.sol | 36 ++ packages/world-module-erc20/src/Context.sol | 10 + .../world-module-erc20/src/ERC20Burnable.sol | 16 + .../world-module-erc20/src/ERC20Module.sol | 74 +++ .../world-module-erc20/src/ERC20Pausable.sol | 12 + packages/world-module-erc20/src/MUDERC20.sol | 326 ++++++++++ packages/world-module-erc20/src/Ownable.sol | 110 ++++ packages/world-module-erc20/src/Pausable.sol | 125 ++++ .../world-module-erc20/src/StoreConsumer.sol | 24 + packages/world-module-erc20/src/WithStore.sol | 22 + packages/world-module-erc20/src/WithWorld.sol | 40 ++ .../world-module-erc20/src/codegen/index.sol | 12 + .../src/codegen/tables/Allowances.sol | 208 ++++++ .../src/codegen/tables/Balances.sol | 196 ++++++ .../src/codegen/tables/ERC20Metadata.sol | 604 ++++++++++++++++++ .../src/codegen/tables/ERC20Registry.sol | 199 ++++++ .../src/codegen/tables/Owner.sol | 184 ++++++ .../src/codegen/tables/Paused.sol | 196 ++++++ .../src/codegen/tables/TotalSupply.sol | 184 ++++++ .../src/examples/ERC20WithInternalStore.sol | 32 + .../src/examples/ERC20WithWorld.sol | 40 ++ .../src/interfaces/IERC20.sol | 67 ++ .../src/interfaces/IERC20Errors.sol | 49 ++ .../src/interfaces/IERC20Events.sol | 18 + .../src/interfaces/IERC20Metadata.sol | 26 + .../world-module-erc20/test/ERC20BaseTest.sol | 363 +++++++++++ .../test/ERC20Burnable.t.sol | 50 ++ .../world-module-erc20/test/ERC20Module.t.sol | 80 +++ .../test/ERC20Pausable.t.sol | 73 +++ .../test/StoreConsumer.t.sol | 90 +++ .../world-module-erc20/ts/exports/index.ts | 25 + packages/world-module-erc20/tsconfig.json | 7 + packages/world-module-erc20/tsup.config.ts | 14 + pnpm-lock.yaml | 118 +++- 43 files changed, 4142 insertions(+), 33 deletions(-) create mode 100644 .changeset/giant-birds-argue.md create mode 100644 packages/world-module-erc20/.gitignore create mode 100644 packages/world-module-erc20/.solhint.json create mode 100644 packages/world-module-erc20/README.md create mode 100644 packages/world-module-erc20/foundry.toml create mode 100644 packages/world-module-erc20/gas-report.json create mode 100644 packages/world-module-erc20/mud.config.ts create mode 100644 packages/world-module-erc20/package.json create mode 100644 packages/world-module-erc20/remappings.txt create mode 100644 packages/world-module-erc20/src/Constants.sol create mode 100644 packages/world-module-erc20/src/Context.sol create mode 100644 packages/world-module-erc20/src/ERC20Burnable.sol create mode 100644 packages/world-module-erc20/src/ERC20Module.sol create mode 100644 packages/world-module-erc20/src/ERC20Pausable.sol create mode 100644 packages/world-module-erc20/src/MUDERC20.sol create mode 100644 packages/world-module-erc20/src/Ownable.sol create mode 100644 packages/world-module-erc20/src/Pausable.sol create mode 100644 packages/world-module-erc20/src/StoreConsumer.sol create mode 100644 packages/world-module-erc20/src/WithStore.sol create mode 100644 packages/world-module-erc20/src/WithWorld.sol create mode 100644 packages/world-module-erc20/src/codegen/index.sol create mode 100644 packages/world-module-erc20/src/codegen/tables/Allowances.sol create mode 100644 packages/world-module-erc20/src/codegen/tables/Balances.sol create mode 100644 packages/world-module-erc20/src/codegen/tables/ERC20Metadata.sol create mode 100644 packages/world-module-erc20/src/codegen/tables/ERC20Registry.sol create mode 100644 packages/world-module-erc20/src/codegen/tables/Owner.sol create mode 100644 packages/world-module-erc20/src/codegen/tables/Paused.sol create mode 100644 packages/world-module-erc20/src/codegen/tables/TotalSupply.sol create mode 100644 packages/world-module-erc20/src/examples/ERC20WithInternalStore.sol create mode 100644 packages/world-module-erc20/src/examples/ERC20WithWorld.sol create mode 100644 packages/world-module-erc20/src/interfaces/IERC20.sol create mode 100644 packages/world-module-erc20/src/interfaces/IERC20Errors.sol create mode 100644 packages/world-module-erc20/src/interfaces/IERC20Events.sol create mode 100644 packages/world-module-erc20/src/interfaces/IERC20Metadata.sol create mode 100644 packages/world-module-erc20/test/ERC20BaseTest.sol create mode 100644 packages/world-module-erc20/test/ERC20Burnable.t.sol create mode 100644 packages/world-module-erc20/test/ERC20Module.t.sol create mode 100644 packages/world-module-erc20/test/ERC20Pausable.t.sol create mode 100644 packages/world-module-erc20/test/StoreConsumer.t.sol create mode 100644 packages/world-module-erc20/ts/exports/index.ts create mode 100644 packages/world-module-erc20/tsconfig.json create mode 100644 packages/world-module-erc20/tsup.config.ts diff --git a/.changeset/giant-birds-argue.md b/.changeset/giant-birds-argue.md new file mode 100644 index 0000000000..422a6e2193 --- /dev/null +++ b/.changeset/giant-birds-argue.md @@ -0,0 +1,16 @@ +--- +"@latticexyz/world-module-erc20": patch +--- + +The new ERC20 World Module provides a simpler alternative to the ERC20 Puppet Module, while also being structured in a more extendable way so users can create tokens with custom functionality. + +To install this module, you can import and define the module configuration from the NPM package: + +```typescript +import { defineERC20Config } from "@latticexyz/world-module-erc20"; + +// Add the output of this function to your World's modules +const config = defineERC20Config({ namespace: "erc20Namespace", name: "MyToken", symbol: "MTK" }); +``` + +For detailed installation instructions, please check out the [`@latticexyz/world-module-erc20` README.md](https://github.com/latticexyz/mud/blob/main/packages/world-module-erc20/README.md). diff --git a/packages/world-module-erc20/.gitignore b/packages/world-module-erc20/.gitignore new file mode 100644 index 0000000000..1e4ded714a --- /dev/null +++ b/packages/world-module-erc20/.gitignore @@ -0,0 +1,2 @@ +cache +out diff --git a/packages/world-module-erc20/.solhint.json b/packages/world-module-erc20/.solhint.json new file mode 100644 index 0000000000..4e2baa8be7 --- /dev/null +++ b/packages/world-module-erc20/.solhint.json @@ -0,0 +1,8 @@ +{ + "extends": "solhint:recommended", + "rules": { + "compiler-version": ["error", ">=0.8.0"], + "avoid-low-level-calls": "off", + "func-visibility": ["warn", { "ignoreConstructors": true }] + } +} diff --git a/packages/world-module-erc20/README.md b/packages/world-module-erc20/README.md new file mode 100644 index 0000000000..376b9d5334 --- /dev/null +++ b/packages/world-module-erc20/README.md @@ -0,0 +1,131 @@ +# ERC20 World Module + +> :warning: **Important note: this module has not been audited yet, so any production use is discouraged for now.** + +## ERC20 contracts + +In order to achieve a similar level of composability to [`OpenZeppelin` ERC20 contract extensions](https://docs.openzeppelin.com/contracts/5.x/api/token/erc20), we provide a way to abstract the underlying Store being used. This allows developers to easily create ERC20 tokens that can either use its own storage as the Store, or attach themselves to an existing World. + +- `StoreConsumer`: all contracts inherit from `StoreConsumer`, which abstracts the way in which `ResourceId`s are encoded. This allows us to have composable contracts whose implementations don't depend on the type of Store being used. +- `WithStore(address) is StoreConsumer`: this contract initializes the store, using the contract's internal storage or the provided external `Store`. It encodes `ResourceId`s using `ResourceIdLib` from the `@latticexyz/store` package. +- `WithWorld(IBaseWorld, bytes14) is WithStore`: initializes the store and also registers the provided namespace in the provided World. It encodes `ResourceId`s using `WorldResourceIdLib` (using the namespace). It also provides an `onlyNamespace` modifier, which can be used to restrict access to certain functions, only allowing calls from addresses that have access to the namespace. + +- `MUDERC20`: base ERC20 implementation adapted from Openzeppelin's ERC20. Contains the ERC20 logic, reads/writes to the store through MUD's codegen libraries and initializes the tables it needs. As these libraries use `StoreSwitch` internally, this contract doesn't need to know about the store it's interacting with (it can be internal storage, an external `Store` or a `World`). + +- Extensions and other contracts: contracts like `Ownable`, `Pausable`, `ERC20Burnable`, etc are adapted from `OpenZeppelin` contracts to use MUD's codegen libraries to read and write from a `Store`. They inherit from `StoreConsumer`, so they can obtain the `ResourceId` for the tables they use using `_encodeResourceId()`. + +### Example 1: Using the contract's storage + +By using `WithStore(address(this))` as the first contract that the implementation inherits from, it allows all the other contracts in the inheritance list to use the contract's storage as a `Store`. + +```solidity +contract ERC20WithInternalStore is WithStore(address(this)), MUDERC20, ERC20Pausable, ERC20Burnable, Ownable { + constructor() MUDERC20("MyERC20", "MTK") Ownable(_msgSender()) {} + + function mint() public onlyOwner { + _mint(to, value); + } + + function pause() public onlyOwner { + _pause(); + } + + function unpause() public onlyOwner { + _unpause(); + } + + // The following functions are overrides required by Solidity. + + function _update(address from, address to, uint256 value) internal override(MUDERC20, ERC20Pausable) { + super._update(from, to, value); + } +} +``` + +### Example 2: Using a World as an external Store and registering a new Namespace + +The `WithWorld` contract internally points the `StoreSwitch` to the provided World and attempts to register the provided namespace. It allows the other contracts in the inheritance list to use the external World as a `Store`, using the provided namespace for all operations. Additionally, all functions that use the `onlyNamespace` modifier can only be called by addresses that have access to the namespace. + +```solidity +contract ERC20WithWorld is WithWorld, MUDERC20, ERC20Pausable, ERC20Burnable { + constructor( + IBaseWorld world, + bytes14 namespace, + string memory name, + string memory symbol + ) WithWorld(world, namespace) MUDERC20(name, symbol) { + // transfer namespace ownership to the creator + world.transferOwnership(getNamespaceId(), _msgSender()); + } + + function mint(address to, uint256 value) public onlyNamespace { + _mint(to, value); + } + + function pause() public onlyNamespace { + _pause(); + } + + function unpause() public onlyNamespace { + _unpause(); + } + + // The following functions are overrides required by Solidity. + + function _update(address from, address to, uint256 value) internal override(MUDERC20, ERC20Pausable) { + super._update(from, to, value); + } +} +``` + +# Module usage + +The ERC20Module receives the namespace, name and symbol of the token as parameters, and deploys the new token. Currently it installs a default ERC20 (`examples/ERC20WithWorld.sol`) with the following features: + +- ERC20Burnable: Allows users to burn their tokens (or the ones approved to them) using the `burn` and `burnFrom` function. +- ERC20Pausable: Supports pausing and unpausing token operations. This is combined with the `pause` and `unpause` public functions that can be called by addresses with access to the token's namespace. +- Minting: Addresses with namespace access can call the `mint` function to mint tokens to any address. + +## Installation + +In your MUD config: + +```typescript +import { defineWorld } from "@latticexyz/world"; +import { defineERC20Config } from "@latticexyz/world-module-erc20"; + +export default defineWorld({ + namespace: "app", + tables: { + Counter: { + schema: { + value: "uint32", + }, + key: [], + }, + }, + modules: [ + defineERC20Config({ + namespace: "erc20Namespace", + name: "MyToken", + symbol: "MTK", + }), + ], +}); +``` + +This will deploy the token and register the provided namespace. + +In order to get the token's address in a script or system: + +```solidity +// Table Id of the ERC20Registry, under the `erc20-module` namespace +ResourceId erc20RegistryResource = WorldResourceIdLib.encode(RESOURCE_TABLE, "erc20-module", "ERC20_REGISTRY"); + +// Namespace where the token was installed +ResourceId namespaceResource = WorldResourceIdLib.encodeNamespace(bytes14("erc20Namespace")); + + +// Get the ERC-20 token address +address tokenAddress = ERC20Registry.getTokenAddress(erc20RegistryResource, namespaceResource); +``` diff --git a/packages/world-module-erc20/foundry.toml b/packages/world-module-erc20/foundry.toml new file mode 100644 index 0000000000..f0e017f5a0 --- /dev/null +++ b/packages/world-module-erc20/foundry.toml @@ -0,0 +1,15 @@ +[profile.default] +solc = "0.8.24" +ffi = false +fuzz_runs = 256 +optimizer = true +optimizer_runs = 3000 +verbosity = 2 +allow_paths = ["../../node_modules", "../"] +src = "src" +out = "out" +bytecode_hash = "none" +extra_output_files = [ + "abi", + "evm.bytecode" +] diff --git a/packages/world-module-erc20/gas-report.json b/packages/world-module-erc20/gas-report.json new file mode 100644 index 0000000000..0bc3a786b5 --- /dev/null +++ b/packages/world-module-erc20/gas-report.json @@ -0,0 +1,224 @@ +[ + { + "file": "test/ERC20BaseTest.sol", + "test": "testApprove", + "name": "world_approve", + "gasUsed": 66343 + }, + { + "file": "test/ERC20BaseTest.sol", + "test": "testBurn", + "name": "world_burn", + "gasUsed": 70873 + }, + { + "file": "test/ERC20BaseTest.sol", + "test": "testMint", + "name": "world_mint", + "gasUsed": 105159 + }, + { + "file": "test/ERC20BaseTest.sol", + "test": "testTransfer", + "name": "world_transfer", + "gasUsed": 82375 + }, + { + "file": "test/ERC20BaseTest.sol", + "test": "testTransferFrom", + "name": "world_transferFrom", + "gasUsed": 99431 + }, + { + "file": "test/ERC20BaseTest.sol", + "test": "testApprove", + "name": "internal_approve", + "gasUsed": 58213 + }, + { + "file": "test/ERC20BaseTest.sol", + "test": "testBurn", + "name": "internal_burn", + "gasUsed": 56417 + }, + { + "file": "test/ERC20BaseTest.sol", + "test": "testMint", + "name": "internal_mint", + "gasUsed": 90703 + }, + { + "file": "test/ERC20BaseTest.sol", + "test": "testTransfer", + "name": "internal_transfer", + "gasUsed": 67747 + }, + { + "file": "test/ERC20BaseTest.sol", + "test": "testTransferFrom", + "name": "internal_transferFrom", + "gasUsed": 79574 + }, + { + "file": "test/ERC20Burnable.t.sol", + "test": "testApprove", + "name": "internal_approve", + "gasUsed": 58213 + }, + { + "file": "test/ERC20Burnable.t.sol", + "test": "testBurn", + "name": "internal_burn", + "gasUsed": 56440 + }, + { + "file": "test/ERC20Burnable.t.sol", + "test": "testBurnByAccount", + "name": "internal_burn", + "gasUsed": 56563 + }, + { + "file": "test/ERC20Burnable.t.sol", + "test": "testMint", + "name": "internal_mint", + "gasUsed": 90703 + }, + { + "file": "test/ERC20Burnable.t.sol", + "test": "testTransfer", + "name": "internal_transfer", + "gasUsed": 67702 + }, + { + "file": "test/ERC20Burnable.t.sol", + "test": "testTransferFrom", + "name": "internal_transferFrom", + "gasUsed": 79574 + }, + { + "file": "test/ERC20Burnable.t.sol", + "test": "testApprove", + "name": "world_approve", + "gasUsed": 66343 + }, + { + "file": "test/ERC20Burnable.t.sol", + "test": "testBurn", + "name": "world_burn", + "gasUsed": 70874 + }, + { + "file": "test/ERC20Burnable.t.sol", + "test": "testBurnByAccount", + "name": "world_burn", + "gasUsed": 70997 + }, + { + "file": "test/ERC20Burnable.t.sol", + "test": "testMint", + "name": "world_mint", + "gasUsed": 105137 + }, + { + "file": "test/ERC20Burnable.t.sol", + "test": "testTransfer", + "name": "world_transfer", + "gasUsed": 82397 + }, + { + "file": "test/ERC20Burnable.t.sol", + "test": "testTransferFrom", + "name": "world_transferFrom", + "gasUsed": 99498 + }, + { + "file": "test/ERC20Module.t.sol", + "test": "testInstall", + "name": "install erc20 module", + "gasUsed": 4524262 + }, + { + "file": "test/ERC20Pausable.t.sol", + "test": "testApprove", + "name": "internal_approve", + "gasUsed": 58213 + }, + { + "file": "test/ERC20Pausable.t.sol", + "test": "testBurn", + "name": "internal_burn", + "gasUsed": 59740 + }, + { + "file": "test/ERC20Pausable.t.sol", + "test": "testMint", + "name": "internal_mint", + "gasUsed": 94004 + }, + { + "file": "test/ERC20Pausable.t.sol", + "test": "testPause", + "name": "internal_pause", + "gasUsed": 56737 + }, + { + "file": "test/ERC20Pausable.t.sol", + "test": "testPause", + "name": "internal_unpause", + "gasUsed": 34845 + }, + { + "file": "test/ERC20Pausable.t.sol", + "test": "testTransfer", + "name": "internal_transfer", + "gasUsed": 71003 + }, + { + "file": "test/ERC20Pausable.t.sol", + "test": "testTransferFrom", + "name": "internal_transferFrom", + "gasUsed": 82876 + }, + { + "file": "test/ERC20Pausable.t.sol", + "test": "testApprove", + "name": "world_approve", + "gasUsed": 66343 + }, + { + "file": "test/ERC20Pausable.t.sol", + "test": "testBurn", + "name": "world_burn", + "gasUsed": 75661 + }, + { + "file": "test/ERC20Pausable.t.sol", + "test": "testMint", + "name": "world_mint", + "gasUsed": 109880 + }, + { + "file": "test/ERC20Pausable.t.sol", + "test": "testPause", + "name": "world_pause", + "gasUsed": 66128 + }, + { + "file": "test/ERC20Pausable.t.sol", + "test": "testPause", + "name": "world_unpause", + "gasUsed": 44214 + }, + { + "file": "test/ERC20Pausable.t.sol", + "test": "testTransfer", + "name": "world_transfer", + "gasUsed": 87096 + }, + { + "file": "test/ERC20Pausable.t.sol", + "test": "testTransferFrom", + "name": "world_transferFrom", + "gasUsed": 104242 + } +] diff --git a/packages/world-module-erc20/mud.config.ts b/packages/world-module-erc20/mud.config.ts new file mode 100644 index 0000000000..88913ebfa6 --- /dev/null +++ b/packages/world-module-erc20/mud.config.ts @@ -0,0 +1,79 @@ +import { defineStore } from "@latticexyz/store"; + +// Used for tablegen +export default defineStore({ + userTypes: { + ResourceId: { filePath: "@latticexyz/store/src/ResourceId.sol", type: "bytes32" }, + }, + tables: { + Owner: { + schema: { + value: "address", + }, + key: [], + codegen: { + tableIdArgument: true, + }, + }, + ERC20Metadata: { + schema: { + decimals: "uint8", + name: "string", + symbol: "string", + }, + key: [], + codegen: { + tableIdArgument: true, + }, + }, + TotalSupply: { + schema: { + totalSupply: "uint256", + }, + key: [], + codegen: { + tableIdArgument: true, + }, + }, + Balances: { + schema: { + account: "address", + value: "uint256", + }, + key: ["account"], + codegen: { + tableIdArgument: true, + }, + }, + Allowances: { + schema: { + account: "address", + spender: "address", + value: "uint256", + }, + key: ["account", "spender"], + codegen: { + tableIdArgument: true, + }, + }, + Paused: { + schema: { + paused: "bool", + }, + key: [], + codegen: { + tableIdArgument: true, + }, + }, + ERC20Registry: { + schema: { + namespaceId: "ResourceId", + tokenAddress: "address", + }, + key: ["namespaceId"], + codegen: { + tableIdArgument: true, + }, + }, + }, +}); diff --git a/packages/world-module-erc20/package.json b/packages/world-module-erc20/package.json new file mode 100644 index 0000000000..9b2f749668 --- /dev/null +++ b/packages/world-module-erc20/package.json @@ -0,0 +1,67 @@ +{ + "name": "@latticexyz/world-module-erc20", + "version": "2.2.11", + "description": "ERC20 world module", + "repository": { + "type": "git", + "url": "https://github.com/latticexyz/mud.git", + "directory": "packages/world-module-erc20" + }, + "license": "MIT", + "type": "module", + "exports": { + "./mud.config": "./dist/mud.config.js", + ".": "./dist/index.js", + "./out/*": "./out/*" + }, + "typesVersions": { + "*": { + "mud.config": [ + "./dist/mud.config.d.ts" + ], + "module": [ + "./dist/module.d.ts" + ] + } + }, + "files": [ + "dist", + "out", + "src" + ], + "scripts": { + "build": "pnpm run build:mud && pnpm run build:abi && pnpm run build:abi-ts && pnpm run build:js", + "build:abi": "forge build", + "build:abi-ts": "abi-ts", + "build:js": "tsup", + "build:mud": "mud tablegen", + "clean": "pnpm run clean:abi && pnpm run clean:js && pnpm run clean:mud", + "clean:abi": "forge clean", + "clean:js": "shx rm -rf dist", + "clean:mud": "shx rm -rf src/**/codegen", + "dev": "tsup --watch", + "gas-report": "gas-report --save gas-report.json", + "lint": "solhint --config ./.solhint.json 'src/**/*.sol'", + "test": "forge test", + "test:ci": "pnpm run test" + }, + "dependencies": { + "@latticexyz/schema-type": "workspace:*", + "@latticexyz/store": "workspace:*", + "@latticexyz/world": "workspace:*" + }, + "devDependencies": { + "@latticexyz/abi-ts": "workspace:*", + "@latticexyz/cli": "workspace:*", + "@latticexyz/gas-report": "workspace:*", + "@types/node": "^18.15.11", + "ds-test": "https://github.com/dapphub/ds-test.git#e282159d5170298eb2455a6c05280ab5a73a4ef0", + "forge-std": "https://github.com/foundry-rs/forge-std.git#74cfb77e308dd188d2f58864aaf44963ae6b88b1", + "solhint": "^3.3.7", + "tsup": "^6.7.0", + "vitest": "0.34.6" + }, + "publishConfig": { + "access": "public" + } +} diff --git a/packages/world-module-erc20/remappings.txt b/packages/world-module-erc20/remappings.txt new file mode 100644 index 0000000000..66be45ecbd --- /dev/null +++ b/packages/world-module-erc20/remappings.txt @@ -0,0 +1,3 @@ +ds-test/=node_modules/ds-test/src/ +forge-std/=node_modules/forge-std/src/ +@latticexyz/=node_modules/@latticexyz/ \ No newline at end of file diff --git a/packages/world-module-erc20/src/Constants.sol b/packages/world-module-erc20/src/Constants.sol new file mode 100644 index 0000000000..cab89ad32b --- /dev/null +++ b/packages/world-module-erc20/src/Constants.sol @@ -0,0 +1,36 @@ +// SPDX-License-Identifier: MIT +pragma solidity >=0.8.24; + +import { RESOURCE_TABLE } from "@latticexyz/store/src/storeResourceTypes.sol"; +import { ResourceId, WorldResourceIdLib } from "@latticexyz/world/src/WorldResourceId.sol"; + +library ModuleConstants { + bytes14 internal constant NAMESPACE = "erc20-module"; + bytes16 internal constant REGISTRY_TABLE_NAME = "ERC20_REGISTRY"; + + function namespaceId() internal pure returns (ResourceId) { + return WorldResourceIdLib.encodeNamespace(NAMESPACE); + } + + function registryTableId() internal pure returns (ResourceId) { + return WorldResourceIdLib.encode(RESOURCE_TABLE, NAMESPACE, REGISTRY_TABLE_NAME); + } +} + +library ERC20TableNames { + bytes16 internal constant TOTAL_SUPPLY = "TOTAL_SUPPLY"; + + bytes16 internal constant BALANCES = "BALANCES"; + + bytes16 internal constant ALLOWANCES = "ALLOWANCES"; + + bytes16 internal constant METADATA = "METADATA"; +} + +library OwnableTableNames { + bytes16 internal constant OWNER = "OWNER"; +} + +library PausableTableNames { + bytes16 internal constant PAUSED = "PAUSED"; +} diff --git a/packages/world-module-erc20/src/Context.sol b/packages/world-module-erc20/src/Context.sol new file mode 100644 index 0000000000..a2a9b74081 --- /dev/null +++ b/packages/world-module-erc20/src/Context.sol @@ -0,0 +1,10 @@ +// SPDX-License-Identifier: MIT +pragma solidity >=0.8.24; + +// @dev Provides information about the current execution context +// We only use it in these contracts in case we want to extend it in the future +abstract contract Context { + function _msgSender() internal view virtual returns (address) { + return msg.sender; + } +} diff --git a/packages/world-module-erc20/src/ERC20Burnable.sol b/packages/world-module-erc20/src/ERC20Burnable.sol new file mode 100644 index 0000000000..62f08c3ffb --- /dev/null +++ b/packages/world-module-erc20/src/ERC20Burnable.sol @@ -0,0 +1,16 @@ +// SPDX-License-Identifier: MIT +// Adapted from OpenZeppelin's [ERC20Burnable extension](https://github.com/OpenZeppelin/openzeppelin-contracts/blob/f989fff93168606c726bc5e831ef50dd6e543f45/contracts/token/ERC20/extensions/ERC20Burnable.sol) +pragma solidity >=0.8.24; + +import { MUDERC20 } from "./MUDERC20.sol"; + +abstract contract ERC20Burnable is MUDERC20 { + function burn(uint256 value) public virtual { + _burn(_msgSender(), value); + } + + function burnFrom(address account, uint256 value) public virtual { + _spendAllowance(account, _msgSender(), value); + _burn(account, value); + } +} diff --git a/packages/world-module-erc20/src/ERC20Module.sol b/packages/world-module-erc20/src/ERC20Module.sol new file mode 100644 index 0000000000..9ea71f2799 --- /dev/null +++ b/packages/world-module-erc20/src/ERC20Module.sol @@ -0,0 +1,74 @@ +// SPDX-License-Identifier: MIT +pragma solidity >=0.8.24; + +import { ResourceIds } from "@latticexyz/store/src/codegen/tables/ResourceIds.sol"; +import { ResourceId } from "@latticexyz/store/src/ResourceId.sol"; + +import { Module } from "@latticexyz/world/src/Module.sol"; +import { WorldResourceIdLib } from "@latticexyz/world/src/WorldResourceId.sol"; +import { IBaseWorld } from "@latticexyz/world/src/codegen/interfaces/IBaseWorld.sol"; +import { revertWithBytes } from "@latticexyz/world/src/revertWithBytes.sol"; + +import { ERC20Registry } from "./codegen/tables/ERC20Registry.sol"; +import { ERC20WithWorld } from "./examples/ERC20WithWorld.sol"; +import { ModuleConstants } from "./Constants.sol"; + +contract ERC20Module is Module { + error ERC20Module_InvalidNamespace(bytes14 namespace); + error ERC20Module_NamespaceAlreadyExists(bytes14 namespace); + + ERC20RegistryLib public immutable registryLib = new ERC20RegistryLib(); + + function install(bytes memory encodedArgs) public { + // TODO: we should probably check just for namespace, not for all args + requireNotInstalled(__self, encodedArgs); + + (bytes14 namespace, string memory name, string memory symbol) = abi.decode(encodedArgs, (bytes14, string, string)); + + ResourceId namespaceId = WorldResourceIdLib.encodeNamespace(namespace); + + // Require the namespace to not be the module's namespace + if (namespace == ModuleConstants.NAMESPACE) { + revert ERC20Module_InvalidNamespace(namespace); + } else if (ResourceIds.getExists(namespaceId)) { + revert ERC20Module_NamespaceAlreadyExists(namespace); + } + + IBaseWorld world = IBaseWorld(_world()); + + ERC20WithWorld token = new ERC20WithWorld(world, namespace, name, symbol); + + // Grant access to the token so it can write to tables after transferring ownership + world.grantAccess(namespaceId, address(token)); + + // The token should have transferred the namespace ownership to this module in its constructor + world.transferOwnership(namespaceId, _msgSender()); + + registryLib.delegateRegister(world, namespaceId, address(token)); + } + + function installRoot(bytes memory) public pure { + revert Module_RootInstallNotSupported(); + } +} + +contract ERC20RegistryLib { + function register(IBaseWorld world, ResourceId namespaceId, address token) public { + ResourceId erc20RegistryTableId = ModuleConstants.registryTableId(); + if (!ResourceIds.getExists(erc20RegistryTableId)) { + world.registerNamespace(ModuleConstants.namespaceId()); + ERC20Registry.register(erc20RegistryTableId); + } + // Register the ERC20 in the ERC20Registry + ERC20Registry.set(erc20RegistryTableId, namespaceId, address(token)); + } +} + +function delegateRegister(ERC20RegistryLib lib, IBaseWorld world, ResourceId namespaceId, address token) { + (bool success, bytes memory returnData) = address(lib).delegatecall( + abi.encodeCall(ERC20RegistryLib.register, (world, namespaceId, token)) + ); + if (!success) revertWithBytes(returnData); +} + +using { delegateRegister } for ERC20RegistryLib; diff --git a/packages/world-module-erc20/src/ERC20Pausable.sol b/packages/world-module-erc20/src/ERC20Pausable.sol new file mode 100644 index 0000000000..1dcf0da8e6 --- /dev/null +++ b/packages/world-module-erc20/src/ERC20Pausable.sol @@ -0,0 +1,12 @@ +// SPDX-License-Identifier: MIT +// Adapted from OpenZeppelin's [ERC20Pausable extenstion](https://github.com/OpenZeppelin/openzeppelin-contracts/blob/f989fff93168606c726bc5e831ef50dd6e543f45/contracts/token/ERC20/extensions/ERC20Pausable.sol) +pragma solidity >=0.8.24; + +import { Pausable } from "./Pausable.sol"; +import { MUDERC20 } from "./MUDERC20.sol"; + +abstract contract ERC20Pausable is MUDERC20, Pausable { + function _update(address from, address to, uint256 value) internal virtual override whenNotPaused { + super._update(from, to, value); + } +} diff --git a/packages/world-module-erc20/src/MUDERC20.sol b/packages/world-module-erc20/src/MUDERC20.sol new file mode 100644 index 0000000000..97b64877f9 --- /dev/null +++ b/packages/world-module-erc20/src/MUDERC20.sol @@ -0,0 +1,326 @@ +// SPDX-License-Identifier: MIT +// Adapted from OpenZeppelin Contracts [token/ERC20/ERC20.sol](https://github.com/OpenZeppelin/openzeppelin-contracts/blob/f989fff93168606c726bc5e831ef50dd6e543f45/contracts/token/ERC20/ERC20.sol) +pragma solidity >=0.8.24; + +import { ResourceId } from "@latticexyz/store/src/ResourceId.sol"; + +import { ERC20Metadata, ERC20MetadataData } from "./codegen/tables/ERC20Metadata.sol"; +import { TotalSupply } from "./codegen/tables/TotalSupply.sol"; +import { Balances } from "./codegen/tables/Balances.sol"; +import { Allowances } from "./codegen/tables/Allowances.sol"; + +import { IERC20 } from "./interfaces/IERC20.sol"; +import { IERC20Metadata } from "./interfaces/IERC20Metadata.sol"; +import { IERC20Errors } from "./interfaces/IERC20Errors.sol"; + +import { Context } from "./Context.sol"; +import { StoreConsumer } from "./StoreConsumer.sol"; + +import { ERC20TableNames } from "./Constants.sol"; + +abstract contract MUDERC20 is Context, IERC20, IERC20Metadata, IERC20Errors, StoreConsumer { + ResourceId internal immutable totalSupplyId; + ResourceId internal immutable balancesId; + ResourceId internal immutable allowancesId; + ResourceId internal immutable metadataId; + + constructor(string memory _name, string memory _symbol) { + // Needs to be inlined in the constructor + totalSupplyId = _encodeTableId(ERC20TableNames.TOTAL_SUPPLY); + balancesId = _encodeTableId(ERC20TableNames.BALANCES); + allowancesId = _encodeTableId(ERC20TableNames.ALLOWANCES); + metadataId = _encodeTableId(ERC20TableNames.METADATA); + + // Register each table + TotalSupply.register(totalSupplyId); + Balances.register(balancesId); + Allowances.register(allowancesId); + ERC20Metadata.register(metadataId); + + _setMetadata(_name, _symbol, 18); + } + + /** + * @dev Returns the name of the token. + */ + function name() public view virtual returns (string memory) { + return _getName(); + } + + /** + * @dev Returns the symbol of the token, usually a shorter version of the + * name. + */ + function symbol() public view virtual returns (string memory) { + return _getSymbol(); + } + + /** + * @dev Returns the number of decimals used to get its user representation. + * For example, if `decimals` equals `2`, a balance of `505` tokens should + * be displayed to a user as `5.05` (`505 / 10 ** 2`). + * + * Tokens usually opt for a value of 18, imitating the relationship between + * Ether and Wei. This is the default value returned by this function, unless + * it's overridden. + * + * NOTE: This information is only used for _display_ purposes: it in + * no way affects any of the arithmetic of the contract, including + * {IERC20-balanceOf} and {IERC20-transfer}. + */ + function decimals() public view virtual returns (uint8) { + return _getDecimals(); + } + + /** + * @dev Returns the value of tokens in existence. + */ + function totalSupply() public view returns (uint256) { + return _getTotalSupply(); + } + + /** + * @dev Returns the value of tokens owned by `account`. + */ + function balanceOf(address account) public view returns (uint256) { + return _getBalance(account); + } + + /** + * @dev Returns the remaining number of tokens that `spender` will be + * allowed to spend on behalf of `owner` through {transferFrom}. This is + * zero by default. + * + * This value changes when {approve} or {transferFrom} are called. + */ + function allowance(address owner, address spender) public view returns (uint256) { + return _getAllowance(owner, spender); + } + + /** + * @dev See {IERC20-transfer}. + * + * Requirements: + * + * - `to` cannot be the zero address. + * - the caller must have a balance of at least `value`. + */ + function transfer(address to, uint256 value) public virtual returns (bool) { + address owner = _msgSender(); + _transfer(owner, to, value); + + return true; + } + + /** + * @dev See {IERC20-approve}. + * + * NOTE: If `value` is the maximum `uint256`, the allowance is not updated on + * `transferFrom`. This is semantically equivalent to an infinite approval. + * + * Requirements: + * + * - `spender` cannot be the zero address. + */ + function approve(address spender, uint256 value) public virtual returns (bool) { + address owner = _msgSender(); + _approve(owner, spender, value, true); + + return true; + } + + /** + * @dev See {IERC20-transferFrom}. + * + * Emits an {Approval} event indicating the updated allowance. This is not + * required by the EIP. See the note at the beginning of {ERC20}. + * + * NOTE: Does not update the allowance if the current allowance + * is the maximum `uint256`. + * + * Requirements: + * + * - `from` and `to` cannot be the zero address. + * - `from` must have a balance of at least `value`. + * - the caller must have allowance for ``from``'s tokens of at least + * `value`. + */ + function transferFrom(address from, address to, uint256 value) public virtual returns (bool) { + address spender = _msgSender(); + _spendAllowance(from, spender, value); + _transfer(from, to, value); + + return true; + } + + /** + * @dev Creates a `value` amount of tokens and assigns them to `account`, by transferring it from address(0). + * Relies on the `_update` mechanism + * + * Emits a {Transfer} event with `from` set to the zero address. + */ + function _mint(address account, uint256 value) internal { + if (account == address(0)) { + revert ERC20InvalidReceiver(address(0)); + } + + _update(address(0), account, value); + } + + /** + * @dev Destroys a `value` amount of tokens from `account`, lowering the total supply. + * Relies on the `_update` mechanism. + * + * Emits a {Transfer} event with `to` set to the zero address. + * + * NOTE: This function is not virtual, {_update} should be overridden instead + */ + function _burn(address account, uint256 value) internal { + if (account == address(0)) { + revert ERC20InvalidSender(address(0)); + } + + _update(account, address(0), value); + } + + /** + * @dev Moves a `value` amount of tokens from `from` to `to`. + * + * This internal function is equivalent to {transfer}, and can be used to + * e.g. implement automatic token fees, slashing mechanisms, etc. + * + * Emits a {Transfer} event. + * + * NOTE: This function is not virtual, {_update} should be overridden instead. + */ + function _transfer(address from, address to, uint256 value) internal { + if (from == address(0)) { + revert ERC20InvalidSender(address(0)); + } + if (to == address(0)) { + revert ERC20InvalidReceiver(address(0)); + } + _update(from, to, value); + } + + /** + * @dev Transfers a `value` amount of tokens from `from` to `to`, or alternatively mints (or burns) if `from` + * (or `to`) is the zero address. All customizations to transfers, mints, and burns should be done by overriding + * this function. + * + * Emits a {Transfer} event. + */ + function _update(address from, address to, uint256 value) internal virtual { + if (from == address(0)) { + // Overflow check required: The rest of the code assumes that totalSupply never overflows + _setTotalSupply(_getTotalSupply() + value); + } else { + uint256 fromBalance = _getBalance(from); + if (fromBalance < value) { + revert ERC20InsufficientBalance(from, fromBalance, value); + } + unchecked { + // Overflow not possible: value <= fromBalance <= totalSupply. + _setBalance(from, fromBalance - value); + } + } + + if (to == address(0)) { + unchecked { + // Overflow not possible: value <= totalSupply or value <= fromBalance <= totalSupply. + _setTotalSupply(_getTotalSupply() - value); + } + } else { + unchecked { + // Overflow not possible: balance + value is at most totalSupply, which we know fits into a uint256. + _setBalance(to, _getBalance(to) + value); + } + } + + emit Transfer(from, to, value); + } + + /** + * @dev Sets `value` as the allowance of `spender` over the `owner`s tokens. + * + * Requirements: + * + * - `owner` cannot be the zero address. + * - `spender` cannot be the zero address. + */ + function _approve(address owner, address spender, uint256 value, bool emitEvent) internal virtual { + if (owner == address(0)) { + revert ERC20InvalidApprover(address(0)); + } + if (spender == address(0)) { + revert ERC20InvalidSpender(address(0)); + } + + _setAllowance(owner, spender, value); + + if (emitEvent) { + emit Approval(owner, spender, value); + } + } + + /** + * @dev Updates `owner` s allowance for `spender` based on spent `value`. + * + * Does not update the allowance value in case of infinite allowance. + * Revert if not enough allowance is available. + * + * Does not emit an {Approval} event. + */ + function _spendAllowance(address owner, address spender, uint256 value) internal virtual { + uint256 currentAllowance = _getAllowance(owner, spender); + if (currentAllowance != type(uint256).max) { + if (currentAllowance < value) { + revert ERC20InsufficientAllowance(spender, currentAllowance, value); + } + unchecked { + _approve(owner, spender, currentAllowance - value, false); + } + } + } + + function _getName() internal view returns (string memory) { + return ERC20Metadata.getName(metadataId); + } + + function _getSymbol() internal view returns (string memory) { + return ERC20Metadata.getSymbol(metadataId); + } + + function _getDecimals() internal view returns (uint8) { + return ERC20Metadata.getDecimals(metadataId); + } + + function _getTotalSupply() internal view returns (uint256) { + return TotalSupply.get(totalSupplyId); + } + + function _getBalance(address account) internal view returns (uint256) { + return Balances.get(balancesId, account); + } + + function _getAllowance(address owner, address spender) internal view returns (uint256) { + return Allowances.get(allowancesId, owner, spender); + } + + function _setTotalSupply(uint256 value) internal virtual { + TotalSupply.set(totalSupplyId, value); + } + + function _setBalance(address account, uint256 value) internal virtual { + Balances.set(balancesId, account, value); + } + + function _setAllowance(address owner, address spender, uint256 value) internal virtual { + Allowances.set(allowancesId, owner, spender, value); + } + + function _setMetadata(string memory _name, string memory _symbol, uint8 _decimals) internal virtual { + ERC20MetadataData memory metadata = ERC20MetadataData(_decimals, _name, _symbol); + ERC20Metadata.set(metadataId, metadata); + } +} diff --git a/packages/world-module-erc20/src/Ownable.sol b/packages/world-module-erc20/src/Ownable.sol new file mode 100644 index 0000000000..9d5870e69b --- /dev/null +++ b/packages/world-module-erc20/src/Ownable.sol @@ -0,0 +1,110 @@ +// SPDX-License-Identifier: MIT +// Adapted from OpenZeppelin Contracts [access/Ownable.sol](https://github.com/OpenZeppelin/openzeppelin-contracts/blob/f989fff93168606c726bc5e831ef50dd6e543f45/contracts/access/Ownable.sol) +pragma solidity >=0.8.24; + +import { ResourceId } from "@latticexyz/store/src/ResourceId.sol"; + +import { Owner } from "./codegen/tables/Owner.sol"; +import { StoreConsumer } from "./StoreConsumer.sol"; +import { Context } from "./Context.sol"; +import { OwnableTableNames } from "./Constants.sol"; + +/** + * @dev Contract module which provides a basic access control mechanism, where + * there is an account (an owner) that can be granted exclusive access to + * specific functions. + * + * The initial owner is set to the address provided by the deployer. This can + * later be changed with {transferOwnership}. + * + * This module is used through inheritance. It will make available the modifier + * `onlyOwner`, which can be applied to your functions to restrict their use to + * the owner. + */ +abstract contract Ownable is Context, StoreConsumer { + ResourceId internal immutable ownerId; + + /** + * @dev The caller account is not authorized to perform an operation. + */ + error OwnableUnauthorizedAccount(address account); + + /** + * @dev The owner is not a valid owner account. (eg. `address(0)`) + */ + error OwnableInvalidOwner(address owner); + + event OwnershipTransferred(address indexed previousOwner, address indexed newOwner); + + /** + * @dev Initializes the contract setting the address provided by the deployer as the initial owner. + */ + constructor(address initialOwner) { + if (initialOwner == address(0)) { + revert OwnableInvalidOwner(address(0)); + } + + ownerId = _encodeTableId(OwnableTableNames.OWNER); + + // Register table + Owner.register(ownerId); + + _transferOwnership(initialOwner); + } + + /** + * @dev Throws if called by any account other than the owner. + */ + modifier onlyOwner() { + _checkOwner(); + _; + } + + /** + * @dev Returns the address of the current owner. + */ + function owner() public view virtual returns (address) { + return Owner.get(ownerId); + } + + /** + * @dev Throws if the sender is not the owner. + */ + function _checkOwner() internal view virtual { + if (owner() != _msgSender()) { + revert OwnableUnauthorizedAccount(_msgSender()); + } + } + + /** + * @dev Leaves the contract without owner. It will not be possible to call + * `onlyOwner` functions. Can only be called by the current owner. + * + * NOTE: Renouncing ownership will leave the contract without an owner, + * thereby disabling any functionality that is only available to the owner. + */ + function renounceOwnership() public virtual onlyOwner { + _transferOwnership(address(0)); + } + + /** + * @dev Transfers ownership of the contract to a new account (`newOwner`). + * Can only be called by the current owner. + */ + function transferOwnership(address newOwner) public virtual onlyOwner { + if (newOwner == address(0)) { + revert OwnableInvalidOwner(address(0)); + } + _transferOwnership(newOwner); + } + + /** + * @dev Transfers ownership of the contract to a new account (`newOwner`). + * Internal function without access restriction. + */ + function _transferOwnership(address newOwner) internal virtual { + address oldOwner = owner(); + Owner.set(ownerId, newOwner); + emit OwnershipTransferred(oldOwner, newOwner); + } +} diff --git a/packages/world-module-erc20/src/Pausable.sol b/packages/world-module-erc20/src/Pausable.sol new file mode 100644 index 0000000000..ca3beba56a --- /dev/null +++ b/packages/world-module-erc20/src/Pausable.sol @@ -0,0 +1,125 @@ +// SPDX-License-Identifier: MIT +// Adapted from OpenZeppelin Contracts [utils/Pausable.sol](https://github.com/OpenZeppelin/openzeppelin-contracts/blob/f989fff93168606c726bc5e831ef50dd6e543f45/contracts/utils/Pausable.sol) +pragma solidity >=0.8.24; + +import { ResourceId } from "@latticexyz/store/src/ResourceId.sol"; + +import { Paused as PausedTable } from "./codegen/tables/Paused.sol"; +import { StoreConsumer } from "./StoreConsumer.sol"; +import { Context } from "./Context.sol"; +import { PausableTableNames } from "./Constants.sol"; + +/** + * @dev Contract module which allows children to implement an emergency stop + * mechanism that can be triggered by an authorized account. + * + * This module is used through inheritance. It will make available the + * modifiers `whenNotPaused` and `whenPaused`, which can be applied to + * the functions of your contract. Note that they will not be pausable by + * simply including this module, only once the modifiers are put in place. + */ +abstract contract Pausable is Context, StoreConsumer { + ResourceId internal immutable pausedId; + + /** + * @dev Emitted when the pause is triggered by `account`. + */ + event Paused(address account); + + /** + * @dev Emitted when the pause is lifted by `account`. + */ + event Unpaused(address account); + + /** + * @dev The operation failed because the contract is paused. + */ + error EnforcedPause(); + + /** + * @dev The operation failed because the contract is not paused. + */ + error ExpectedPause(); + + /** + * @dev Initializes the contract in unpaused state. + */ + constructor() { + pausedId = _encodeTableId(PausableTableNames.PAUSED); + PausedTable.register(pausedId); + PausedTable.set(pausedId, false); + } + + /** + * @dev Modifier to make a function callable only when the contract is not paused. + * + * Requirements: + * + * - The contract must not be paused. + */ + modifier whenNotPaused() { + _requireNotPaused(); + _; + } + + /** + * @dev Modifier to make a function callable only when the contract is paused. + * + * Requirements: + * + * - The contract must be paused. + */ + modifier whenPaused() { + _requirePaused(); + _; + } + + /** + * @dev Returns true if the contract is paused, and false otherwise. + */ + function paused() public view virtual returns (bool) { + return PausedTable.get(pausedId); + } + + /** + * @dev Throws if the contract is paused. + */ + function _requireNotPaused() internal view virtual { + if (paused()) { + revert EnforcedPause(); + } + } + + /** + * @dev Throws if the contract is not paused. + */ + function _requirePaused() internal view virtual { + if (!paused()) { + revert ExpectedPause(); + } + } + + /** + * @dev Triggers stopped state. + * + * Requirements: + * + * - The contract must not be paused. + */ + function _pause() internal virtual whenNotPaused { + PausedTable.set(pausedId, true); + emit Paused(_msgSender()); + } + + /** + * @dev Returns to normal state. + * + * Requirements: + * + * - The contract must be paused. + */ + function _unpause() internal virtual whenPaused { + PausedTable.set(pausedId, false); + emit Unpaused(_msgSender()); + } +} diff --git a/packages/world-module-erc20/src/StoreConsumer.sol b/packages/world-module-erc20/src/StoreConsumer.sol new file mode 100644 index 0000000000..29d128bc45 --- /dev/null +++ b/packages/world-module-erc20/src/StoreConsumer.sol @@ -0,0 +1,24 @@ +// SPDX-License-Identifier: MIT +pragma solidity >=0.8.24; + +import { StoreSwitch } from "@latticexyz/store/src/StoreSwitch.sol"; +import { RESOURCE_TABLE, RESOURCE_OFFCHAIN_TABLE } from "@latticexyz/store/src/storeResourceTypes.sol"; +import { ResourceId } from "@latticexyz/store/src/ResourceId.sol"; + +import { Context } from "./Context.sol"; + +abstract contract StoreConsumer { + function getStore() public view returns (address) { + return StoreSwitch.getStoreAddress(); + } + + function _encodeResourceId(bytes2 typeId, bytes16 name) internal view virtual returns (ResourceId); + + function _encodeTableId(bytes16 name) internal view returns (ResourceId) { + return _encodeResourceId(RESOURCE_TABLE, name); + } + + function _encodeOffchainTableId(bytes16 name) internal view returns (ResourceId) { + return _encodeResourceId(RESOURCE_OFFCHAIN_TABLE, name); + } +} diff --git a/packages/world-module-erc20/src/WithStore.sol b/packages/world-module-erc20/src/WithStore.sol new file mode 100644 index 0000000000..7660156a4a --- /dev/null +++ b/packages/world-module-erc20/src/WithStore.sol @@ -0,0 +1,22 @@ +// SPDX-License-Identifier: MIT +pragma solidity >=0.8.24; + +import { StoreCore } from "@latticexyz/store/src/Store.sol"; +import { StoreSwitch } from "@latticexyz/store/src/StoreSwitch.sol"; +import { ResourceId, ResourceIdLib } from "@latticexyz/store/src/ResourceId.sol"; +import { Context } from "./Context.sol"; +import { StoreConsumer } from "./StoreConsumer.sol"; + +abstract contract WithStore is Context, StoreConsumer { + constructor(address store) { + StoreSwitch.setStoreAddress(store); + + if (store == address(this)) { + StoreCore.registerInternalTables(); + } + } + + function _encodeResourceId(bytes2 typeId, bytes16 name) internal view virtual override returns (ResourceId) { + return ResourceIdLib.encode(typeId, name); + } +} diff --git a/packages/world-module-erc20/src/WithWorld.sol b/packages/world-module-erc20/src/WithWorld.sol new file mode 100644 index 0000000000..77295db24f --- /dev/null +++ b/packages/world-module-erc20/src/WithWorld.sol @@ -0,0 +1,40 @@ +// SPDX-License-Identifier: MIT +pragma solidity >=0.8.24; + +import { ResourceId } from "@latticexyz/store/src/ResourceId.sol"; +import { ResourceAccess } from "@latticexyz/world/src/codegen/tables/ResourceAccess.sol"; +import { IBaseWorld } from "@latticexyz/world/src/codegen/interfaces/IBaseWorld.sol"; +import { WorldResourceIdLib } from "@latticexyz/world/src/WorldResourceId.sol"; + +import { WithStore } from "./WithStore.sol"; + +abstract contract WithWorld is WithStore { + bytes14 public immutable namespace; + + error WithWorld_CallerHasNoNamespaceAccess(); + + modifier onlyNamespace() { + address sender = _msgSender(); + if (!ResourceAccess.get(getNamespaceId(), sender)) { + revert WithWorld_CallerHasNoNamespaceAccess(); + } + _; + } + + constructor(IBaseWorld world, bytes14 _namespace) WithStore(address(world)) { + namespace = _namespace; + + ResourceId namespaceId = getNamespaceId(); + + // This will revert if namespace already exists + world.registerNamespace(namespaceId); + } + + function getNamespaceId() public view returns (ResourceId) { + return WorldResourceIdLib.encodeNamespace(namespace); + } + + function _encodeResourceId(bytes2 typeId, bytes16 name) internal view virtual override returns (ResourceId) { + return WorldResourceIdLib.encode(typeId, namespace, name); + } +} diff --git a/packages/world-module-erc20/src/codegen/index.sol b/packages/world-module-erc20/src/codegen/index.sol new file mode 100644 index 0000000000..cc239551ff --- /dev/null +++ b/packages/world-module-erc20/src/codegen/index.sol @@ -0,0 +1,12 @@ +// SPDX-License-Identifier: MIT +pragma solidity >=0.8.24; + +/* Autogenerated file. Do not edit manually. */ + +import { Owner } from "./tables/Owner.sol"; +import { ERC20Metadata, ERC20MetadataData } from "./tables/ERC20Metadata.sol"; +import { TotalSupply } from "./tables/TotalSupply.sol"; +import { Balances } from "./tables/Balances.sol"; +import { Allowances } from "./tables/Allowances.sol"; +import { Paused } from "./tables/Paused.sol"; +import { ERC20Registry } from "./tables/ERC20Registry.sol"; diff --git a/packages/world-module-erc20/src/codegen/tables/Allowances.sol b/packages/world-module-erc20/src/codegen/tables/Allowances.sol new file mode 100644 index 0000000000..b67787d611 --- /dev/null +++ b/packages/world-module-erc20/src/codegen/tables/Allowances.sol @@ -0,0 +1,208 @@ +// SPDX-License-Identifier: MIT +pragma solidity >=0.8.24; + +/* Autogenerated file. Do not edit manually. */ + +// Import store internals +import { IStore } from "@latticexyz/store/src/IStore.sol"; +import { StoreSwitch } from "@latticexyz/store/src/StoreSwitch.sol"; +import { StoreCore } from "@latticexyz/store/src/StoreCore.sol"; +import { Bytes } from "@latticexyz/store/src/Bytes.sol"; +import { Memory } from "@latticexyz/store/src/Memory.sol"; +import { SliceLib } from "@latticexyz/store/src/Slice.sol"; +import { EncodeArray } from "@latticexyz/store/src/tightcoder/EncodeArray.sol"; +import { FieldLayout } from "@latticexyz/store/src/FieldLayout.sol"; +import { Schema } from "@latticexyz/store/src/Schema.sol"; +import { EncodedLengths, EncodedLengthsLib } from "@latticexyz/store/src/EncodedLengths.sol"; +import { ResourceId } from "@latticexyz/store/src/ResourceId.sol"; + +library Allowances { + FieldLayout constant _fieldLayout = + FieldLayout.wrap(0x0020010020000000000000000000000000000000000000000000000000000000); + + // Hex-encoded key schema of (address, address) + Schema constant _keySchema = Schema.wrap(0x0028020061610000000000000000000000000000000000000000000000000000); + // Hex-encoded value schema of (uint256) + Schema constant _valueSchema = Schema.wrap(0x002001001f000000000000000000000000000000000000000000000000000000); + + /** + * @notice Get the table's key field names. + * @return keyNames An array of strings with the names of key fields. + */ + function getKeyNames() internal pure returns (string[] memory keyNames) { + keyNames = new string[](2); + keyNames[0] = "account"; + keyNames[1] = "spender"; + } + + /** + * @notice Get the table's value field names. + * @return fieldNames An array of strings with the names of value fields. + */ + function getFieldNames() internal pure returns (string[] memory fieldNames) { + fieldNames = new string[](1); + fieldNames[0] = "value"; + } + + /** + * @notice Register the table with its config. + */ + function register(ResourceId _tableId) internal { + StoreSwitch.registerTable(_tableId, _fieldLayout, _keySchema, _valueSchema, getKeyNames(), getFieldNames()); + } + + /** + * @notice Register the table with its config. + */ + function _register(ResourceId _tableId) internal { + StoreCore.registerTable(_tableId, _fieldLayout, _keySchema, _valueSchema, getKeyNames(), getFieldNames()); + } + + /** + * @notice Get value. + */ + function getValue(ResourceId _tableId, address account, address spender) internal view returns (uint256 value) { + bytes32[] memory _keyTuple = new bytes32[](2); + _keyTuple[0] = bytes32(uint256(uint160(account))); + _keyTuple[1] = bytes32(uint256(uint160(spender))); + + bytes32 _blob = StoreSwitch.getStaticField(_tableId, _keyTuple, 0, _fieldLayout); + return (uint256(bytes32(_blob))); + } + + /** + * @notice Get value. + */ + function _getValue(ResourceId _tableId, address account, address spender) internal view returns (uint256 value) { + bytes32[] memory _keyTuple = new bytes32[](2); + _keyTuple[0] = bytes32(uint256(uint160(account))); + _keyTuple[1] = bytes32(uint256(uint160(spender))); + + bytes32 _blob = StoreCore.getStaticField(_tableId, _keyTuple, 0, _fieldLayout); + return (uint256(bytes32(_blob))); + } + + /** + * @notice Get value. + */ + function get(ResourceId _tableId, address account, address spender) internal view returns (uint256 value) { + bytes32[] memory _keyTuple = new bytes32[](2); + _keyTuple[0] = bytes32(uint256(uint160(account))); + _keyTuple[1] = bytes32(uint256(uint160(spender))); + + bytes32 _blob = StoreSwitch.getStaticField(_tableId, _keyTuple, 0, _fieldLayout); + return (uint256(bytes32(_blob))); + } + + /** + * @notice Get value. + */ + function _get(ResourceId _tableId, address account, address spender) internal view returns (uint256 value) { + bytes32[] memory _keyTuple = new bytes32[](2); + _keyTuple[0] = bytes32(uint256(uint160(account))); + _keyTuple[1] = bytes32(uint256(uint160(spender))); + + bytes32 _blob = StoreCore.getStaticField(_tableId, _keyTuple, 0, _fieldLayout); + return (uint256(bytes32(_blob))); + } + + /** + * @notice Set value. + */ + function setValue(ResourceId _tableId, address account, address spender, uint256 value) internal { + bytes32[] memory _keyTuple = new bytes32[](2); + _keyTuple[0] = bytes32(uint256(uint160(account))); + _keyTuple[1] = bytes32(uint256(uint160(spender))); + + StoreSwitch.setStaticField(_tableId, _keyTuple, 0, abi.encodePacked((value)), _fieldLayout); + } + + /** + * @notice Set value. + */ + function _setValue(ResourceId _tableId, address account, address spender, uint256 value) internal { + bytes32[] memory _keyTuple = new bytes32[](2); + _keyTuple[0] = bytes32(uint256(uint160(account))); + _keyTuple[1] = bytes32(uint256(uint160(spender))); + + StoreCore.setStaticField(_tableId, _keyTuple, 0, abi.encodePacked((value)), _fieldLayout); + } + + /** + * @notice Set value. + */ + function set(ResourceId _tableId, address account, address spender, uint256 value) internal { + bytes32[] memory _keyTuple = new bytes32[](2); + _keyTuple[0] = bytes32(uint256(uint160(account))); + _keyTuple[1] = bytes32(uint256(uint160(spender))); + + StoreSwitch.setStaticField(_tableId, _keyTuple, 0, abi.encodePacked((value)), _fieldLayout); + } + + /** + * @notice Set value. + */ + function _set(ResourceId _tableId, address account, address spender, uint256 value) internal { + bytes32[] memory _keyTuple = new bytes32[](2); + _keyTuple[0] = bytes32(uint256(uint160(account))); + _keyTuple[1] = bytes32(uint256(uint160(spender))); + + StoreCore.setStaticField(_tableId, _keyTuple, 0, abi.encodePacked((value)), _fieldLayout); + } + + /** + * @notice Delete all data for given keys. + */ + function deleteRecord(ResourceId _tableId, address account, address spender) internal { + bytes32[] memory _keyTuple = new bytes32[](2); + _keyTuple[0] = bytes32(uint256(uint160(account))); + _keyTuple[1] = bytes32(uint256(uint160(spender))); + + StoreSwitch.deleteRecord(_tableId, _keyTuple); + } + + /** + * @notice Delete all data for given keys. + */ + function _deleteRecord(ResourceId _tableId, address account, address spender) internal { + bytes32[] memory _keyTuple = new bytes32[](2); + _keyTuple[0] = bytes32(uint256(uint160(account))); + _keyTuple[1] = bytes32(uint256(uint160(spender))); + + StoreCore.deleteRecord(_tableId, _keyTuple, _fieldLayout); + } + + /** + * @notice Tightly pack static (fixed length) data using this table's schema. + * @return The static data, encoded into a sequence of bytes. + */ + function encodeStatic(uint256 value) internal pure returns (bytes memory) { + return abi.encodePacked(value); + } + + /** + * @notice Encode all of a record's fields. + * @return The static (fixed length) data, encoded into a sequence of bytes. + * @return The lengths of the dynamic fields (packed into a single bytes32 value). + * @return The dynamic (variable length) data, encoded into a sequence of bytes. + */ + function encode(uint256 value) internal pure returns (bytes memory, EncodedLengths, bytes memory) { + bytes memory _staticData = encodeStatic(value); + + EncodedLengths _encodedLengths; + bytes memory _dynamicData; + + return (_staticData, _encodedLengths, _dynamicData); + } + + /** + * @notice Encode keys as a bytes32 array using this table's field layout. + */ + function encodeKeyTuple(address account, address spender) internal pure returns (bytes32[] memory) { + bytes32[] memory _keyTuple = new bytes32[](2); + _keyTuple[0] = bytes32(uint256(uint160(account))); + _keyTuple[1] = bytes32(uint256(uint160(spender))); + + return _keyTuple; + } +} diff --git a/packages/world-module-erc20/src/codegen/tables/Balances.sol b/packages/world-module-erc20/src/codegen/tables/Balances.sol new file mode 100644 index 0000000000..03bf11aba0 --- /dev/null +++ b/packages/world-module-erc20/src/codegen/tables/Balances.sol @@ -0,0 +1,196 @@ +// SPDX-License-Identifier: MIT +pragma solidity >=0.8.24; + +/* Autogenerated file. Do not edit manually. */ + +// Import store internals +import { IStore } from "@latticexyz/store/src/IStore.sol"; +import { StoreSwitch } from "@latticexyz/store/src/StoreSwitch.sol"; +import { StoreCore } from "@latticexyz/store/src/StoreCore.sol"; +import { Bytes } from "@latticexyz/store/src/Bytes.sol"; +import { Memory } from "@latticexyz/store/src/Memory.sol"; +import { SliceLib } from "@latticexyz/store/src/Slice.sol"; +import { EncodeArray } from "@latticexyz/store/src/tightcoder/EncodeArray.sol"; +import { FieldLayout } from "@latticexyz/store/src/FieldLayout.sol"; +import { Schema } from "@latticexyz/store/src/Schema.sol"; +import { EncodedLengths, EncodedLengthsLib } from "@latticexyz/store/src/EncodedLengths.sol"; +import { ResourceId } from "@latticexyz/store/src/ResourceId.sol"; + +library Balances { + FieldLayout constant _fieldLayout = + FieldLayout.wrap(0x0020010020000000000000000000000000000000000000000000000000000000); + + // Hex-encoded key schema of (address) + Schema constant _keySchema = Schema.wrap(0x0014010061000000000000000000000000000000000000000000000000000000); + // Hex-encoded value schema of (uint256) + Schema constant _valueSchema = Schema.wrap(0x002001001f000000000000000000000000000000000000000000000000000000); + + /** + * @notice Get the table's key field names. + * @return keyNames An array of strings with the names of key fields. + */ + function getKeyNames() internal pure returns (string[] memory keyNames) { + keyNames = new string[](1); + keyNames[0] = "account"; + } + + /** + * @notice Get the table's value field names. + * @return fieldNames An array of strings with the names of value fields. + */ + function getFieldNames() internal pure returns (string[] memory fieldNames) { + fieldNames = new string[](1); + fieldNames[0] = "value"; + } + + /** + * @notice Register the table with its config. + */ + function register(ResourceId _tableId) internal { + StoreSwitch.registerTable(_tableId, _fieldLayout, _keySchema, _valueSchema, getKeyNames(), getFieldNames()); + } + + /** + * @notice Register the table with its config. + */ + function _register(ResourceId _tableId) internal { + StoreCore.registerTable(_tableId, _fieldLayout, _keySchema, _valueSchema, getKeyNames(), getFieldNames()); + } + + /** + * @notice Get value. + */ + function getValue(ResourceId _tableId, address account) internal view returns (uint256 value) { + bytes32[] memory _keyTuple = new bytes32[](1); + _keyTuple[0] = bytes32(uint256(uint160(account))); + + bytes32 _blob = StoreSwitch.getStaticField(_tableId, _keyTuple, 0, _fieldLayout); + return (uint256(bytes32(_blob))); + } + + /** + * @notice Get value. + */ + function _getValue(ResourceId _tableId, address account) internal view returns (uint256 value) { + bytes32[] memory _keyTuple = new bytes32[](1); + _keyTuple[0] = bytes32(uint256(uint160(account))); + + bytes32 _blob = StoreCore.getStaticField(_tableId, _keyTuple, 0, _fieldLayout); + return (uint256(bytes32(_blob))); + } + + /** + * @notice Get value. + */ + function get(ResourceId _tableId, address account) internal view returns (uint256 value) { + bytes32[] memory _keyTuple = new bytes32[](1); + _keyTuple[0] = bytes32(uint256(uint160(account))); + + bytes32 _blob = StoreSwitch.getStaticField(_tableId, _keyTuple, 0, _fieldLayout); + return (uint256(bytes32(_blob))); + } + + /** + * @notice Get value. + */ + function _get(ResourceId _tableId, address account) internal view returns (uint256 value) { + bytes32[] memory _keyTuple = new bytes32[](1); + _keyTuple[0] = bytes32(uint256(uint160(account))); + + bytes32 _blob = StoreCore.getStaticField(_tableId, _keyTuple, 0, _fieldLayout); + return (uint256(bytes32(_blob))); + } + + /** + * @notice Set value. + */ + function setValue(ResourceId _tableId, address account, uint256 value) internal { + bytes32[] memory _keyTuple = new bytes32[](1); + _keyTuple[0] = bytes32(uint256(uint160(account))); + + StoreSwitch.setStaticField(_tableId, _keyTuple, 0, abi.encodePacked((value)), _fieldLayout); + } + + /** + * @notice Set value. + */ + function _setValue(ResourceId _tableId, address account, uint256 value) internal { + bytes32[] memory _keyTuple = new bytes32[](1); + _keyTuple[0] = bytes32(uint256(uint160(account))); + + StoreCore.setStaticField(_tableId, _keyTuple, 0, abi.encodePacked((value)), _fieldLayout); + } + + /** + * @notice Set value. + */ + function set(ResourceId _tableId, address account, uint256 value) internal { + bytes32[] memory _keyTuple = new bytes32[](1); + _keyTuple[0] = bytes32(uint256(uint160(account))); + + StoreSwitch.setStaticField(_tableId, _keyTuple, 0, abi.encodePacked((value)), _fieldLayout); + } + + /** + * @notice Set value. + */ + function _set(ResourceId _tableId, address account, uint256 value) internal { + bytes32[] memory _keyTuple = new bytes32[](1); + _keyTuple[0] = bytes32(uint256(uint160(account))); + + StoreCore.setStaticField(_tableId, _keyTuple, 0, abi.encodePacked((value)), _fieldLayout); + } + + /** + * @notice Delete all data for given keys. + */ + function deleteRecord(ResourceId _tableId, address account) internal { + bytes32[] memory _keyTuple = new bytes32[](1); + _keyTuple[0] = bytes32(uint256(uint160(account))); + + StoreSwitch.deleteRecord(_tableId, _keyTuple); + } + + /** + * @notice Delete all data for given keys. + */ + function _deleteRecord(ResourceId _tableId, address account) internal { + bytes32[] memory _keyTuple = new bytes32[](1); + _keyTuple[0] = bytes32(uint256(uint160(account))); + + StoreCore.deleteRecord(_tableId, _keyTuple, _fieldLayout); + } + + /** + * @notice Tightly pack static (fixed length) data using this table's schema. + * @return The static data, encoded into a sequence of bytes. + */ + function encodeStatic(uint256 value) internal pure returns (bytes memory) { + return abi.encodePacked(value); + } + + /** + * @notice Encode all of a record's fields. + * @return The static (fixed length) data, encoded into a sequence of bytes. + * @return The lengths of the dynamic fields (packed into a single bytes32 value). + * @return The dynamic (variable length) data, encoded into a sequence of bytes. + */ + function encode(uint256 value) internal pure returns (bytes memory, EncodedLengths, bytes memory) { + bytes memory _staticData = encodeStatic(value); + + EncodedLengths _encodedLengths; + bytes memory _dynamicData; + + return (_staticData, _encodedLengths, _dynamicData); + } + + /** + * @notice Encode keys as a bytes32 array using this table's field layout. + */ + function encodeKeyTuple(address account) internal pure returns (bytes32[] memory) { + bytes32[] memory _keyTuple = new bytes32[](1); + _keyTuple[0] = bytes32(uint256(uint160(account))); + + return _keyTuple; + } +} diff --git a/packages/world-module-erc20/src/codegen/tables/ERC20Metadata.sol b/packages/world-module-erc20/src/codegen/tables/ERC20Metadata.sol new file mode 100644 index 0000000000..30f9d1eb21 --- /dev/null +++ b/packages/world-module-erc20/src/codegen/tables/ERC20Metadata.sol @@ -0,0 +1,604 @@ +// SPDX-License-Identifier: MIT +pragma solidity >=0.8.24; + +/* Autogenerated file. Do not edit manually. */ + +// Import store internals +import { IStore } from "@latticexyz/store/src/IStore.sol"; +import { StoreSwitch } from "@latticexyz/store/src/StoreSwitch.sol"; +import { StoreCore } from "@latticexyz/store/src/StoreCore.sol"; +import { Bytes } from "@latticexyz/store/src/Bytes.sol"; +import { Memory } from "@latticexyz/store/src/Memory.sol"; +import { SliceLib } from "@latticexyz/store/src/Slice.sol"; +import { EncodeArray } from "@latticexyz/store/src/tightcoder/EncodeArray.sol"; +import { FieldLayout } from "@latticexyz/store/src/FieldLayout.sol"; +import { Schema } from "@latticexyz/store/src/Schema.sol"; +import { EncodedLengths, EncodedLengthsLib } from "@latticexyz/store/src/EncodedLengths.sol"; +import { ResourceId } from "@latticexyz/store/src/ResourceId.sol"; + +struct ERC20MetadataData { + uint8 decimals; + string name; + string symbol; +} + +library ERC20Metadata { + FieldLayout constant _fieldLayout = + FieldLayout.wrap(0x0001010201000000000000000000000000000000000000000000000000000000); + + // Hex-encoded key schema of () + Schema constant _keySchema = Schema.wrap(0x0000000000000000000000000000000000000000000000000000000000000000); + // Hex-encoded value schema of (uint8, string, string) + Schema constant _valueSchema = Schema.wrap(0x0001010200c5c500000000000000000000000000000000000000000000000000); + + /** + * @notice Get the table's key field names. + * @return keyNames An array of strings with the names of key fields. + */ + function getKeyNames() internal pure returns (string[] memory keyNames) { + keyNames = new string[](0); + } + + /** + * @notice Get the table's value field names. + * @return fieldNames An array of strings with the names of value fields. + */ + function getFieldNames() internal pure returns (string[] memory fieldNames) { + fieldNames = new string[](3); + fieldNames[0] = "decimals"; + fieldNames[1] = "name"; + fieldNames[2] = "symbol"; + } + + /** + * @notice Register the table with its config. + */ + function register(ResourceId _tableId) internal { + StoreSwitch.registerTable(_tableId, _fieldLayout, _keySchema, _valueSchema, getKeyNames(), getFieldNames()); + } + + /** + * @notice Register the table with its config. + */ + function _register(ResourceId _tableId) internal { + StoreCore.registerTable(_tableId, _fieldLayout, _keySchema, _valueSchema, getKeyNames(), getFieldNames()); + } + + /** + * @notice Get decimals. + */ + function getDecimals(ResourceId _tableId) internal view returns (uint8 decimals) { + bytes32[] memory _keyTuple = new bytes32[](0); + + bytes32 _blob = StoreSwitch.getStaticField(_tableId, _keyTuple, 0, _fieldLayout); + return (uint8(bytes1(_blob))); + } + + /** + * @notice Get decimals. + */ + function _getDecimals(ResourceId _tableId) internal view returns (uint8 decimals) { + bytes32[] memory _keyTuple = new bytes32[](0); + + bytes32 _blob = StoreCore.getStaticField(_tableId, _keyTuple, 0, _fieldLayout); + return (uint8(bytes1(_blob))); + } + + /** + * @notice Set decimals. + */ + function setDecimals(ResourceId _tableId, uint8 decimals) internal { + bytes32[] memory _keyTuple = new bytes32[](0); + + StoreSwitch.setStaticField(_tableId, _keyTuple, 0, abi.encodePacked((decimals)), _fieldLayout); + } + + /** + * @notice Set decimals. + */ + function _setDecimals(ResourceId _tableId, uint8 decimals) internal { + bytes32[] memory _keyTuple = new bytes32[](0); + + StoreCore.setStaticField(_tableId, _keyTuple, 0, abi.encodePacked((decimals)), _fieldLayout); + } + + /** + * @notice Get name. + */ + function getName(ResourceId _tableId) internal view returns (string memory name) { + bytes32[] memory _keyTuple = new bytes32[](0); + + bytes memory _blob = StoreSwitch.getDynamicField(_tableId, _keyTuple, 0); + return (string(_blob)); + } + + /** + * @notice Get name. + */ + function _getName(ResourceId _tableId) internal view returns (string memory name) { + bytes32[] memory _keyTuple = new bytes32[](0); + + bytes memory _blob = StoreCore.getDynamicField(_tableId, _keyTuple, 0); + return (string(_blob)); + } + + /** + * @notice Set name. + */ + function setName(ResourceId _tableId, string memory name) internal { + bytes32[] memory _keyTuple = new bytes32[](0); + + StoreSwitch.setDynamicField(_tableId, _keyTuple, 0, bytes((name))); + } + + /** + * @notice Set name. + */ + function _setName(ResourceId _tableId, string memory name) internal { + bytes32[] memory _keyTuple = new bytes32[](0); + + StoreCore.setDynamicField(_tableId, _keyTuple, 0, bytes((name))); + } + + /** + * @notice Get the length of name. + */ + function lengthName(ResourceId _tableId) internal view returns (uint256) { + bytes32[] memory _keyTuple = new bytes32[](0); + + uint256 _byteLength = StoreSwitch.getDynamicFieldLength(_tableId, _keyTuple, 0); + unchecked { + return _byteLength / 1; + } + } + + /** + * @notice Get the length of name. + */ + function _lengthName(ResourceId _tableId) internal view returns (uint256) { + bytes32[] memory _keyTuple = new bytes32[](0); + + uint256 _byteLength = StoreCore.getDynamicFieldLength(_tableId, _keyTuple, 0); + unchecked { + return _byteLength / 1; + } + } + + /** + * @notice Get an item of name. + * @dev Reverts with Store_IndexOutOfBounds if `_index` is out of bounds for the array. + */ + function getItemName(ResourceId _tableId, uint256 _index) internal view returns (string memory) { + bytes32[] memory _keyTuple = new bytes32[](0); + + unchecked { + bytes memory _blob = StoreSwitch.getDynamicFieldSlice(_tableId, _keyTuple, 0, _index * 1, (_index + 1) * 1); + return (string(_blob)); + } + } + + /** + * @notice Get an item of name. + * @dev Reverts with Store_IndexOutOfBounds if `_index` is out of bounds for the array. + */ + function _getItemName(ResourceId _tableId, uint256 _index) internal view returns (string memory) { + bytes32[] memory _keyTuple = new bytes32[](0); + + unchecked { + bytes memory _blob = StoreCore.getDynamicFieldSlice(_tableId, _keyTuple, 0, _index * 1, (_index + 1) * 1); + return (string(_blob)); + } + } + + /** + * @notice Push a slice to name. + */ + function pushName(ResourceId _tableId, string memory _slice) internal { + bytes32[] memory _keyTuple = new bytes32[](0); + + StoreSwitch.pushToDynamicField(_tableId, _keyTuple, 0, bytes((_slice))); + } + + /** + * @notice Push a slice to name. + */ + function _pushName(ResourceId _tableId, string memory _slice) internal { + bytes32[] memory _keyTuple = new bytes32[](0); + + StoreCore.pushToDynamicField(_tableId, _keyTuple, 0, bytes((_slice))); + } + + /** + * @notice Pop a slice from name. + */ + function popName(ResourceId _tableId) internal { + bytes32[] memory _keyTuple = new bytes32[](0); + + StoreSwitch.popFromDynamicField(_tableId, _keyTuple, 0, 1); + } + + /** + * @notice Pop a slice from name. + */ + function _popName(ResourceId _tableId) internal { + bytes32[] memory _keyTuple = new bytes32[](0); + + StoreCore.popFromDynamicField(_tableId, _keyTuple, 0, 1); + } + + /** + * @notice Update a slice of name at `_index`. + */ + function updateName(ResourceId _tableId, uint256 _index, string memory _slice) internal { + bytes32[] memory _keyTuple = new bytes32[](0); + + unchecked { + bytes memory _encoded = bytes((_slice)); + StoreSwitch.spliceDynamicData(_tableId, _keyTuple, 0, uint40(_index * 1), uint40(_encoded.length), _encoded); + } + } + + /** + * @notice Update a slice of name at `_index`. + */ + function _updateName(ResourceId _tableId, uint256 _index, string memory _slice) internal { + bytes32[] memory _keyTuple = new bytes32[](0); + + unchecked { + bytes memory _encoded = bytes((_slice)); + StoreCore.spliceDynamicData(_tableId, _keyTuple, 0, uint40(_index * 1), uint40(_encoded.length), _encoded); + } + } + + /** + * @notice Get symbol. + */ + function getSymbol(ResourceId _tableId) internal view returns (string memory symbol) { + bytes32[] memory _keyTuple = new bytes32[](0); + + bytes memory _blob = StoreSwitch.getDynamicField(_tableId, _keyTuple, 1); + return (string(_blob)); + } + + /** + * @notice Get symbol. + */ + function _getSymbol(ResourceId _tableId) internal view returns (string memory symbol) { + bytes32[] memory _keyTuple = new bytes32[](0); + + bytes memory _blob = StoreCore.getDynamicField(_tableId, _keyTuple, 1); + return (string(_blob)); + } + + /** + * @notice Set symbol. + */ + function setSymbol(ResourceId _tableId, string memory symbol) internal { + bytes32[] memory _keyTuple = new bytes32[](0); + + StoreSwitch.setDynamicField(_tableId, _keyTuple, 1, bytes((symbol))); + } + + /** + * @notice Set symbol. + */ + function _setSymbol(ResourceId _tableId, string memory symbol) internal { + bytes32[] memory _keyTuple = new bytes32[](0); + + StoreCore.setDynamicField(_tableId, _keyTuple, 1, bytes((symbol))); + } + + /** + * @notice Get the length of symbol. + */ + function lengthSymbol(ResourceId _tableId) internal view returns (uint256) { + bytes32[] memory _keyTuple = new bytes32[](0); + + uint256 _byteLength = StoreSwitch.getDynamicFieldLength(_tableId, _keyTuple, 1); + unchecked { + return _byteLength / 1; + } + } + + /** + * @notice Get the length of symbol. + */ + function _lengthSymbol(ResourceId _tableId) internal view returns (uint256) { + bytes32[] memory _keyTuple = new bytes32[](0); + + uint256 _byteLength = StoreCore.getDynamicFieldLength(_tableId, _keyTuple, 1); + unchecked { + return _byteLength / 1; + } + } + + /** + * @notice Get an item of symbol. + * @dev Reverts with Store_IndexOutOfBounds if `_index` is out of bounds for the array. + */ + function getItemSymbol(ResourceId _tableId, uint256 _index) internal view returns (string memory) { + bytes32[] memory _keyTuple = new bytes32[](0); + + unchecked { + bytes memory _blob = StoreSwitch.getDynamicFieldSlice(_tableId, _keyTuple, 1, _index * 1, (_index + 1) * 1); + return (string(_blob)); + } + } + + /** + * @notice Get an item of symbol. + * @dev Reverts with Store_IndexOutOfBounds if `_index` is out of bounds for the array. + */ + function _getItemSymbol(ResourceId _tableId, uint256 _index) internal view returns (string memory) { + bytes32[] memory _keyTuple = new bytes32[](0); + + unchecked { + bytes memory _blob = StoreCore.getDynamicFieldSlice(_tableId, _keyTuple, 1, _index * 1, (_index + 1) * 1); + return (string(_blob)); + } + } + + /** + * @notice Push a slice to symbol. + */ + function pushSymbol(ResourceId _tableId, string memory _slice) internal { + bytes32[] memory _keyTuple = new bytes32[](0); + + StoreSwitch.pushToDynamicField(_tableId, _keyTuple, 1, bytes((_slice))); + } + + /** + * @notice Push a slice to symbol. + */ + function _pushSymbol(ResourceId _tableId, string memory _slice) internal { + bytes32[] memory _keyTuple = new bytes32[](0); + + StoreCore.pushToDynamicField(_tableId, _keyTuple, 1, bytes((_slice))); + } + + /** + * @notice Pop a slice from symbol. + */ + function popSymbol(ResourceId _tableId) internal { + bytes32[] memory _keyTuple = new bytes32[](0); + + StoreSwitch.popFromDynamicField(_tableId, _keyTuple, 1, 1); + } + + /** + * @notice Pop a slice from symbol. + */ + function _popSymbol(ResourceId _tableId) internal { + bytes32[] memory _keyTuple = new bytes32[](0); + + StoreCore.popFromDynamicField(_tableId, _keyTuple, 1, 1); + } + + /** + * @notice Update a slice of symbol at `_index`. + */ + function updateSymbol(ResourceId _tableId, uint256 _index, string memory _slice) internal { + bytes32[] memory _keyTuple = new bytes32[](0); + + unchecked { + bytes memory _encoded = bytes((_slice)); + StoreSwitch.spliceDynamicData(_tableId, _keyTuple, 1, uint40(_index * 1), uint40(_encoded.length), _encoded); + } + } + + /** + * @notice Update a slice of symbol at `_index`. + */ + function _updateSymbol(ResourceId _tableId, uint256 _index, string memory _slice) internal { + bytes32[] memory _keyTuple = new bytes32[](0); + + unchecked { + bytes memory _encoded = bytes((_slice)); + StoreCore.spliceDynamicData(_tableId, _keyTuple, 1, uint40(_index * 1), uint40(_encoded.length), _encoded); + } + } + + /** + * @notice Get the full data. + */ + function get(ResourceId _tableId) internal view returns (ERC20MetadataData memory _table) { + bytes32[] memory _keyTuple = new bytes32[](0); + + (bytes memory _staticData, EncodedLengths _encodedLengths, bytes memory _dynamicData) = StoreSwitch.getRecord( + _tableId, + _keyTuple, + _fieldLayout + ); + return decode(_staticData, _encodedLengths, _dynamicData); + } + + /** + * @notice Get the full data. + */ + function _get(ResourceId _tableId) internal view returns (ERC20MetadataData memory _table) { + bytes32[] memory _keyTuple = new bytes32[](0); + + (bytes memory _staticData, EncodedLengths _encodedLengths, bytes memory _dynamicData) = StoreCore.getRecord( + _tableId, + _keyTuple, + _fieldLayout + ); + return decode(_staticData, _encodedLengths, _dynamicData); + } + + /** + * @notice Set the full data using individual values. + */ + function set(ResourceId _tableId, uint8 decimals, string memory name, string memory symbol) internal { + bytes memory _staticData = encodeStatic(decimals); + + EncodedLengths _encodedLengths = encodeLengths(name, symbol); + bytes memory _dynamicData = encodeDynamic(name, symbol); + + bytes32[] memory _keyTuple = new bytes32[](0); + + StoreSwitch.setRecord(_tableId, _keyTuple, _staticData, _encodedLengths, _dynamicData); + } + + /** + * @notice Set the full data using individual values. + */ + function _set(ResourceId _tableId, uint8 decimals, string memory name, string memory symbol) internal { + bytes memory _staticData = encodeStatic(decimals); + + EncodedLengths _encodedLengths = encodeLengths(name, symbol); + bytes memory _dynamicData = encodeDynamic(name, symbol); + + bytes32[] memory _keyTuple = new bytes32[](0); + + StoreCore.setRecord(_tableId, _keyTuple, _staticData, _encodedLengths, _dynamicData, _fieldLayout); + } + + /** + * @notice Set the full data using the data struct. + */ + function set(ResourceId _tableId, ERC20MetadataData memory _table) internal { + bytes memory _staticData = encodeStatic(_table.decimals); + + EncodedLengths _encodedLengths = encodeLengths(_table.name, _table.symbol); + bytes memory _dynamicData = encodeDynamic(_table.name, _table.symbol); + + bytes32[] memory _keyTuple = new bytes32[](0); + + StoreSwitch.setRecord(_tableId, _keyTuple, _staticData, _encodedLengths, _dynamicData); + } + + /** + * @notice Set the full data using the data struct. + */ + function _set(ResourceId _tableId, ERC20MetadataData memory _table) internal { + bytes memory _staticData = encodeStatic(_table.decimals); + + EncodedLengths _encodedLengths = encodeLengths(_table.name, _table.symbol); + bytes memory _dynamicData = encodeDynamic(_table.name, _table.symbol); + + bytes32[] memory _keyTuple = new bytes32[](0); + + StoreCore.setRecord(_tableId, _keyTuple, _staticData, _encodedLengths, _dynamicData, _fieldLayout); + } + + /** + * @notice Decode the tightly packed blob of static data using this table's field layout. + */ + function decodeStatic(bytes memory _blob) internal pure returns (uint8 decimals) { + decimals = (uint8(Bytes.getBytes1(_blob, 0))); + } + + /** + * @notice Decode the tightly packed blob of dynamic data using the encoded lengths. + */ + function decodeDynamic( + EncodedLengths _encodedLengths, + bytes memory _blob + ) internal pure returns (string memory name, string memory symbol) { + uint256 _start; + uint256 _end; + unchecked { + _end = _encodedLengths.atIndex(0); + } + name = (string(SliceLib.getSubslice(_blob, _start, _end).toBytes())); + + _start = _end; + unchecked { + _end += _encodedLengths.atIndex(1); + } + symbol = (string(SliceLib.getSubslice(_blob, _start, _end).toBytes())); + } + + /** + * @notice Decode the tightly packed blobs using this table's field layout. + * @param _staticData Tightly packed static fields. + * @param _encodedLengths Encoded lengths of dynamic fields. + * @param _dynamicData Tightly packed dynamic fields. + */ + function decode( + bytes memory _staticData, + EncodedLengths _encodedLengths, + bytes memory _dynamicData + ) internal pure returns (ERC20MetadataData memory _table) { + (_table.decimals) = decodeStatic(_staticData); + + (_table.name, _table.symbol) = decodeDynamic(_encodedLengths, _dynamicData); + } + + /** + * @notice Delete all data for given keys. + */ + function deleteRecord(ResourceId _tableId) internal { + bytes32[] memory _keyTuple = new bytes32[](0); + + StoreSwitch.deleteRecord(_tableId, _keyTuple); + } + + /** + * @notice Delete all data for given keys. + */ + function _deleteRecord(ResourceId _tableId) internal { + bytes32[] memory _keyTuple = new bytes32[](0); + + StoreCore.deleteRecord(_tableId, _keyTuple, _fieldLayout); + } + + /** + * @notice Tightly pack static (fixed length) data using this table's schema. + * @return The static data, encoded into a sequence of bytes. + */ + function encodeStatic(uint8 decimals) internal pure returns (bytes memory) { + return abi.encodePacked(decimals); + } + + /** + * @notice Tightly pack dynamic data lengths using this table's schema. + * @return _encodedLengths The lengths of the dynamic fields (packed into a single bytes32 value). + */ + function encodeLengths( + string memory name, + string memory symbol + ) internal pure returns (EncodedLengths _encodedLengths) { + // Lengths are effectively checked during copy by 2**40 bytes exceeding gas limits + unchecked { + _encodedLengths = EncodedLengthsLib.pack(bytes(name).length, bytes(symbol).length); + } + } + + /** + * @notice Tightly pack dynamic (variable length) data using this table's schema. + * @return The dynamic data, encoded into a sequence of bytes. + */ + function encodeDynamic(string memory name, string memory symbol) internal pure returns (bytes memory) { + return abi.encodePacked(bytes((name)), bytes((symbol))); + } + + /** + * @notice Encode all of a record's fields. + * @return The static (fixed length) data, encoded into a sequence of bytes. + * @return The lengths of the dynamic fields (packed into a single bytes32 value). + * @return The dynamic (variable length) data, encoded into a sequence of bytes. + */ + function encode( + uint8 decimals, + string memory name, + string memory symbol + ) internal pure returns (bytes memory, EncodedLengths, bytes memory) { + bytes memory _staticData = encodeStatic(decimals); + + EncodedLengths _encodedLengths = encodeLengths(name, symbol); + bytes memory _dynamicData = encodeDynamic(name, symbol); + + return (_staticData, _encodedLengths, _dynamicData); + } + + /** + * @notice Encode keys as a bytes32 array using this table's field layout. + */ + function encodeKeyTuple() internal pure returns (bytes32[] memory) { + bytes32[] memory _keyTuple = new bytes32[](0); + + return _keyTuple; + } +} diff --git a/packages/world-module-erc20/src/codegen/tables/ERC20Registry.sol b/packages/world-module-erc20/src/codegen/tables/ERC20Registry.sol new file mode 100644 index 0000000000..421ebfd51e --- /dev/null +++ b/packages/world-module-erc20/src/codegen/tables/ERC20Registry.sol @@ -0,0 +1,199 @@ +// SPDX-License-Identifier: MIT +pragma solidity >=0.8.24; + +/* Autogenerated file. Do not edit manually. */ + +// Import store internals +import { IStore } from "@latticexyz/store/src/IStore.sol"; +import { StoreSwitch } from "@latticexyz/store/src/StoreSwitch.sol"; +import { StoreCore } from "@latticexyz/store/src/StoreCore.sol"; +import { Bytes } from "@latticexyz/store/src/Bytes.sol"; +import { Memory } from "@latticexyz/store/src/Memory.sol"; +import { SliceLib } from "@latticexyz/store/src/Slice.sol"; +import { EncodeArray } from "@latticexyz/store/src/tightcoder/EncodeArray.sol"; +import { FieldLayout } from "@latticexyz/store/src/FieldLayout.sol"; +import { Schema } from "@latticexyz/store/src/Schema.sol"; +import { EncodedLengths, EncodedLengthsLib } from "@latticexyz/store/src/EncodedLengths.sol"; +import { ResourceId } from "@latticexyz/store/src/ResourceId.sol"; + +// Import user types +import { ResourceId } from "@latticexyz/store/src/ResourceId.sol"; + +library ERC20Registry { + FieldLayout constant _fieldLayout = + FieldLayout.wrap(0x0014010014000000000000000000000000000000000000000000000000000000); + + // Hex-encoded key schema of (bytes32) + Schema constant _keySchema = Schema.wrap(0x002001005f000000000000000000000000000000000000000000000000000000); + // Hex-encoded value schema of (address) + Schema constant _valueSchema = Schema.wrap(0x0014010061000000000000000000000000000000000000000000000000000000); + + /** + * @notice Get the table's key field names. + * @return keyNames An array of strings with the names of key fields. + */ + function getKeyNames() internal pure returns (string[] memory keyNames) { + keyNames = new string[](1); + keyNames[0] = "namespaceId"; + } + + /** + * @notice Get the table's value field names. + * @return fieldNames An array of strings with the names of value fields. + */ + function getFieldNames() internal pure returns (string[] memory fieldNames) { + fieldNames = new string[](1); + fieldNames[0] = "tokenAddress"; + } + + /** + * @notice Register the table with its config. + */ + function register(ResourceId _tableId) internal { + StoreSwitch.registerTable(_tableId, _fieldLayout, _keySchema, _valueSchema, getKeyNames(), getFieldNames()); + } + + /** + * @notice Register the table with its config. + */ + function _register(ResourceId _tableId) internal { + StoreCore.registerTable(_tableId, _fieldLayout, _keySchema, _valueSchema, getKeyNames(), getFieldNames()); + } + + /** + * @notice Get tokenAddress. + */ + function getTokenAddress(ResourceId _tableId, ResourceId namespaceId) internal view returns (address tokenAddress) { + bytes32[] memory _keyTuple = new bytes32[](1); + _keyTuple[0] = ResourceId.unwrap(namespaceId); + + bytes32 _blob = StoreSwitch.getStaticField(_tableId, _keyTuple, 0, _fieldLayout); + return (address(bytes20(_blob))); + } + + /** + * @notice Get tokenAddress. + */ + function _getTokenAddress(ResourceId _tableId, ResourceId namespaceId) internal view returns (address tokenAddress) { + bytes32[] memory _keyTuple = new bytes32[](1); + _keyTuple[0] = ResourceId.unwrap(namespaceId); + + bytes32 _blob = StoreCore.getStaticField(_tableId, _keyTuple, 0, _fieldLayout); + return (address(bytes20(_blob))); + } + + /** + * @notice Get tokenAddress. + */ + function get(ResourceId _tableId, ResourceId namespaceId) internal view returns (address tokenAddress) { + bytes32[] memory _keyTuple = new bytes32[](1); + _keyTuple[0] = ResourceId.unwrap(namespaceId); + + bytes32 _blob = StoreSwitch.getStaticField(_tableId, _keyTuple, 0, _fieldLayout); + return (address(bytes20(_blob))); + } + + /** + * @notice Get tokenAddress. + */ + function _get(ResourceId _tableId, ResourceId namespaceId) internal view returns (address tokenAddress) { + bytes32[] memory _keyTuple = new bytes32[](1); + _keyTuple[0] = ResourceId.unwrap(namespaceId); + + bytes32 _blob = StoreCore.getStaticField(_tableId, _keyTuple, 0, _fieldLayout); + return (address(bytes20(_blob))); + } + + /** + * @notice Set tokenAddress. + */ + function setTokenAddress(ResourceId _tableId, ResourceId namespaceId, address tokenAddress) internal { + bytes32[] memory _keyTuple = new bytes32[](1); + _keyTuple[0] = ResourceId.unwrap(namespaceId); + + StoreSwitch.setStaticField(_tableId, _keyTuple, 0, abi.encodePacked((tokenAddress)), _fieldLayout); + } + + /** + * @notice Set tokenAddress. + */ + function _setTokenAddress(ResourceId _tableId, ResourceId namespaceId, address tokenAddress) internal { + bytes32[] memory _keyTuple = new bytes32[](1); + _keyTuple[0] = ResourceId.unwrap(namespaceId); + + StoreCore.setStaticField(_tableId, _keyTuple, 0, abi.encodePacked((tokenAddress)), _fieldLayout); + } + + /** + * @notice Set tokenAddress. + */ + function set(ResourceId _tableId, ResourceId namespaceId, address tokenAddress) internal { + bytes32[] memory _keyTuple = new bytes32[](1); + _keyTuple[0] = ResourceId.unwrap(namespaceId); + + StoreSwitch.setStaticField(_tableId, _keyTuple, 0, abi.encodePacked((tokenAddress)), _fieldLayout); + } + + /** + * @notice Set tokenAddress. + */ + function _set(ResourceId _tableId, ResourceId namespaceId, address tokenAddress) internal { + bytes32[] memory _keyTuple = new bytes32[](1); + _keyTuple[0] = ResourceId.unwrap(namespaceId); + + StoreCore.setStaticField(_tableId, _keyTuple, 0, abi.encodePacked((tokenAddress)), _fieldLayout); + } + + /** + * @notice Delete all data for given keys. + */ + function deleteRecord(ResourceId _tableId, ResourceId namespaceId) internal { + bytes32[] memory _keyTuple = new bytes32[](1); + _keyTuple[0] = ResourceId.unwrap(namespaceId); + + StoreSwitch.deleteRecord(_tableId, _keyTuple); + } + + /** + * @notice Delete all data for given keys. + */ + function _deleteRecord(ResourceId _tableId, ResourceId namespaceId) internal { + bytes32[] memory _keyTuple = new bytes32[](1); + _keyTuple[0] = ResourceId.unwrap(namespaceId); + + StoreCore.deleteRecord(_tableId, _keyTuple, _fieldLayout); + } + + /** + * @notice Tightly pack static (fixed length) data using this table's schema. + * @return The static data, encoded into a sequence of bytes. + */ + function encodeStatic(address tokenAddress) internal pure returns (bytes memory) { + return abi.encodePacked(tokenAddress); + } + + /** + * @notice Encode all of a record's fields. + * @return The static (fixed length) data, encoded into a sequence of bytes. + * @return The lengths of the dynamic fields (packed into a single bytes32 value). + * @return The dynamic (variable length) data, encoded into a sequence of bytes. + */ + function encode(address tokenAddress) internal pure returns (bytes memory, EncodedLengths, bytes memory) { + bytes memory _staticData = encodeStatic(tokenAddress); + + EncodedLengths _encodedLengths; + bytes memory _dynamicData; + + return (_staticData, _encodedLengths, _dynamicData); + } + + /** + * @notice Encode keys as a bytes32 array using this table's field layout. + */ + function encodeKeyTuple(ResourceId namespaceId) internal pure returns (bytes32[] memory) { + bytes32[] memory _keyTuple = new bytes32[](1); + _keyTuple[0] = ResourceId.unwrap(namespaceId); + + return _keyTuple; + } +} diff --git a/packages/world-module-erc20/src/codegen/tables/Owner.sol b/packages/world-module-erc20/src/codegen/tables/Owner.sol new file mode 100644 index 0000000000..497c940313 --- /dev/null +++ b/packages/world-module-erc20/src/codegen/tables/Owner.sol @@ -0,0 +1,184 @@ +// SPDX-License-Identifier: MIT +pragma solidity >=0.8.24; + +/* Autogenerated file. Do not edit manually. */ + +// Import store internals +import { IStore } from "@latticexyz/store/src/IStore.sol"; +import { StoreSwitch } from "@latticexyz/store/src/StoreSwitch.sol"; +import { StoreCore } from "@latticexyz/store/src/StoreCore.sol"; +import { Bytes } from "@latticexyz/store/src/Bytes.sol"; +import { Memory } from "@latticexyz/store/src/Memory.sol"; +import { SliceLib } from "@latticexyz/store/src/Slice.sol"; +import { EncodeArray } from "@latticexyz/store/src/tightcoder/EncodeArray.sol"; +import { FieldLayout } from "@latticexyz/store/src/FieldLayout.sol"; +import { Schema } from "@latticexyz/store/src/Schema.sol"; +import { EncodedLengths, EncodedLengthsLib } from "@latticexyz/store/src/EncodedLengths.sol"; +import { ResourceId } from "@latticexyz/store/src/ResourceId.sol"; + +library Owner { + FieldLayout constant _fieldLayout = + FieldLayout.wrap(0x0014010014000000000000000000000000000000000000000000000000000000); + + // Hex-encoded key schema of () + Schema constant _keySchema = Schema.wrap(0x0000000000000000000000000000000000000000000000000000000000000000); + // Hex-encoded value schema of (address) + Schema constant _valueSchema = Schema.wrap(0x0014010061000000000000000000000000000000000000000000000000000000); + + /** + * @notice Get the table's key field names. + * @return keyNames An array of strings with the names of key fields. + */ + function getKeyNames() internal pure returns (string[] memory keyNames) { + keyNames = new string[](0); + } + + /** + * @notice Get the table's value field names. + * @return fieldNames An array of strings with the names of value fields. + */ + function getFieldNames() internal pure returns (string[] memory fieldNames) { + fieldNames = new string[](1); + fieldNames[0] = "value"; + } + + /** + * @notice Register the table with its config. + */ + function register(ResourceId _tableId) internal { + StoreSwitch.registerTable(_tableId, _fieldLayout, _keySchema, _valueSchema, getKeyNames(), getFieldNames()); + } + + /** + * @notice Register the table with its config. + */ + function _register(ResourceId _tableId) internal { + StoreCore.registerTable(_tableId, _fieldLayout, _keySchema, _valueSchema, getKeyNames(), getFieldNames()); + } + + /** + * @notice Get value. + */ + function getValue(ResourceId _tableId) internal view returns (address value) { + bytes32[] memory _keyTuple = new bytes32[](0); + + bytes32 _blob = StoreSwitch.getStaticField(_tableId, _keyTuple, 0, _fieldLayout); + return (address(bytes20(_blob))); + } + + /** + * @notice Get value. + */ + function _getValue(ResourceId _tableId) internal view returns (address value) { + bytes32[] memory _keyTuple = new bytes32[](0); + + bytes32 _blob = StoreCore.getStaticField(_tableId, _keyTuple, 0, _fieldLayout); + return (address(bytes20(_blob))); + } + + /** + * @notice Get value. + */ + function get(ResourceId _tableId) internal view returns (address value) { + bytes32[] memory _keyTuple = new bytes32[](0); + + bytes32 _blob = StoreSwitch.getStaticField(_tableId, _keyTuple, 0, _fieldLayout); + return (address(bytes20(_blob))); + } + + /** + * @notice Get value. + */ + function _get(ResourceId _tableId) internal view returns (address value) { + bytes32[] memory _keyTuple = new bytes32[](0); + + bytes32 _blob = StoreCore.getStaticField(_tableId, _keyTuple, 0, _fieldLayout); + return (address(bytes20(_blob))); + } + + /** + * @notice Set value. + */ + function setValue(ResourceId _tableId, address value) internal { + bytes32[] memory _keyTuple = new bytes32[](0); + + StoreSwitch.setStaticField(_tableId, _keyTuple, 0, abi.encodePacked((value)), _fieldLayout); + } + + /** + * @notice Set value. + */ + function _setValue(ResourceId _tableId, address value) internal { + bytes32[] memory _keyTuple = new bytes32[](0); + + StoreCore.setStaticField(_tableId, _keyTuple, 0, abi.encodePacked((value)), _fieldLayout); + } + + /** + * @notice Set value. + */ + function set(ResourceId _tableId, address value) internal { + bytes32[] memory _keyTuple = new bytes32[](0); + + StoreSwitch.setStaticField(_tableId, _keyTuple, 0, abi.encodePacked((value)), _fieldLayout); + } + + /** + * @notice Set value. + */ + function _set(ResourceId _tableId, address value) internal { + bytes32[] memory _keyTuple = new bytes32[](0); + + StoreCore.setStaticField(_tableId, _keyTuple, 0, abi.encodePacked((value)), _fieldLayout); + } + + /** + * @notice Delete all data for given keys. + */ + function deleteRecord(ResourceId _tableId) internal { + bytes32[] memory _keyTuple = new bytes32[](0); + + StoreSwitch.deleteRecord(_tableId, _keyTuple); + } + + /** + * @notice Delete all data for given keys. + */ + function _deleteRecord(ResourceId _tableId) internal { + bytes32[] memory _keyTuple = new bytes32[](0); + + StoreCore.deleteRecord(_tableId, _keyTuple, _fieldLayout); + } + + /** + * @notice Tightly pack static (fixed length) data using this table's schema. + * @return The static data, encoded into a sequence of bytes. + */ + function encodeStatic(address value) internal pure returns (bytes memory) { + return abi.encodePacked(value); + } + + /** + * @notice Encode all of a record's fields. + * @return The static (fixed length) data, encoded into a sequence of bytes. + * @return The lengths of the dynamic fields (packed into a single bytes32 value). + * @return The dynamic (variable length) data, encoded into a sequence of bytes. + */ + function encode(address value) internal pure returns (bytes memory, EncodedLengths, bytes memory) { + bytes memory _staticData = encodeStatic(value); + + EncodedLengths _encodedLengths; + bytes memory _dynamicData; + + return (_staticData, _encodedLengths, _dynamicData); + } + + /** + * @notice Encode keys as a bytes32 array using this table's field layout. + */ + function encodeKeyTuple() internal pure returns (bytes32[] memory) { + bytes32[] memory _keyTuple = new bytes32[](0); + + return _keyTuple; + } +} diff --git a/packages/world-module-erc20/src/codegen/tables/Paused.sol b/packages/world-module-erc20/src/codegen/tables/Paused.sol new file mode 100644 index 0000000000..382633cd0e --- /dev/null +++ b/packages/world-module-erc20/src/codegen/tables/Paused.sol @@ -0,0 +1,196 @@ +// SPDX-License-Identifier: MIT +pragma solidity >=0.8.24; + +/* Autogenerated file. Do not edit manually. */ + +// Import store internals +import { IStore } from "@latticexyz/store/src/IStore.sol"; +import { StoreSwitch } from "@latticexyz/store/src/StoreSwitch.sol"; +import { StoreCore } from "@latticexyz/store/src/StoreCore.sol"; +import { Bytes } from "@latticexyz/store/src/Bytes.sol"; +import { Memory } from "@latticexyz/store/src/Memory.sol"; +import { SliceLib } from "@latticexyz/store/src/Slice.sol"; +import { EncodeArray } from "@latticexyz/store/src/tightcoder/EncodeArray.sol"; +import { FieldLayout } from "@latticexyz/store/src/FieldLayout.sol"; +import { Schema } from "@latticexyz/store/src/Schema.sol"; +import { EncodedLengths, EncodedLengthsLib } from "@latticexyz/store/src/EncodedLengths.sol"; +import { ResourceId } from "@latticexyz/store/src/ResourceId.sol"; + +library Paused { + FieldLayout constant _fieldLayout = + FieldLayout.wrap(0x0001010001000000000000000000000000000000000000000000000000000000); + + // Hex-encoded key schema of () + Schema constant _keySchema = Schema.wrap(0x0000000000000000000000000000000000000000000000000000000000000000); + // Hex-encoded value schema of (bool) + Schema constant _valueSchema = Schema.wrap(0x0001010060000000000000000000000000000000000000000000000000000000); + + /** + * @notice Get the table's key field names. + * @return keyNames An array of strings with the names of key fields. + */ + function getKeyNames() internal pure returns (string[] memory keyNames) { + keyNames = new string[](0); + } + + /** + * @notice Get the table's value field names. + * @return fieldNames An array of strings with the names of value fields. + */ + function getFieldNames() internal pure returns (string[] memory fieldNames) { + fieldNames = new string[](1); + fieldNames[0] = "paused"; + } + + /** + * @notice Register the table with its config. + */ + function register(ResourceId _tableId) internal { + StoreSwitch.registerTable(_tableId, _fieldLayout, _keySchema, _valueSchema, getKeyNames(), getFieldNames()); + } + + /** + * @notice Register the table with its config. + */ + function _register(ResourceId _tableId) internal { + StoreCore.registerTable(_tableId, _fieldLayout, _keySchema, _valueSchema, getKeyNames(), getFieldNames()); + } + + /** + * @notice Get paused. + */ + function getPaused(ResourceId _tableId) internal view returns (bool paused) { + bytes32[] memory _keyTuple = new bytes32[](0); + + bytes32 _blob = StoreSwitch.getStaticField(_tableId, _keyTuple, 0, _fieldLayout); + return (_toBool(uint8(bytes1(_blob)))); + } + + /** + * @notice Get paused. + */ + function _getPaused(ResourceId _tableId) internal view returns (bool paused) { + bytes32[] memory _keyTuple = new bytes32[](0); + + bytes32 _blob = StoreCore.getStaticField(_tableId, _keyTuple, 0, _fieldLayout); + return (_toBool(uint8(bytes1(_blob)))); + } + + /** + * @notice Get paused. + */ + function get(ResourceId _tableId) internal view returns (bool paused) { + bytes32[] memory _keyTuple = new bytes32[](0); + + bytes32 _blob = StoreSwitch.getStaticField(_tableId, _keyTuple, 0, _fieldLayout); + return (_toBool(uint8(bytes1(_blob)))); + } + + /** + * @notice Get paused. + */ + function _get(ResourceId _tableId) internal view returns (bool paused) { + bytes32[] memory _keyTuple = new bytes32[](0); + + bytes32 _blob = StoreCore.getStaticField(_tableId, _keyTuple, 0, _fieldLayout); + return (_toBool(uint8(bytes1(_blob)))); + } + + /** + * @notice Set paused. + */ + function setPaused(ResourceId _tableId, bool paused) internal { + bytes32[] memory _keyTuple = new bytes32[](0); + + StoreSwitch.setStaticField(_tableId, _keyTuple, 0, abi.encodePacked((paused)), _fieldLayout); + } + + /** + * @notice Set paused. + */ + function _setPaused(ResourceId _tableId, bool paused) internal { + bytes32[] memory _keyTuple = new bytes32[](0); + + StoreCore.setStaticField(_tableId, _keyTuple, 0, abi.encodePacked((paused)), _fieldLayout); + } + + /** + * @notice Set paused. + */ + function set(ResourceId _tableId, bool paused) internal { + bytes32[] memory _keyTuple = new bytes32[](0); + + StoreSwitch.setStaticField(_tableId, _keyTuple, 0, abi.encodePacked((paused)), _fieldLayout); + } + + /** + * @notice Set paused. + */ + function _set(ResourceId _tableId, bool paused) internal { + bytes32[] memory _keyTuple = new bytes32[](0); + + StoreCore.setStaticField(_tableId, _keyTuple, 0, abi.encodePacked((paused)), _fieldLayout); + } + + /** + * @notice Delete all data for given keys. + */ + function deleteRecord(ResourceId _tableId) internal { + bytes32[] memory _keyTuple = new bytes32[](0); + + StoreSwitch.deleteRecord(_tableId, _keyTuple); + } + + /** + * @notice Delete all data for given keys. + */ + function _deleteRecord(ResourceId _tableId) internal { + bytes32[] memory _keyTuple = new bytes32[](0); + + StoreCore.deleteRecord(_tableId, _keyTuple, _fieldLayout); + } + + /** + * @notice Tightly pack static (fixed length) data using this table's schema. + * @return The static data, encoded into a sequence of bytes. + */ + function encodeStatic(bool paused) internal pure returns (bytes memory) { + return abi.encodePacked(paused); + } + + /** + * @notice Encode all of a record's fields. + * @return The static (fixed length) data, encoded into a sequence of bytes. + * @return The lengths of the dynamic fields (packed into a single bytes32 value). + * @return The dynamic (variable length) data, encoded into a sequence of bytes. + */ + function encode(bool paused) internal pure returns (bytes memory, EncodedLengths, bytes memory) { + bytes memory _staticData = encodeStatic(paused); + + EncodedLengths _encodedLengths; + bytes memory _dynamicData; + + return (_staticData, _encodedLengths, _dynamicData); + } + + /** + * @notice Encode keys as a bytes32 array using this table's field layout. + */ + function encodeKeyTuple() internal pure returns (bytes32[] memory) { + bytes32[] memory _keyTuple = new bytes32[](0); + + return _keyTuple; + } +} + +/** + * @notice Cast a value to a bool. + * @dev Boolean values are encoded as uint8 (1 = true, 0 = false), but Solidity doesn't allow casting between uint8 and bool. + * @param value The uint8 value to convert. + * @return result The boolean value. + */ +function _toBool(uint8 value) pure returns (bool result) { + assembly { + result := value + } +} diff --git a/packages/world-module-erc20/src/codegen/tables/TotalSupply.sol b/packages/world-module-erc20/src/codegen/tables/TotalSupply.sol new file mode 100644 index 0000000000..ee12089747 --- /dev/null +++ b/packages/world-module-erc20/src/codegen/tables/TotalSupply.sol @@ -0,0 +1,184 @@ +// SPDX-License-Identifier: MIT +pragma solidity >=0.8.24; + +/* Autogenerated file. Do not edit manually. */ + +// Import store internals +import { IStore } from "@latticexyz/store/src/IStore.sol"; +import { StoreSwitch } from "@latticexyz/store/src/StoreSwitch.sol"; +import { StoreCore } from "@latticexyz/store/src/StoreCore.sol"; +import { Bytes } from "@latticexyz/store/src/Bytes.sol"; +import { Memory } from "@latticexyz/store/src/Memory.sol"; +import { SliceLib } from "@latticexyz/store/src/Slice.sol"; +import { EncodeArray } from "@latticexyz/store/src/tightcoder/EncodeArray.sol"; +import { FieldLayout } from "@latticexyz/store/src/FieldLayout.sol"; +import { Schema } from "@latticexyz/store/src/Schema.sol"; +import { EncodedLengths, EncodedLengthsLib } from "@latticexyz/store/src/EncodedLengths.sol"; +import { ResourceId } from "@latticexyz/store/src/ResourceId.sol"; + +library TotalSupply { + FieldLayout constant _fieldLayout = + FieldLayout.wrap(0x0020010020000000000000000000000000000000000000000000000000000000); + + // Hex-encoded key schema of () + Schema constant _keySchema = Schema.wrap(0x0000000000000000000000000000000000000000000000000000000000000000); + // Hex-encoded value schema of (uint256) + Schema constant _valueSchema = Schema.wrap(0x002001001f000000000000000000000000000000000000000000000000000000); + + /** + * @notice Get the table's key field names. + * @return keyNames An array of strings with the names of key fields. + */ + function getKeyNames() internal pure returns (string[] memory keyNames) { + keyNames = new string[](0); + } + + /** + * @notice Get the table's value field names. + * @return fieldNames An array of strings with the names of value fields. + */ + function getFieldNames() internal pure returns (string[] memory fieldNames) { + fieldNames = new string[](1); + fieldNames[0] = "totalSupply"; + } + + /** + * @notice Register the table with its config. + */ + function register(ResourceId _tableId) internal { + StoreSwitch.registerTable(_tableId, _fieldLayout, _keySchema, _valueSchema, getKeyNames(), getFieldNames()); + } + + /** + * @notice Register the table with its config. + */ + function _register(ResourceId _tableId) internal { + StoreCore.registerTable(_tableId, _fieldLayout, _keySchema, _valueSchema, getKeyNames(), getFieldNames()); + } + + /** + * @notice Get totalSupply. + */ + function getTotalSupply(ResourceId _tableId) internal view returns (uint256 totalSupply) { + bytes32[] memory _keyTuple = new bytes32[](0); + + bytes32 _blob = StoreSwitch.getStaticField(_tableId, _keyTuple, 0, _fieldLayout); + return (uint256(bytes32(_blob))); + } + + /** + * @notice Get totalSupply. + */ + function _getTotalSupply(ResourceId _tableId) internal view returns (uint256 totalSupply) { + bytes32[] memory _keyTuple = new bytes32[](0); + + bytes32 _blob = StoreCore.getStaticField(_tableId, _keyTuple, 0, _fieldLayout); + return (uint256(bytes32(_blob))); + } + + /** + * @notice Get totalSupply. + */ + function get(ResourceId _tableId) internal view returns (uint256 totalSupply) { + bytes32[] memory _keyTuple = new bytes32[](0); + + bytes32 _blob = StoreSwitch.getStaticField(_tableId, _keyTuple, 0, _fieldLayout); + return (uint256(bytes32(_blob))); + } + + /** + * @notice Get totalSupply. + */ + function _get(ResourceId _tableId) internal view returns (uint256 totalSupply) { + bytes32[] memory _keyTuple = new bytes32[](0); + + bytes32 _blob = StoreCore.getStaticField(_tableId, _keyTuple, 0, _fieldLayout); + return (uint256(bytes32(_blob))); + } + + /** + * @notice Set totalSupply. + */ + function setTotalSupply(ResourceId _tableId, uint256 totalSupply) internal { + bytes32[] memory _keyTuple = new bytes32[](0); + + StoreSwitch.setStaticField(_tableId, _keyTuple, 0, abi.encodePacked((totalSupply)), _fieldLayout); + } + + /** + * @notice Set totalSupply. + */ + function _setTotalSupply(ResourceId _tableId, uint256 totalSupply) internal { + bytes32[] memory _keyTuple = new bytes32[](0); + + StoreCore.setStaticField(_tableId, _keyTuple, 0, abi.encodePacked((totalSupply)), _fieldLayout); + } + + /** + * @notice Set totalSupply. + */ + function set(ResourceId _tableId, uint256 totalSupply) internal { + bytes32[] memory _keyTuple = new bytes32[](0); + + StoreSwitch.setStaticField(_tableId, _keyTuple, 0, abi.encodePacked((totalSupply)), _fieldLayout); + } + + /** + * @notice Set totalSupply. + */ + function _set(ResourceId _tableId, uint256 totalSupply) internal { + bytes32[] memory _keyTuple = new bytes32[](0); + + StoreCore.setStaticField(_tableId, _keyTuple, 0, abi.encodePacked((totalSupply)), _fieldLayout); + } + + /** + * @notice Delete all data for given keys. + */ + function deleteRecord(ResourceId _tableId) internal { + bytes32[] memory _keyTuple = new bytes32[](0); + + StoreSwitch.deleteRecord(_tableId, _keyTuple); + } + + /** + * @notice Delete all data for given keys. + */ + function _deleteRecord(ResourceId _tableId) internal { + bytes32[] memory _keyTuple = new bytes32[](0); + + StoreCore.deleteRecord(_tableId, _keyTuple, _fieldLayout); + } + + /** + * @notice Tightly pack static (fixed length) data using this table's schema. + * @return The static data, encoded into a sequence of bytes. + */ + function encodeStatic(uint256 totalSupply) internal pure returns (bytes memory) { + return abi.encodePacked(totalSupply); + } + + /** + * @notice Encode all of a record's fields. + * @return The static (fixed length) data, encoded into a sequence of bytes. + * @return The lengths of the dynamic fields (packed into a single bytes32 value). + * @return The dynamic (variable length) data, encoded into a sequence of bytes. + */ + function encode(uint256 totalSupply) internal pure returns (bytes memory, EncodedLengths, bytes memory) { + bytes memory _staticData = encodeStatic(totalSupply); + + EncodedLengths _encodedLengths; + bytes memory _dynamicData; + + return (_staticData, _encodedLengths, _dynamicData); + } + + /** + * @notice Encode keys as a bytes32 array using this table's field layout. + */ + function encodeKeyTuple() internal pure returns (bytes32[] memory) { + bytes32[] memory _keyTuple = new bytes32[](0); + + return _keyTuple; + } +} diff --git a/packages/world-module-erc20/src/examples/ERC20WithInternalStore.sol b/packages/world-module-erc20/src/examples/ERC20WithInternalStore.sol new file mode 100644 index 0000000000..4b1752c729 --- /dev/null +++ b/packages/world-module-erc20/src/examples/ERC20WithInternalStore.sol @@ -0,0 +1,32 @@ +// SPDX-License-Identifier: MIT +pragma solidity >=0.8.24; + +// Adapted example from OpenZeppelin's Contract Wizard: https://wizard.openzeppelin.com/ + +import { WithStore } from "../WithStore.sol"; +import { Ownable } from "../Ownable.sol"; +import { ERC20Pausable } from "../ERC20Pausable.sol"; +import { ERC20Burnable } from "../ERC20Burnable.sol"; +import { MUDERC20 } from "../MUDERC20.sol"; + +contract ERC20WithInternalStore is WithStore(address(this)), MUDERC20, ERC20Pausable, ERC20Burnable, Ownable { + constructor() MUDERC20("MyERC20", "MTK") Ownable(_msgSender()) {} + + function mint(address to, uint256 value) public onlyOwner { + _mint(to, value); + } + + function pause() public onlyOwner { + _pause(); + } + + function unpause() public onlyOwner { + _unpause(); + } + + // The following functions are overrides required by Solidity. + + function _update(address from, address to, uint256 value) internal override(MUDERC20, ERC20Pausable) { + super._update(from, to, value); + } +} diff --git a/packages/world-module-erc20/src/examples/ERC20WithWorld.sol b/packages/world-module-erc20/src/examples/ERC20WithWorld.sol new file mode 100644 index 0000000000..809efa3b2a --- /dev/null +++ b/packages/world-module-erc20/src/examples/ERC20WithWorld.sol @@ -0,0 +1,40 @@ +// SPDX-License-Identifier: MIT +pragma solidity >=0.8.24; + +// Adapted example from OpenZeppelin's Contract Wizard: https://wizard.openzeppelin.com/ + +import { IBaseWorld } from "@latticexyz/world/src/codegen/interfaces/IBaseWorld.sol"; +import { WithWorld } from "../WithWorld.sol"; +import { ERC20Pausable } from "../ERC20Pausable.sol"; +import { ERC20Burnable } from "../ERC20Burnable.sol"; +import { MUDERC20 } from "../MUDERC20.sol"; + +contract ERC20WithWorld is WithWorld, MUDERC20, ERC20Pausable, ERC20Burnable { + constructor( + IBaseWorld world, + bytes14 namespace, + string memory name, + string memory symbol + ) WithWorld(world, namespace) MUDERC20(name, symbol) { + // Transfer namespace ownership to the creator + world.transferOwnership(getNamespaceId(), _msgSender()); + } + + function mint(address to, uint256 value) public onlyNamespace { + _mint(to, value); + } + + function pause() public onlyNamespace { + _pause(); + } + + function unpause() public onlyNamespace { + _unpause(); + } + + // The following functions are overrides required by Solidity. + + function _update(address from, address to, uint256 value) internal override(MUDERC20, ERC20Pausable) { + super._update(from, to, value); + } +} diff --git a/packages/world-module-erc20/src/interfaces/IERC20.sol b/packages/world-module-erc20/src/interfaces/IERC20.sol new file mode 100644 index 0000000000..f535f5aac4 --- /dev/null +++ b/packages/world-module-erc20/src/interfaces/IERC20.sol @@ -0,0 +1,67 @@ +// SPDX-License-Identifier: MIT +// OpenZeppelin Contracts (last updated v5.0.0) (token/ERC20/IERC20.sol) + +pragma solidity >=0.8.24; + +import { IERC20Events } from "./IERC20Events.sol"; + +/** + * @dev Interface of the ERC20 standard as defined in the EIP. + */ +interface IERC20 is IERC20Events { + /** + * @dev Returns the value of tokens in existence. + */ + function totalSupply() external view returns (uint256); + + /** + * @dev Returns the value of tokens owned by `account`. + */ + function balanceOf(address account) external view returns (uint256); + + /** + * @dev Moves a `value` amount of tokens from the caller's account to `to`. + * + * Returns a boolean value indicating whether the operation succeeded. + * + * Emits a {Transfer} event. + */ + function transfer(address to, uint256 value) external returns (bool); + + /** + * @dev Returns the remaining number of tokens that `spender` will be + * allowed to spend on behalf of `owner` through {transferFrom}. This is + * zero by default. + * + * This value changes when {approve} or {transferFrom} are called. + */ + function allowance(address owner, address spender) external view returns (uint256); + + /** + * @dev Sets a `value` amount of tokens as the allowance of `spender` over the + * caller's tokens. + * + * Returns a boolean value indicating whether the operation succeeded. + * + * IMPORTANT: Beware that changing an allowance with this method brings the risk + * that someone may use both the old and the new allowance by unfortunate + * transaction ordering. One possible solution to mitigate this race + * condition is to first reduce the spender's allowance to 0 and set the + * desired value afterwards: + * https://github.com/ethereum/EIPs/issues/20#issuecomment-263524729 + * + * Emits an {Approval} event. + */ + function approve(address spender, uint256 value) external returns (bool); + + /** + * @dev Moves a `value` amount of tokens from `from` to `to` using the + * allowance mechanism. `value` is then deducted from the caller's + * allowance. + * + * Returns a boolean value indicating whether the operation succeeded. + * + * Emits a {Transfer} event. + */ + function transferFrom(address from, address to, uint256 value) external returns (bool); +} diff --git a/packages/world-module-erc20/src/interfaces/IERC20Errors.sol b/packages/world-module-erc20/src/interfaces/IERC20Errors.sol new file mode 100644 index 0000000000..1d55c71bf7 --- /dev/null +++ b/packages/world-module-erc20/src/interfaces/IERC20Errors.sol @@ -0,0 +1,49 @@ +// SPDX-License-Identifier: MIT +// OpenZeppelin Contracts (last updated v5.0.0) (interfaces/draft-IERC6093.sol) +pragma solidity >=0.8.24; + +/** + * @dev Standard ERC20 Errors + * Interface of the https://eips.ethereum.org/EIPS/eip-6093[ERC-6093] custom errors for ERC20 tokens. + */ +interface IERC20Errors { + /** + * @dev Indicates an error related to the current `balance` of a `sender`. Used in transfers. + * @param sender Address whose tokens are being transferred. + * @param balance Current balance for the interacting account. + * @param needed Minimum amount required to perform a transfer. + */ + error ERC20InsufficientBalance(address sender, uint256 balance, uint256 needed); + + /** + * @dev Indicates a failure with the token `sender`. Used in transfers. + * @param sender Address whose tokens are being transferred. + */ + error ERC20InvalidSender(address sender); + + /** + * @dev Indicates a failure with the token `receiver`. Used in transfers. + * @param receiver Address to which tokens are being transferred. + */ + error ERC20InvalidReceiver(address receiver); + + /** + * @dev Indicates a failure with the `spender`’s `allowance`. Used in transfers. + * @param spender Address that may be allowed to operate on tokens without being their owner. + * @param allowance Amount of tokens a `spender` is allowed to operate with. + * @param needed Minimum amount required to perform a transfer. + */ + error ERC20InsufficientAllowance(address spender, uint256 allowance, uint256 needed); + + /** + * @dev Indicates a failure with the `approver` of a token to be approved. Used in approvals. + * @param approver Address initiating an approval operation. + */ + error ERC20InvalidApprover(address approver); + + /** + * @dev Indicates a failure with the `spender` to be approved. Used in approvals. + * @param spender Address that may be allowed to operate on tokens without being their owner. + */ + error ERC20InvalidSpender(address spender); +} diff --git a/packages/world-module-erc20/src/interfaces/IERC20Events.sol b/packages/world-module-erc20/src/interfaces/IERC20Events.sol new file mode 100644 index 0000000000..f86a921f3e --- /dev/null +++ b/packages/world-module-erc20/src/interfaces/IERC20Events.sol @@ -0,0 +1,18 @@ +// SPDX-License-Identifier: MIT +pragma solidity >=0.8.24; + +interface IERC20Events { + /** + * @dev Emitted when `value` tokens are moved from one account (`from`) to + * another (`to`). + * + * Note that `value` may be zero. + */ + event Transfer(address indexed from, address indexed to, uint256 value); + + /** + * @dev Emitted when the allowance of a `spender` for an `owner` is set by + * a call to {approve}. `value` is the new allowance. + */ + event Approval(address indexed owner, address indexed spender, uint256 value); +} diff --git a/packages/world-module-erc20/src/interfaces/IERC20Metadata.sol b/packages/world-module-erc20/src/interfaces/IERC20Metadata.sol new file mode 100644 index 0000000000..7d4beffe99 --- /dev/null +++ b/packages/world-module-erc20/src/interfaces/IERC20Metadata.sol @@ -0,0 +1,26 @@ +// SPDX-License-Identifier: MIT +// OpenZeppelin Contracts (last updated v5.0.0) (token/ERC20/extensions/IERC20Metadata.sol) + +pragma solidity ^0.8.20; + +import { IERC20 } from "./IERC20.sol"; + +/** + * @dev Interface for the optional metadata functions from the ERC-20 standard. + */ +interface IERC20Metadata is IERC20 { + /** + * @dev Returns the name of the token. + */ + function name() external view returns (string memory); + + /** + * @dev Returns the symbol of the token. + */ + function symbol() external view returns (string memory); + + /** + * @dev Returns the decimals places of the token. + */ + function decimals() external view returns (uint8); +} diff --git a/packages/world-module-erc20/test/ERC20BaseTest.sol b/packages/world-module-erc20/test/ERC20BaseTest.sol new file mode 100644 index 0000000000..b6e680a6ea --- /dev/null +++ b/packages/world-module-erc20/test/ERC20BaseTest.sol @@ -0,0 +1,363 @@ +// SPDX-License-Identifier: MIT +pragma solidity >=0.8.24; + +import { Test } from "forge-std/Test.sol"; +import { console } from "forge-std/console.sol"; + +import { GasReporter } from "@latticexyz/gas-report/src/GasReporter.sol"; + +import { createWorld } from "@latticexyz/world/test/createWorld.sol"; +import { ResourceAccess } from "@latticexyz/world/src/codegen/tables/ResourceAccess.sol"; +import { WorldResourceIdLib } from "@latticexyz/world/src/WorldResourceId.sol"; + +import { StoreSwitch } from "@latticexyz/store/src/StoreSwitch.sol"; + +import { ERC20MetadataData } from "../src/codegen/tables/ERC20Metadata.sol"; +import { IERC20 } from "../src/interfaces/IERC20.sol"; +import { IERC20Metadata } from "../src/interfaces/IERC20Metadata.sol"; +import { IERC20Errors } from "../src/interfaces/IERC20Errors.sol"; +import { IERC20Events } from "../src/interfaces/IERC20Events.sol"; +import { WithStore } from "../src/WithStore.sol"; +import { WithWorld } from "../src/WithWorld.sol"; +import { MUDERC20 } from "../src/MUDERC20.sol"; + +library TestConstants { + bytes14 constant ERC20_NAMESPACE = "mockerc20ns"; +} + +// Mock to include mint and burn functions +abstract contract MockERC20Base is MUDERC20 { + constructor() MUDERC20("Token", "TKN") {} + + function __mint(address to, uint256 amount) public { + _mint(to, amount); + } + + function __burn(address from, uint256 amount) public { + _burn(from, amount); + } +} + +contract MockERC20WithInternalStore is WithStore(address(this)), MockERC20Base {} + +contract MockERC20WithWorld is WithWorld, MockERC20Base { + constructor() WithWorld(createWorld(), TestConstants.ERC20_NAMESPACE) {} +} + +abstract contract ERC20BehaviorTest is Test, GasReporter, IERC20Events, IERC20Errors { + MockERC20Base token; + + function createToken() internal virtual returns (MockERC20Base); + + // TODO: startGasReport should be marked virtual so we can override + function startGasReportWithPrefix(string memory name) internal { + startGasReport(reportNameWithPrefix(name)); + } + + function reportNameWithPrefix(string memory name) private view returns (string memory) { + string memory prefix = token.getStore() == address(token) ? "internal_" : "world_"; + return string.concat(prefix, name); + } + + function setUp() public { + token = createToken(); + StoreSwitch.setStoreAddress(token.getStore()); + } + + function testSetUp() public { + assertTrue(address(token) != address(0)); + } + + ///////////////////////////////////////////////// + // SOLADY ERC20 TEST CASES + // (https://github.com/Vectorized/solady/blob/main/test/ERC20.t.sol) + ///////////////////////////////////////////////// + + function testMetadata() public { + assertEq(token.name(), "Token"); + assertEq(token.symbol(), "TKN"); + assertEq(token.decimals(), 18); + } + + function testMint() public { + vm.expectEmit(true, true, true, true); + emit Transfer(address(0), address(0xBEEF), 1e18); + startGasReportWithPrefix("mint"); + token.__mint(address(0xBEEF), 1e18); + endGasReport(); + + assertEq(token.totalSupply(), 1e18); + assertEq(token.balanceOf(address(0xBEEF)), 1e18); + } + + function testBurn() public { + token.__mint(address(0xBEEF), 1e18); + + vm.expectEmit(true, true, true, true); + emit Transfer(address(0xBEEF), address(0), 0.9e18); + startGasReportWithPrefix("burn"); + token.__burn(address(0xBEEF), 0.9e18); + endGasReport(); + + assertEq(token.totalSupply(), 1e18 - 0.9e18); + assertEq(token.balanceOf(address(0xBEEF)), 0.1e18); + } + + function testApprove() public { + vm.expectEmit(true, true, true, true); + emit Approval(address(this), address(0xBEEF), 1e18); + startGasReportWithPrefix("approve"); + bool success = token.approve(address(0xBEEF), 1e18); + endGasReport(); + assertTrue(success); + + assertEq(token.allowance(address(this), address(0xBEEF)), 1e18); + } + + function testTransfer() public { + token.__mint(address(this), 1e18); + + vm.expectEmit(true, true, true, true); + emit Transfer(address(this), address(0xBEEF), 1e18); + startGasReportWithPrefix("transfer"); + bool success = token.transfer(address(0xBEEF), 1e18); + endGasReport(); + assertTrue(success); + assertEq(token.totalSupply(), 1e18); + + assertEq(token.balanceOf(address(this)), 0); + assertEq(token.balanceOf(address(0xBEEF)), 1e18); + } + + function testTransferFrom() public { + address from = address(0xABCD); + + token.__mint(from, 1e18); + + vm.prank(from); + token.approve(address(this), 1e18); + + vm.expectEmit(true, true, true, true); + emit Transfer(from, address(0xBEEF), 1e18); + startGasReportWithPrefix("transferFrom"); + bool success = token.transferFrom(from, address(0xBEEF), 1e18); + endGasReport(); + assertTrue(success); + assertEq(token.totalSupply(), 1e18); + + assertEq(token.allowance(from, address(this)), 0); + + assertEq(token.balanceOf(from), 0); + assertEq(token.balanceOf(address(0xBEEF)), 1e18); + } + + function testInfiniteApproveTransferFrom() public { + address from = address(0xABCD); + + token.__mint(from, 1e18); + + vm.prank(from); + token.approve(address(this), type(uint256).max); + + assertTrue(token.transferFrom(from, address(0xBEEF), 1e18)); + assertEq(token.totalSupply(), 1e18); + + assertEq(token.allowance(from, address(this)), type(uint256).max); + + assertEq(token.balanceOf(from), 0); + assertEq(token.balanceOf(address(0xBEEF)), 1e18); + } + + function testMintOverMaxUintReverts() public { + token.__mint(address(this), type(uint256).max); + vm.expectRevert(); + token.__mint(address(this), 1); + } + + function testTransferInsufficientBalanceReverts() public { + token.__mint(address(this), 0.9e18); + vm.expectRevert( + abi.encodeWithSelector(IERC20Errors.ERC20InsufficientBalance.selector, address(this), 0.9e18, 1e18) + ); + token.transfer(address(0xBEEF), 1e18); + } + + function testTransferFromInsufficientAllowanceReverts() public { + address from = address(0xABCD); + + token.__mint(from, 1e18); + + vm.prank(from); + token.approve(address(this), 0.9e18); + + vm.expectRevert(abi.encodeWithSelector(ERC20InsufficientAllowance.selector, address(this), 0.9e18, 1e18)); + token.transferFrom(from, address(0xBEEF), 1e18); + } + + function testTransferFromInsufficientBalanceReverts() public { + address from = address(0xABCD); + + token.__mint(from, 0.9e18); + + vm.prank(from); + token.approve(address(this), 1e18); + + vm.expectRevert(abi.encodeWithSelector(ERC20InsufficientBalance.selector, from, 0.9e18, 1e18)); + token.transferFrom(from, address(0xBEEF), 1e18); + } + + function testMint(address to, uint256 amount) public { + vm.assume(to != address(0)); + + vm.expectEmit(true, true, true, true); + emit Transfer(address(0), to, amount); + token.__mint(to, amount); + + assertEq(token.totalSupply(), amount); + assertEq(token.balanceOf(to), amount); + } + + function testBurn(address from, uint256 mintAmount, uint256 burnAmount) public { + vm.assume(from != address(0)); + vm.assume(burnAmount <= mintAmount); + + token.__mint(from, mintAmount); + vm.expectEmit(true, true, true, true); + emit Transfer(from, address(0), burnAmount); + token.__burn(from, burnAmount); + + assertEq(token.totalSupply(), mintAmount - burnAmount); + assertEq(token.balanceOf(from), mintAmount - burnAmount); + } + + function testApprove(address to, uint256 amount) public { + vm.assume(to != address(0)); + + assertTrue(token.approve(to, amount)); + + assertEq(token.allowance(address(this), to), amount); + } + + function testTransfer(address to, uint256 amount) public { + vm.assume(to != address(0)); + token.__mint(address(this), amount); + + vm.expectEmit(true, true, true, true); + emit Transfer(address(this), to, amount); + assertTrue(token.transfer(to, amount)); + assertEq(token.totalSupply(), amount); + + if (address(this) == to) { + assertEq(token.balanceOf(address(this)), amount); + } else { + assertEq(token.balanceOf(address(this)), 0); + assertEq(token.balanceOf(to), amount); + } + } + + function testTransferFrom(address spender, address from, address to, uint256 approval, uint256 amount) public { + vm.assume(from != address(0)); + vm.assume(to != address(0)); + vm.assume(spender != address(0)); + vm.assume(amount <= approval); + + token.__mint(from, amount); + assertEq(token.balanceOf(from), amount); + + vm.prank(from); + token.approve(spender, approval); + + vm.expectEmit(true, true, true, true); + emit Transfer(from, to, amount); + vm.prank(spender); + assertTrue(token.transferFrom(from, to, amount)); + assertEq(token.totalSupply(), amount); + + if (approval == type(uint256).max) { + assertEq(token.allowance(from, spender), approval); + } else { + assertEq(token.allowance(from, spender), approval - amount); + } + + if (from == to) { + assertEq(token.balanceOf(from), amount); + } else { + assertEq(token.balanceOf(from), 0); + assertEq(token.balanceOf(to), amount); + } + } + + function testBurnInsufficientBalanceReverts(address to, uint256 mintAmount, uint256 burnAmount) public { + vm.assume(to != address(0)); + vm.assume(mintAmount < type(uint256).max); + vm.assume(burnAmount > mintAmount); + + token.__mint(to, mintAmount); + vm.expectRevert(abi.encodeWithSelector(ERC20InsufficientBalance.selector, to, mintAmount, burnAmount)); + token.__burn(to, burnAmount); + } + + function testTransferInsufficientBalanceReverts(address to, uint256 mintAmount, uint256 sendAmount) public { + vm.assume(to != address(0)); + vm.assume(mintAmount < type(uint256).max); + vm.assume(sendAmount > mintAmount); + + token.__mint(address(this), mintAmount); + vm.expectRevert(abi.encodeWithSelector(ERC20InsufficientBalance.selector, address(this), mintAmount, sendAmount)); + token.transfer(to, sendAmount); + } + + function testTransferFromInsufficientAllowanceReverts(address to, uint256 approval, uint256 amount) public { + vm.assume(to != address(0)); + vm.assume(approval < type(uint256).max); + vm.assume(amount > approval); + + address from = address(0xABCD); + + token.__mint(from, amount); + + vm.prank(from); + token.approve(address(this), approval); + + vm.expectRevert(abi.encodeWithSelector(ERC20InsufficientAllowance.selector, address(this), approval, amount)); + token.transferFrom(from, to, amount); + } + + function testTransferFromInsufficientBalanceReverts(address to, uint256 mintAmount, uint256 sendAmount) public { + vm.assume(to != address(0)); + vm.assume(mintAmount < type(uint256).max); + vm.assume(sendAmount > mintAmount); + + address from = address(0xABCD); + + token.__mint(from, mintAmount); + + vm.prank(from); + token.approve(address(this), sendAmount); + + vm.expectRevert(abi.encodeWithSelector(ERC20InsufficientBalance.selector, from, mintAmount, sendAmount)); + token.transferFrom(from, to, sendAmount); + } +} + +abstract contract ERC20WithInternalStoreBehaviorTest is ERC20BehaviorTest {} + +// Concrete tests for basic internal store ERC20 behavior +contract ERC20WithInternalStoreTest is ERC20WithInternalStoreBehaviorTest { + function createToken() internal virtual override returns (MockERC20Base) { + return new MockERC20WithInternalStore(); + } +} + +abstract contract ERC20WithWorldBehaviorTest is ERC20BehaviorTest { + function testNamespaceAccess() public { + assertTrue(ResourceAccess.get(WorldResourceIdLib.encodeNamespace(TestConstants.ERC20_NAMESPACE), address(token))); + } +} + +// Concrete tests for basic namespace ERC20 behavior +contract ERC20WithWorldTest is ERC20WithWorldBehaviorTest { + function createToken() internal virtual override returns (MockERC20Base) { + return new MockERC20WithWorld(); + } +} diff --git a/packages/world-module-erc20/test/ERC20Burnable.t.sol b/packages/world-module-erc20/test/ERC20Burnable.t.sol new file mode 100644 index 0000000000..027791a598 --- /dev/null +++ b/packages/world-module-erc20/test/ERC20Burnable.t.sol @@ -0,0 +1,50 @@ +// SPDX-License-Identifier: MIT +pragma solidity >=0.8.24; + +import { Test } from "forge-std/Test.sol"; +import { console } from "forge-std/console.sol"; + +import { StoreSwitch } from "@latticexyz/store/src/StoreSwitch.sol"; + +import { GasReporter } from "@latticexyz/gas-report/src/GasReporter.sol"; +import { IBaseWorld } from "@latticexyz/world/src/codegen/interfaces/IBaseWorld.sol"; +import { createWorld } from "@latticexyz/world/test/createWorld.sol"; + +import { ERC20MetadataData } from "../src/codegen/tables/ERC20Metadata.sol"; +import { IERC20Errors } from "../src/interfaces/IERC20Errors.sol"; +import { IERC20Events } from "../src/interfaces/IERC20Events.sol"; +import { MUDERC20 } from "../src/MUDERC20.sol"; +import { ERC20Burnable } from "../src/ERC20Burnable.sol"; +import { MockERC20Base, MockERC20WithInternalStore, MockERC20WithWorld, ERC20BehaviorTest, ERC20WithInternalStoreBehaviorTest, ERC20WithWorldBehaviorTest } from "./ERC20BaseTest.sol"; + +contract MockERC20WithInternalStoreBurnable is MockERC20WithInternalStore, ERC20Burnable {} + +contract MockERC20WithWorldBurnable is MockERC20WithWorld, ERC20Burnable {} + +abstract contract ERC20BurnableTest is ERC20BehaviorTest { + function testBurnByAccount() public { + token.__mint(address(0xBEEF), 1e18); + + vm.expectEmit(true, true, true, true); + emit Transfer(address(0xBEEF), address(0), 0.9e18); + startGasReportWithPrefix("burn"); + vm.prank(address(0xBEEF)); + ERC20Burnable(address(token)).burn(0.9e18); + endGasReport(); + + assertEq(token.totalSupply(), 1e18 - 0.9e18); + assertEq(token.balanceOf(address(0xBEEF)), 0.1e18); + } +} + +contract ERC20BurnableWithInternalStoreTest is ERC20WithInternalStoreBehaviorTest, ERC20BurnableTest { + function createToken() internal override returns (MockERC20Base) { + return new MockERC20WithInternalStoreBurnable(); + } +} + +contract ERC20BurnableWithWorldTest is ERC20WithWorldBehaviorTest, ERC20BurnableTest { + function createToken() internal override returns (MockERC20Base) { + return new MockERC20WithWorldBurnable(); + } +} diff --git a/packages/world-module-erc20/test/ERC20Module.t.sol b/packages/world-module-erc20/test/ERC20Module.t.sol new file mode 100644 index 0000000000..b0863581b6 --- /dev/null +++ b/packages/world-module-erc20/test/ERC20Module.t.sol @@ -0,0 +1,80 @@ +// SPDX-License-Identifier: MIT +pragma solidity >=0.8.24; + +import { Test } from "forge-std/Test.sol"; +import { console } from "forge-std/console.sol"; + +import { GasReporter } from "@latticexyz/gas-report/src/GasReporter.sol"; + +import { StoreSwitch } from "@latticexyz/store/src/StoreSwitch.sol"; +import { RESOURCE_TABLE } from "@latticexyz/store/src/storeResourceTypes.sol"; + +import { createWorld } from "@latticexyz/world/test/createWorld.sol"; +import { ResourceId, WorldResourceIdLib, WorldResourceIdInstance } from "@latticexyz/world/src/WorldResourceId.sol"; +import { IBaseWorld } from "@latticexyz/world/src/codegen/interfaces/IBaseWorld.sol"; +import { IWorldErrors } from "@latticexyz/world/src/IWorldErrors.sol"; +import { IModuleErrors } from "@latticexyz/world/src/IModuleErrors.sol"; +import { NamespaceOwner } from "@latticexyz/world/src/codegen/tables/NamespaceOwner.sol"; +import { ResourceAccess } from "@latticexyz/world/src/codegen/tables/ResourceAccess.sol"; + +import { ModuleConstants } from "../src/Constants.sol"; +import { ERC20Module } from "../src/ERC20Module.sol"; +import { ERC20Registry } from "../src/codegen/tables/ERC20Registry.sol"; + +library TestConstants { + bytes14 constant ERC20_NAMESPACE = "erc20namespace"; +} + +contract ERC20ModuleTest is Test, GasReporter { + using WorldResourceIdInstance for ResourceId; + + IBaseWorld world; + ERC20Module erc20Module = new ERC20Module(); + + function setUp() public { + world = createWorld(); + StoreSwitch.setStoreAddress(address(world)); + } + + function testInstall() public { + bytes memory args = abi.encode(TestConstants.ERC20_NAMESPACE, "myERC20Token", "MTK"); + startGasReport("install erc20 module"); + world.installModule(erc20Module, args); + endGasReport(); + + ResourceId erc20RegistryTableId = ModuleConstants.registryTableId(); + ResourceId erc20NamespaceId = WorldResourceIdLib.encodeNamespace(TestConstants.ERC20_NAMESPACE); + + // Token should retain access to the namespace + address token = ERC20Registry.get( + erc20RegistryTableId, + WorldResourceIdLib.encodeNamespace(TestConstants.ERC20_NAMESPACE) + ); + + // Module should grant the token access to the token namespace + assertTrue(ResourceAccess.get(erc20NamespaceId, token), "Token does not have access to its namespace"); + + // Module should transfer token namespace ownership to the creator + assertEq(NamespaceOwner.get(erc20NamespaceId), address(this), "Token did not transfer ownership"); + + vm.expectRevert(IModuleErrors.Module_AlreadyInstalled.selector); + world.installModule(erc20Module, args); + } + + function testInstallExistingNamespace() public { + ResourceId moduleNamespaceId = ModuleConstants.namespaceId(); + world.registerNamespace(moduleNamespaceId); + + bytes memory args = abi.encode(TestConstants.ERC20_NAMESPACE, "myERC20Token", "MTK"); + + // Installing will revert because module namespace already exists + vm.expectRevert( + abi.encodeWithSelector( + IWorldErrors.World_ResourceAlreadyExists.selector, + moduleNamespaceId, + moduleNamespaceId.toString() + ) + ); + world.installModule(erc20Module, args); + } +} diff --git a/packages/world-module-erc20/test/ERC20Pausable.t.sol b/packages/world-module-erc20/test/ERC20Pausable.t.sol new file mode 100644 index 0000000000..0d0b4feee8 --- /dev/null +++ b/packages/world-module-erc20/test/ERC20Pausable.t.sol @@ -0,0 +1,73 @@ +// SPDX-License-Identifier: MIT +pragma solidity >=0.8.24; + +import { Test } from "forge-std/Test.sol"; +import { console } from "forge-std/console.sol"; + +import { GasReporter } from "@latticexyz/gas-report/src/GasReporter.sol"; +import { IBaseWorld } from "@latticexyz/world/src/codegen/interfaces/IBaseWorld.sol"; + +import { ERC20MetadataData } from "../src/codegen/tables/ERC20Metadata.sol"; +import { IERC20Errors } from "../src/interfaces/IERC20Errors.sol"; +import { IERC20Events } from "../src/interfaces/IERC20Events.sol"; +import { MUDERC20 } from "../src/MUDERC20.sol"; +import { Pausable, ERC20Pausable } from "../src/ERC20Pausable.sol"; +import { MockERC20Base, MockERC20WithInternalStore, MockERC20WithWorld, ERC20BehaviorTest, ERC20WithInternalStoreBehaviorTest, ERC20WithWorldBehaviorTest } from "./ERC20BaseTest.sol"; + +// Mock to include mint and burn functions +abstract contract MockERC20Pausable is MUDERC20, ERC20Pausable { + function pause() public { + _pause(); + } + + function unpause() public { + _unpause(); + } + + function _update(address from, address to, uint256 value) internal virtual override(MUDERC20, ERC20Pausable) { + super._update(from, to, value); + } +} + +contract MockERC20WithInternalStorePausable is MockERC20WithInternalStore, MockERC20Pausable { + function _update(address from, address to, uint256 value) internal override(MUDERC20, MockERC20Pausable) { + super._update(from, to, value); + } +} + +contract MockERC20WithWorldPausable is MockERC20WithWorld, MockERC20Pausable { + function _update(address from, address to, uint256 value) internal override(MUDERC20, MockERC20Pausable) { + super._update(from, to, value); + } +} + +abstract contract ERC20PausableBehaviorTest is ERC20BehaviorTest { + function testPause() public { + startGasReportWithPrefix("pause"); + MockERC20Pausable(address(token)).pause(); + endGasReport(); + + vm.expectRevert(abi.encodeWithSelector(Pausable.EnforcedPause.selector)); + token.transfer(address(0xBEEF), 0); + + startGasReportWithPrefix("unpause"); + MockERC20Pausable(address(token)).unpause(); + endGasReport(); + + vm.expectEmit(true, true, true, true); + emit Transfer(address(this), address(0xBEEF), 0); + token.transfer(address(0xBEEF), 0); + } +} + +contract ERC20PausableWithInternalStoreTest is ERC20WithInternalStoreBehaviorTest, ERC20PausableBehaviorTest { + function createToken() internal override returns (MockERC20Base) { + return new MockERC20WithInternalStorePausable(); + } +} + +contract ERC20PausableWithWorldTest is ERC20WithWorldBehaviorTest, ERC20PausableBehaviorTest { + function createToken() internal override returns (MockERC20Base) { + return new MockERC20WithWorldPausable(); + } +} diff --git a/packages/world-module-erc20/test/StoreConsumer.t.sol b/packages/world-module-erc20/test/StoreConsumer.t.sol new file mode 100644 index 0000000000..505698949f --- /dev/null +++ b/packages/world-module-erc20/test/StoreConsumer.t.sol @@ -0,0 +1,90 @@ +// SPDX-License-Identifier: MIT +pragma solidity >=0.8.24; + +import { Test } from "forge-std/Test.sol"; +import { console } from "forge-std/console.sol"; + +import { GasReporter } from "@latticexyz/gas-report/src/GasReporter.sol"; + +import { IBaseWorld } from "@latticexyz/world/src/codegen/interfaces/IBaseWorld.sol"; +import { ResourceAccess } from "@latticexyz/world/src/codegen/tables/ResourceAccess.sol"; +import { WorldResourceIdLib } from "@latticexyz/world/src/WorldResourceId.sol"; +import { createWorld } from "@latticexyz/world/test/createWorld.sol"; + +import { ResourceId, ResourceIdLib } from "@latticexyz/store/src/ResourceId.sol"; +import { Tables, ResourceIds } from "@latticexyz/store/src/codegen/index.sol"; +import { StoreCore } from "@latticexyz/store/src/Store.sol"; +import { StoreSwitch } from "@latticexyz/store/src/StoreSwitch.sol"; + +import { StoreConsumer } from "../src/StoreConsumer.sol"; +import { WithStore } from "../src/WithStore.sol"; +import { WithWorld } from "../src/WithWorld.sol"; + +abstract contract MockStoreConsumer is StoreConsumer { + function getStoreAddress() public view virtual returns (address) { + return StoreSwitch.getStoreAddress(); + } + + function encodeResourceId(bytes2 typeId, bytes16 name) public view returns (ResourceId) { + return _encodeResourceId(typeId, name); + } +} + +contract MockWithStore is WithStore, MockStoreConsumer { + constructor(address store) WithStore(store) {} +} + +contract MockWithInternalStore is MockWithStore(address(this)) {} + +contract MockWithWorld is WithWorld, MockStoreConsumer { + constructor(IBaseWorld world, bytes14 namespace) WithWorld(world, namespace) { + ResourceId namespaceId = getNamespaceId(); + world.grantAccess(namespaceId, address(this)); + + // Transfer ownership to the creator so we can test `onlyNamespace` + world.transferOwnership(namespaceId, _msgSender()); + } + function onlyCallableByNamespace() public view onlyNamespace {} +} + +contract StoreConsumerTest is Test, GasReporter { + function testWithStore() public { + MockWithStore mock = new MockWithStore(address(0xBEEF)); + assertEq(mock.getStoreAddress(), address(0xBEEF)); + } + + function testWithStoreInternal() public { + MockWithInternalStore mock = new MockWithInternalStore(); + assertEq(mock.getStoreAddress(), address(mock)); + } + + function testWithWorld() public { + IBaseWorld world = createWorld(); + bytes14 namespace = "myNamespace"; + MockWithWorld mock = new MockWithWorld(world, namespace); + assertEq(mock.getStoreAddress(), address(world)); + + StoreSwitch.setStoreAddress(address(world)); + + ResourceId namespaceId = WorldResourceIdLib.encodeNamespace(namespace); + assertTrue(ResourceIds.getExists(namespaceId), "Namespace not registered"); + } + + function testOnlyNamespace() public { + IBaseWorld world = createWorld(); + bytes14 namespace = "myNamespace"; + ResourceId namespaceId = WorldResourceIdLib.encodeNamespace(namespace); + MockWithWorld mock = new MockWithWorld(world, namespace); + StoreSwitch.setStoreAddress(address(world)); + + address alice = address(0x1234); + + vm.prank(alice); + vm.expectRevert(); + mock.onlyCallableByNamespace(); + + world.grantAccess(namespaceId, alice); + vm.prank(alice); + mock.onlyCallableByNamespace(); + } +} diff --git a/packages/world-module-erc20/ts/exports/index.ts b/packages/world-module-erc20/ts/exports/index.ts new file mode 100644 index 0000000000..dc7389f17c --- /dev/null +++ b/packages/world-module-erc20/ts/exports/index.ts @@ -0,0 +1,25 @@ +import { encodeAbiParameters, stringToHex } from "viem"; + +export type DefineERC20ConfigInput = { + namespace: string; + name: string; + symbol: string; +}; + +export function defineERC20Config({ namespace, name, symbol }: DefineERC20ConfigInput) { + const erc20ModuleArgs = encodeAbiParameters( + [{ type: "bytes14" }, { type: "string" }, { type: "string" }], + [stringToHex(namespace, { size: 14 }), name, symbol], + ); + + return { + artifactPath: "@latticexyz/world-module-erc20/out/ERC20Module.sol/ERC20Module.json", + root: false, + args: [ + { + type: "bytes", + value: erc20ModuleArgs, + }, + ], + }; +} diff --git a/packages/world-module-erc20/tsconfig.json b/packages/world-module-erc20/tsconfig.json new file mode 100644 index 0000000000..9b0bf57752 --- /dev/null +++ b/packages/world-module-erc20/tsconfig.json @@ -0,0 +1,7 @@ +{ + "extends": "../../tsconfig.json", + "compilerOptions": { + "outDir": "dist" + }, + "include": ["mud.config.ts", "ts"] +} diff --git a/packages/world-module-erc20/tsup.config.ts b/packages/world-module-erc20/tsup.config.ts new file mode 100644 index 0000000000..e4f6e18a78 --- /dev/null +++ b/packages/world-module-erc20/tsup.config.ts @@ -0,0 +1,14 @@ +import { defineConfig } from "tsup"; + +export default defineConfig({ + entry: { + "mud.config": "mud.config.ts", + index: "ts/exports/index.ts", + }, + target: "esnext", + format: ["esm"], + dts: !process.env.TSUP_SKIP_DTS, + sourcemap: true, + clean: true, + minify: true, +}); diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 4702c78d44..daadc3d5c0 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -6,6 +6,18 @@ settings: catalogs: default: + '@ark/attest': + specifier: 0.12.1 + version: 0.12.1 + '@ark/util': + specifier: 0.2.2 + version: 0.2.2 + abitype: + specifier: 1.0.6 + version: 1.0.6 + arktype: + specifier: 2.0.0-beta.6 + version: 2.0.0-beta.6 viem: specifier: 2.21.19 version: 2.21.19 @@ -821,10 +833,10 @@ importers: version: 8.3.4 jest: specifier: ^29.3.1 - version: 29.5.0(@types/node@20.12.12) + version: 29.5.0(@types/node@18.19.50) ts-jest: specifier: ^29.0.5 - version: 29.0.5(@babel/core@7.25.2)(@jest/types@29.6.3)(babel-jest@29.5.0(@babel/core@7.25.2))(jest@29.5.0(@types/node@20.12.12))(typescript@5.4.2) + version: 29.0.5(@babel/core@7.21.4)(@jest/types@29.6.3)(babel-jest@29.5.0(@babel/core@7.21.4))(jest@29.5.0(@types/node@18.19.50))(typescript@5.4.2) tsup: specifier: ^6.7.0 version: 6.7.0(postcss@8.4.47)(typescript@5.4.2) @@ -1200,10 +1212,10 @@ importers: version: 27.4.1 jest: specifier: ^29.3.1 - version: 29.5.0(@types/node@18.15.11) + version: 29.5.0(@types/node@20.12.12) ts-jest: specifier: ^29.0.5 - version: 29.0.5(@babel/core@7.21.4)(@jest/types@29.6.3)(babel-jest@29.5.0(@babel/core@7.21.4))(jest@29.5.0(@types/node@18.15.11))(typescript@5.4.2) + version: 29.0.5(@babel/core@7.25.2)(@jest/types@29.6.3)(babel-jest@29.5.0(@babel/core@7.25.2))(jest@29.5.0(@types/node@20.12.12))(typescript@5.4.2) tsup: specifier: ^6.7.0 version: 6.7.0(postcss@8.4.47)(typescript@5.4.2) @@ -1278,6 +1290,46 @@ importers: specifier: 0.34.6 version: 0.34.6(jsdom@22.1.0(bufferutil@4.0.8)(utf-8-validate@5.0.10))(terser@5.33.0) + packages/world-module-erc20: + dependencies: + '@latticexyz/schema-type': + specifier: workspace:* + version: link:../schema-type + '@latticexyz/store': + specifier: workspace:* + version: link:../store + '@latticexyz/world': + specifier: workspace:* + version: link:../world + devDependencies: + '@latticexyz/abi-ts': + specifier: workspace:* + version: link:../abi-ts + '@latticexyz/cli': + specifier: workspace:* + version: link:../cli + '@latticexyz/gas-report': + specifier: workspace:* + version: link:../gas-report + '@types/node': + specifier: ^18.15.11 + version: 18.19.50 + ds-test: + specifier: https://github.com/dapphub/ds-test.git#e282159d5170298eb2455a6c05280ab5a73a4ef0 + version: https://codeload.github.com/dapphub/ds-test/tar.gz/e282159d5170298eb2455a6c05280ab5a73a4ef0 + forge-std: + specifier: https://github.com/foundry-rs/forge-std.git#74cfb77e308dd188d2f58864aaf44963ae6b88b1 + version: https://codeload.github.com/foundry-rs/forge-std/tar.gz/74cfb77e308dd188d2f58864aaf44963ae6b88b1 + solhint: + specifier: ^3.3.7 + version: 3.3.7 + tsup: + specifier: ^6.7.0 + version: 6.7.0(postcss@8.4.47)(typescript@5.4.2) + vitest: + specifier: 0.34.6 + version: 0.34.6(jsdom@22.1.0(bufferutil@4.0.8)(utf-8-validate@5.0.10))(terser@5.33.0) + packages/world-module-metadata: dependencies: '@latticexyz/schema-type': @@ -13966,7 +14018,7 @@ snapshots: '@jest/console@29.5.0': dependencies: '@jest/types': 29.5.0 - '@types/node': 18.15.11 + '@types/node': 18.19.50 chalk: 4.1.2 jest-message-util: 29.5.0 jest-util: 29.5.0 @@ -13979,14 +14031,14 @@ snapshots: '@jest/test-result': 29.5.0 '@jest/transform': 29.5.0 '@jest/types': 29.5.0 - '@types/node': 18.15.11 + '@types/node': 18.19.50 ansi-escapes: 4.3.2 chalk: 4.1.2 ci-info: 3.8.0 exit: 0.1.2 graceful-fs: 4.2.11 jest-changed-files: 29.5.0 - jest-config: 29.5.0(@types/node@18.15.11) + jest-config: 29.5.0(@types/node@18.19.50) jest-haste-map: 29.5.0 jest-message-util: 29.5.0 jest-regex-util: 29.4.3 @@ -14014,7 +14066,7 @@ snapshots: dependencies: '@jest/fake-timers': 29.5.0 '@jest/types': 29.5.0 - '@types/node': 18.15.11 + '@types/node': 18.19.50 jest-mock: 29.5.0 '@jest/environment@29.7.0': @@ -14039,7 +14091,7 @@ snapshots: dependencies: '@jest/types': 29.5.0 '@sinonjs/fake-timers': 10.0.2 - '@types/node': 18.15.11 + '@types/node': 18.19.50 jest-message-util: 29.5.0 jest-mock: 29.5.0 jest-util: 29.7.0 @@ -14070,7 +14122,7 @@ snapshots: '@jest/transform': 29.5.0 '@jest/types': 29.5.0 '@jridgewell/trace-mapping': 0.3.18 - '@types/node': 18.15.11 + '@types/node': 18.19.50 chalk: 4.1.2 collect-v8-coverage: 1.0.1 exit: 0.1.2 @@ -14152,7 +14204,7 @@ snapshots: '@jest/schemas': 29.4.3 '@types/istanbul-lib-coverage': 2.0.4 '@types/istanbul-reports': 3.0.1 - '@types/node': 18.15.11 + '@types/node': 18.19.50 '@types/yargs': 17.0.24 chalk: 4.1.2 @@ -16404,7 +16456,7 @@ snapshots: '@types/connect@3.4.38': dependencies: - '@types/node': 18.15.11 + '@types/node': 18.19.50 '@types/content-disposition@0.5.8': {} @@ -16413,7 +16465,7 @@ snapshots: '@types/connect': 3.4.38 '@types/express': 4.17.21 '@types/keygrip': 1.0.6 - '@types/node': 18.15.11 + '@types/node': 18.19.50 '@types/debug@4.1.7': dependencies: @@ -18915,7 +18967,7 @@ snapshots: debug: 4.3.7 enhanced-resolve: 5.17.1 eslint: 8.57.0 - eslint-module-utils: 2.8.1(@typescript-eslint/parser@7.1.1(eslint@8.57.0)(typescript@5.4.2))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.1)(eslint@8.57.0) + eslint-module-utils: 2.8.1(@typescript-eslint/parser@7.1.1(eslint@8.57.0)(typescript@5.4.2))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.1(@typescript-eslint/parser@7.1.1(eslint@8.57.0)(typescript@5.4.2))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.29.1)(eslint@8.57.0))(eslint@8.57.0) eslint-plugin-import: 2.29.1(@typescript-eslint/parser@7.1.1(eslint@8.57.0)(typescript@5.4.2))(eslint-import-resolver-typescript@3.6.1)(eslint@8.57.0) fast-glob: 3.3.2 get-tsconfig: 4.7.5 @@ -18927,7 +18979,7 @@ snapshots: - eslint-import-resolver-webpack - supports-color - eslint-module-utils@2.8.1(@typescript-eslint/parser@7.1.1(eslint@8.57.0)(typescript@5.4.2))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.1)(eslint@8.57.0): + eslint-module-utils@2.8.1(@typescript-eslint/parser@7.1.1(eslint@8.57.0)(typescript@5.4.2))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.1(@typescript-eslint/parser@7.1.1(eslint@8.57.0)(typescript@5.4.2))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.29.1)(eslint@8.57.0))(eslint@8.57.0): dependencies: debug: 3.2.7 optionalDependencies: @@ -18948,7 +19000,7 @@ snapshots: doctrine: 2.1.0 eslint: 8.57.0 eslint-import-resolver-node: 0.3.9 - eslint-module-utils: 2.8.1(@typescript-eslint/parser@7.1.1(eslint@8.57.0)(typescript@5.4.2))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.1)(eslint@8.57.0) + eslint-module-utils: 2.8.1(@typescript-eslint/parser@7.1.1(eslint@8.57.0)(typescript@5.4.2))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.1(@typescript-eslint/parser@7.1.1(eslint@8.57.0)(typescript@5.4.2))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.29.1)(eslint@8.57.0))(eslint@8.57.0) hasown: 2.0.2 is-core-module: 2.15.0 is-glob: 4.0.3 @@ -20311,7 +20363,7 @@ snapshots: '@jest/expect': 29.5.0 '@jest/test-result': 29.5.0 '@jest/types': 29.5.0 - '@types/node': 18.15.11 + '@types/node': 18.19.50 chalk: 4.1.2 co: 4.6.0 dedent: 0.7.0 @@ -20330,7 +20382,7 @@ snapshots: transitivePeerDependencies: - supports-color - jest-cli@29.5.0(@types/node@18.15.11): + jest-cli@29.5.0(@types/node@18.19.50): dependencies: '@jest/core': 29.5.0 '@jest/test-result': 29.5.0 @@ -20339,7 +20391,7 @@ snapshots: exit: 0.1.2 graceful-fs: 4.2.11 import-local: 3.1.0 - jest-config: 29.5.0(@types/node@18.15.11) + jest-config: 29.5.0(@types/node@18.19.50) jest-util: 29.5.0 jest-validate: 29.5.0 prompts: 2.4.2 @@ -20368,7 +20420,7 @@ snapshots: - supports-color - ts-node - jest-config@29.5.0(@types/node@18.15.11): + jest-config@29.5.0(@types/node@18.19.50): dependencies: '@babel/core': 7.21.4 '@jest/test-sequencer': 29.5.0 @@ -20393,7 +20445,7 @@ snapshots: slash: 3.0.0 strip-json-comments: 3.1.1 optionalDependencies: - '@types/node': 18.15.11 + '@types/node': 18.19.50 transitivePeerDependencies: - supports-color @@ -20457,7 +20509,7 @@ snapshots: '@jest/environment': 29.5.0 '@jest/fake-timers': 29.5.0 '@jest/types': 29.5.0 - '@types/node': 18.15.11 + '@types/node': 18.19.50 jest-mock: 29.5.0 jest-util: 29.7.0 @@ -20480,7 +20532,7 @@ snapshots: dependencies: '@jest/types': 29.5.0 '@types/graceful-fs': 4.1.6 - '@types/node': 18.15.11 + '@types/node': 18.19.50 anymatch: 3.1.3 fb-watchman: 2.0.2 graceful-fs: 4.2.11 @@ -20538,7 +20590,7 @@ snapshots: jest-mock@29.5.0: dependencies: '@jest/types': 29.5.0 - '@types/node': 18.15.11 + '@types/node': 18.19.50 jest-util: 29.7.0 jest-mock@29.7.0: @@ -20579,7 +20631,7 @@ snapshots: '@jest/test-result': 29.5.0 '@jest/transform': 29.5.0 '@jest/types': 29.5.0 - '@types/node': 18.15.11 + '@types/node': 18.19.50 chalk: 4.1.2 emittery: 0.13.1 graceful-fs: 4.2.11 @@ -20607,7 +20659,7 @@ snapshots: '@jest/test-result': 29.5.0 '@jest/transform': 29.5.0 '@jest/types': 29.5.0 - '@types/node': 18.15.11 + '@types/node': 18.19.50 chalk: 4.1.2 cjs-module-lexer: 1.2.2 collect-v8-coverage: 1.0.1 @@ -20656,7 +20708,7 @@ snapshots: jest-util@29.5.0: dependencies: '@jest/types': 29.5.0 - '@types/node': 18.15.11 + '@types/node': 18.19.50 chalk: 4.1.2 ci-info: 3.8.0 graceful-fs: 4.2.11 @@ -20693,7 +20745,7 @@ snapshots: dependencies: '@jest/test-result': 29.5.0 '@jest/types': 29.5.0 - '@types/node': 18.15.11 + '@types/node': 18.19.50 ansi-escapes: 4.3.2 chalk: 4.1.2 emittery: 0.13.1 @@ -20702,7 +20754,7 @@ snapshots: jest-worker@29.5.0: dependencies: - '@types/node': 18.15.11 + '@types/node': 18.19.50 jest-util: 29.7.0 merge-stream: 2.0.0 supports-color: 8.1.1 @@ -20714,12 +20766,12 @@ snapshots: merge-stream: 2.0.0 supports-color: 8.1.1 - jest@29.5.0(@types/node@18.15.11): + jest@29.5.0(@types/node@18.19.50): dependencies: '@jest/core': 29.5.0 '@jest/types': 29.5.0 import-local: 3.1.0 - jest-cli: 29.5.0(@types/node@18.15.11) + jest-cli: 29.5.0(@types/node@18.19.50) transitivePeerDependencies: - '@types/node' - supports-color @@ -23618,11 +23670,11 @@ snapshots: ts-interface-checker@0.1.13: {} - ts-jest@29.0.5(@babel/core@7.21.4)(@jest/types@29.6.3)(babel-jest@29.5.0(@babel/core@7.21.4))(jest@29.5.0(@types/node@18.15.11))(typescript@5.4.2): + ts-jest@29.0.5(@babel/core@7.21.4)(@jest/types@29.6.3)(babel-jest@29.5.0(@babel/core@7.21.4))(jest@29.5.0(@types/node@18.19.50))(typescript@5.4.2): dependencies: bs-logger: 0.2.6 fast-json-stable-stringify: 2.1.0 - jest: 29.5.0(@types/node@18.15.11) + jest: 29.5.0(@types/node@18.19.50) jest-util: 29.7.0 json5: 2.2.3 lodash.memoize: 4.1.2 From b3b4c559e57286e7ee58c8c079c6093835944d04 Mon Sep 17 00:00:00 2001 From: V Date: Tue, 22 Oct 2024 08:41:48 -0300 Subject: [PATCH 07/10] test: update gas report for erc20 module (#3316) --- packages/world-module-erc20/gas-report.json | 40 ++++++++++----------- 1 file changed, 20 insertions(+), 20 deletions(-) diff --git a/packages/world-module-erc20/gas-report.json b/packages/world-module-erc20/gas-report.json index 0bc3a786b5..4694a6acdf 100644 --- a/packages/world-module-erc20/gas-report.json +++ b/packages/world-module-erc20/gas-report.json @@ -2,62 +2,62 @@ { "file": "test/ERC20BaseTest.sol", "test": "testApprove", - "name": "world_approve", - "gasUsed": 66343 + "name": "internal_approve", + "gasUsed": 58213 }, { "file": "test/ERC20BaseTest.sol", "test": "testBurn", - "name": "world_burn", - "gasUsed": 70873 + "name": "internal_burn", + "gasUsed": 56417 }, { "file": "test/ERC20BaseTest.sol", "test": "testMint", - "name": "world_mint", - "gasUsed": 105159 + "name": "internal_mint", + "gasUsed": 90703 }, { "file": "test/ERC20BaseTest.sol", "test": "testTransfer", - "name": "world_transfer", - "gasUsed": 82375 + "name": "internal_transfer", + "gasUsed": 67747 }, { "file": "test/ERC20BaseTest.sol", "test": "testTransferFrom", - "name": "world_transferFrom", - "gasUsed": 99431 + "name": "internal_transferFrom", + "gasUsed": 79574 }, { "file": "test/ERC20BaseTest.sol", "test": "testApprove", - "name": "internal_approve", - "gasUsed": 58213 + "name": "world_approve", + "gasUsed": 66343 }, { "file": "test/ERC20BaseTest.sol", "test": "testBurn", - "name": "internal_burn", - "gasUsed": 56417 + "name": "world_burn", + "gasUsed": 70873 }, { "file": "test/ERC20BaseTest.sol", "test": "testMint", - "name": "internal_mint", - "gasUsed": 90703 + "name": "world_mint", + "gasUsed": 105159 }, { "file": "test/ERC20BaseTest.sol", "test": "testTransfer", - "name": "internal_transfer", - "gasUsed": 67747 + "name": "world_transfer", + "gasUsed": 82375 }, { "file": "test/ERC20BaseTest.sol", "test": "testTransferFrom", - "name": "internal_transferFrom", - "gasUsed": 79574 + "name": "world_transferFrom", + "gasUsed": 99431 }, { "file": "test/ERC20Burnable.t.sol", From d5c270023abc325f25af868d3db1a0bdc3e62d6d Mon Sep 17 00:00:00 2001 From: Kevin Ingersoll Date: Tue, 22 Oct 2024 13:56:24 +0100 Subject: [PATCH 08/10] fix(gas-report): include contract name in file of output (#3317) --- .changeset/brown-panthers-melt.md | 5 + packages/cli/src/commands/index.ts | 4 +- packages/gas-report/bin/gas-report.js | 2 +- packages/gas-report/package.json | 6 +- .../gas-report/ts/{ => bin}/gas-report.ts | 2 +- packages/gas-report/ts/command.ts | 81 ++++ packages/gas-report/ts/common.ts | 16 + packages/gas-report/ts/exports/internal.ts | 1 + packages/gas-report/ts/index.ts | 211 ---------- packages/gas-report/ts/printGasReport.ts | 27 ++ packages/gas-report/ts/runGasReport.ts | 87 ++++ packages/gas-report/ts/saveGasReport.ts | 8 + packages/gas-report/tsup.config.ts | 2 +- packages/schema-type/gas-report.json | 36 +- packages/store/gas-report.json | 376 +++++++++--------- packages/world-module-erc20/gas-report.json | 74 ++-- .../world-module-metadata/gas-report.json | 6 +- packages/world-modules/gas-report.json | 112 +++--- packages/world/gas-report.json | 106 ++--- tsconfig.paths.json | 2 +- 20 files changed, 589 insertions(+), 575 deletions(-) create mode 100644 .changeset/brown-panthers-melt.md rename packages/gas-report/ts/{ => bin}/gas-report.ts (95%) create mode 100644 packages/gas-report/ts/command.ts create mode 100644 packages/gas-report/ts/common.ts create mode 100644 packages/gas-report/ts/exports/internal.ts delete mode 100644 packages/gas-report/ts/index.ts create mode 100644 packages/gas-report/ts/printGasReport.ts create mode 100644 packages/gas-report/ts/runGasReport.ts create mode 100644 packages/gas-report/ts/saveGasReport.ts diff --git a/.changeset/brown-panthers-melt.md b/.changeset/brown-panthers-melt.md new file mode 100644 index 0000000000..90c6354ec8 --- /dev/null +++ b/.changeset/brown-panthers-melt.md @@ -0,0 +1,5 @@ +--- +"@latticexyz/gas-report": patch +--- + +Gas report output now include contract name as part of the `file` to help with stable ordering when sorting output. diff --git a/packages/cli/src/commands/index.ts b/packages/cli/src/commands/index.ts index 84ffef5bb6..c77bb3b638 100644 --- a/packages/cli/src/commands/index.ts +++ b/packages/cli/src/commands/index.ts @@ -1,6 +1,6 @@ import { CommandModule } from "yargs"; -import gasReport from "@latticexyz/gas-report"; +import { command as gasReport } from "@latticexyz/gas-report/internal"; import abiTs from "@latticexyz/abi-ts"; import build from "./build"; @@ -21,7 +21,7 @@ export const commands: CommandModule[] = [ build, deploy, devnode, - gasReport as CommandModule, + gasReport, hello, tablegen, worldgen, diff --git a/packages/gas-report/bin/gas-report.js b/packages/gas-report/bin/gas-report.js index aea35994f0..a24ccf717d 100755 --- a/packages/gas-report/bin/gas-report.js +++ b/packages/gas-report/bin/gas-report.js @@ -1,4 +1,4 @@ #!/usr/bin/env node // workaround for https://github.com/pnpm/pnpm/issues/1801 -import "../dist/gas-report.js"; +import "../dist/bin/gas-report.js"; diff --git a/packages/gas-report/package.json b/packages/gas-report/package.json index 10e102c374..3ce4df5e16 100644 --- a/packages/gas-report/package.json +++ b/packages/gas-report/package.json @@ -10,12 +10,12 @@ "license": "MIT", "type": "module", "exports": { - ".": "./dist/index.js" + "./internal": "./dist/exports/internal.js" }, "typesVersions": { "*": { - "index": [ - "./dist/index.d.ts" + "internal": [ + "./dist/exports/internal.d.ts" ] } }, diff --git a/packages/gas-report/ts/gas-report.ts b/packages/gas-report/ts/bin/gas-report.ts similarity index 95% rename from packages/gas-report/ts/gas-report.ts rename to packages/gas-report/ts/bin/gas-report.ts index a7e749ef39..5b553f42a3 100755 --- a/packages/gas-report/ts/gas-report.ts +++ b/packages/gas-report/ts/bin/gas-report.ts @@ -2,7 +2,7 @@ import yargs from "yargs"; import { hideBin } from "yargs/helpers"; -import gasReport from "."; +import { command as gasReport } from "../command"; // Load .env file into process.env import * as dotenv from "dotenv"; diff --git a/packages/gas-report/ts/command.ts b/packages/gas-report/ts/command.ts new file mode 100644 index 0000000000..9b409e0182 --- /dev/null +++ b/packages/gas-report/ts/command.ts @@ -0,0 +1,81 @@ +import type { CommandModule } from "yargs"; +import { readFileSync } from "fs"; +import chalk from "chalk"; +import { runGasReport } from "./runGasReport"; +import { printGasReport } from "./printGasReport"; +import { saveGasReport } from "./saveGasReport"; +import { CommandOptions, GasReport } from "./common"; + +/** + * Print the gas report to the console, save it to a file and compare it to a previous gas report if provided. + * + * Requires foundry to be installed. Inherit from GasReporter and use startGasReport/endGasReport to measure gas used. + * + * ```solidity + * contract MyContractTest is Test, GasReporter { + * function testBuffer() public pure { + * startGasReport("allocate a buffer"); + * Buffer buffer = Buffer_.allocate(32); + * endGasReport(); + * + * bytes32 value = keccak256("some data"); + * + * startGasReport("append 32 bytes to a buffer"); + * buffer.append(value); + * endGasReport(); + * } + * } + * ``` + */ + +export const command: CommandModule = { + command: "gas-report", + + describe: "Create a gas report", + + builder(yargs) { + return yargs.options({ + save: { type: "string", desc: "Save the gas report to a file" }, + compare: { type: "string", desc: "Compare to an existing gas report" }, + stdin: { + type: "boolean", + desc: "Parse the gas report logs from stdin instead of running an internal test command", + }, + }); + }, + + async handler(options) { + let gasReport: GasReport; + try { + gasReport = await runGasReport(options); + } catch (error) { + console.error(error); + setTimeout(() => process.exit()); + return; + } + + // If this gas report should be compared to an existing one, load the existing one + let { compare } = options; + if (compare) { + try { + const compareGasReport: GasReport = JSON.parse(readFileSync(compare, "utf8")); + // Merge the previous gas report with the new one + gasReport = gasReport.map((entry) => { + const prevEntry = compareGasReport.find((e) => e.file === entry.file && e.name === entry.name); + return { ...entry, prevGasUsed: prevEntry?.gasUsed }; + }); + } catch { + console.log(chalk.red(`Gas report to compare not found: ${compare}`)); + compare = undefined; + } + } + + // Print gas report + printGasReport(gasReport, compare); + + // Save gas report to file if requested + if (options.save) saveGasReport(gasReport, options.save); + + process.exit(0); + }, +}; diff --git a/packages/gas-report/ts/common.ts b/packages/gas-report/ts/common.ts new file mode 100644 index 0000000000..4301202329 --- /dev/null +++ b/packages/gas-report/ts/common.ts @@ -0,0 +1,16 @@ +export type GasReportEntry = { + file: string; + test: string; + name: string; + gasUsed: number; + prevGasUsed?: number; +}; + +export type GasReport = GasReportEntry[]; + +export type CommandOptions = { + path: string[]; + save?: string; + compare?: string; + stdin?: boolean; +}; diff --git a/packages/gas-report/ts/exports/internal.ts b/packages/gas-report/ts/exports/internal.ts new file mode 100644 index 0000000000..3f886b70f3 --- /dev/null +++ b/packages/gas-report/ts/exports/internal.ts @@ -0,0 +1 @@ +export * from "../command"; diff --git a/packages/gas-report/ts/index.ts b/packages/gas-report/ts/index.ts deleted file mode 100644 index e8d41f731b..0000000000 --- a/packages/gas-report/ts/index.ts +++ /dev/null @@ -1,211 +0,0 @@ -import type { CommandModule } from "yargs"; -import { readFileSync, writeFileSync } from "fs"; -import { execa } from "execa"; -import chalk from "chalk"; -import { table, getBorderCharacters } from "table"; -import stripAnsi from "strip-ansi"; -import toArray from "stream-to-array"; - -/** - * Print the gas report to the console, save it to a file and compare it to a previous gas report if provided. - * - * Requires foundry to be installed. Inherit from GasReporter and use startGasReport/endGasReport to measure gas used. - * - * ```solidity - * contract MyContractTest is Test, GasReporter { - * function testBuffer() public pure { - * startGasReport("allocate a buffer"); - * Buffer buffer = Buffer_.allocate(32); - * endGasReport(); - * - * bytes32 value = keccak256("some data"); - * - * startGasReport("append 32 bytes to a buffer"); - * buffer.append(value); - * endGasReport(); - * } - * } - * ``` - */ - -type Options = { - path: string[]; - save?: string; - compare?: string; - stdin?: boolean; -}; - -type GasReportEntry = { - file: string; - test: string; - name: string; - gasUsed: number; - prevGasUsed?: number; -}; - -type GasReport = GasReportEntry[]; - -const commandModule: CommandModule = { - command: "gas-report", - - describe: "Create a gas report", - - builder(yargs) { - return yargs.options({ - save: { type: "string", desc: "Save the gas report to a file" }, - compare: { type: "string", desc: "Compare to an existing gas report" }, - stdin: { - type: "boolean", - desc: "Parse the gas report logs from stdin instead of running an internal test command", - }, - }); - }, - - async handler(options) { - let gasReport: GasReport; - try { - gasReport = await runGasReport(options); - } catch (error) { - console.error(error); - setTimeout(() => process.exit()); - return; - } - - // If this gas report should be compared to an existing one, load the existing one - let { compare } = options; - if (compare) { - try { - const compareGasReport: GasReport = JSON.parse(readFileSync(compare, "utf8")); - // Merge the previous gas report with the new one - gasReport = gasReport.map((entry) => { - const prevEntry = compareGasReport.find((e) => e.file === entry.file && e.name === entry.name); - return { ...entry, prevGasUsed: prevEntry?.gasUsed }; - }); - } catch { - console.log(chalk.red(`Gas report to compare not found: ${compare}`)); - compare = undefined; - } - } - - // Print gas report - printGasReport(gasReport, compare); - - // Save gas report to file if requested - if (options.save) saveGasReport(gasReport, options.save); - - process.exit(0); - }, -}; - -export default commandModule; - -async function runGasReport(options: Options): Promise { - console.log("Running gas report"); - const gasReport: GasReport = []; - - // Extract the logs from the child process - let logs: string; - try { - if (options.stdin) { - // Read the logs from stdin and pipe them to stdout for visibility - console.log("Waiting for stdin..."); - process.stdin.pipe(process.stdout); - logs = (await toArray(process.stdin)).map((chunk) => chunk.toString()).join("\n"); - console.log("Done reading stdin"); - } else { - // Run the default test command to capture the logs - const child = execa("forge", ["test", "-vvv", "--isolate"], { - stdio: ["inherit", "pipe", "inherit"], - env: { GAS_REPORTER_ENABLED: "true" }, - }); - logs = (await child).stdout; - } - // eslint-disable-next-line @typescript-eslint/no-explicit-any - } catch (error: any) { - console.log(error.stdout ?? error); - console.log(chalk.red("\n-----------\nError while running the gas report (see above)")); - throw error; - } - - // Extract the gas reports from the logs - const lines = logs.split("\n").map(stripAnsi); - const gasReportPattern = /^\s*GAS REPORT: (\d+) (.*)$/; - const testFunctionPattern = /^\[(?:PASS|FAIL).*\] (\w+)\(\)/; - // Matches "Running" for forge versions before 2024-02-15 - // And "Ran" for forge versions after 2024-02-15 - const testFilePattern = /^(?:Running|Ran) \d+ tests? for (.*):(.*)$/; - - function nearestLine(pattern: RegExp, startIndex: number = lines.length - 1): number { - for (let i = startIndex; i >= 0; i--) { - const line = lines[i]; - if (pattern.test(line)) { - return i; - } - } - return -1; - } - - for (let i = 0; i < lines.length; i++) { - const matches = lines[i].match(gasReportPattern); - if (!matches) continue; - - const gasUsed = parseInt(matches[1]); - const name = matches[2]; - - const testFunctionLineIndex = nearestLine(testFunctionPattern, i); - if (testFunctionLineIndex === -1) { - throw new Error("Could not find nearest test function, did `forge test` output change?"); - } - const testFileLineIndex = nearestLine(testFilePattern, testFunctionLineIndex); - if (testFileLineIndex === -1) { - throw new Error("Could not find nearest test filename, did `forge test` output change?"); - } - - const functionMatches = lines[testFunctionLineIndex].match(testFunctionPattern); - if (!functionMatches) { - throw new Error("Could not parse test function name, did `forge test` output change?"); - } - const fileMatches = lines[testFileLineIndex].match(testFilePattern); - if (!fileMatches) { - throw new Error("Could not parse test filename, did `forge test` output change?"); - } - - const test = functionMatches[1]; - const file = fileMatches[1]; - - gasReport.push({ file, test, name, gasUsed }); - } - - gasReport.sort((a, b) => a.file.localeCompare(b.file)); - - return gasReport; -} - -function printGasReport(gasReport: GasReport, compare?: string) { - if (compare) console.log(chalk.bold(`Gas report compared to ${compare}`)); - - const headers = [ - chalk.bold("File"), - chalk.bold("Test"), - chalk.bold("Name"), - chalk.bold("Gas used"), - ...(compare ? [chalk.bold("Prev gas used"), chalk.bold("Difference")] : []), - ]; - - const values = gasReport.map((entry) => { - const diff = entry.prevGasUsed ? entry.gasUsed - entry.prevGasUsed : 0; - const diffEntry = diff > 0 ? chalk.red(`+${diff}`) : diff < 0 ? chalk.green(`${diff}`) : diff; - const compareColumns = compare ? [entry.prevGasUsed ?? "n/a", diffEntry] : []; - const gasUsedEntry = diff > 0 ? chalk.red(entry.gasUsed) : diff < 0 ? chalk.green(entry.gasUsed) : entry.gasUsed; - return [entry.file, entry.test, entry.name, gasUsedEntry, ...compareColumns]; - }); - - const rows = [headers, ...values]; - - console.log(table(rows, { border: getBorderCharacters("norc") })); -} - -function saveGasReport(gasReport: GasReport, path: string) { - console.log(chalk.bold(`Saving gas report to ${path}`)); - writeFileSync(path, `${JSON.stringify(gasReport, null, 2)}\n`); -} diff --git a/packages/gas-report/ts/printGasReport.ts b/packages/gas-report/ts/printGasReport.ts new file mode 100644 index 0000000000..743c667ef8 --- /dev/null +++ b/packages/gas-report/ts/printGasReport.ts @@ -0,0 +1,27 @@ +import chalk from "chalk"; +import { table, getBorderCharacters } from "table"; +import { GasReport } from "./common"; + +export function printGasReport(gasReport: GasReport, compare?: string) { + if (compare) console.log(chalk.bold(`Gas report compared to ${compare}`)); + + const headers = [ + chalk.bold("File"), + chalk.bold("Test"), + chalk.bold("Name"), + chalk.bold("Gas used"), + ...(compare ? [chalk.bold("Prev gas used"), chalk.bold("Difference")] : []), + ]; + + const values = gasReport.map((entry) => { + const diff = entry.prevGasUsed ? entry.gasUsed - entry.prevGasUsed : 0; + const diffEntry = diff > 0 ? chalk.red(`+${diff}`) : diff < 0 ? chalk.green(`${diff}`) : diff; + const compareColumns = compare ? [entry.prevGasUsed ?? "n/a", diffEntry] : []; + const gasUsedEntry = diff > 0 ? chalk.red(entry.gasUsed) : diff < 0 ? chalk.green(entry.gasUsed) : entry.gasUsed; + return [entry.file, entry.test, entry.name, gasUsedEntry, ...compareColumns]; + }); + + const rows = [headers, ...values]; + + console.log(table(rows, { border: getBorderCharacters("norc") })); +} diff --git a/packages/gas-report/ts/runGasReport.ts b/packages/gas-report/ts/runGasReport.ts new file mode 100644 index 0000000000..836068b58e --- /dev/null +++ b/packages/gas-report/ts/runGasReport.ts @@ -0,0 +1,87 @@ +import { execa } from "execa"; +import chalk from "chalk"; +import stripAnsi from "strip-ansi"; +import toArray from "stream-to-array"; +import { CommandOptions, GasReport } from "./common"; + +export async function runGasReport(options: CommandOptions): Promise { + console.log("Running gas report"); + const gasReport: GasReport = []; + + // Extract the logs from the child process + let logs: string; + try { + if (options.stdin) { + // Read the logs from stdin and pipe them to stdout for visibility + console.log("Waiting for stdin..."); + process.stdin.pipe(process.stdout); + logs = (await toArray(process.stdin)).map((chunk) => chunk.toString()).join("\n"); + console.log("Done reading stdin"); + } else { + // Run the default test command to capture the logs + const child = execa("forge", ["test", "-vvv", "--isolate"], { + stdio: ["inherit", "pipe", "inherit"], + env: { GAS_REPORTER_ENABLED: "true" }, + }); + logs = (await child).stdout; + } + // eslint-disable-next-line @typescript-eslint/no-explicit-any + } catch (error: any) { + console.log(error.stdout ?? error); + console.log(chalk.red("\n-----------\nError while running the gas report (see above)")); + throw error; + } + + // Extract the gas reports from the logs + const lines = logs.split("\n").map(stripAnsi); + const gasReportPattern = /^\s*GAS REPORT: (\d+) (.*)$/; + const testFunctionPattern = /^\[(?:PASS|FAIL).*\] (\w+)\(\)/; + // Matches "Running" for forge versions before 2024-02-15 + // And "Ran" for forge versions after 2024-02-15 + const testFilePattern = /^(?:Running|Ran) \d+ tests? for (.*:.*)$/; + + function nearestLine(pattern: RegExp, startIndex: number = lines.length - 1): number { + for (let i = startIndex; i >= 0; i--) { + const line = lines[i]; + if (pattern.test(line)) { + return i; + } + } + return -1; + } + + for (let i = 0; i < lines.length; i++) { + const matches = lines[i].match(gasReportPattern); + if (!matches) continue; + + const gasUsed = parseInt(matches[1]); + const name = matches[2]; + + const testFunctionLineIndex = nearestLine(testFunctionPattern, i); + if (testFunctionLineIndex === -1) { + throw new Error("Could not find nearest test function, did `forge test` output change?"); + } + const testFileLineIndex = nearestLine(testFilePattern, testFunctionLineIndex); + if (testFileLineIndex === -1) { + throw new Error("Could not find nearest test filename, did `forge test` output change?"); + } + + const functionMatches = lines[testFunctionLineIndex].match(testFunctionPattern); + if (!functionMatches) { + throw new Error("Could not parse test function name, did `forge test` output change?"); + } + const fileMatches = lines[testFileLineIndex].match(testFilePattern); + if (!fileMatches) { + throw new Error("Could not parse test filename, did `forge test` output change?"); + } + + const test = functionMatches[1]; + const file = fileMatches[1]; + + gasReport.push({ file, test, name, gasUsed }); + } + + gasReport.sort((a, b) => a.file.localeCompare(b.file)); + + return gasReport; +} diff --git a/packages/gas-report/ts/saveGasReport.ts b/packages/gas-report/ts/saveGasReport.ts new file mode 100644 index 0000000000..b3a50b261f --- /dev/null +++ b/packages/gas-report/ts/saveGasReport.ts @@ -0,0 +1,8 @@ +import { writeFileSync } from "fs"; +import chalk from "chalk"; +import { GasReport } from "./common"; + +export function saveGasReport(gasReport: GasReport, path: string) { + console.log(chalk.bold(`Saving gas report to ${path}`)); + writeFileSync(path, `${JSON.stringify(gasReport, null, 2)}\n`); +} diff --git a/packages/gas-report/tsup.config.ts b/packages/gas-report/tsup.config.ts index 9af3b2af9a..a8aa90e47a 100644 --- a/packages/gas-report/tsup.config.ts +++ b/packages/gas-report/tsup.config.ts @@ -1,7 +1,7 @@ import { defineConfig } from "tsup"; export default defineConfig((opts) => ({ - entry: ["ts/index.ts", "ts/gas-report.ts"], + entry: ["ts/exports/internal.ts", "ts/bin/gas-report.ts"], target: "esnext", format: ["esm"], sourcemap: true, diff --git a/packages/schema-type/gas-report.json b/packages/schema-type/gas-report.json index 79ce1f0213..33a4bdf04e 100644 --- a/packages/schema-type/gas-report.json +++ b/packages/schema-type/gas-report.json @@ -1,108 +1,108 @@ [ { - "file": "test/solidity/SchemaType.t.sol", + "file": "test/solidity/SchemaType.t.sol:SchemaTypeTest", "test": "testGas", "name": "getStaticByteLength: uint8", "gasUsed": 124 }, { - "file": "test/solidity/SchemaType.t.sol", + "file": "test/solidity/SchemaType.t.sol:SchemaTypeTest", "test": "testGas", "name": "getStaticByteLength: uint32", "gasUsed": 123 }, { - "file": "test/solidity/SchemaType.t.sol", + "file": "test/solidity/SchemaType.t.sol:SchemaTypeTest", "test": "testGas", "name": "getStaticByteLength: uint256", "gasUsed": 123 }, { - "file": "test/solidity/SchemaType.t.sol", + "file": "test/solidity/SchemaType.t.sol:SchemaTypeTest", "test": "testGas", "name": "getStaticByteLength: int8", "gasUsed": 123 }, { - "file": "test/solidity/SchemaType.t.sol", + "file": "test/solidity/SchemaType.t.sol:SchemaTypeTest", "test": "testGas", "name": "getStaticByteLength: int32", "gasUsed": 123 }, { - "file": "test/solidity/SchemaType.t.sol", + "file": "test/solidity/SchemaType.t.sol:SchemaTypeTest", "test": "testGas", "name": "getStaticByteLength: int256", "gasUsed": 123 }, { - "file": "test/solidity/SchemaType.t.sol", + "file": "test/solidity/SchemaType.t.sol:SchemaTypeTest", "test": "testGas", "name": "getStaticByteLength: bytes1", "gasUsed": 123 }, { - "file": "test/solidity/SchemaType.t.sol", + "file": "test/solidity/SchemaType.t.sol:SchemaTypeTest", "test": "testGas", "name": "getStaticByteLength: bytes4", "gasUsed": 123 }, { - "file": "test/solidity/SchemaType.t.sol", + "file": "test/solidity/SchemaType.t.sol:SchemaTypeTest", "test": "testGas", "name": "getStaticByteLength: bytes32", "gasUsed": 123 }, { - "file": "test/solidity/SchemaType.t.sol", + "file": "test/solidity/SchemaType.t.sol:SchemaTypeTest", "test": "testGas", "name": "getStaticByteLength: bool", "gasUsed": 123 }, { - "file": "test/solidity/SchemaType.t.sol", + "file": "test/solidity/SchemaType.t.sol:SchemaTypeTest", "test": "testGas", "name": "getStaticByteLength: address", "gasUsed": 165 }, { - "file": "test/solidity/SchemaType.t.sol", + "file": "test/solidity/SchemaType.t.sol:SchemaTypeTest", "test": "testGas", "name": "getStaticByteLength: uint8[]", "gasUsed": 166 }, { - "file": "test/solidity/SchemaType.t.sol", + "file": "test/solidity/SchemaType.t.sol:SchemaTypeTest", "test": "testGas", "name": "getStaticByteLength: uint32[]", "gasUsed": 166 }, { - "file": "test/solidity/SchemaType.t.sol", + "file": "test/solidity/SchemaType.t.sol:SchemaTypeTest", "test": "testGas", "name": "getStaticByteLength: uint256[]", "gasUsed": 166 }, { - "file": "test/solidity/SchemaType.t.sol", + "file": "test/solidity/SchemaType.t.sol:SchemaTypeTest", "test": "testGas", "name": "getStaticByteLength: int256[]", "gasUsed": 166 }, { - "file": "test/solidity/SchemaType.t.sol", + "file": "test/solidity/SchemaType.t.sol:SchemaTypeTest", "test": "testGas", "name": "getStaticByteLength: bytes32[]", "gasUsed": 166 }, { - "file": "test/solidity/SchemaType.t.sol", + "file": "test/solidity/SchemaType.t.sol:SchemaTypeTest", "test": "testGas", "name": "getStaticByteLength: bytes", "gasUsed": 166 }, { - "file": "test/solidity/SchemaType.t.sol", + "file": "test/solidity/SchemaType.t.sol:SchemaTypeTest", "test": "testGas", "name": "getStaticByteLength: string", "gasUsed": 166 diff --git a/packages/store/gas-report.json b/packages/store/gas-report.json index b3b2ab23e7..2d5d001dac 100644 --- a/packages/store/gas-report.json +++ b/packages/store/gas-report.json @@ -1,1128 +1,1128 @@ [ { - "file": "test/Bytes.t.sol", + "file": "test/Bytes.t.sol:BytesTest", "test": "testGetBytes3", "name": "get bytes3 with offset 1", "gasUsed": 13 }, { - "file": "test/Bytes.t.sol", + "file": "test/Bytes.t.sol:BytesTest", "test": "testGetBytes32", "name": "get bytes32 with offset 10", "gasUsed": 13 }, { - "file": "test/Bytes.t.sol", + "file": "test/Bytes.t.sol:BytesTest", "test": "testSetBytes4Memory", "name": "set bytes4 in bytes memory", "gasUsed": 37 }, { - "file": "test/Callbacks.t.sol", + "file": "test/Callbacks.t.sol:CallbacksTest", "test": "testSetAndGet", "name": "Callbacks: set field", "gasUsed": 56118 }, { - "file": "test/Callbacks.t.sol", + "file": "test/Callbacks.t.sol:CallbacksTest", "test": "testSetAndGet", "name": "Callbacks: get field (warm)", "gasUsed": 2616 }, { - "file": "test/Callbacks.t.sol", + "file": "test/Callbacks.t.sol:CallbacksTest", "test": "testSetAndGet", "name": "Callbacks: push 1 element", "gasUsed": 32402 }, { - "file": "test/EncodedLengths.t.sol", + "file": "test/EncodedLengths.t.sol:EncodedLengthsTest", "test": "testAtIndex", "name": "get value at index of EncodedLengths", "gasUsed": 24 }, { - "file": "test/EncodedLengths.t.sol", + "file": "test/EncodedLengths.t.sol:EncodedLengthsTest", "test": "testGas", "name": "pack 1 length into EncodedLengths", "gasUsed": 35 }, { - "file": "test/EncodedLengths.t.sol", + "file": "test/EncodedLengths.t.sol:EncodedLengthsTest", "test": "testGas", "name": "pack 4 lengths into EncodedLengths", "gasUsed": 169 }, { - "file": "test/EncodedLengths.t.sol", + "file": "test/EncodedLengths.t.sol:EncodedLengthsTest", "test": "testGas", "name": "get total of EncodedLengths", "gasUsed": 15 }, { - "file": "test/EncodedLengths.t.sol", + "file": "test/EncodedLengths.t.sol:EncodedLengthsTest", "test": "testSetAtIndex", "name": "set value at index of EncodedLengths", "gasUsed": 283 }, { - "file": "test/FieldLayout.t.sol", + "file": "test/FieldLayout.t.sol:FieldLayoutTest", "test": "testEncodeDecodeFieldLayout", "name": "initialize field layout array with 5 entries", "gasUsed": 439 }, { - "file": "test/FieldLayout.t.sol", + "file": "test/FieldLayout.t.sol:FieldLayoutTest", "test": "testEncodeDecodeFieldLayout", "name": "encode field layout with 5+1 entries", "gasUsed": 2440 }, { - "file": "test/FieldLayout.t.sol", + "file": "test/FieldLayout.t.sol:FieldLayoutTest", "test": "testEncodeDecodeFieldLayout", "name": "get static byte length at index", "gasUsed": 19 }, { - "file": "test/FieldLayout.t.sol", + "file": "test/FieldLayout.t.sol:FieldLayoutTest", "test": "testGetNumDynamicFields", "name": "get number of dynamic fields from field layout", "gasUsed": 389 }, { - "file": "test/FieldLayout.t.sol", + "file": "test/FieldLayout.t.sol:FieldLayoutTest", "test": "testGetNumFields", "name": "get number of all fields from field layout", "gasUsed": 34 }, { - "file": "test/FieldLayout.t.sol", + "file": "test/FieldLayout.t.sol:FieldLayoutTest", "test": "testGetNumStaticFields", "name": "get number of static fields from field layout", "gasUsed": 311 }, { - "file": "test/FieldLayout.t.sol", + "file": "test/FieldLayout.t.sol:FieldLayoutTest", "test": "testGetStaticFieldLayoutLength", "name": "get static data length from field layout", "gasUsed": 228 }, { - "file": "test/FieldLayout.t.sol", + "file": "test/FieldLayout.t.sol:FieldLayoutTest", "test": "testIsEmptyFalse", "name": "check if field layout is empty (non-empty field layout)", "gasUsed": 7 }, { - "file": "test/FieldLayout.t.sol", + "file": "test/FieldLayout.t.sol:FieldLayoutTest", "test": "testIsEmptyTrue", "name": "check if field layout is empty (empty field layout)", "gasUsed": 7 }, { - "file": "test/FieldLayout.t.sol", + "file": "test/FieldLayout.t.sol:FieldLayoutTest", "test": "testValidate", "name": "validate field layout", "gasUsed": 6570 }, { - "file": "test/Gas.t.sol", + "file": "test/Gas.t.sol:GasTest", "test": "testCompareAbiEncodeVsCustom", "name": "abi encode (static)", "gasUsed": 133 }, { - "file": "test/Gas.t.sol", + "file": "test/Gas.t.sol:GasTest", "test": "testCompareAbiEncodeVsCustom", "name": "abi encode (dynamic)", "gasUsed": 824 }, { - "file": "test/Gas.t.sol", + "file": "test/Gas.t.sol:GasTest", "test": "testCompareAbiEncodeVsCustom", "name": "abi encode", "gasUsed": 918 }, { - "file": "test/Gas.t.sol", + "file": "test/Gas.t.sol:GasTest", "test": "testCompareAbiEncodeVsCustom", "name": "abi decode", "gasUsed": 1716 }, { - "file": "test/Gas.t.sol", + "file": "test/Gas.t.sol:GasTest", "test": "testCompareAbiEncodeVsCustom", "name": "custom encode (static)", "gasUsed": 191 }, { - "file": "test/Gas.t.sol", + "file": "test/Gas.t.sol:GasTest", "test": "testCompareAbiEncodeVsCustom", "name": "custom encode (length)", "gasUsed": 141 }, { - "file": "test/Gas.t.sol", + "file": "test/Gas.t.sol:GasTest", "test": "testCompareAbiEncodeVsCustom", "name": "custom encode (dynamic)", "gasUsed": 1101 }, { - "file": "test/Gas.t.sol", + "file": "test/Gas.t.sol:GasTest", "test": "testCompareAbiEncodeVsCustom", "name": "custom encode", "gasUsed": 1520 }, { - "file": "test/Gas.t.sol", + "file": "test/Gas.t.sol:GasTest", "test": "testCompareAbiEncodeVsCustom", "name": "custom decode", "gasUsed": 1954 }, { - "file": "test/Gas.t.sol", + "file": "test/Gas.t.sol:GasTest", "test": "testCompareAbiEncodeVsCustom", "name": "pass abi encoded bytes to external contract", "gasUsed": 29493 }, { - "file": "test/Gas.t.sol", + "file": "test/Gas.t.sol:GasTest", "test": "testCompareAbiEncodeVsCustom", "name": "pass custom encoded bytes to external contract", "gasUsed": 23310 }, { - "file": "test/Gas.t.sol", + "file": "test/Gas.t.sol:GasTest", "test": "testCompareStorageWriteMUD", "name": "MUD storage write (cold, 1 word)", "gasUsed": 22354 }, { - "file": "test/Gas.t.sol", + "file": "test/Gas.t.sol:GasTest", "test": "testCompareStorageWriteMUD", "name": "MUD storage write (cold, 1 word, partial)", "gasUsed": 22421 }, { - "file": "test/Gas.t.sol", + "file": "test/Gas.t.sol:GasTest", "test": "testCompareStorageWriteMUD", "name": "MUD storage write (cold, 10 words)", "gasUsed": 199810 }, { - "file": "test/Gas.t.sol", + "file": "test/Gas.t.sol:GasTest", "test": "testCompareStorageWriteMUD", "name": "MUD storage write (warm, 1 word)", "gasUsed": 354 }, { - "file": "test/Gas.t.sol", + "file": "test/Gas.t.sol:GasTest", "test": "testCompareStorageWriteMUD", "name": "MUD storage write (warm, 1 word, partial)", "gasUsed": 521 }, { - "file": "test/Gas.t.sol", + "file": "test/Gas.t.sol:GasTest", "test": "testCompareStorageWriteMUD", "name": "MUD storage write (warm, 10 words)", "gasUsed": 1810 }, { - "file": "test/Gas.t.sol", + "file": "test/Gas.t.sol:GasTest", "test": "testCompareStorageWriteSolidity", "name": "solidity storage write (cold, 1 word)", "gasUsed": 22107 }, { - "file": "test/Gas.t.sol", + "file": "test/Gas.t.sol:GasTest", "test": "testCompareStorageWriteSolidity", "name": "solidity storage write (cold, 1 word, partial)", "gasUsed": 22136 }, { - "file": "test/Gas.t.sol", + "file": "test/Gas.t.sol:GasTest", "test": "testCompareStorageWriteSolidity", "name": "solidity storage write (cold, 10 words)", "gasUsed": 200020 }, { - "file": "test/Gas.t.sol", + "file": "test/Gas.t.sol:GasTest", "test": "testCompareStorageWriteSolidity", "name": "solidity storage write (warm, 1 word)", "gasUsed": 107 }, { - "file": "test/Gas.t.sol", + "file": "test/Gas.t.sol:GasTest", "test": "testCompareStorageWriteSolidity", "name": "solidity storage write (warm, 1 word, partial)", "gasUsed": 236 }, { - "file": "test/Gas.t.sol", + "file": "test/Gas.t.sol:GasTest", "test": "testCompareStorageWriteSolidity", "name": "solidity storage write (warm, 10 words)", "gasUsed": 2265 }, { - "file": "test/GasStorageLoad.t.sol", + "file": "test/GasStorageLoad.t.sol:GasStorageLoadTest", "test": "testCompareStorageLoadMUD", "name": "MUD storage load (cold, 1 word)", "gasUsed": 2411 }, { - "file": "test/GasStorageLoad.t.sol", + "file": "test/GasStorageLoad.t.sol:GasStorageLoadTest", "test": "testCompareStorageLoadMUD", "name": "MUD storage load (cold, 1 word, partial)", "gasUsed": 2481 }, { - "file": "test/GasStorageLoad.t.sol", + "file": "test/GasStorageLoad.t.sol:GasStorageLoadTest", "test": "testCompareStorageLoadMUD", "name": "MUD storage load (cold, 10 words)", "gasUsed": 19911 }, { - "file": "test/GasStorageLoad.t.sol", + "file": "test/GasStorageLoad.t.sol:GasStorageLoadTest", "test": "testCompareStorageLoadMUD", "name": "MUD storage load (warm, 1 word)", "gasUsed": 412 }, { - "file": "test/GasStorageLoad.t.sol", + "file": "test/GasStorageLoad.t.sol:GasStorageLoadTest", "test": "testCompareStorageLoadMUD", "name": "MUD storage load field (warm, 1 word)", "gasUsed": 2300 }, { - "file": "test/GasStorageLoad.t.sol", + "file": "test/GasStorageLoad.t.sol:GasStorageLoadTest", "test": "testCompareStorageLoadMUD", "name": "MUD storage load (warm, 1 word, partial)", "gasUsed": 481 }, { - "file": "test/GasStorageLoad.t.sol", + "file": "test/GasStorageLoad.t.sol:GasStorageLoadTest", "test": "testCompareStorageLoadMUD", "name": "MUD storage load field (warm, 1 word, partial)", "gasUsed": 300 }, { - "file": "test/GasStorageLoad.t.sol", + "file": "test/GasStorageLoad.t.sol:GasStorageLoadTest", "test": "testCompareStorageLoadMUD", "name": "MUD storage load (warm, 10 words)", "gasUsed": 1916 }, { - "file": "test/GasStorageLoad.t.sol", + "file": "test/GasStorageLoad.t.sol:GasStorageLoadTest", "test": "testCompareStorageLoadSolidity", "name": "solidity storage load (cold, 1 word)", "gasUsed": 2109 }, { - "file": "test/GasStorageLoad.t.sol", + "file": "test/GasStorageLoad.t.sol:GasStorageLoadTest", "test": "testCompareStorageLoadSolidity", "name": "solidity storage load (cold, 1 word, partial)", "gasUsed": 2126 }, { - "file": "test/GasStorageLoad.t.sol", + "file": "test/GasStorageLoad.t.sol:GasStorageLoadTest", "test": "testCompareStorageLoadSolidity", "name": "solidity storage load (cold, 10 words)", "gasUsed": 19894 }, { - "file": "test/GasStorageLoad.t.sol", + "file": "test/GasStorageLoad.t.sol:GasStorageLoadTest", "test": "testCompareStorageLoadSolidity", "name": "solidity storage load (warm, 1 word)", "gasUsed": 109 }, { - "file": "test/GasStorageLoad.t.sol", + "file": "test/GasStorageLoad.t.sol:GasStorageLoadTest", "test": "testCompareStorageLoadSolidity", "name": "solidity storage load (warm, 1 word, partial)", "gasUsed": 126 }, { - "file": "test/GasStorageLoad.t.sol", + "file": "test/GasStorageLoad.t.sol:GasStorageLoadTest", "test": "testCompareStorageLoadSolidity", "name": "solidity storage load (warm, 10 words)", "gasUsed": 1897 }, { - "file": "test/Hook.t.sol", + "file": "test/Hook.t.sol:HookTest", "test": "testIsEnabled", "name": "check if hook is enabled", "gasUsed": 108 }, { - "file": "test/KeyEncoding.t.sol", + "file": "test/KeyEncoding.t.sol:KeyEncodingTest", "test": "testRegisterAndGetFieldLayout", "name": "register KeyEncoding table", "gasUsed": 721008 }, { - "file": "test/Mixed.t.sol", + "file": "test/Mixed.t.sol:MixedTest", "test": "testCompareSolidity", "name": "store Mixed struct in storage (native solidity)", "gasUsed": 92007 }, { - "file": "test/Mixed.t.sol", + "file": "test/Mixed.t.sol:MixedTest", "test": "testDeleteExternalCold", "name": "delete record from Mixed (external, cold)", "gasUsed": 23515 }, { - "file": "test/Mixed.t.sol", + "file": "test/Mixed.t.sol:MixedTest", "test": "testDeleteInternalCold", "name": "delete record from Mixed (internal, cold)", "gasUsed": 18724 }, { - "file": "test/Mixed.t.sol", + "file": "test/Mixed.t.sol:MixedTest", "test": "testSetGetDeleteExternal", "name": "set record in Mixed (external, cold)", "gasUsed": 107665 }, { - "file": "test/Mixed.t.sol", + "file": "test/Mixed.t.sol:MixedTest", "test": "testSetGetDeleteExternal", "name": "get record from Mixed (external, warm)", "gasUsed": 6746 }, { - "file": "test/Mixed.t.sol", + "file": "test/Mixed.t.sol:MixedTest", "test": "testSetGetDeleteExternal", "name": "delete record from Mixed (external, warm)", "gasUsed": 7826 }, { - "file": "test/Mixed.t.sol", + "file": "test/Mixed.t.sol:MixedTest", "test": "testSetGetDeleteInternal", "name": "set record in Mixed (internal, cold)", "gasUsed": 102831 }, { - "file": "test/Mixed.t.sol", + "file": "test/Mixed.t.sol:MixedTest", "test": "testSetGetDeleteInternal", "name": "get record from Mixed (internal, warm)", "gasUsed": 6434 }, { - "file": "test/Mixed.t.sol", + "file": "test/Mixed.t.sol:MixedTest", "test": "testSetGetDeleteInternal", "name": "delete record from Mixed (internal, warm)", "gasUsed": 7033 }, { - "file": "test/ResourceId.t.sol", + "file": "test/ResourceId.t.sol:ResourceIdTest", "test": "testEncode", "name": "encode table ID with name and type", "gasUsed": 80 }, { - "file": "test/ResourceId.t.sol", + "file": "test/ResourceId.t.sol:ResourceIdTest", "test": "testGetType", "name": "get type from a table ID", "gasUsed": 4 }, { - "file": "test/Schema.t.sol", + "file": "test/Schema.t.sol:SchemaTest", "test": "testEncodeDecodeSchema", "name": "initialize schema array with 6 entries", "gasUsed": 856 }, { - "file": "test/Schema.t.sol", + "file": "test/Schema.t.sol:SchemaTest", "test": "testEncodeDecodeSchema", "name": "encode schema with 6 entries", "gasUsed": 3609 }, { - "file": "test/Schema.t.sol", + "file": "test/Schema.t.sol:SchemaTest", "test": "testEncodeDecodeSchema", "name": "get schema type at index", "gasUsed": 119 }, { - "file": "test/Schema.t.sol", + "file": "test/Schema.t.sol:SchemaTest", "test": "testGetNumDynamicFields", "name": "get number of dynamic fields from schema", "gasUsed": 389 }, { - "file": "test/Schema.t.sol", + "file": "test/Schema.t.sol:SchemaTest", "test": "testGetNumFields", "name": "get number of all fields from schema", "gasUsed": 34 }, { - "file": "test/Schema.t.sol", + "file": "test/Schema.t.sol:SchemaTest", "test": "testGetNumStaticFields", "name": "get number of static fields from schema", "gasUsed": 311 }, { - "file": "test/Schema.t.sol", + "file": "test/Schema.t.sol:SchemaTest", "test": "testGetStaticSchemaLength", "name": "get static data length from schema", "gasUsed": 228 }, { - "file": "test/Schema.t.sol", + "file": "test/Schema.t.sol:SchemaTest", "test": "testIsEmptyFalse", "name": "check if schema is empty (non-empty schema)", "gasUsed": 7 }, { - "file": "test/Schema.t.sol", + "file": "test/Schema.t.sol:SchemaTest", "test": "testIsEmptyTrue", "name": "check if schema is empty (empty schema)", "gasUsed": 7 }, { - "file": "test/Schema.t.sol", + "file": "test/Schema.t.sol:SchemaTest", "test": "testValidate", "name": "validate schema", "gasUsed": 12485 }, { - "file": "test/Slice.t.sol", + "file": "test/Slice.t.sol:SliceTest", "test": "testFromBytes", "name": "make Slice from bytes", "gasUsed": 31 }, { - "file": "test/Slice.t.sol", + "file": "test/Slice.t.sol:SliceTest", "test": "testFromBytes", "name": "get Slice length", "gasUsed": 10 }, { - "file": "test/Slice.t.sol", + "file": "test/Slice.t.sol:SliceTest", "test": "testFromBytes", "name": "get Slice pointer", "gasUsed": 33 }, { - "file": "test/Slice.t.sol", + "file": "test/Slice.t.sol:SliceTest", "test": "testSubslice", "name": "subslice bytes (no copy) [1:4]", "gasUsed": 318 }, { - "file": "test/Slice.t.sol", + "file": "test/Slice.t.sol:SliceTest", "test": "testSubslice", "name": "subslice bytes (no copy) [4:37]", "gasUsed": 318 }, { - "file": "test/Slice.t.sol", + "file": "test/Slice.t.sol:SliceTest", "test": "testToBytes", "name": "Slice (0 bytes) to bytes memory", "gasUsed": 298 }, { - "file": "test/Slice.t.sol", + "file": "test/Slice.t.sol:SliceTest", "test": "testToBytes", "name": "Slice (2 bytes) to bytes memory", "gasUsed": 531 }, { - "file": "test/Slice.t.sol", + "file": "test/Slice.t.sol:SliceTest", "test": "testToBytes", "name": "Slice (32 bytes) to bytes memory", "gasUsed": 545 }, { - "file": "test/Slice.t.sol", + "file": "test/Slice.t.sol:SliceTest", "test": "testToBytes", "name": "Slice (34 bytes) to bytes memory", "gasUsed": 747 }, { - "file": "test/Slice.t.sol", + "file": "test/Slice.t.sol:SliceTest", "test": "testToBytes", "name": "Slice (1024 bytes) to bytes memory", "gasUsed": 7264 }, { - "file": "test/Slice.t.sol", + "file": "test/Slice.t.sol:SliceTest", "test": "testToBytes", "name": "Slice (1024x1024 bytes) to bytes memory", "gasUsed": 9205065 }, { - "file": "test/Slice.t.sol", + "file": "test/Slice.t.sol:SliceTest", "test": "testToBytes32", "name": "Slice to bytes32", "gasUsed": 81 }, { - "file": "test/Storage.t.sol", + "file": "test/Storage.t.sol:StorageTest", "test": "testStoreLoad", "name": "store 1 storage slot", "gasUsed": 22354 }, { - "file": "test/Storage.t.sol", + "file": "test/Storage.t.sol:StorageTest", "test": "testStoreLoad", "name": "store 34 bytes over 3 storage slots (with offset and safeTrail))", "gasUsed": 23025 }, { - "file": "test/Storage.t.sol", + "file": "test/Storage.t.sol:StorageTest", "test": "testStoreLoad", "name": "load 34 bytes over 3 storage slots (with offset and safeTrail))", "gasUsed": 871 }, { - "file": "test/StoreCoreDynamic.t.sol", + "file": "test/StoreCoreDynamic.t.sol:StoreCoreDynamicTest", "test": "testGetDynamicFieldSlice", "name": "get field slice (cold, 1 slot)", "gasUsed": 5566 }, { - "file": "test/StoreCoreDynamic.t.sol", + "file": "test/StoreCoreDynamic.t.sol:StoreCoreDynamicTest", "test": "testGetDynamicFieldSlice", "name": "get field slice (warm, 1 slot)", "gasUsed": 1668 }, { - "file": "test/StoreCoreDynamic.t.sol", + "file": "test/StoreCoreDynamic.t.sol:StoreCoreDynamicTest", "test": "testGetDynamicFieldSlice", "name": "get field slice (semi-cold, 1 slot)", "gasUsed": 3661 }, { - "file": "test/StoreCoreDynamic.t.sol", + "file": "test/StoreCoreDynamic.t.sol:StoreCoreDynamicTest", "test": "testGetDynamicFieldSlice", "name": "get field slice (warm, 2 slots)", "gasUsed": 3885 }, { - "file": "test/StoreCoreDynamic.t.sol", + "file": "test/StoreCoreDynamic.t.sol:StoreCoreDynamicTest", "test": "testGetSecondFieldLength", "name": "get field length (cold, 1 slot)", "gasUsed": 3163 }, { - "file": "test/StoreCoreDynamic.t.sol", + "file": "test/StoreCoreDynamic.t.sol:StoreCoreDynamicTest", "test": "testGetSecondFieldLength", "name": "get field length (warm, 1 slot)", "gasUsed": 1161 }, { - "file": "test/StoreCoreDynamic.t.sol", + "file": "test/StoreCoreDynamic.t.sol:StoreCoreDynamicTest", "test": "testGetThirdFieldLength", "name": "get field length (warm due to , 2 slots)", "gasUsed": 3164 }, { - "file": "test/StoreCoreDynamic.t.sol", + "file": "test/StoreCoreDynamic.t.sol:StoreCoreDynamicTest", "test": "testGetThirdFieldLength", "name": "get field length (warm, 2 slots)", "gasUsed": 1160 }, { - "file": "test/StoreCoreDynamic.t.sol", + "file": "test/StoreCoreDynamic.t.sol:StoreCoreDynamicTest", "test": "testPopFromSecondField", "name": "pop from field (cold, 1 slot, 1 uint32 item)", "gasUsed": 18107 }, { - "file": "test/StoreCoreDynamic.t.sol", + "file": "test/StoreCoreDynamic.t.sol:StoreCoreDynamicTest", "test": "testPopFromSecondField", "name": "pop from field (warm, 1 slot, 1 uint32 item)", "gasUsed": 12114 }, { - "file": "test/StoreCoreDynamic.t.sol", + "file": "test/StoreCoreDynamic.t.sol:StoreCoreDynamicTest", "test": "testPopFromThirdField", "name": "pop from field (cold, 2 slots, 10 uint32 items)", "gasUsed": 15876 }, { - "file": "test/StoreCoreDynamic.t.sol", + "file": "test/StoreCoreDynamic.t.sol:StoreCoreDynamicTest", "test": "testPopFromThirdField", "name": "pop from field (warm, 2 slots, 10 uint32 items)", "gasUsed": 11883 }, { - "file": "test/StoreCoreGas.t.sol", + "file": "test/StoreCoreGas.t.sol:StoreCoreGasTest", "test": "testAccessEmptyData", "name": "access non-existing record", "gasUsed": 7073 }, { - "file": "test/StoreCoreGas.t.sol", + "file": "test/StoreCoreGas.t.sol:StoreCoreGasTest", "test": "testAccessEmptyData", "name": "access static field of non-existing record", "gasUsed": 1330 }, { - "file": "test/StoreCoreGas.t.sol", + "file": "test/StoreCoreGas.t.sol:StoreCoreGasTest", "test": "testAccessEmptyData", "name": "access dynamic field of non-existing record", "gasUsed": 2062 }, { - "file": "test/StoreCoreGas.t.sol", + "file": "test/StoreCoreGas.t.sol:StoreCoreGasTest", "test": "testAccessEmptyData", "name": "access length of dynamic field of non-existing record", "gasUsed": 1159 }, { - "file": "test/StoreCoreGas.t.sol", + "file": "test/StoreCoreGas.t.sol:StoreCoreGasTest", "test": "testDeleteData", "name": "delete record (complex data, 3 slots)", "gasUsed": 7373 }, { - "file": "test/StoreCoreGas.t.sol", + "file": "test/StoreCoreGas.t.sol:StoreCoreGasTest", "test": "testDeleteDataOffchainTable", "name": "StoreCore: delete record in offchain table", "gasUsed": 28225 }, { - "file": "test/StoreCoreGas.t.sol", + "file": "test/StoreCoreGas.t.sol:StoreCoreGasTest", "test": "testGetStaticDataLocation", "name": "get static data location (single key)", "gasUsed": 217 }, { - "file": "test/StoreCoreGas.t.sol", + "file": "test/StoreCoreGas.t.sol:StoreCoreGasTest", "test": "testGetStaticDataLocation", "name": "get static data location (single key tuple)", "gasUsed": 387 }, { - "file": "test/StoreCoreGas.t.sol", + "file": "test/StoreCoreGas.t.sol:StoreCoreGasTest", "test": "testHasFieldLayout", "name": "Check for existence of table (existent)", "gasUsed": 1032 }, { - "file": "test/StoreCoreGas.t.sol", + "file": "test/StoreCoreGas.t.sol:StoreCoreGasTest", "test": "testHasFieldLayout", "name": "check for existence of table (non-existent)", "gasUsed": 3033 }, { - "file": "test/StoreCoreGas.t.sol", + "file": "test/StoreCoreGas.t.sol:StoreCoreGasTest", "test": "testHooks", "name": "register subscriber", "gasUsed": 56589 }, { - "file": "test/StoreCoreGas.t.sol", + "file": "test/StoreCoreGas.t.sol:StoreCoreGasTest", "test": "testHooks", "name": "set record on table with subscriber", "gasUsed": 98286 }, { - "file": "test/StoreCoreGas.t.sol", + "file": "test/StoreCoreGas.t.sol:StoreCoreGasTest", "test": "testHooks", "name": "set static field on table with subscriber", "gasUsed": 52904 }, { - "file": "test/StoreCoreGas.t.sol", + "file": "test/StoreCoreGas.t.sol:StoreCoreGasTest", "test": "testHooks", "name": "delete record on table with subscriber", "gasUsed": 47489 }, { - "file": "test/StoreCoreGas.t.sol", + "file": "test/StoreCoreGas.t.sol:StoreCoreGasTest", "test": "testHooksDynamicData", "name": "register subscriber", "gasUsed": 56589 }, { - "file": "test/StoreCoreGas.t.sol", + "file": "test/StoreCoreGas.t.sol:StoreCoreGasTest", "test": "testHooksDynamicData", "name": "set (dynamic) record on table with subscriber", "gasUsed": 191643 }, { - "file": "test/StoreCoreGas.t.sol", + "file": "test/StoreCoreGas.t.sol:StoreCoreGasTest", "test": "testHooksDynamicData", "name": "set (dynamic) field on table with subscriber", "gasUsed": 60374 }, { - "file": "test/StoreCoreGas.t.sol", + "file": "test/StoreCoreGas.t.sol:StoreCoreGasTest", "test": "testHooksDynamicData", "name": "delete (dynamic) record on table with subscriber", "gasUsed": 49266 }, { - "file": "test/StoreCoreGas.t.sol", + "file": "test/StoreCoreGas.t.sol:StoreCoreGasTest", "test": "testPushToDynamicField", "name": "push to field (1 slot, 1 uint32 item)", "gasUsed": 9539 }, { - "file": "test/StoreCoreGas.t.sol", + "file": "test/StoreCoreGas.t.sol:StoreCoreGasTest", "test": "testPushToDynamicField", "name": "push to field (2 slots, 10 uint32 items)", "gasUsed": 32211 }, { - "file": "test/StoreCoreGas.t.sol", + "file": "test/StoreCoreGas.t.sol:StoreCoreGasTest", "test": "testRegisterAndGetFieldLayout", "name": "StoreCore: register table", "gasUsed": 651056 }, { - "file": "test/StoreCoreGas.t.sol", + "file": "test/StoreCoreGas.t.sol:StoreCoreGasTest", "test": "testRegisterAndGetFieldLayout", "name": "StoreCore: get field layout (warm)", "gasUsed": 509 }, { - "file": "test/StoreCoreGas.t.sol", + "file": "test/StoreCoreGas.t.sol:StoreCoreGasTest", "test": "testRegisterAndGetFieldLayout", "name": "StoreCore: get value schema (warm)", "gasUsed": 1441 }, { - "file": "test/StoreCoreGas.t.sol", + "file": "test/StoreCoreGas.t.sol:StoreCoreGasTest", "test": "testRegisterAndGetFieldLayout", "name": "StoreCore: get key schema (warm)", "gasUsed": 2313 }, { - "file": "test/StoreCoreGas.t.sol", + "file": "test/StoreCoreGas.t.sol:StoreCoreGasTest", "test": "testSetAndGetDynamicData", "name": "set complex record with dynamic data (4 slots)", "gasUsed": 101867 }, { - "file": "test/StoreCoreGas.t.sol", + "file": "test/StoreCoreGas.t.sol:StoreCoreGasTest", "test": "testSetAndGetDynamicData", "name": "get complex record with dynamic data (4 slots)", "gasUsed": 4234 }, { - "file": "test/StoreCoreGas.t.sol", + "file": "test/StoreCoreGas.t.sol:StoreCoreGasTest", "test": "testSetAndGetDynamicData", "name": "compare: Set complex record with dynamic data using native solidity", "gasUsed": 116845 }, { - "file": "test/StoreCoreGas.t.sol", + "file": "test/StoreCoreGas.t.sol:StoreCoreGasTest", "test": "testSetAndGetDynamicData", "name": "compare: Set complex record with dynamic data using abi.encode", "gasUsed": 267535 }, { - "file": "test/StoreCoreGas.t.sol", + "file": "test/StoreCoreGas.t.sol:StoreCoreGasTest", "test": "testSetAndGetDynamicDataLength", "name": "set dynamic length of dynamic index 0", "gasUsed": 22867 }, { - "file": "test/StoreCoreGas.t.sol", + "file": "test/StoreCoreGas.t.sol:StoreCoreGasTest", "test": "testSetAndGetDynamicDataLength", "name": "set dynamic length of dynamic index 1", "gasUsed": 968 }, { - "file": "test/StoreCoreGas.t.sol", + "file": "test/StoreCoreGas.t.sol:StoreCoreGasTest", "test": "testSetAndGetDynamicDataLength", "name": "reduce dynamic length of dynamic index 0", "gasUsed": 958 }, { - "file": "test/StoreCoreGas.t.sol", + "file": "test/StoreCoreGas.t.sol:StoreCoreGasTest", "test": "testSetAndGetField", "name": "set static field (1 slot)", "gasUsed": 31026 }, { - "file": "test/StoreCoreGas.t.sol", + "file": "test/StoreCoreGas.t.sol:StoreCoreGasTest", "test": "testSetAndGetField", "name": "get static field (1 slot)", "gasUsed": 1331 }, { - "file": "test/StoreCoreGas.t.sol", + "file": "test/StoreCoreGas.t.sol:StoreCoreGasTest", "test": "testSetAndGetField", "name": "set static field (overlap 2 slot)", "gasUsed": 29609 }, { - "file": "test/StoreCoreGas.t.sol", + "file": "test/StoreCoreGas.t.sol:StoreCoreGasTest", "test": "testSetAndGetField", "name": "get static field (overlap 2 slot)", "gasUsed": 1812 }, { - "file": "test/StoreCoreGas.t.sol", + "file": "test/StoreCoreGas.t.sol:StoreCoreGasTest", "test": "testSetAndGetField", "name": "set dynamic field (1 slot, first dynamic field)", "gasUsed": 54022 }, { - "file": "test/StoreCoreGas.t.sol", + "file": "test/StoreCoreGas.t.sol:StoreCoreGasTest", "test": "testSetAndGetField", "name": "get dynamic field (1 slot, first dynamic field)", "gasUsed": 2227 }, { - "file": "test/StoreCoreGas.t.sol", + "file": "test/StoreCoreGas.t.sol:StoreCoreGasTest", "test": "testSetAndGetField", "name": "set dynamic field (1 slot, second dynamic field)", "gasUsed": 32247 }, { - "file": "test/StoreCoreGas.t.sol", + "file": "test/StoreCoreGas.t.sol:StoreCoreGasTest", "test": "testSetAndGetField", "name": "get dynamic field (1 slot, second dynamic field)", "gasUsed": 2229 }, { - "file": "test/StoreCoreGas.t.sol", + "file": "test/StoreCoreGas.t.sol:StoreCoreGasTest", "test": "testSetAndGetStaticData", "name": "set static record (1 slot)", "gasUsed": 32142 }, { - "file": "test/StoreCoreGas.t.sol", + "file": "test/StoreCoreGas.t.sol:StoreCoreGasTest", "test": "testSetAndGetStaticData", "name": "get static record (1 slot)", "gasUsed": 1552 }, { - "file": "test/StoreCoreGas.t.sol", + "file": "test/StoreCoreGas.t.sol:StoreCoreGasTest", "test": "testSetAndGetStaticDataSpanningWords", "name": "set static record (2 slots)", "gasUsed": 54650 }, { - "file": "test/StoreCoreGas.t.sol", + "file": "test/StoreCoreGas.t.sol:StoreCoreGasTest", "test": "testSetAndGetStaticDataSpanningWords", "name": "get static record (2 slots)", "gasUsed": 1737 }, { - "file": "test/StoreCoreGas.t.sol", + "file": "test/StoreCoreGas.t.sol:StoreCoreGasTest", "test": "testSetDataOffchainTable", "name": "StoreCore: set record in offchain table", "gasUsed": 32517 }, { - "file": "test/StoreCoreGas.t.sol", + "file": "test/StoreCoreGas.t.sol:StoreCoreGasTest", "test": "testUpdateInDynamicField", "name": "update in field (1 slot, 1 uint32 item)", "gasUsed": 8899 }, { - "file": "test/StoreCoreGas.t.sol", + "file": "test/StoreCoreGas.t.sol:StoreCoreGasTest", "test": "testUpdateInDynamicField", "name": "push to field (2 slots, 6 uint64 items)", "gasUsed": 9342 }, { - "file": "test/StoreHook.t.sol", + "file": "test/StoreHook.t.sol:StoreHookTest", "test": "testCallHook", "name": "call an enabled hook", "gasUsed": 37660 }, { - "file": "test/StoreHook.t.sol", + "file": "test/StoreHook.t.sol:StoreHookTest", "test": "testCallHook", "name": "call a disabled hook", "gasUsed": 123 }, { - "file": "test/StoreHook.t.sol", + "file": "test/StoreHook.t.sol:StoreHookTest", "test": "testGetAddress", "name": "get store hook address", "gasUsed": 1 }, { - "file": "test/StoreHook.t.sol", + "file": "test/StoreHook.t.sol:StoreHookTest", "test": "testIsEnabled", "name": "check if store hook is enabled", "gasUsed": 108 }, { - "file": "test/StoreHooks.t.sol", + "file": "test/StoreHooks.t.sol:StoreHooksTest", "test": "testOneSlot", "name": "StoreHooks: set field with one elements (cold)", "gasUsed": 58112 }, { - "file": "test/StoreHooks.t.sol", + "file": "test/StoreHooks.t.sol:StoreHooksTest", "test": "testTable", "name": "StoreHooks: set field (cold)", "gasUsed": 58112 }, { - "file": "test/StoreHooks.t.sol", + "file": "test/StoreHooks.t.sol:StoreHooksTest", "test": "testTable", "name": "StoreHooks: get field (warm)", "gasUsed": 2612 }, { - "file": "test/StoreHooks.t.sol", + "file": "test/StoreHooks.t.sol:StoreHooksTest", "test": "testTable", "name": "StoreHooks: push 1 element (cold)", "gasUsed": 12491 }, { - "file": "test/StoreHooks.t.sol", + "file": "test/StoreHooks.t.sol:StoreHooksTest", "test": "testTable", "name": "StoreHooks: pop 1 element (warm)", "gasUsed": 9795 }, { - "file": "test/StoreHooks.t.sol", + "file": "test/StoreHooks.t.sol:StoreHooksTest", "test": "testTable", "name": "StoreHooks: push 1 element (warm)", "gasUsed": 10507 }, { - "file": "test/StoreHooks.t.sol", + "file": "test/StoreHooks.t.sol:StoreHooksTest", "test": "testTable", "name": "StoreHooks: update 1 element (warm)", "gasUsed": 29741 }, { - "file": "test/StoreHooks.t.sol", + "file": "test/StoreHooks.t.sol:StoreHooksTest", "test": "testTable", "name": "StoreHooks: delete record (warm)", "gasUsed": 9552 }, { - "file": "test/StoreHooks.t.sol", + "file": "test/StoreHooks.t.sol:StoreHooksTest", "test": "testTable", "name": "StoreHooks: set field (warm)", "gasUsed": 30255 }, { - "file": "test/StoreHooks.t.sol", + "file": "test/StoreHooks.t.sol:StoreHooksTest", "test": "testThreeSlots", "name": "StoreHooks: set field with three elements (cold)", "gasUsed": 80803 }, { - "file": "test/StoreHooks.t.sol", + "file": "test/StoreHooks.t.sol:StoreHooksTest", "test": "testTwoSlots", "name": "StoreHooks: set field with two elements (cold)", "gasUsed": 80715 }, { - "file": "test/StoreHooksColdLoad.t.sol", + "file": "test/StoreHooksColdLoad.t.sol:StoreHooksColdLoadTest", "test": "testDelete", "name": "StoreHooks: delete record (cold)", "gasUsed": 18424 }, { - "file": "test/StoreHooksColdLoad.t.sol", + "file": "test/StoreHooksColdLoad.t.sol:StoreHooksColdLoadTest", "test": "testGet", "name": "StoreHooks: get field (cold)", "gasUsed": 8610 }, { - "file": "test/StoreHooksColdLoad.t.sol", + "file": "test/StoreHooksColdLoad.t.sol:StoreHooksColdLoadTest", "test": "testGetItem", "name": "StoreHooks: get 1 element (cold)", "gasUsed": 8224 }, { - "file": "test/StoreHooksColdLoad.t.sol", + "file": "test/StoreHooksColdLoad.t.sol:StoreHooksColdLoadTest", "test": "testLength", "name": "StoreHooks: get length (cold)", "gasUsed": 5172 }, { - "file": "test/StoreHooksColdLoad.t.sol", + "file": "test/StoreHooksColdLoad.t.sol:StoreHooksColdLoadTest", "test": "testPop", "name": "StoreHooks: pop 1 element (cold)", "gasUsed": 18231 }, { - "file": "test/StoreHooksColdLoad.t.sol", + "file": "test/StoreHooksColdLoad.t.sol:StoreHooksColdLoadTest", "test": "testUpdate", "name": "StoreHooks: update 1 element (cold)", "gasUsed": 20194 }, { - "file": "test/StoreSwitch.t.sol", + "file": "test/StoreSwitch.t.sol:StoreSwitchTest", "test": "testDelegatecall", "name": "get Store address", "gasUsed": 2170 }, { - "file": "test/StoreSwitch.t.sol", + "file": "test/StoreSwitch.t.sol:StoreSwitchTest", "test": "testNoDelegatecall", "name": "get Store address", "gasUsed": 2173 }, { - "file": "test/tightcoder/DecodeSlice.t.sol", + "file": "test/tightcoder/DecodeSlice.t.sol:DecodeSliceTest", "test": "testToArrayUint32", "name": "decode packed uint32[]", "gasUsed": 486 }, { - "file": "test/tightcoder/DecodeSlice.t.sol", + "file": "test/tightcoder/DecodeSlice.t.sol:DecodeSliceTest", "test": "testToBytes32Array", "name": "decode packed bytes32[]", "gasUsed": 475 }, { - "file": "test/tightcoder/EncodeArray.t.sol", + "file": "test/tightcoder/EncodeArray.t.sol:EncodeArrayTest", "test": "testEncodeUint16Array", "name": "encode packed uint16[]", "gasUsed": 594 }, { - "file": "test/tightcoder/EncodeArray.t.sol", + "file": "test/tightcoder/EncodeArray.t.sol:EncodeArrayTest", "test": "testEncodeUint32Array", "name": "encode packed uint32[]", "gasUsed": 503 }, { - "file": "test/tightcoder/EncodeArray.t.sol", + "file": "test/tightcoder/EncodeArray.t.sol:EncodeArrayTest", "test": "testEncodeUint8Array", "name": "encode packed uint8[]", "gasUsed": 492 }, { - "file": "test/tightcoder/TightCoder.t.sol", + "file": "test/tightcoder/TightCoder.t.sol:TightCoderTest", "test": "testFromAndToUint32Array", "name": "decode packed uint32[]", "gasUsed": 486 }, { - "file": "test/tightcoder/TightCoder.t.sol", + "file": "test/tightcoder/TightCoder.t.sol:TightCoderTest", "test": "testToAndFromBytes24Array", "name": "encode packed bytes24[]", "gasUsed": 503 }, { - "file": "test/tightcoder/TightCoder.t.sol", + "file": "test/tightcoder/TightCoder.t.sol:TightCoderTest", "test": "testToAndFromBytes24Array", "name": "decode packed bytes24[]", "gasUsed": 486 }, { - "file": "test/Vector2.t.sol", + "file": "test/Vector2.t.sol:Vector2Test", "test": "testRegisterAndGetFieldLayout", "name": "register Vector2 field layout", "gasUsed": 447063 }, { - "file": "test/Vector2.t.sol", + "file": "test/Vector2.t.sol:Vector2Test", "test": "testSetAndGet", "name": "set Vector2 record", "gasUsed": 32824 }, { - "file": "test/Vector2.t.sol", + "file": "test/Vector2.t.sol:Vector2Test", "test": "testSetAndGet", "name": "get Vector2 record", "gasUsed": 2306 diff --git a/packages/world-module-erc20/gas-report.json b/packages/world-module-erc20/gas-report.json index 4694a6acdf..8adb4db902 100644 --- a/packages/world-module-erc20/gas-report.json +++ b/packages/world-module-erc20/gas-report.json @@ -1,222 +1,222 @@ [ { - "file": "test/ERC20BaseTest.sol", + "file": "test/ERC20BaseTest.sol:ERC20WithInternalStoreTest", "test": "testApprove", "name": "internal_approve", "gasUsed": 58213 }, { - "file": "test/ERC20BaseTest.sol", + "file": "test/ERC20BaseTest.sol:ERC20WithInternalStoreTest", "test": "testBurn", "name": "internal_burn", "gasUsed": 56417 }, { - "file": "test/ERC20BaseTest.sol", + "file": "test/ERC20BaseTest.sol:ERC20WithInternalStoreTest", "test": "testMint", "name": "internal_mint", "gasUsed": 90703 }, { - "file": "test/ERC20BaseTest.sol", + "file": "test/ERC20BaseTest.sol:ERC20WithInternalStoreTest", "test": "testTransfer", "name": "internal_transfer", "gasUsed": 67747 }, { - "file": "test/ERC20BaseTest.sol", + "file": "test/ERC20BaseTest.sol:ERC20WithInternalStoreTest", "test": "testTransferFrom", "name": "internal_transferFrom", "gasUsed": 79574 }, { - "file": "test/ERC20BaseTest.sol", + "file": "test/ERC20BaseTest.sol:ERC20WithWorldTest", "test": "testApprove", "name": "world_approve", "gasUsed": 66343 }, { - "file": "test/ERC20BaseTest.sol", + "file": "test/ERC20BaseTest.sol:ERC20WithWorldTest", "test": "testBurn", "name": "world_burn", "gasUsed": 70873 }, { - "file": "test/ERC20BaseTest.sol", + "file": "test/ERC20BaseTest.sol:ERC20WithWorldTest", "test": "testMint", "name": "world_mint", "gasUsed": 105159 }, { - "file": "test/ERC20BaseTest.sol", + "file": "test/ERC20BaseTest.sol:ERC20WithWorldTest", "test": "testTransfer", "name": "world_transfer", "gasUsed": 82375 }, { - "file": "test/ERC20BaseTest.sol", + "file": "test/ERC20BaseTest.sol:ERC20WithWorldTest", "test": "testTransferFrom", "name": "world_transferFrom", "gasUsed": 99431 }, { - "file": "test/ERC20Burnable.t.sol", + "file": "test/ERC20Burnable.t.sol:ERC20BurnableWithInternalStoreTest", "test": "testApprove", "name": "internal_approve", "gasUsed": 58213 }, { - "file": "test/ERC20Burnable.t.sol", + "file": "test/ERC20Burnable.t.sol:ERC20BurnableWithInternalStoreTest", "test": "testBurn", "name": "internal_burn", "gasUsed": 56440 }, { - "file": "test/ERC20Burnable.t.sol", + "file": "test/ERC20Burnable.t.sol:ERC20BurnableWithInternalStoreTest", "test": "testBurnByAccount", "name": "internal_burn", "gasUsed": 56563 }, { - "file": "test/ERC20Burnable.t.sol", + "file": "test/ERC20Burnable.t.sol:ERC20BurnableWithInternalStoreTest", "test": "testMint", "name": "internal_mint", "gasUsed": 90703 }, { - "file": "test/ERC20Burnable.t.sol", + "file": "test/ERC20Burnable.t.sol:ERC20BurnableWithInternalStoreTest", "test": "testTransfer", "name": "internal_transfer", "gasUsed": 67702 }, { - "file": "test/ERC20Burnable.t.sol", + "file": "test/ERC20Burnable.t.sol:ERC20BurnableWithInternalStoreTest", "test": "testTransferFrom", "name": "internal_transferFrom", "gasUsed": 79574 }, { - "file": "test/ERC20Burnable.t.sol", + "file": "test/ERC20Burnable.t.sol:ERC20BurnableWithWorldTest", "test": "testApprove", "name": "world_approve", "gasUsed": 66343 }, { - "file": "test/ERC20Burnable.t.sol", + "file": "test/ERC20Burnable.t.sol:ERC20BurnableWithWorldTest", "test": "testBurn", "name": "world_burn", "gasUsed": 70874 }, { - "file": "test/ERC20Burnable.t.sol", + "file": "test/ERC20Burnable.t.sol:ERC20BurnableWithWorldTest", "test": "testBurnByAccount", "name": "world_burn", "gasUsed": 70997 }, { - "file": "test/ERC20Burnable.t.sol", + "file": "test/ERC20Burnable.t.sol:ERC20BurnableWithWorldTest", "test": "testMint", "name": "world_mint", "gasUsed": 105137 }, { - "file": "test/ERC20Burnable.t.sol", + "file": "test/ERC20Burnable.t.sol:ERC20BurnableWithWorldTest", "test": "testTransfer", "name": "world_transfer", "gasUsed": 82397 }, { - "file": "test/ERC20Burnable.t.sol", + "file": "test/ERC20Burnable.t.sol:ERC20BurnableWithWorldTest", "test": "testTransferFrom", "name": "world_transferFrom", "gasUsed": 99498 }, { - "file": "test/ERC20Module.t.sol", + "file": "test/ERC20Module.t.sol:ERC20ModuleTest", "test": "testInstall", "name": "install erc20 module", "gasUsed": 4524262 }, { - "file": "test/ERC20Pausable.t.sol", + "file": "test/ERC20Pausable.t.sol:ERC20PausableWithInternalStoreTest", "test": "testApprove", "name": "internal_approve", "gasUsed": 58213 }, { - "file": "test/ERC20Pausable.t.sol", + "file": "test/ERC20Pausable.t.sol:ERC20PausableWithInternalStoreTest", "test": "testBurn", "name": "internal_burn", "gasUsed": 59740 }, { - "file": "test/ERC20Pausable.t.sol", + "file": "test/ERC20Pausable.t.sol:ERC20PausableWithInternalStoreTest", "test": "testMint", "name": "internal_mint", "gasUsed": 94004 }, { - "file": "test/ERC20Pausable.t.sol", + "file": "test/ERC20Pausable.t.sol:ERC20PausableWithInternalStoreTest", "test": "testPause", "name": "internal_pause", "gasUsed": 56737 }, { - "file": "test/ERC20Pausable.t.sol", + "file": "test/ERC20Pausable.t.sol:ERC20PausableWithInternalStoreTest", "test": "testPause", "name": "internal_unpause", "gasUsed": 34845 }, { - "file": "test/ERC20Pausable.t.sol", + "file": "test/ERC20Pausable.t.sol:ERC20PausableWithInternalStoreTest", "test": "testTransfer", "name": "internal_transfer", "gasUsed": 71003 }, { - "file": "test/ERC20Pausable.t.sol", + "file": "test/ERC20Pausable.t.sol:ERC20PausableWithInternalStoreTest", "test": "testTransferFrom", "name": "internal_transferFrom", "gasUsed": 82876 }, { - "file": "test/ERC20Pausable.t.sol", + "file": "test/ERC20Pausable.t.sol:ERC20PausableWithWorldTest", "test": "testApprove", "name": "world_approve", "gasUsed": 66343 }, { - "file": "test/ERC20Pausable.t.sol", + "file": "test/ERC20Pausable.t.sol:ERC20PausableWithWorldTest", "test": "testBurn", "name": "world_burn", "gasUsed": 75661 }, { - "file": "test/ERC20Pausable.t.sol", + "file": "test/ERC20Pausable.t.sol:ERC20PausableWithWorldTest", "test": "testMint", "name": "world_mint", "gasUsed": 109880 }, { - "file": "test/ERC20Pausable.t.sol", + "file": "test/ERC20Pausable.t.sol:ERC20PausableWithWorldTest", "test": "testPause", "name": "world_pause", "gasUsed": 66128 }, { - "file": "test/ERC20Pausable.t.sol", + "file": "test/ERC20Pausable.t.sol:ERC20PausableWithWorldTest", "test": "testPause", "name": "world_unpause", "gasUsed": 44214 }, { - "file": "test/ERC20Pausable.t.sol", + "file": "test/ERC20Pausable.t.sol:ERC20PausableWithWorldTest", "test": "testTransfer", "name": "world_transfer", "gasUsed": 87096 }, { - "file": "test/ERC20Pausable.t.sol", + "file": "test/ERC20Pausable.t.sol:ERC20PausableWithWorldTest", "test": "testTransferFrom", "name": "world_transferFrom", "gasUsed": 104242 diff --git a/packages/world-module-metadata/gas-report.json b/packages/world-module-metadata/gas-report.json index 3709dce3c6..db85ecdf4d 100644 --- a/packages/world-module-metadata/gas-report.json +++ b/packages/world-module-metadata/gas-report.json @@ -1,18 +1,18 @@ [ { - "file": "test/MetadataModule.t.sol", + "file": "test/MetadataModule.t.sol:MetadataModuleTest", "test": "testDeleteResourceTag", "name": "delete resource tag", "gasUsed": 70301 }, { - "file": "test/MetadataModule.t.sol", + "file": "test/MetadataModule.t.sol:MetadataModuleTest", "test": "testInstall", "name": "install metadata module", "gasUsed": 1109853 }, { - "file": "test/MetadataModule.t.sol", + "file": "test/MetadataModule.t.sol:MetadataModuleTest", "test": "testSetResourceTag", "name": "set resource tag", "gasUsed": 116708 diff --git a/packages/world-modules/gas-report.json b/packages/world-modules/gas-report.json index cc113365fe..e026ade173 100644 --- a/packages/world-modules/gas-report.json +++ b/packages/world-modules/gas-report.json @@ -1,336 +1,336 @@ [ { - "file": "test/CallWithSignatureModule.t.sol", + "file": "test/CallWithSignatureModule.t.sol:Unstable_CallWithSignatureModuleTest", "test": "testInstallRoot", "name": "install delegation module", "gasUsed": 691133 }, { - "file": "test/CallWithSignatureModule.t.sol", + "file": "test/CallWithSignatureModule.t.sol:Unstable_CallWithSignatureModuleTest", "test": "testRegisterDelegationWithSignature", "name": "register an unlimited delegation with signature", "gasUsed": 138532 }, { - "file": "test/ERC20.t.sol", + "file": "test/ERC20.t.sol:ERC20Test", "test": "testApprove", "name": "approve", "gasUsed": 133324 }, { - "file": "test/ERC20.t.sol", + "file": "test/ERC20.t.sol:ERC20Test", "test": "testBurn", "name": "burn", "gasUsed": 141096 }, { - "file": "test/ERC20.t.sol", + "file": "test/ERC20.t.sol:ERC20Test", "test": "testMint", "name": "mint", "gasUsed": 179835 }, { - "file": "test/ERC20.t.sol", + "file": "test/ERC20.t.sol:ERC20Test", "test": "testTransfer", "name": "transfer", "gasUsed": 145002 }, { - "file": "test/ERC20.t.sol", + "file": "test/ERC20.t.sol:ERC20Test", "test": "testTransferFrom", "name": "transferFrom", "gasUsed": 183189 }, { - "file": "test/ERC721.t.sol", + "file": "test/ERC721.t.sol:ERC721Test", "test": "testApproveAllGas", "name": "setApprovalForAll", "gasUsed": 132887 }, { - "file": "test/ERC721.t.sol", + "file": "test/ERC721.t.sol:ERC721Test", "test": "testApproveGas", "name": "approve", "gasUsed": 136246 }, { - "file": "test/ERC721.t.sol", + "file": "test/ERC721.t.sol:ERC721Test", "test": "testBurnGas", "name": "burn", "gasUsed": 156244 }, { - "file": "test/ERC721.t.sol", + "file": "test/ERC721.t.sol:ERC721Test", "test": "testMintGas", "name": "mint", "gasUsed": 187144 }, { - "file": "test/ERC721.t.sol", + "file": "test/ERC721.t.sol:ERC721Test", "test": "testSafeMintToEOAGas", "name": "safeMint", "gasUsed": 189855 }, { - "file": "test/ERC721.t.sol", + "file": "test/ERC721.t.sol:ERC721Test", "test": "testSafeTransferFromToEOAGas", "name": "safeTransferFrom", "gasUsed": 201362 }, { - "file": "test/ERC721.t.sol", + "file": "test/ERC721.t.sol:ERC721Test", "test": "testTransferFromGas", "name": "transferFrom", "gasUsed": 190290 }, { - "file": "test/KeysInTableModule.t.sol", + "file": "test/KeysInTableModule.t.sol:KeysInTableModuleTest", "test": "testInstallComposite", "name": "install keys in table module", "gasUsed": 1463317 }, { - "file": "test/KeysInTableModule.t.sol", + "file": "test/KeysInTableModule.t.sol:KeysInTableModuleTest", "test": "testInstallGas", "name": "install keys in table module", "gasUsed": 1463317 }, { - "file": "test/KeysInTableModule.t.sol", + "file": "test/KeysInTableModule.t.sol:KeysInTableModuleTest", "test": "testInstallGas", "name": "set a record on a table with keysInTableModule installed", "gasUsed": 191359 }, { - "file": "test/KeysInTableModule.t.sol", + "file": "test/KeysInTableModule.t.sol:KeysInTableModuleTest", "test": "testInstallSingleton", "name": "install keys in table module", "gasUsed": 1463317 }, { - "file": "test/KeysInTableModule.t.sol", + "file": "test/KeysInTableModule.t.sol:KeysInTableModuleTest", "test": "testSetAndDeleteRecordHookCompositeGas", "name": "install keys in table module", "gasUsed": 1463317 }, { - "file": "test/KeysInTableModule.t.sol", + "file": "test/KeysInTableModule.t.sol:KeysInTableModuleTest", "test": "testSetAndDeleteRecordHookCompositeGas", "name": "change a composite record on a table with keysInTableModule installed", "gasUsed": 63600 }, { - "file": "test/KeysInTableModule.t.sol", + "file": "test/KeysInTableModule.t.sol:KeysInTableModuleTest", "test": "testSetAndDeleteRecordHookCompositeGas", "name": "delete a composite record on a table with keysInTableModule installed", "gasUsed": 223443 }, { - "file": "test/KeysInTableModule.t.sol", + "file": "test/KeysInTableModule.t.sol:KeysInTableModuleTest", "test": "testSetAndDeleteRecordHookGas", "name": "install keys in table module", "gasUsed": 1463317 }, { - "file": "test/KeysInTableModule.t.sol", + "file": "test/KeysInTableModule.t.sol:KeysInTableModuleTest", "test": "testSetAndDeleteRecordHookGas", "name": "change a record on a table with keysInTableModule installed", "gasUsed": 62271 }, { - "file": "test/KeysInTableModule.t.sol", + "file": "test/KeysInTableModule.t.sol:KeysInTableModuleTest", "test": "testSetAndDeleteRecordHookGas", "name": "delete a record on a table with keysInTableModule installed", "gasUsed": 141684 }, { - "file": "test/KeysWithValueModule.t.sol", + "file": "test/KeysWithValueModule.t.sol:KeysWithValueModuleTest", "test": "testGetKeysWithValueGas", "name": "install keys with value module", "gasUsed": 719012 }, { - "file": "test/KeysWithValueModule.t.sol", + "file": "test/KeysWithValueModule.t.sol:KeysWithValueModuleTest", "test": "testGetKeysWithValueGas", "name": "Get list of keys with a given value", "gasUsed": 5659 }, { - "file": "test/KeysWithValueModule.t.sol", + "file": "test/KeysWithValueModule.t.sol:KeysWithValueModuleTest", "test": "testGetTargetTableId", "name": "compute the target table selector", "gasUsed": 2624 }, { - "file": "test/KeysWithValueModule.t.sol", + "file": "test/KeysWithValueModule.t.sol:KeysWithValueModuleTest", "test": "testInstall", "name": "install keys with value module", "gasUsed": 719012 }, { - "file": "test/KeysWithValueModule.t.sol", + "file": "test/KeysWithValueModule.t.sol:KeysWithValueModuleTest", "test": "testInstall", "name": "set a record on a table with KeysWithValueModule installed", "gasUsed": 168254 }, { - "file": "test/KeysWithValueModule.t.sol", + "file": "test/KeysWithValueModule.t.sol:KeysWithValueModuleTest", "test": "testSetAndDeleteRecordHook", "name": "install keys with value module", "gasUsed": 719012 }, { - "file": "test/KeysWithValueModule.t.sol", + "file": "test/KeysWithValueModule.t.sol:KeysWithValueModuleTest", "test": "testSetAndDeleteRecordHook", "name": "change a record on a table with KeysWithValueModule installed", "gasUsed": 159608 }, { - "file": "test/KeysWithValueModule.t.sol", + "file": "test/KeysWithValueModule.t.sol:KeysWithValueModuleTest", "test": "testSetAndDeleteRecordHook", "name": "delete a record on a table with KeysWithValueModule installed", "gasUsed": 78835 }, { - "file": "test/KeysWithValueModule.t.sol", + "file": "test/KeysWithValueModule.t.sol:KeysWithValueModuleTest", "test": "testSetField", "name": "install keys with value module", "gasUsed": 719012 }, { - "file": "test/KeysWithValueModule.t.sol", + "file": "test/KeysWithValueModule.t.sol:KeysWithValueModuleTest", "test": "testSetField", "name": "set a field on a table with KeysWithValueModule installed", "gasUsed": 179348 }, { - "file": "test/KeysWithValueModule.t.sol", + "file": "test/KeysWithValueModule.t.sol:KeysWithValueModuleTest", "test": "testSetField", "name": "change a field on a table with KeysWithValueModule installed", "gasUsed": 158835 }, { - "file": "test/query.t.sol", + "file": "test/query.t.sol:QueryTest", "test": "testCombinedHasHasValueNotQuery", "name": "CombinedHasHasValueNotQuery", "gasUsed": 93122 }, { - "file": "test/query.t.sol", + "file": "test/query.t.sol:QueryTest", "test": "testCombinedHasHasValueQuery", "name": "CombinedHasHasValueQuery", "gasUsed": 48652 }, { - "file": "test/query.t.sol", + "file": "test/query.t.sol:QueryTest", "test": "testCombinedHasNotQuery", "name": "CombinedHasNotQuery", "gasUsed": 113825 }, { - "file": "test/query.t.sol", + "file": "test/query.t.sol:QueryTest", "test": "testCombinedHasQuery", "name": "CombinedHasQuery", "gasUsed": 71833 }, { - "file": "test/query.t.sol", + "file": "test/query.t.sol:QueryTest", "test": "testCombinedHasValueNotQuery", "name": "CombinedHasValueNotQuery", "gasUsed": 74307 }, { - "file": "test/query.t.sol", + "file": "test/query.t.sol:QueryTest", "test": "testCombinedHasValueQuery", "name": "CombinedHasValueQuery", "gasUsed": 15030 }, { - "file": "test/query.t.sol", + "file": "test/query.t.sol:QueryTest", "test": "testHasQuery", "name": "HasQuery", "gasUsed": 15980 }, { - "file": "test/query.t.sol", + "file": "test/query.t.sol:QueryTest", "test": "testHasQuery1000Keys", "name": "HasQuery with 1000 keys", "gasUsed": 5341823 }, { - "file": "test/query.t.sol", + "file": "test/query.t.sol:QueryTest", "test": "testHasQuery100Keys", "name": "HasQuery with 100 keys", "gasUsed": 504505 }, { - "file": "test/query.t.sol", + "file": "test/query.t.sol:QueryTest", "test": "testHasValueQuery", "name": "HasValueQuery", "gasUsed": 7290 }, { - "file": "test/query.t.sol", + "file": "test/query.t.sol:QueryTest", "test": "testNotValueQuery", "name": "NotValueQuery", "gasUsed": 42889 }, { - "file": "test/StandardDelegationsModule.t.sol", + "file": "test/StandardDelegationsModule.t.sol:StandardDelegationsModuleTest", "test": "testCallFromCallboundDelegation", "name": "register a callbound delegation", "gasUsed": 138994 }, { - "file": "test/StandardDelegationsModule.t.sol", + "file": "test/StandardDelegationsModule.t.sol:StandardDelegationsModuleTest", "test": "testCallFromCallboundDelegation", "name": "call a system via a callbound delegation", "gasUsed": 67297 }, { - "file": "test/StandardDelegationsModule.t.sol", + "file": "test/StandardDelegationsModule.t.sol:StandardDelegationsModuleTest", "test": "testCallFromSystemDelegation", "name": "register a systembound delegation", "gasUsed": 136116 }, { - "file": "test/StandardDelegationsModule.t.sol", + "file": "test/StandardDelegationsModule.t.sol:StandardDelegationsModuleTest", "test": "testCallFromSystemDelegation", "name": "call a system via a systembound delegation", "gasUsed": 69661 }, { - "file": "test/StandardDelegationsModule.t.sol", + "file": "test/StandardDelegationsModule.t.sol:StandardDelegationsModuleTest", "test": "testCallFromTimeboundDelegation", "name": "register a timebound delegation", "gasUsed": 132659 }, { - "file": "test/StandardDelegationsModule.t.sol", + "file": "test/StandardDelegationsModule.t.sol:StandardDelegationsModuleTest", "test": "testCallFromTimeboundDelegation", "name": "call a system via a timebound delegation", "gasUsed": 58227 }, { - "file": "test/UniqueEntityModule.t.sol", + "file": "test/UniqueEntityModule.t.sol:UniqueEntityModuleTest", "test": "testInstall", "name": "install unique entity module", "gasUsed": 718745 }, { - "file": "test/UniqueEntityModule.t.sol", + "file": "test/UniqueEntityModule.t.sol:UniqueEntityModuleTest", "test": "testInstall", "name": "get a unique entity nonce (non-root module)", "gasUsed": 77840 }, { - "file": "test/UniqueEntityModule.t.sol", + "file": "test/UniqueEntityModule.t.sol:UniqueEntityModuleTest", "test": "testInstallRoot", "name": "installRoot unique entity module", "gasUsed": 689190 }, { - "file": "test/UniqueEntityModule.t.sol", + "file": "test/UniqueEntityModule.t.sol:UniqueEntityModuleTest", "test": "testInstallRoot", "name": "get a unique entity nonce (root module)", "gasUsed": 77843 diff --git a/packages/world/gas-report.json b/packages/world/gas-report.json index f3e0425119..0b342e977c 100644 --- a/packages/world/gas-report.json +++ b/packages/world/gas-report.json @@ -1,318 +1,318 @@ [ { - "file": "test/AccessControl.t.sol", + "file": "test/AccessControl.t.sol:AccessControlTest", "test": "testAccessControl", "name": "AccessControl: hasAccess (cold)", "gasUsed": 9111 }, { - "file": "test/AccessControl.t.sol", + "file": "test/AccessControl.t.sol:AccessControlTest", "test": "testAccessControl", "name": "AccessControl: hasAccess (warm, namespace only)", "gasUsed": 1595 }, { - "file": "test/AccessControl.t.sol", + "file": "test/AccessControl.t.sol:AccessControlTest", "test": "testAccessControl", "name": "AccessControl: hasAccess (warm)", "gasUsed": 3117 }, { - "file": "test/AccessControl.t.sol", + "file": "test/AccessControl.t.sol:AccessControlTest", "test": "testRequireAccess", "name": "AccessControl: requireAccess (cold)", "gasUsed": 9154 }, { - "file": "test/AccessControl.t.sol", + "file": "test/AccessControl.t.sol:AccessControlTest", "test": "testRequireAccess", "name": "AccessControl: requireAccess (warm)", "gasUsed": 3156 }, { - "file": "test/AccessControl.t.sol", + "file": "test/AccessControl.t.sol:AccessControlTest", "test": "testRequireOwner", "name": "AccessControl: requireOwner (cold)", "gasUsed": 5401 }, { - "file": "test/AccessControl.t.sol", + "file": "test/AccessControl.t.sol:AccessControlTest", "test": "testRequireOwner", "name": "AccessControl: requireOwner (warm)", "gasUsed": 1402 }, { - "file": "test/BatchCall.t.sol", + "file": "test/BatchCall.t.sol:BatchCallTest", "test": "testBatchCallFromReturnData", "name": "call systems with batchCallFrom", "gasUsed": 80765 }, { - "file": "test/BatchCall.t.sol", + "file": "test/BatchCall.t.sol:BatchCallTest", "test": "testBatchCallReturnData", "name": "call systems with batchCall", "gasUsed": 77560 }, { - "file": "test/Factories.t.sol", + "file": "test/Factories.t.sol:FactoriesTest", "test": "testCreate2Factory", "name": "deploy contract via Create2", "gasUsed": 4970818 }, { - "file": "test/Factories.t.sol", + "file": "test/Factories.t.sol:FactoriesTest", "test": "testWorldFactoryGas", "name": "deploy world via WorldFactory", "gasUsed": 12847807 }, { - "file": "test/World.t.sol", + "file": "test/World.t.sol:WorldTest", "test": "testCall", "name": "call a system via the World", "gasUsed": 40081 }, { - "file": "test/World.t.sol", + "file": "test/World.t.sol:WorldTest", "test": "testCallFromNamespaceDelegation", "name": "call a system via a namespace fallback delegation", "gasUsed": 59816 }, { - "file": "test/World.t.sol", + "file": "test/World.t.sol:WorldTest", "test": "testCallFromUnlimitedDelegation", "name": "register an unlimited delegation", "gasUsed": 74771 }, { - "file": "test/World.t.sol", + "file": "test/World.t.sol:WorldTest", "test": "testCallFromUnlimitedDelegation", "name": "call a system via an unlimited delegation", "gasUsed": 40663 }, { - "file": "test/World.t.sol", + "file": "test/World.t.sol:WorldTest", "test": "testDeleteRecord", "name": "Delete record", "gasUsed": 36718 }, { - "file": "test/World.t.sol", + "file": "test/World.t.sol:WorldTest", "test": "testPushToDynamicField", "name": "Push data to the table", "gasUsed": 110775 }, { - "file": "test/World.t.sol", + "file": "test/World.t.sol:WorldTest", "test": "testRegisterFunctionSelector", "name": "Register a function selector", "gasUsed": 116605 }, { - "file": "test/World.t.sol", + "file": "test/World.t.sol:WorldTest", "test": "testRegisterNamespace", "name": "Register a new namespace", "gasUsed": 144062 }, { - "file": "test/World.t.sol", + "file": "test/World.t.sol:WorldTest", "test": "testRegisterRootFunctionSelector", "name": "Register a root function selector", "gasUsed": 113693 }, { - "file": "test/World.t.sol", + "file": "test/World.t.sol:WorldTest", "test": "testRegisterSystem", "name": "register a system", "gasUsed": 185349 }, { - "file": "test/World.t.sol", + "file": "test/World.t.sol:WorldTest", "test": "testRegisterTable", "name": "Register a new table in the namespace", "gasUsed": 572927 }, { - "file": "test/World.t.sol", + "file": "test/World.t.sol:WorldTest", "test": "testRenounceNamespace", "name": "Renounce namespace ownership", "gasUsed": 62190 }, { - "file": "test/World.t.sol", + "file": "test/World.t.sol:WorldTest", "test": "testSetField", "name": "Write data to a table field", "gasUsed": 60884 }, { - "file": "test/World.t.sol", + "file": "test/World.t.sol:WorldTest", "test": "testSetRecord", "name": "Write data to the table", "gasUsed": 64743 }, { - "file": "test/World.t.sol", + "file": "test/World.t.sol:WorldTest", "test": "testUnregisterNamespaceDelegation", "name": "unregister a namespace delegation", "gasUsed": 60328 }, { - "file": "test/World.t.sol", + "file": "test/World.t.sol:WorldTest", "test": "testUnregisterUnlimitedDelegation", "name": "unregister an unlimited delegation", "gasUsed": 54526 }, { - "file": "test/WorldDynamicUpdate.t.sol", + "file": "test/WorldDynamicUpdate.t.sol:UpdateInDynamicFieldTest", "test": "testPopFromDynamicField", "name": "pop 1 address (cold)", "gasUsed": 51686 }, { - "file": "test/WorldDynamicUpdate.t.sol", + "file": "test/WorldDynamicUpdate.t.sol:UpdateInDynamicFieldTest", "test": "testPopFromDynamicField", "name": "pop 1 address (warm)", "gasUsed": 45632 }, { - "file": "test/WorldDynamicUpdate.t.sol", + "file": "test/WorldDynamicUpdate.t.sol:UpdateInDynamicFieldTest", "test": "testSpliceDynamicData", "name": "update in field 1 item (cold)", "gasUsed": 52822 }, { - "file": "test/WorldDynamicUpdate.t.sol", + "file": "test/WorldDynamicUpdate.t.sol:UpdateInDynamicFieldTest", "test": "testSpliceDynamicData", "name": "update in field 1 item (warm)", "gasUsed": 44023 }, { - "file": "test/WorldProxy.t.sol", + "file": "test/WorldProxy.t.sol:WorldProxyTest", "test": "testCall", "name": "call a system via the World", "gasUsed": 45004 }, { - "file": "test/WorldProxy.t.sol", + "file": "test/WorldProxy.t.sol:WorldProxyTest", "test": "testCallFromNamespaceDelegation", "name": "call a system via a namespace fallback delegation", "gasUsed": 64745 }, { - "file": "test/WorldProxy.t.sol", + "file": "test/WorldProxy.t.sol:WorldProxyTest", "test": "testCallFromUnlimitedDelegation", "name": "register an unlimited delegation", "gasUsed": 79685 }, { - "file": "test/WorldProxy.t.sol", + "file": "test/WorldProxy.t.sol:WorldProxyTest", "test": "testCallFromUnlimitedDelegation", "name": "call a system via an unlimited delegation", "gasUsed": 45592 }, { - "file": "test/WorldProxy.t.sol", + "file": "test/WorldProxy.t.sol:WorldProxyTest", "test": "testDeleteRecord", "name": "Delete record", "gasUsed": 41626 }, { - "file": "test/WorldProxy.t.sol", + "file": "test/WorldProxy.t.sol:WorldProxyTest", "test": "testPushToDynamicField", "name": "Push data to the table", "gasUsed": 115719 }, { - "file": "test/WorldProxy.t.sol", + "file": "test/WorldProxy.t.sol:WorldProxyTest", "test": "testRegisterFunctionSelector", "name": "Register a function selector", "gasUsed": 121522 }, { - "file": "test/WorldProxy.t.sol", + "file": "test/WorldProxy.t.sol:WorldProxyTest", "test": "testRegisterNamespace", "name": "Register a new namespace", "gasUsed": 148961 }, { - "file": "test/WorldProxy.t.sol", + "file": "test/WorldProxy.t.sol:WorldProxyTest", "test": "testRegisterRootFunctionSelector", "name": "Register a root function selector", "gasUsed": 118628 }, { - "file": "test/WorldProxy.t.sol", + "file": "test/WorldProxy.t.sol:WorldProxyTest", "test": "testRegisterSystem", "name": "register a system", "gasUsed": 190257 }, { - "file": "test/WorldProxy.t.sol", + "file": "test/WorldProxy.t.sol:WorldProxyTest", "test": "testRegisterTable", "name": "Register a new table in the namespace", "gasUsed": 577937 }, { - "file": "test/WorldProxy.t.sol", + "file": "test/WorldProxy.t.sol:WorldProxyTest", "test": "testRenounceNamespace", "name": "Renounce namespace ownership", "gasUsed": 67089 }, { - "file": "test/WorldProxy.t.sol", + "file": "test/WorldProxy.t.sol:WorldProxyTest", "test": "testSetField", "name": "Write data to a table field", "gasUsed": 65822 }, { - "file": "test/WorldProxy.t.sol", + "file": "test/WorldProxy.t.sol:WorldProxyTest", "test": "testSetRecord", "name": "Write data to the table", "gasUsed": 69687 }, { - "file": "test/WorldProxy.t.sol", + "file": "test/WorldProxy.t.sol:WorldProxyTest", "test": "testUnregisterNamespaceDelegation", "name": "unregister a namespace delegation", "gasUsed": 65227 }, { - "file": "test/WorldProxy.t.sol", + "file": "test/WorldProxy.t.sol:WorldProxyTest", "test": "testUnregisterUnlimitedDelegation", "name": "unregister an unlimited delegation", "gasUsed": 59425 }, { - "file": "test/WorldProxyFactory.t.sol", + "file": "test/WorldProxyFactory.t.sol:WorldProxyFactoryTest", "test": "testWorldProxyFactoryGas", "name": "deploy world via WorldProxyFactory", "gasUsed": 9143347 }, { - "file": "test/WorldProxyFactory.t.sol", + "file": "test/WorldProxyFactory.t.sol:WorldProxyFactoryTest", "test": "testWorldProxyFactoryGas", "name": "set WorldProxy implementation", "gasUsed": 37553 }, { - "file": "test/WorldResourceId.t.sol", + "file": "test/WorldResourceId.t.sol:WorldResourceIdTest", "test": "testGetNamespace", "name": "encode namespace, name and type", "gasUsed": 33 }, { - "file": "test/WorldResourceId.t.sol", + "file": "test/WorldResourceId.t.sol:WorldResourceIdTest", "test": "testGetNamespaceId", "name": "get namespace ID from a resource ID", "gasUsed": 22 }, { - "file": "test/WorldResourceId.t.sol", + "file": "test/WorldResourceId.t.sol:WorldResourceIdTest", "test": "testGetType", "name": "get type from a resource ID", "gasUsed": 4 }, { - "file": "test/WorldResourceId.t.sol", + "file": "test/WorldResourceId.t.sol:WorldResourceIdTest", "test": "testToString", "name": "convert resource ID to string", "gasUsed": 2390 diff --git a/tsconfig.paths.json b/tsconfig.paths.json index d90cad91fc..669cc7f5ca 100644 --- a/tsconfig.paths.json +++ b/tsconfig.paths.json @@ -18,7 +18,7 @@ "@latticexyz/config/node": ["./packages/config/src/deprecated/node"], "@latticexyz/dev-tools": ["./packages/dev-tools/src/index.ts"], "@latticexyz/explorer/observer": ["./packages/explorer/src/exports/observer.ts"], - "@latticexyz/gas-report": ["./packages/gas-report/ts/index.ts"], + "@latticexyz/gas-report/internal": ["./packages/gas-report/ts/exports/internal.ts"], "@latticexyz/protocol-parser": ["./packages/protocol-parser/src/index.ts"], "@latticexyz/protocol-parser/*": ["./packages/protocol-parser/src/exports/*.ts"], "@latticexyz/react": ["./packages/react/src/index.ts"], From 75e93bac492f9000c482d6a26a5c8e29079dd32d Mon Sep 17 00:00:00 2001 From: Kevin Ingersoll Date: Tue, 22 Oct 2024 19:54:49 +0100 Subject: [PATCH 09/10] feat(abi-ts): extension option (#3315) --- .changeset/nine-numbers-yell.md | 5 ++++ packages/abi-ts/bin/abi-ts.js | 2 +- packages/abi-ts/package.json | 6 ++--- packages/abi-ts/src/{ => bin}/abi-ts.ts | 2 +- packages/abi-ts/src/{index.ts => command.ts} | 26 +++++++++++++------- packages/abi-ts/src/exports/internal.ts | 1 + packages/abi-ts/tsup.config.ts | 2 +- packages/cli/src/commands/index.ts | 2 +- tsconfig.paths.json | 2 +- 9 files changed, 31 insertions(+), 17 deletions(-) create mode 100644 .changeset/nine-numbers-yell.md rename packages/abi-ts/src/{ => bin}/abi-ts.ts (94%) rename packages/abi-ts/src/{index.ts => command.ts} (55%) create mode 100644 packages/abi-ts/src/exports/internal.ts diff --git a/.changeset/nine-numbers-yell.md b/.changeset/nine-numbers-yell.md new file mode 100644 index 0000000000..8550ebeec5 --- /dev/null +++ b/.changeset/nine-numbers-yell.md @@ -0,0 +1,5 @@ +--- +"@latticexyz/abi-ts": patch +--- + +Added an `--extension` option to customize the resulting TS or DTS output. It defaults to the previous behavior of `.json.d.ts`, but can now be set to `.d.json.ts` for compatibility with newer TS versions and `.json.ts` or just `.ts` for a pure TS file. diff --git a/packages/abi-ts/bin/abi-ts.js b/packages/abi-ts/bin/abi-ts.js index 7a8b201993..9061166957 100755 --- a/packages/abi-ts/bin/abi-ts.js +++ b/packages/abi-ts/bin/abi-ts.js @@ -1,4 +1,4 @@ #!/usr/bin/env node // workaround for https://github.com/pnpm/pnpm/issues/1801 -import "../dist/abi-ts.js"; +import "../dist/bin/abi-ts.js"; diff --git a/packages/abi-ts/package.json b/packages/abi-ts/package.json index 581cfeaca8..977a8323ad 100644 --- a/packages/abi-ts/package.json +++ b/packages/abi-ts/package.json @@ -10,12 +10,12 @@ "license": "MIT", "type": "module", "exports": { - ".": "./dist/index.js" + "./internal": "./dist/exports/internal.js" }, "typesVersions": { "*": { - "index": [ - "./dist/index.d.ts" + "internal": [ + "./dist/exports/internal.d.ts" ] } }, diff --git a/packages/abi-ts/src/abi-ts.ts b/packages/abi-ts/src/bin/abi-ts.ts similarity index 94% rename from packages/abi-ts/src/abi-ts.ts rename to packages/abi-ts/src/bin/abi-ts.ts index 9ca7f03174..1b3e4827bf 100755 --- a/packages/abi-ts/src/abi-ts.ts +++ b/packages/abi-ts/src/bin/abi-ts.ts @@ -2,7 +2,7 @@ import yargs from "yargs"; import { hideBin } from "yargs/helpers"; -import abiTsCommand from "."; +import { command as abiTsCommand } from "../command"; import chalk from "chalk"; // $0 makes this a default command (as opposed to a sub-command), diff --git a/packages/abi-ts/src/index.ts b/packages/abi-ts/src/command.ts similarity index 55% rename from packages/abi-ts/src/index.ts rename to packages/abi-ts/src/command.ts index 7c859443d0..f4529c757d 100644 --- a/packages/abi-ts/src/index.ts +++ b/packages/abi-ts/src/command.ts @@ -1,29 +1,35 @@ import type { CommandModule } from "yargs"; import { readFileSync, writeFileSync } from "fs"; +import path from "path"; import { globSync } from "glob"; import { debug } from "./debug"; type Options = { input: string; - output: string; + extension: string; }; -const commandModule: CommandModule = { +export const command: CommandModule = { command: "abi-ts", - describe: "Convert a directory of JSON ABI files to a directory of TS files with `as const`", + describe: "Convert a directory of JSON ABI files to a directory of TS or DTS files with `as const`.", builder(yargs) { return yargs.options({ input: { type: "string", - desc: "Input glob of ABI JSON files", + desc: "Input glob of ABI JSON files.", default: "**/*.abi.json", }, + extension: { + type: "string", + desc: "Extension of the resulting ABI TS or DTS file.", + default: ".json.d.ts", + }, }); }, - handler({ input }) { + handler({ input, extension: tsExtension }) { const files = globSync(input).sort(); if (!files.length) { @@ -38,8 +44,12 @@ const commandModule: CommandModule = { continue; } - const ts = `declare const abi: ${json}; export default abi;\n`; - const tsFilename = `${jsonFilename}.d.ts`; + const jsonExtension = path.extname(jsonFilename); + const tsFilename = `${jsonFilename.substring(0, jsonFilename.lastIndexOf(jsonExtension))}${tsExtension}`; + + const ts = tsExtension.includes(".d.") + ? `declare const abi: ${json};\n\nexport default abi;\n` + : `const abi = ${json};\n\nexport default abi;\n`; debug("Writing", tsFilename); writeFileSync(tsFilename, ts); @@ -48,5 +58,3 @@ const commandModule: CommandModule = { process.exit(0); }, }; - -export default commandModule; diff --git a/packages/abi-ts/src/exports/internal.ts b/packages/abi-ts/src/exports/internal.ts new file mode 100644 index 0000000000..3f886b70f3 --- /dev/null +++ b/packages/abi-ts/src/exports/internal.ts @@ -0,0 +1 @@ +export * from "../command"; diff --git a/packages/abi-ts/tsup.config.ts b/packages/abi-ts/tsup.config.ts index 090fe8db6f..8a576478a7 100644 --- a/packages/abi-ts/tsup.config.ts +++ b/packages/abi-ts/tsup.config.ts @@ -1,7 +1,7 @@ import { defineConfig } from "tsup"; export default defineConfig((opts) => ({ - entry: ["src/index.ts", "src/abi-ts.ts"], + entry: ["src/exports/internal.ts", "src/bin/abi-ts.ts"], target: "esnext", format: ["esm"], sourcemap: true, diff --git a/packages/cli/src/commands/index.ts b/packages/cli/src/commands/index.ts index c77bb3b638..cce1230fa6 100644 --- a/packages/cli/src/commands/index.ts +++ b/packages/cli/src/commands/index.ts @@ -1,7 +1,7 @@ import { CommandModule } from "yargs"; import { command as gasReport } from "@latticexyz/gas-report/internal"; -import abiTs from "@latticexyz/abi-ts"; +import { command as abiTs } from "@latticexyz/abi-ts/internal"; import build from "./build"; import devnode from "./devnode"; diff --git a/tsconfig.paths.json b/tsconfig.paths.json index 669cc7f5ca..4fb96c893c 100644 --- a/tsconfig.paths.json +++ b/tsconfig.paths.json @@ -1,7 +1,7 @@ { "compilerOptions": { "paths": { - "@latticexyz/abi-ts": ["./packages/abi-ts/src/index.ts"], + "@latticexyz/abi-ts/internal": ["./packages/abi-ts/src/exports/internal.ts"], "@latticexyz/block-logs-stream": ["./packages/block-logs-stream/src/index.ts"], "@latticexyz/cli": ["./packages/cli/src/index.ts"], "@latticexyz/common": ["./packages/common/src/index.ts"], From 4e92fded1a08ed7e209ac475081169a91606ca92 Mon Sep 17 00:00:00 2001 From: Kevin Ingersoll Date: Tue, 22 Oct 2024 20:44:56 +0100 Subject: [PATCH 10/10] test: move vitest options to config (#3318) --- packages/store/package.json | 2 +- vitest.config.ts | 3 +++ 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/packages/store/package.json b/packages/store/package.json index e02b62f5bd..35faa3af75 100644 --- a/packages/store/package.json +++ b/packages/store/package.json @@ -55,7 +55,7 @@ "dev": "tsup --watch", "gas-report": "gas-report --save gas-report.json", "lint": "solhint --config ./.solhint.json 'src/**/*.sol'", - "test": "vitest --run --passWithNoTests --no-file-parallelism && forge test", + "test": "vitest --run && forge test", "test:ci": "pnpm run test" }, "dependencies": { diff --git a/vitest.config.ts b/vitest.config.ts index 5969e8ee90..f370361b00 100644 --- a/vitest.config.ts +++ b/vitest.config.ts @@ -4,10 +4,13 @@ export default defineConfig({ test: { globalSetup: [`${__dirname}/test-setup/global/arktype.ts`], setupFiles: [], + passWithNoTests: true, // Temporarily set a low teardown timeout because anvil hangs otherwise // Could move this timeout to anvil setup after https://github.com/wevm/anvil.js/pull/46 teardownTimeout: 500, hookTimeout: 15000, + // Temporarily disable file parallelism until we improve MUD config imports (https://github.com/latticexyz/mud/pull/3290) + fileParallelism: false, }, server: { watch: {