From 077a8e8b1019fc5030c68013c825fce67a56ff00 Mon Sep 17 00:00:00 2001 From: gianmalarcon Date: Thu, 26 Sep 2024 17:40:03 +0700 Subject: [PATCH] test --- .github/workflows/main.yml | 8 +- .tool-versions | 3 + README.md | 24 +- package.json | 3 +- packages/nextjs/.env.example | 2 +- packages/nextjs/.gitignore | 3 +- .../_components/DownloadContracts.tsx | 177 ++ packages/nextjs/app/configure/page.tsx | 18 + .../_components/contract/ContractInput.tsx | 10 +- .../_components/contract/DisplayVariable.tsx | 13 +- .../contract/ReadOnlyFunctionForm.tsx | 25 +- .../contract/WriteOnlyFunctionForm.tsx | 58 +- .../_components/contract/utilsContract.tsx | 63 +- .../_components/contract/utilsDisplay.tsx | 8 + packages/nextjs/app/page.tsx | 4 +- packages/nextjs/components/Footer.tsx | 15 +- packages/nextjs/components/Header.tsx | 41 +- .../AddressInfoDropdown.tsx | 5 +- .../CustomConnectButton/ConnectModal.tsx | 3 +- .../CustomConnectButton/NetworkOptions.tsx | 26 +- .../CustomConnectButton/Wallet.tsx | 33 +- .../WrongNetworkDropdown.tsx | 4 +- .../CustomConnectButton/index.tsx | 66 +- .../scaffold-stark/FaucetButton.tsx | 2 +- .../scaffold-stark/Input/IntegerInput.tsx | 11 +- .../contracts/configExternalContracts.ts | 8 + .../nextjs/contracts/predeployedContracts.ts | 955 +----- .../__tests__/useDeployedContractInfo.test.ts | 638 ++++ .../__tests__/useOutsideClick.test.ts | 64 + .../scaffold-stark/useScaffoldContract.ts | 4 +- .../scaffold-stark/useScaffoldEthBalance.ts | 4 +- .../useScaffoldMultiWriteContract.ts | 31 +- .../scaffold-stark/useScaffoldReadContract.ts | 14 +- .../scaffold-stark/useScaffoldStrkBalance.ts | 4 +- .../useScaffoldWriteContract.ts | 67 +- .../hooks/scaffold-stark/useTargetNetwork.ts | 2 +- .../hooks/scaffold-stark/useTransactor.tsx | 10 +- packages/nextjs/hooks/useAccount.ts | 32 + .../hooks/useConditionalStarkProfile.tsx | 2 +- packages/nextjs/package.json | 22 +- packages/nextjs/public/debug-image.png | Bin 2693584 -> 0 bytes packages/nextjs/scaffold.config.ts | 2 +- .../web3/stark-burner/BurnerConnector.ts | 55 +- packages/nextjs/tailwind.config.ts | 7 + packages/nextjs/utils/Constants.ts | 283 ++ .../nextjs/utils/scaffold-stark/common.ts | 13 +- .../nextjs/utils/scaffold-stark/contract.ts | 673 +++-- .../scaffold-stark/fetchPriceFromCoingecko.ts | 2 - packages/nextjs/vitest.config.ts | 15 + packages/snfoundry/.env.example | 2 +- packages/snfoundry/contracts/Scarb.lock | 72 +- packages/snfoundry/contracts/Scarb.toml | 12 +- .../contracts/src/YourContract.cairo | 22 +- .../contracts/src/test/TestContract.cairo | 17 + packages/snfoundry/package.json | 2 +- .../snfoundry/scripts-ts/deploy-contract.ts | 29 +- .../scripts-ts/helpers/deploy-wrapper.ts | 6 +- packages/snfoundry/scripts-ts/helpers/fees.ts | 122 + .../snfoundry/scripts-ts/helpers/networks.ts | 43 +- packages/snfoundry/scripts-ts/types.ts | 3 +- yarn.lock | 2627 ++++++++++++++--- 61 files changed, 4669 insertions(+), 1820 deletions(-) create mode 100644 .tool-versions create mode 100644 packages/nextjs/app/configure/_components/DownloadContracts.tsx create mode 100644 packages/nextjs/app/configure/page.tsx create mode 100644 packages/nextjs/contracts/configExternalContracts.ts create mode 100644 packages/nextjs/hooks/scaffold-stark/__tests__/useDeployedContractInfo.test.ts create mode 100644 packages/nextjs/hooks/scaffold-stark/__tests__/useOutsideClick.test.ts create mode 100644 packages/nextjs/hooks/useAccount.ts delete mode 100644 packages/nextjs/public/debug-image.png create mode 100644 packages/nextjs/utils/Constants.ts create mode 100644 packages/nextjs/vitest.config.ts create mode 100644 packages/snfoundry/scripts-ts/helpers/fees.ts diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index a12d3537..28d3f4e9 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -26,13 +26,13 @@ jobs: uses: actions/checkout@master - name: Install scarb - run: curl --proto '=https' --tlsv1.2 -sSf https://docs.swmansion.com/scarb/install.sh | sh -s -- -v 2.6.5 + run: curl --proto '=https' --tlsv1.2 -sSf https://docs.swmansion.com/scarb/install.sh | sh -s -- -v 2.8.2 - name: Install snfoundryup run: curl -L https://raw.githubusercontent.com/foundry-rs/starknet-foundry/master/scripts/install.sh | sh - name: Install snforge - run: snfoundryup -v 0.27.0 + run: snfoundryup -v 0.30.0 - name: Run snforge tests run: snforge test @@ -60,6 +60,10 @@ jobs: run: yarn next:check-types working-directory: ./packages/nextjs + - name: Run Next.js tests + run: yarn test run + working-directory: ./packages/nextjs + - name: Build Next.js project run: yarn build working-directory: ./packages/nextjs diff --git a/.tool-versions b/.tool-versions new file mode 100644 index 00000000..66f521a6 --- /dev/null +++ b/.tool-versions @@ -0,0 +1,3 @@ +scarb 2.8.2 +starknet-foundry 0.30.0 +starknet-devnet 0.2.0 diff --git a/README.md b/README.md index 107e8f7d..c27a7b1f 100644 --- a/README.md +++ b/README.md @@ -22,13 +22,25 @@ Before you begin, you need to install the following tools: - [asdf](https://asdf-vm.com/guide/getting-started.html) - [Cairo 1.0 extension for VSCode](https://marketplace.visualstudio.com/items?itemName=starkware.cairo1) +### Starknet-devnet version + +To ensure the proper functioning of scaffold-stark, your local `starknet-devnet` version must be `0.2.0`. To accomplish this, first check your local starknet-devnet version: + +```sh +starknet-devnet --version +``` + +If your local starknet-devnet version is not `0.2.0`, you need to install it. + +- Install Starknet-devnet `0.2.0` via `asdf` ([instructions](https://github.com/gianalarcon/asdf-starknet-devnet/blob/main/README.md)). + ### Compatible versions -- Starknet-devnet - v0.0.4 -- Scarb - v2.6.5 -- Snforge - v0.27.0 -- Cairo - v2.6.4 -- RPC - v0.7.0 +- Starknet-devnet - v0.2.0 +- Scarb - v2.8.2 +- Snforge - v0.30.0 +- Cairo - v2.8.2 +- RPC - v0.7.1 Make sure you have the compatible versions otherwise refer to [Scaffold-Stark Requirements](https://github.com/Scaffold-Stark/scaffold-stark-2?.tab=readme-ov-file#requirements) @@ -47,6 +59,8 @@ yarn install yarn chain ``` +> To run a fork : `yarn chain --fork-network [--fork-block ]` + > in a second terminal window, 🛰 deploy your contract (locally): ```sh diff --git a/package.json b/package.json index c0b68886..e2a37732 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "ss-2", - "version": "0.2.10", + "version": "0.3.2", "author": "Q3 Labs", "license": "MIT", "private": true, @@ -21,6 +21,7 @@ "next:check-types": "yarn workspace @ss-2/nextjs check-types", "vercel": "yarn workspace @ss-2/nextjs vercel", "vercel:yolo": "yarn workspace @ss-2/nextjs vercel:yolo", + "test:nextjs": "yarn workspace @ss-2/nextjs test", "format": "yarn workspace @ss-2/nextjs format && yarn workspace @ss-2/snfoundry format", "format:check": "yarn workspace @ss-2/nextjs format:check && yarn workspace @ss-2/snfoundry format:check", "prepare": "husky" diff --git a/packages/nextjs/.env.example b/packages/nextjs/.env.example index 10cffdbc..a207edc1 100644 --- a/packages/nextjs/.env.example +++ b/packages/nextjs/.env.example @@ -1 +1 @@ -NEXT_PUBLIC_PROVIDER_URL=https://starknet-sepolia.public.blastapi.io/rpc/v0_7 \ No newline at end of file +NEXT_PUBLIC_PROVIDER_URL=https://starknet-sepolia.public.blastapi.io/rpc/v0_7 diff --git a/packages/nextjs/.gitignore b/packages/nextjs/.gitignore index fd3dbb57..00717d50 100644 --- a/packages/nextjs/.gitignore +++ b/packages/nextjs/.gitignore @@ -26,7 +26,8 @@ yarn-debug.log* yarn-error.log* # local env files -.env*.local +.env.local +.env # vercel .vercel diff --git a/packages/nextjs/app/configure/_components/DownloadContracts.tsx b/packages/nextjs/app/configure/_components/DownloadContracts.tsx new file mode 100644 index 00000000..528b521c --- /dev/null +++ b/packages/nextjs/app/configure/_components/DownloadContracts.tsx @@ -0,0 +1,177 @@ +"use client"; + +import { useProvider } from "@starknet-react/core"; +import React, { useState } from "react"; +import { useTargetNetwork } from "~~/hooks/scaffold-stark/useTargetNetwork"; +import configExternalContracts from "~~/contracts/configExternalContracts"; +import { deepMergeContracts } from "~~/utils/scaffold-stark/contract"; +import { ArrowDownTrayIcon } from "@heroicons/react/24/outline"; +import Link from "next/link"; + +export default function DownloadContracts() { + const { provider } = useProvider(); + const [address, setAddress] = useState(""); + + const { targetNetwork } = useTargetNetwork(); + const [symbol, setSymbol] = useState(""); + const handleInputChange: React.ChangeEventHandler = (e) => { + const value = e.target.value; + setSymbol(value); + }; + + const handleDownload = async () => { + if (!address) return; + try { + const [apiResponse, classHash] = await Promise.all([ + provider.getClassAt(address), + provider.getClassHashAt(address), + ]); + + const contractData = { + [targetNetwork.network]: { + [symbol]: { + address, + classHash, + abi: apiResponse.abi, + }, + }, + }; + const mergedPredeployedContracts = deepMergeContracts( + contractData, + configExternalContracts, + ); + + generateContractsFile(mergedPredeployedContracts); + } catch (error) { + console.error(error); + return; + } + }; + + const generateContractsFile = (contractsData: Object) => { + const generatedContractComment = `/** +* This file is autogenerated by Scaffold-Stark. +* You should not edit it manually or your changes might be overwritten. +*/`; + + const fileContent = JSON.stringify(contractsData, null, 2); + const configExternalContracts = `${generatedContractComment}\n\nconst configExternalContracts = ${fileContent} as const;\n\nexport default configExternalContracts;`; + + const blob = new Blob([configExternalContracts], { + type: "text/typescript", + }); + const url = URL.createObjectURL(blob); + const a = document.createElement("a"); + a.href = url; + a.download = "configExternalContracts.ts"; + a.click(); + URL.revokeObjectURL(url); + }; + + return ( +
+
+
+ Fetch Contract Configuration File from Contract Address +
+
+
+
Instructions
+

+ This tool allows you to fetch the ABI of a contract by entering + its address. It will download a configuration file that can be + used to replace or append to your local{" "} + predeployedContracts.ts{" "} + file, allowing you to debug in the{" "} + /debug page. +

+
    +
  1. + Enter the contract address and name within the designated input + fields. +
  2. +
  3. + Click the{" "} + + Download Contract File + {" "} + button. +
  4. +
  5. + The tool will fetch the ABI, address, and classHash from the + network and generate a configuration file. +
  6. +
  7. + Download the file and replace it to your local{" "} + + configExternalContracts.ts + {" "} + file. +
  8. +
  9. + Use the{" "} + + /debug + {" "} + page to interact with and test the contract using the scaffold + hooks. +
  10. +
+

+ Ensure that the format of the ABI matches the expected format in + your project before replacing the file. +

+
+
+ {targetNetwork && ( +
+
+ Network +
+ {targetNetwork.name} +
+ )} +
+
+ Contract +
+ +
+
+
+ Address +
+
+ setAddress(e.target.value)} + /> +
+ +
+
+
+
+
+ ); +} diff --git a/packages/nextjs/app/configure/page.tsx b/packages/nextjs/app/configure/page.tsx new file mode 100644 index 00000000..661e8c25 --- /dev/null +++ b/packages/nextjs/app/configure/page.tsx @@ -0,0 +1,18 @@ +import DownloadContracts from "./_components/DownloadContracts"; +import type { NextPage } from "next"; +import { getMetadata } from "~~/utils/scaffold-stark/getMetadata"; + +export const metadata = getMetadata({ + title: "Configure Contracts", + description: "Configure your deployed 🏗 Scaffold-Stark 2 contracts", +}); + +const Configure: NextPage = () => { + return ( + <> + + + ); +}; + +export default Configure; diff --git a/packages/nextjs/app/debug/_components/contract/ContractInput.tsx b/packages/nextjs/app/debug/_components/contract/ContractInput.tsx index 15c4d02b..9cde96ed 100644 --- a/packages/nextjs/app/debug/_components/contract/ContractInput.tsx +++ b/packages/nextjs/app/debug/_components/contract/ContractInput.tsx @@ -63,7 +63,15 @@ export const ContractInput = ({ isCairoBigInt(paramType.type) || isCairoU256(paramType.type) ) { - return ; + return ( + + setFormErrorMessage(errMessage) + } + /> + ); } else if (isCairoType(paramType.type)) { return ; } else { diff --git a/packages/nextjs/app/debug/_components/contract/DisplayVariable.tsx b/packages/nextjs/app/debug/_components/contract/DisplayVariable.tsx index c89c6566..d3f293ef 100644 --- a/packages/nextjs/app/debug/_components/contract/DisplayVariable.tsx +++ b/packages/nextjs/app/debug/_components/contract/DisplayVariable.tsx @@ -7,7 +7,7 @@ import { useAnimationConfig } from "~~/hooks/scaffold-stark"; import { AbiFunction } from "~~/utils/scaffold-stark/contract"; import { Abi } from "abi-wan-kanabi"; import { Address } from "@starknet-react/chains"; -import { useContractRead } from "@starknet-react/core"; +import { useReadContract } from "@starknet-react/core"; import { BlockNumber } from "starknet"; import { displayTxResult } from "./utilsDisplay"; import { useTheme } from "next-themes"; @@ -31,7 +31,8 @@ export const DisplayVariable = ({ isLoading, isFetching, refetch, - } = useContractRead({ + error, + } = useReadContract({ address: contractAddress, functionName: abiFunction.name, abi: [...abi], @@ -42,6 +43,14 @@ export const DisplayVariable = ({ const { resolvedTheme } = useTheme(); const isDarkMode = resolvedTheme === "dark"; + // error logging + useEffect(() => { + if (error) { + console.error(error?.message); + console.error(error.stack); + } + }, [error]); + useEffect(() => { refetch(); }, [refetch, refreshDisplayVariables]); diff --git a/packages/nextjs/app/debug/_components/contract/ReadOnlyFunctionForm.tsx b/packages/nextjs/app/debug/_components/contract/ReadOnlyFunctionForm.tsx index 3990bdc6..ea641a83 100644 --- a/packages/nextjs/app/debug/_components/contract/ReadOnlyFunctionForm.tsx +++ b/packages/nextjs/app/debug/_components/contract/ReadOnlyFunctionForm.tsx @@ -1,6 +1,6 @@ "use client"; -import { useState, useRef, useEffect, useCallback } from "react"; +import { useState, useRef, useEffect } from "react"; import { Abi } from "abi-wan-kanabi"; import { Address } from "@starknet-react/chains"; import { @@ -12,7 +12,7 @@ import { } from "~~/app/debug/_components/contract"; import { AbiFunction } from "~~/utils/scaffold-stark/contract"; import { BlockNumber } from "starknet"; -import { useContractRead } from "@starknet-react/core"; +import { useReadContract } from "@starknet-react/core"; import { ContractInput } from "./ContractInput"; type ReadOnlyFunctionFormProps = { @@ -33,16 +33,22 @@ export const ReadOnlyFunctionForm = ({ const [formErrorMessage, setFormErrorMessage] = useState(null); const lastForm = useRef(form); - const { isFetching, data, refetch } = useContractRead({ + const { isFetching, data, refetch, error } = useReadContract({ address: contractAddress, functionName: abiFunction.name, abi: [...abi], - args: inputValue ? inputValue.flat(Infinity) : [], - enabled: Boolean(inputValue), - parseArgs: false, + args: inputValue || [], + enabled: !!inputValue, blockIdentifier: "pending" as BlockNumber, }); + useEffect(() => { + if (error) { + console.error(error?.message); + console.error(error.stack); + } + }, [error]); + const transformedFunction = transformAbiFunction(abiFunction); const inputElements = transformedFunction.inputs.map((input, inputIndex) => { const key = getFunctionInputKey(abiFunction.name, input, inputIndex); @@ -59,14 +65,15 @@ export const ReadOnlyFunctionForm = ({ ); }); - const handleRead = useCallback(() => { - const newInputValue = getParsedContractFunctionArgs(form, false); + const handleRead = () => { + const newInputValue = getParsedContractFunctionArgs(form, false, true); + if (JSON.stringify(form) !== JSON.stringify(lastForm.current)) { setInputValue(newInputValue); lastForm.current = form; } refetch(); - }, [form, refetch]); + }; return (
diff --git a/packages/nextjs/app/debug/_components/contract/WriteOnlyFunctionForm.tsx b/packages/nextjs/app/debug/_components/contract/WriteOnlyFunctionForm.tsx index 6ac49769..a3276dbb 100644 --- a/packages/nextjs/app/debug/_components/contract/WriteOnlyFunctionForm.tsx +++ b/packages/nextjs/app/debug/_components/contract/WriteOnlyFunctionForm.tsx @@ -1,6 +1,6 @@ "use client"; -import { useEffect, useState } from "react"; +import { useEffect, useMemo, useState } from "react"; import { ContractInput, getFunctionInputKey, @@ -10,10 +10,9 @@ import { } from "~~/app/debug/_components/contract"; import { useTargetNetwork } from "~~/hooks/scaffold-stark/useTargetNetwork"; import { - useAccount, - useContractWrite, + useSendTransaction, useNetwork, - useWaitForTransaction, + useTransactionReceipt, } from "@starknet-react/core"; import { Abi } from "abi-wan-kanabi"; import { AbiFunction } from "~~/utils/scaffold-stark/contract"; @@ -21,6 +20,7 @@ import { Address } from "@starknet-react/chains"; import { InvokeTransactionReceiptResponse } from "starknet"; import { TxReceipt } from "./TxReceipt"; import { useTransactor } from "~~/hooks/scaffold-stark"; +import { useAccount } from "~~/hooks/useAccount"; type WriteOnlyFunctionFormProps = { abi: Abi; @@ -39,42 +39,49 @@ export const WriteOnlyFunctionForm = ({ getInitialFormState(abiFunction), ); const [formErrorMessage, setFormErrorMessage] = useState(null); - const { status: walletStatus } = useAccount(); + const { status: walletStatus, isConnected, account, chainId } = useAccount(); const { chain } = useNetwork(); const writeTxn = useTransactor(); const { targetNetwork } = useTargetNetwork(); - const writeDisabled = - !chain || - chain?.network !== targetNetwork.network || - walletStatus === "disconnected"; - // side effect to update error state when not connected - useEffect(() => { - setFormErrorMessage( - writeDisabled ? "Wallet not connected or in the wrong network" : null, - ); - }, [writeDisabled]); + const writeDisabled = useMemo( + () => + !chain || + chain?.network !== targetNetwork.network || + walletStatus === "disconnected", + [chain, targetNetwork.network, walletStatus], + ); const { data: result, isPending: isLoading, - writeAsync, - } = useContractWrite({ + sendAsync, + error, + } = useSendTransaction({ calls: [ { contractAddress, entrypoint: abiFunction.name, // use infinity to completely flatten array from n dimensions to 1 dimension + // writing in starknet next still needs rawArgs parsing, use v2 parsing calldata: getParsedContractFunctionArgs(form, false).flat(Infinity), }, ], }); + // side effect for error logging + useEffect(() => { + if (error) { + console.error(error?.message); + console.error(error.stack); + } + }, [error]); + const handleWrite = async () => { - if (writeAsync) { + if (sendAsync) { try { - const makeWriteWithParams = () => writeAsync(); + const makeWriteWithParams = () => sendAsync(); await writeTxn(makeWriteWithParams); onChange(); } catch (e: any) { @@ -92,7 +99,7 @@ export const WriteOnlyFunctionForm = ({ const [displayedTxResult, setDisplayedTxResult] = useState(); - const { data: txResult } = useWaitForTransaction({ + const { data: txResult } = useTransactionReceipt({ hash: result?.transaction_hash, }); useEffect(() => { @@ -120,6 +127,11 @@ export const WriteOnlyFunctionForm = ({ }); const zeroInputs = inputs.length === 0; + const errorMsg = (() => { + if (writeDisabled) return "Wallet not connected or on wrong network"; + return formErrorMessage; + })(); + return (
- ) : null} + ) : null} */}
  • diff --git a/packages/nextjs/components/scaffold-stark/CustomConnectButton/WrongNetworkDropdown.tsx b/packages/nextjs/components/scaffold-stark/CustomConnectButton/WrongNetworkDropdown.tsx index 2537f94d..a3d9dec6 100644 --- a/packages/nextjs/components/scaffold-stark/CustomConnectButton/WrongNetworkDropdown.tsx +++ b/packages/nextjs/components/scaffold-stark/CustomConnectButton/WrongNetworkDropdown.tsx @@ -17,11 +17,13 @@ export const WrongNetworkDropdown = () => { Wrong network +