From 46c20b5aab8c8ba8d61889ed7772d3db31c3554f Mon Sep 17 00:00:00 2001 From: GianMarco Date: Sun, 11 Aug 2024 16:28:22 +0700 Subject: [PATCH] Update challenge decentralized staking (#98) Co-authored-by: Nadai2010 --- .github/workflows/demo.yaml | 63 - .github/workflows/release-create-stark.yaml | 114 -- .github/workflows/test_contract.yml | 4 +- .gitmodules | 4 - CONTRIBUTING.md | 2 +- package.json | 2 +- packages/nextjs/.env.example | 2 +- .../app/debug/_components/contract/Array.tsx | 129 ++ .../_components/contract/ContractInput.tsx | 54 +- .../debug/_components/contract/ContractUI.tsx | 6 +- .../contract/ReadOnlyFunctionForm.tsx | 43 +- .../app/debug/_components/contract/Struct.tsx | 119 ++ .../contract/WriteOnlyFunctionForm.tsx | 29 +- .../_components/contract/utilsContract.tsx | 20 + .../_components/contract/utilsDisplay.tsx | 38 +- packages/nextjs/components/Header.tsx | 14 +- .../stake/StakeContractInteraction.tsx | 25 +- .../nextjs/contracts/deployedContracts.ts | 622 ++++++- packages/nextjs/package.json | 7 +- .../nextjs/utils/scaffold-stark/common.ts | 15 +- .../nextjs/utils/scaffold-stark/contract.ts | 255 ++- packages/nextjs/utils/scaffold-stark/types.ts | 28 +- packages/snfoundry/.env.example | 14 +- packages/snfoundry/contracts/Scarb.lock | 10 +- packages/snfoundry/contracts/Scarb.toml | 8 +- packages/snfoundry/contracts/src/Staker.cairo | 5 +- .../src/mock_contracts/MockETHToken.cairo | 4 +- .../contracts/src/test/TestContract.cairo | 12 +- packages/snfoundry/local-devnet | 1 - packages/snfoundry/package.json | 8 +- .../snfoundry/scripts-ts/deploy-contract.ts | 197 ++- packages/snfoundry/scripts-ts/deploy.ts | 10 +- packages/snfoundry/tsconfig.json | 2 +- yarn.lock | 1495 ++++------------- 34 files changed, 1732 insertions(+), 1629 deletions(-) delete mode 100644 .github/workflows/demo.yaml delete mode 100644 .github/workflows/release-create-stark.yaml delete mode 100644 .gitmodules create mode 100644 packages/nextjs/app/debug/_components/contract/Array.tsx create mode 100644 packages/nextjs/app/debug/_components/contract/Struct.tsx delete mode 160000 packages/snfoundry/local-devnet diff --git a/.github/workflows/demo.yaml b/.github/workflows/demo.yaml deleted file mode 100644 index 7451bda5..00000000 --- a/.github/workflows/demo.yaml +++ /dev/null @@ -1,63 +0,0 @@ -name: scaffold-stark-demo workflow - -on: - pull_request: - types: [closed] - branches: [main] - paths: - - 'packages/nextjs/**' - -jobs: - version-bump-nextjs: - runs-on: ubuntu-22.04 - steps: - - - name: Checkout Source Repository - uses: actions/checkout@v4 - with: - repository: Quantum3-Labs/scaffold-stark-2 - token: ${{ secrets.ORG_GITHUB_TOKEN }} - path: source_repo - - - name: Modify scaffoldConfig in Source Repository - run: | - cd source_repo - sed -i 's/targetNetworks: \[chains.devnet\]/targetNetworks: \[chains.sepolia\]/' packages/nextjs/scaffold.config.ts - cat packages/nextjs/scaffold.config.ts - - - name: Set up Node.js - uses: actions/setup-node@v3 - with: - node-version: 20 - registry-url: 'https://registry.yarnpkg.com' - - - name: Deploy to vercel - if: success() - id: deploy - env: - VERCEL_ORG_ID: ${{ secrets.VERCEL_ORG_ID }} - VERCEL_PROJECT_ID: ${{ secrets.VERCEL_PROJECT_ID }} - VERCEL_TOKEN: ${{ secrets.VERCEL_TOKEN }} - run: | - cd source_repo - yarn install - vercel link --yes --project $VERCEL_PROJECT_ID --token $VERCEL_TOKEN --scope $VERCEL_ORG_ID - vercel --build-env NEXT_PUBLIC_IGNORE_BUILD_ERROR=true --prod --token $VERCEL_TOKEN --scope $VERCEL_ORG_ID - - - name: Notify Slack on Success - if: success() - uses: slackapi/slack-github-action@v1.26.0 - with: - channel-id: ${{ secrets.SLACK_CHANNEL_ID }} - slack-message: "GitHub deployed to vercel result: ${{ job.status }}\nRepository Name: ${{ github.repository }}\n${{ github.event.pull_request.html_url || github.event.head_commit.url }}" - env: - SLACK_BOT_TOKEN: ${{ secrets.SLACK_BOT_TOKEN }} - - - name: Notify Slack on Failure - if: failure() - uses: slackapi/slack-github-action@v1.26.0 - with: - channel-id: ${{ secrets.SLACK_CHANNEL_ID }} - slack-message: "GitHub deployed to vercel result: ${{ job.status }}\nRepository Name: ${{ github.repository }}\n${{ github.event.pull_request.html_url || github.event.head_commit.url }}" - env: - SLACK_BOT_TOKEN: ${{ secrets.SLACK_BOT_TOKEN }} diff --git a/.github/workflows/release-create-stark.yaml b/.github/workflows/release-create-stark.yaml deleted file mode 100644 index 7bda6f8b..00000000 --- a/.github/workflows/release-create-stark.yaml +++ /dev/null @@ -1,114 +0,0 @@ -name: Version Bump and Notify - -on: - pull_request: - types: [closed] - branches: [main] - -jobs: - version-bump: - runs-on: ubuntu-22.04 - - steps: - - name: Checkout Source Repository - uses: actions/checkout@v4 - with: - repository: Quantum3-Labs/scaffold-stark-2 - token: ${{ secrets.ORG_GITHUB_TOKEN }} - path: source_repo - - - name: Checkout Destination Repository - uses: actions/checkout@v4 - with: - repository: Quantum3-Labs/create-stark - token: ${{ secrets.ORG_GITHUB_TOKEN }} - path: destination_repo - - - name: Determine version bump type - id: version - run: | - cd source_repo - commit_message=$(git log -1 --pretty=%B) - if [[ "$commit_message" == *"[major]"* ]]; then - echo "type=major" >> "$GITHUB_ENV" - elif [[ "$commit_message" == *"[minor]"* ]]; then - echo "type=minor" >> "$GITHUB_ENV" - else - echo "type=patch" >> "$GITHUB_ENV" - fi - - - name: Bump version in Source Repository - id: bump-version-source - run: | - cd source_repo - git config --global user.name 'github-actions[bot]' - git config --global user.email 'github-actions[bot]@users.noreply.github.com' - new_version=$(npm version ${{ env.type }} -m "chore(release): %s [skip ci]") - echo "NEW_VERSION=${new_version}" >> "$GITHUB_ENV" - git push origin main --follow-tags - - - name: Copy Files to Destination Repository - run: | - rsync -av --delete --exclude='.git' source_repo/ destination_repo/templates/base - cd destination_repo - git add . - git commit -m "chore: sync files from scaffold-stark-2 [skip ci]" - - - name: Format .gitignore files - run: | - find destination_repo/templates/base -type f -name ".gitignore" | while read -r gitignore_file; do - mjs_file="${gitignore_file%/*}/.gitignore.template.mjs" - gitignore_content=$(cat "$gitignore_file") - cat > "$mjs_file" <<-EOF - const contents = () => - \`${gitignore_content}\` - - export default contents; - EOF - rm "$gitignore_file" - done - cd destination_repo - git add . - git commit -m "Processed $gitignore_file into $mjs_file" - - - name: Bump version in Destination Repository - id: bump-version-destination - run: | - cd destination_repo - git config --global user.name 'github-actions[bot]' - git config --global user.email 'github-actions[bot]@users.noreply.github.com' - new_version=$(npm version ${{ env.type }} -m "chore(release): %s [skip ci]") - git push origin main --follow-tags - - - name: Set up Node.js - uses: actions/setup-node@v3 - with: - node-version: '16' - registry-url: 'https://registry.npmjs.org/' - - - name: Publish release - if: success() - id: publish-release - run: | - cd destination_repo - npm install && npm run build && npm publish - env: - NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} - - - name: Notify Slack on Success - if: success() - uses: slackapi/slack-github-action@v1.26.0 - with: - channel-id: ${{ secrets.SLACK_CHANNEL_ID }} - slack-message: "GitHub Action succeeded for version bump to ${{ env.NEW_VERSION }}." - env: - SLACK_BOT_TOKEN: ${{ secrets.SLACK_BOT_TOKEN }} - - - name: Notify Slack on Failure - if: failure() - uses: slackapi/slack-github-action@v1.26.0 - with: - channel-id: ${{ secrets.SLACK_CHANNEL_ID }} - slack-message: "GitHub Action failed for version bump." - env: - SLACK_BOT_TOKEN: ${{ secrets.SLACK_BOT_TOKEN }} \ No newline at end of file diff --git a/.github/workflows/test_contract.yml b/.github/workflows/test_contract.yml index b6a5b83e..3424b5ed 100644 --- a/.github/workflows/test_contract.yml +++ b/.github/workflows/test_contract.yml @@ -16,13 +16,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.5.4 + run: curl --proto '=https' --tlsv1.2 -sSf https://docs.swmansion.com/scarb/install.sh | sh -s -- -v 2.6.5 - 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.25.0 + run: snfoundryup -v 0.27.0 - name: Run snforge tests run: snforge test diff --git a/.gitmodules b/.gitmodules deleted file mode 100644 index bbe18cc7..00000000 --- a/.gitmodules +++ /dev/null @@ -1,4 +0,0 @@ -[submodule "packages/snfoundry/local-devnet"] - path = packages/snfoundry/local-devnet - url = https://github.com/0xSpaceShard/starknet-devnet-rs - branch = json-rpc-v0.5.1 \ No newline at end of file diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 89a319a0..a6e589a2 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -37,4 +37,4 @@ If your changes involve updates to how users interact with Scaffold-Stark or Spe ## Need Help? -Reach out via our community channels if you encounter issues or need clarification on contributing. +Reach out via our community channels if you encounter issues or need clarification on contributing [here](https://t.me/+wO3PtlRAreo4MDI9). diff --git a/package.json b/package.json index 993eeba8..e21e893e 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "ss-2", - "version": "0.0.2", + "version": "0.2.3", "author": "Q3 Labs", "license": "MIT", "private": true, diff --git a/packages/nextjs/.env.example b/packages/nextjs/.env.example index 7b8be8be..10cffdbc 100644 --- a/packages/nextjs/.env.example +++ b/packages/nextjs/.env.example @@ -1 +1 @@ -NEXT_PUBLIC_PROVIDER_URL=https://starknet-sepolia.infura.io/v3/c45bd0ce3e584ba4a5e6a5928c9c0b0f \ No newline at end of file +NEXT_PUBLIC_PROVIDER_URL=https://starknet-sepolia.public.blastapi.io/rpc/v0_7 \ No newline at end of file diff --git a/packages/nextjs/app/debug/_components/contract/Array.tsx b/packages/nextjs/app/debug/_components/contract/Array.tsx new file mode 100644 index 00000000..d5803960 --- /dev/null +++ b/packages/nextjs/app/debug/_components/contract/Array.tsx @@ -0,0 +1,129 @@ +import { Dispatch, SetStateAction, useEffect, useMemo, useState } from "react"; +import { getFunctionInputKey, getInitialTupleFormState } from "./utilsContract"; +import { + AbiEnum, + AbiParameter, + AbiStruct, +} from "~~/utils/scaffold-stark/contract"; +import { replacer } from "~~/utils/scaffold-stark/common"; +import { ContractInput } from "./ContractInput"; +import { Abi } from "abi-wan-kanabi"; +import { parseGenericType } from "~~/utils/scaffold-stark"; + +type ArrayProps = { + abi: Abi; + abiParameter: AbiParameter; + parentForm: Record | undefined; + setParentForm: (form: Record) => void; + parentStateObjectKey: string; + setFormErrorMessage: Dispatch>; +}; + +export const ArrayInput = ({ + abi, + parentForm, + setParentForm, + parentStateObjectKey, + abiParameter, + setFormErrorMessage, +}: ArrayProps) => { + // array in object representation + const [inputArr, setInputArr] = useState({}); + const [arrLength, setArrLength] = useState(-1); + + const elementType = useMemo(() => { + const parsed = parseGenericType(abiParameter.type); + return Array.isArray(parsed) ? parsed[0] : parsed; + }, [abiParameter.type]); + + // side effect to transform data before setState + useEffect(() => { + // non empty objects only + setParentForm({ + ...parentForm, + [parentStateObjectKey]: Object.values(inputArr).filter( + (item) => item !== null, + ), + }); + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [JSON.stringify(inputArr, replacer)]); + + return ( +
+
+ +
+

array (length: {arrLength + 1})

+
+
+ {/* do note here that the "index" are basically array keys */} + {Object.keys(inputArr).map((index) => { + return ( + + | ((arg: Record) => void), + ) => { + let nextInputObject: Record = nextInputRecipe; + + // set state recipe function, handle + if (typeof nextInputRecipe === "function") { + nextInputObject = nextInputRecipe(parentForm!); + } + + const currentInputArray = { ...inputArr }; + + // we do some nasty workaround + currentInputArray[index] = + nextInputObject?.[`input_${index}`] || null; + + setInputArr(currentInputArray); + }} + form={inputArr[index]} + stateObjectKey={`input_${index}`} + paramType={ + { + name: `${abiParameter.name}[${index}]`, + type: elementType, + } as AbiParameter + } + setFormErrorMessage={setFormErrorMessage} + /> + ); + })} +
+ + +
+
+
+
+ ); +}; diff --git a/packages/nextjs/app/debug/_components/contract/ContractInput.tsx b/packages/nextjs/app/debug/_components/contract/ContractInput.tsx index 6eda3e8d..9ba526c0 100644 --- a/packages/nextjs/app/debug/_components/contract/ContractInput.tsx +++ b/packages/nextjs/app/debug/_components/contract/ContractInput.tsx @@ -8,21 +8,29 @@ import { isCairoArray, isCairoBigInt, isCairoInt, + isCairoType, isCairoU256, } from "~~/utils/scaffold-stark"; +import { Struct } from "./Struct"; +import { Abi } from "abi-wan-kanabi"; +import { ArrayInput } from "./Array"; type ContractInputProps = { + abi?: Abi; setForm: Dispatch>>; form: Record | undefined; stateObjectKey: string; paramType: AbiParameter; + setFormErrorMessage: Dispatch>; }; export const ContractInput = ({ + abi, setForm, form, stateObjectKey, paramType, + setFormErrorMessage, }: ContractInputProps) => { const inputProps = { name: stateObjectKey, @@ -39,18 +47,40 @@ export const ContractInput = ({ }; const renderInput = () => { - switch (paramType.type) { - default: - if ( - !isCairoArray(paramType.type) && - (isCairoInt(paramType.type) || - isCairoBigInt(paramType.type) || - isCairoU256(paramType.type)) - ) { - return ; - } else { - return ; - } + if (isCairoArray(paramType.type)) { + return ( + + ); + } else if ( + isCairoInt(paramType.type) || + isCairoBigInt(paramType.type) || + isCairoU256(paramType.type) + ) { + return ; + } else if (isCairoType(paramType.type)) { + return ; + } else { + return ( + member.name === paramType.type, + )} + /> + ); } }; diff --git a/packages/nextjs/app/debug/_components/contract/ContractUI.tsx b/packages/nextjs/app/debug/_components/contract/ContractUI.tsx index ad8fa23f..4757e2fd 100644 --- a/packages/nextjs/app/debug/_components/contract/ContractUI.tsx +++ b/packages/nextjs/app/debug/_components/contract/ContractUI.tsx @@ -71,7 +71,7 @@ export const ContractUI = ({ {targetNetwork && (

- Network:{" "} + Network:{" "} {targetNetwork.name}

)} @@ -88,7 +88,7 @@ export const ContractUI = ({
-

Read

+

Read

@@ -102,7 +102,7 @@ export const ContractUI = ({
-

Write

+

Write

diff --git a/packages/nextjs/app/debug/_components/contract/ReadOnlyFunctionForm.tsx b/packages/nextjs/app/debug/_components/contract/ReadOnlyFunctionForm.tsx index ea256671..53d4bb23 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 } from "react"; +import { useState, useRef, useEffect } from "react"; import { Abi } from "abi-wan-kanabi"; import { Address } from "@starknet-react/chains"; import { @@ -30,14 +30,16 @@ export const ReadOnlyFunctionForm = ({ getInitialFormState(abiFunction), ); const [inputValue, setInputValue] = useState(undefined); + const [formErrorMessage, setFormErrorMessage] = useState(null); const lastForm = useRef(form); const { isFetching, data, refetch } = useContractRead({ address: contractAddress, functionName: abiFunction.name, abi: [...abi], - args: inputValue ? inputValue.flat() : [], + args: inputValue ? inputValue.flat(Infinity) : [], enabled: false, + parseArgs: false, blockIdentifier: "pending" as BlockNumber, }); @@ -46,23 +48,24 @@ export const ReadOnlyFunctionForm = ({ const key = getFunctionInputKey(abiFunction.name, input, inputIndex); return ( ); }); - const handleRead = async () => { - const newInputValue = getParsedContractFunctionArgs(form, true); - if (JSON.stringify(form) === JSON.stringify(lastForm.current)) { - await refetch(); - } else { + const handleRead = () => { + const newInputValue = getParsedContractFunctionArgs(form, false); + if (JSON.stringify(form) !== JSON.stringify(lastForm.current)) { setInputValue(newInputValue); lastForm.current = form; } + refetch(); }; return ( @@ -82,16 +85,24 @@ export const ReadOnlyFunctionForm = ({
)}
- + +
); diff --git a/packages/nextjs/app/debug/_components/contract/Struct.tsx b/packages/nextjs/app/debug/_components/contract/Struct.tsx new file mode 100644 index 00000000..ec7f236a --- /dev/null +++ b/packages/nextjs/app/debug/_components/contract/Struct.tsx @@ -0,0 +1,119 @@ +import { Dispatch, SetStateAction, useEffect, useState } from "react"; +import { getFunctionInputKey, getInitialTupleFormState } from "./utilsContract"; +import { AbiEnum, AbiStruct } from "~~/utils/scaffold-stark/contract"; +import { replacer } from "~~/utils/scaffold-stark/common"; +import { ContractInput } from "./ContractInput"; +import { Abi } from "abi-wan-kanabi"; + +type StructProps = { + abi?: Abi; + parentForm: Record | undefined; + setParentForm: (form: Record) => void; + parentStateObjectKey: string; + abiMember?: AbiStruct | AbiEnum; + setFormErrorMessage: Dispatch>; +}; + +export const Struct = ({ + parentForm, + setParentForm, + parentStateObjectKey, + abiMember, + abi, + setFormErrorMessage, +}: StructProps) => { + const [form, setForm] = useState>(() => + getInitialTupleFormState( + abiMember ?? { type: "struct", name: "", members: [] }, + ), + ); + + // side effect to transform data before setState + useEffect(() => { + const values = Object.values(form); + const argsStruct: Record = {}; + if (!abiMember) return; + + if (abiMember.type === "struct") { + abiMember.members.forEach((member, index) => { + argsStruct[member.name || `input_${index}_`] = { + type: member.type, + value: values[index], + }; + }); + } else { + abiMember.variants.forEach((variant, index) => { + argsStruct[variant.name || `input_${index}_`] = { + type: variant.type, + value: values[index], + }; + }); + + // check for enum validity + if (values.filter((item) => (item || "").length > 0).length > 1) { + setFormErrorMessage("Enums can only have one defined value"); + } else { + setFormErrorMessage(null); + } + } + + setParentForm({ + ...parentForm, + [parentStateObjectKey]: + abiMember.type === "struct" ? argsStruct : { variant: argsStruct }, + }); + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [abiMember, JSON.stringify(form, replacer)]); + + if (!abiMember) return null; + + return ( +
+
+ +
+

{abiMember.type}

+
+
+ {abiMember.type === "struct" + ? abiMember.members.map((member, index) => { + const key = getFunctionInputKey( + abiMember.name || "struct", + member, + index, + ); + return ( + + ); + }) + : abiMember.variants.map((variant, index) => { + const key = getFunctionInputKey( + abiMember.name || "tuple", + variant, + index, + ); + return ( + + ); + })} +
+
+
+ ); +}; diff --git a/packages/nextjs/app/debug/_components/contract/WriteOnlyFunctionForm.tsx b/packages/nextjs/app/debug/_components/contract/WriteOnlyFunctionForm.tsx index bbf496d7..c86d8423 100644 --- a/packages/nextjs/app/debug/_components/contract/WriteOnlyFunctionForm.tsx +++ b/packages/nextjs/app/debug/_components/contract/WriteOnlyFunctionForm.tsx @@ -10,6 +10,7 @@ import { } from "~~/app/debug/_components/contract"; import { useTargetNetwork } from "~~/hooks/scaffold-stark/useTargetNetwork"; import { + useAccount, useContractWrite, useNetwork, useWaitForTransaction, @@ -37,10 +38,22 @@ export const WriteOnlyFunctionForm = ({ const [form, setForm] = useState>(() => getInitialFormState(abiFunction), ); + const [formErrorMessage, setFormErrorMessage] = useState(null); + const { status: walletStatus } = useAccount(); const { chain } = useNetwork(); const writeTxn = useTransactor(); const { targetNetwork } = useTargetNetwork(); - const writeDisabled = !chain || chain?.network !== targetNetwork.network; + 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 { data: result, @@ -51,7 +64,9 @@ export const WriteOnlyFunctionForm = ({ { contractAddress, entrypoint: abiFunction.name, - calldata: getParsedContractFunctionArgs(form, false).flat(), + + // use infinity to completely flatten array from n dimensions to 1 dimension + calldata: getParsedContractFunctionArgs(form, false).flat(Infinity), }, ], }); @@ -90,6 +105,7 @@ export const WriteOnlyFunctionForm = ({ const key = getFunctionInputKey(abiFunction.name, input, inputIndex); return ( { setDisplayedTxResult(undefined); @@ -98,6 +114,7 @@ export const WriteOnlyFunctionForm = ({ form={form} stateObjectKey={key} paramType={input} + setFormErrorMessage={setFormErrorMessage} /> ); }); @@ -124,16 +141,14 @@ export const WriteOnlyFunctionForm = ({ )}