From 3b363c111e5dd199d3c60800f51264dcecf0903f Mon Sep 17 00:00:00 2001 From: bigq Date: Mon, 17 Jul 2023 11:00:57 +0200 Subject: [PATCH 01/12] feat: clean leading zeros on zk proofs, removed vaultId checking and delete storage if not empty --- abi/Airdrop.json | 16 ++++------- front/src/app/components/Header.tsx | 2 +- front/src/app/page.tsx | 2 +- front/src/utils/misc.ts | 11 +++++--- front/src/utils/useContract.tsx | 2 +- src/Airdrop.sol | 43 +++++++++++++++++------------ 6 files changed, 40 insertions(+), 36 deletions(-) diff --git a/abi/Airdrop.json b/abi/Airdrop.json index 47a1b58..c3ee870 100644 --- a/abi/Airdrop.json +++ b/abi/Airdrop.json @@ -110,17 +110,6 @@ "name": "AlreadyClaimed", "type": "error" }, - { - "inputs": [ - { - "internalType": "enum AuthType", - "name": "authType", - "type": "uint8" - } - ], - "name": "AuthTypeNotFoundInVerifiedResult", - "type": "error" - }, { "anonymous": false, "inputs": [ @@ -399,6 +388,11 @@ "internalType": "bytes", "name": "response", "type": "bytes" + }, + { + "internalType": "address", + "name": "to", + "type": "address" } ], "name": "claimWithSismo", diff --git a/front/src/app/components/Header.tsx b/front/src/app/components/Header.tsx index e373736..0c06128 100644 --- a/front/src/app/components/Header.tsx +++ b/front/src/app/components/Header.tsx @@ -16,7 +16,7 @@ const Header: React.FC = () => { the frontend
3. The frontend forwards the response to ERC20 smart contract via claimWithSismo function{" "}
- 4. The smart contract the proofs contained in the response, mints ERC20 tokens and stores + 4. The smart contract verifies the proofs contained in the response, mints ERC20 tokens and stores verified claims and auths
5. The frontend reads the verified claims and auths from the contract and displays them

diff --git a/front/src/app/page.tsx b/front/src/app/page.tsx index b6806ee..e1abb66 100644 --- a/front/src/app/page.tsx +++ b/front/src/app/page.tsx @@ -114,7 +114,7 @@ export default function Home() { {verifiedAuths && ( <>

- {amountClaimed} tokens were claimd in total on {address}. + {amountClaimed} tokens were claimed in total on {address}.

Verified Auths

diff --git a/front/src/utils/misc.ts b/front/src/utils/misc.ts index ffe7a9a..64025d8 100644 --- a/front/src/utils/misc.ts +++ b/front/src/utils/misc.ts @@ -38,10 +38,13 @@ export function readibleHex(userId: string, startLength = 6, endLength = 4, sepa return userId.substring(0, startLength) + separator + userId.substring(userId.length - endLength); } -export function getProofDataForAuth(verifiedAuths: VerifiedAuth[], authType: AuthType): string | null { +export function getProofDataForAuth( + verifiedAuths: VerifiedAuth[], + authType: AuthType +): string | null { for (const auth of verifiedAuths) { if (auth.proofData && auth.authType === authType) { - return readibleHex("0x" + (auth.proofData as unknown as number).toString(16)); + return readibleHex((auth.proofData as unknown as number).toString(16)); } } @@ -56,7 +59,7 @@ export function getProofDataForClaim( ): string | null { for (const claim of verifiedClaims) { if (claim.proofData && claim.claimType === claimType && claim.groupId === groupId) { - return readibleHex("0x" + (claim.proofData as unknown as number).toString(16)); + return readibleHex((claim.proofData as unknown as number).toString(16)); } } @@ -70,4 +73,4 @@ export function getuserIdFromHex(hexUserId: string) { } else { return hexUserId; // returns the original string if '00' is not found } -} \ No newline at end of file +} diff --git a/front/src/utils/useContract.tsx b/front/src/utils/useContract.tsx index bb6777f..0dd4728 100644 --- a/front/src/utils/useContract.tsx +++ b/front/src/utils/useContract.tsx @@ -48,7 +48,7 @@ export default function useContract({ address: transactions[0].contractAddress as `0x${string}`, abi: [...AirdropABI, ...errorsABI], functionName: "claimWithSismo", - args: [responseBytes], + args: [responseBytes, address], chain, enabled: Boolean(responseBytes), }; diff --git a/src/Airdrop.sol b/src/Airdrop.sol index 2cd593d..52dbe18 100644 --- a/src/Airdrop.sol +++ b/src/Airdrop.sol @@ -43,7 +43,7 @@ contract Airdrop is ERC20, SismoConnect { _setClaims(claimRequests); } - function claimWithSismo(bytes memory response) public { + function claimWithSismo(bytes memory response, address to) public { SismoConnectVerifiedResult memory result = verify({ responseBytes: response, // checking response against requested auths @@ -51,25 +51,16 @@ contract Airdrop is ERC20, SismoConnect { // checking response against requested claims claims: _claimRequests, // checking response against requested message signature - signature: buildSignature({message: abi.encode(msg.sender)}) + signature: buildSignature({message: abi.encode(to)}) }); - // it is the anonymous identifier of a user's vault for a specific app - // --> vaultId = hash(userVaultSecret, appId) - // used to avoid double claims - uint256 vaultId = result.getUserId(AuthType.VAULT); - - // checking if the user has already claimed - if (claimed[vaultId]) { - revert AlreadyClaimed(); - } - - // marking that the user has claimed - claimed[vaultId] = true; - // airdrop amount = number of verified proofs uint256 airdropAmount = (result.auths.length + result.claims.length) * 10 ** 18; - _mint(msg.sender, airdropAmount); + _mint(to, airdropAmount); + + // cleaning previous results of the verification + _cleanVerifiedAuths(); + _cleanVerifiedClaims(); // storing the result of the verification for (uint256 i = 0; i < result.auths.length; i++) { @@ -80,11 +71,10 @@ contract Airdrop is ERC20, SismoConnect { _verifiedClaims.push(result.claims[i]); emit ClaimVerified(result.claims[i]); } - _verifiedSignedMessage =result.signedMessage; + _verifiedSignedMessage = result.signedMessage; emit SignedMessageVerified(result.signedMessage); } - function getVerifiedClaims() external view returns (VerifiedClaim[] memory) { return _verifiedClaims; } @@ -109,4 +99,21 @@ contract Airdrop is ERC20, SismoConnect { } } + function _cleanVerifiedAuths() private { + uint256 verifiedAuthsLength = _verifiedAuths.length; + if (verifiedAuthsLength != 0) { + for (uint256 i = 0; i < verifiedAuthsLength; i++) { + _verifiedAuths.pop(); + } + } + } + + function _cleanVerifiedClaims() private { + uint256 verifiedClaimsLength = _verifiedClaims.length; + if (verifiedClaimsLength != 0) { + for (uint256 i = 0; i < verifiedClaimsLength; i++) { + _verifiedClaims.pop(); + } + } + } } From cd0efa34753a5f9c4b1886669840e4ae344a0619 Mon Sep 17 00:00:00 2001 From: bigq Date: Mon, 17 Jul 2023 12:36:18 +0200 Subject: [PATCH 02/12] feat: add contract object instead of wagmi hooks --- front/src/app/page.tsx | 4 +- front/src/utils/misc.ts | 2 +- front/src/utils/useContract.tsx | 69 ++++++++++++++++++--------------- front/src/utils/wagmi.tsx | 6 ++- 4 files changed, 45 insertions(+), 36 deletions(-) diff --git a/front/src/app/page.tsx b/front/src/app/page.tsx index e1abb66..810db74 100644 --- a/front/src/app/page.tsx +++ b/front/src/app/page.tsx @@ -7,7 +7,7 @@ import { useConnectModal } from "@rainbow-me/rainbowkit"; import { getProofDataForAuth, getProofDataForClaim, - getuserIdFromHex, + getUserIdFromHex, signMessage, } from "@/utils/misc"; import { mumbaiFork } from "@/utils/wagmi"; @@ -129,7 +129,7 @@ export default function Home() { diff --git a/front/src/utils/misc.ts b/front/src/utils/misc.ts index 64025d8..22fe392 100644 --- a/front/src/utils/misc.ts +++ b/front/src/utils/misc.ts @@ -66,7 +66,7 @@ export function getProofDataForClaim( return null; // returns null if no matching authType is found } -export function getuserIdFromHex(hexUserId: string) { +export function getUserIdFromHex(hexUserId: string) { const index = hexUserId.lastIndexOf("000000"); if (index !== -1) { return hexUserId.substring(index + 6); diff --git a/front/src/utils/useContract.tsx b/front/src/utils/useContract.tsx index 0dd4728..21a7576 100644 --- a/front/src/utils/useContract.tsx +++ b/front/src/utils/useContract.tsx @@ -1,13 +1,21 @@ import { useEffect, useState } from "react"; -import { Chain, TransactionReceipt, formatEther } from "viem"; +import { + Chain, + TransactionReceipt, + WalletClient, + createWalletClient, + custom, + formatEther, + getContract, +} from "viem"; import { useAccount, - useContractWrite, useNetwork, usePrepareContractWrite, useSwitchNetwork, + useWalletClient, } from "wagmi"; -import { waitForTransaction, readContract } from "@wagmi/core"; +import { waitForTransaction, getPublicClient } from "@wagmi/core"; import { abi as AirdropABI } from "../../../abi/Airdrop.json"; import { errorsABI } from "./errorsABI"; import { formatError } from "./misc"; @@ -44,16 +52,15 @@ export default function useContract({ onConnect: async ({ address }) => address && (await fundMyAccountOnLocalFork(address)), }); const { switchNetworkAsync } = useSwitchNetwork(); - const contractCallInputs = { + const publicClient = getPublicClient(); + const { data: walletClient } = useWalletClient(); + + const contract = getContract({ address: transactions[0].contractAddress as `0x${string}`, abi: [...AirdropABI, ...errorsABI], - functionName: "claimWithSismo", - args: [responseBytes, address], - chain, - enabled: Boolean(responseBytes), - }; - const { config, error: wagmiSimulateError } = usePrepareContractWrite(contractCallInputs); - const { writeAsync } = useContractWrite(config); + publicClient, + walletClient: walletClient as WalletClient, + }); useEffect(() => { if (!responseBytes) return; @@ -67,10 +74,18 @@ export default function useContract({ }, [currentChain]); useEffect(() => { - if (!wagmiSimulateError) return; - if (!isConnected) return; - return setError(formatError(wagmiSimulateError)); - }, [wagmiSimulateError, isConnected]); + if (!address) return; + if (!responseBytes) return; + async function simulate() { + try { + await contract.simulate.claimWithSismo([responseBytes, address]); + } catch (e: any) { + return setError(formatError(e)); + } + } + + simulate(); + }, [address, responseBytes]); /* ************ Handle the airdrop claim button click ******************* */ async function claimAirdrop() { @@ -79,7 +94,7 @@ export default function useContract({ try { if (currentChain?.id !== chain.id) await switchNetworkAsync?.(chain.id); setPageState("confirmingTransaction"); - const tx = await writeAsync?.(); + const hash = await contract.write.claimWithSismo([responseBytes, address]); setPageState("verifying"); let txReceipt: TransactionReceipt | undefined; if (chain.id === 5151111) { @@ -93,35 +108,27 @@ export default function useContract({ ); }, 10000) ); - const txReceiptPromise = tx && waitForTransaction({ hash: tx.hash }); + const txReceiptPromise = hash && waitForTransaction({ hash: hash }); const race = await Promise.race([txReceiptPromise, timeout]); txReceipt = race as TransactionReceipt; } else { - txReceipt = tx && (await waitForTransaction({ hash: tx.hash })); + txReceipt = hash && (await waitForTransaction({ hash: hash })); } if (txReceipt?.status === "success") { setAmountClaimed( - formatEther((await readAirdropContract("balanceOf", [address])) as unknown as bigint) + formatEther((await contract.read.balanceOf([address])) as unknown as bigint) ); - setVerifiedClaims((await readAirdropContract("getVerifiedClaims")) as VerifiedClaim[]); - setVerifiedAuths((await readAirdropContract("getVerifiedAuths")) as VerifiedAuth[]); - setVerifiedSignedMessage((await readAirdropContract("getVerifiedSignedMessage")) as string); + setVerifiedClaims((await contract.read.getVerifiedClaims()) as VerifiedClaim[]); + setVerifiedAuths((await contract.read.getVerifiedAuths()) as VerifiedAuth[]); + setVerifiedSignedMessage((await contract.read.getVerifiedSignedMessage()) as string); setPageState("verified"); } } catch (e: any) { + console.error(e); setError(formatError(e)); } } - const readAirdropContract = async (functionName: string, args?: string[]) => { - return readContract({ - address: transactions[0].contractAddress as `0x${string}}`, - abi: AirdropABI, - functionName, - args: args || [], - }); - }; - function reset() { setAmountClaimed(""); setError(""); diff --git a/front/src/utils/wagmi.tsx b/front/src/utils/wagmi.tsx index 3990f65..0dc8a12 100644 --- a/front/src/utils/wagmi.tsx +++ b/front/src/utils/wagmi.tsx @@ -1,6 +1,6 @@ "use client"; -import '@rainbow-me/rainbowkit/styles.css'; +import "@rainbow-me/rainbowkit/styles.css"; import { useEffect, useState } from "react"; import { @@ -72,7 +72,9 @@ export function WagmiProvider({ children }: { children: React.ReactNode }) { return ( - {mounted && children} + + {mounted && children} + ); } From ad9870ea068c4d2550b0299ee22dbc8265c3027f Mon Sep 17 00:00:00 2001 From: bigq Date: Mon, 17 Jul 2023 16:15:16 +0200 Subject: [PATCH 03/12] feat: extract contract interaction logic to page.tsx instead of useContract.tsx --- front/src/app/page.tsx | 135 +++++++++++++++++++++++++------- front/src/utils/useContract.tsx | 126 +++++++++-------------------- 2 files changed, 141 insertions(+), 120 deletions(-) diff --git a/front/src/app/page.tsx b/front/src/app/page.tsx index 810db74..4ff93a1 100644 --- a/front/src/app/page.tsx +++ b/front/src/app/page.tsx @@ -1,10 +1,11 @@ "use client"; -import { useState } from "react"; +import { useEffect, useState } from "react"; import Header from "./components/Header"; import { useAccount, useNetwork, useSwitchNetwork } from "wagmi"; import { useConnectModal } from "@rainbow-me/rainbowkit"; import { + formatError, getProofDataForAuth, getProofDataForClaim, getUserIdFromHex, @@ -12,17 +13,34 @@ import { } from "@/utils/misc"; import { mumbaiFork } from "@/utils/wagmi"; import { - SismoConnectButton, // the Sismo Connect React button displayed below + SismoConnectButton, + VerifiedAuth, + VerifiedClaim, // the Sismo Connect React button displayed below } from "@sismo-core/sismo-connect-react"; import { fundMyAccountOnLocalFork } from "@/utils/fundMyAccountOnLocalFork"; import { AUTHS, CLAIMS, CONFIG, AuthType, ClaimType } from "@/app/sismo-connect-config"; import useContract from "@/utils/useContract"; +import { formatEther } from "viem"; /* ******************** Defines the chain to use *************************** */ const CHAIN = mumbaiFork; export default function Home() { + const [appState, setAppState] = useState<{ + pageState: string; + amountClaimed: string; + verifiedClaims: VerifiedClaim[] | undefined; + verifiedAuths: VerifiedAuth[] | undefined; + verifiedSignedMessage: string | undefined; + }>({ + pageState: "init", + amountClaimed: "", + verifiedClaims: undefined, + verifiedAuths: undefined, + verifiedSignedMessage: undefined, + }); const [responseBytes, setResponseBytes] = useState(null); + const [claimError, setClaimError] = useState(null); const { isConnected, address } = useAccount({ onConnect: async ({ address }) => address && (await fundMyAccountOnLocalFork(address)), }); @@ -30,23 +48,72 @@ export default function Home() { const { switchNetwork } = useSwitchNetwork(); const { openConnectModal, connectModalOpen } = useConnectModal(); - const { - claimAirdrop, - reset, - amountClaimed, - error, - pageState, - verifiedAuths, - verifiedClaims, - verifiedSignedMessage, - } = useContract({ responseBytes, chain: CHAIN }); + const { airdropContract, switchNetworkAsync, waitingForTransaction, error } = useContract({ + responseBytes, + chain: CHAIN, + }); + + useEffect(() => { + if (!responseBytes) return; + setAppState((prev) => { + return { ...prev, pageState: "responseReceived" }; + }); + }, [responseBytes]); /* ************************* Reset state **************************** */ function resetApp() { - reset(); + setAppState({ + pageState: "init", + amountClaimed: "", + verifiedClaims: undefined, + verifiedAuths: undefined, + verifiedSignedMessage: undefined, + }); + setClaimError(""); + const url = new URL(window.location.href); + url.searchParams.delete("sismoConnectResponseCompressed"); + window.history.replaceState({}, "", url.toString()); setResponseBytes(""); } + /* ************ Handle the airdrop claim button click ******************* */ + async function claimAirdrop() { + if (!address) return; + setClaimError(""); + try { + if (chain?.id !== CHAIN.id) await switchNetworkAsync?.(CHAIN.id); + setAppState((prev) => { + return { ...prev, pageState: "confirmingTransaction" }; + }); + const hash = await airdropContract.write.claimWithSismo([responseBytes, address]); + setAppState((prev) => { + return { ...prev, pageState: "verifying" }; + }); + let txReceipt = await waitingForTransaction(hash); + if (txReceipt?.status === "success") { + const amountClaimed = formatEther( + (await airdropContract.read.balanceOf([address])) as bigint + ); + const verifiedClaims = (await airdropContract.read.getVerifiedClaims()) as VerifiedClaim[]; + const verifiedAuths = (await airdropContract.read.getVerifiedAuths()) as VerifiedAuth[]; + const verifiedSignedMessage = + (await airdropContract.read.getVerifiedSignedMessage()) as string; + setAppState((prev) => { + return { + ...prev, + amountClaimed, + verifiedClaims, + verifiedAuths, + verifiedSignedMessage, + pageState: "verified", + }; + }); + } + } catch (e: any) { + setClaimError(formatError(e)); + } + } + return ( <>
@@ -60,14 +127,14 @@ export default function Home() { <> <> {" "} - {pageState != "init" && } + {appState.pageState != "init" && }

{`Chain: ${chain?.name} [${chain?.id}]`}
Your airdrop destination address is: {address}

- {pageState == "init" && ( + {appState.pageState == "init" && ( <> )}
- {pageState == "responseReceived" && ( + {appState.pageState == "responseReceived" && ( )} - {pageState == "confirmingTransaction" && ( + {appState.pageState == "confirmingTransaction" && ( )} - {pageState == "verifying" && ( + {appState.pageState == "verifying" && ( Verifying ZK Proofs onchain... )} - {pageState == "verified" && ZK Proofs Verified! } + {appState.pageState == "verified" && ( + ZK Proofs Verified! + )}
- {isConnected && !amountClaimed && error && ( + {isConnected && !appState.amountClaimed && (error || claimError) && ( <>

{error}

{error.slice(0, 16) === "Please switch to" && ( @@ -111,10 +180,10 @@ export default function Home() { )} {/* Table of the Sismo Connect requests and verified result */} {/* Table for Verified Auths */} - {verifiedAuths && ( + {appState.verifiedAuths && ( <>

- {amountClaimed} tokens were claimed in total on {address}. + {appState.amountClaimed} tokens were claimed in total on {address}.

Verified Auths

{AuthType[auth.authType]} - {getuserIdFromHex( + {getUserIdFromHex( "0x" + (auth.userId! as unknown as number).toString(16) )}
@@ -125,7 +194,7 @@ export default function Home() { - {verifiedAuths.map((auth, index) => ( + {appState.verifiedAuths.map((auth, index) => ( - {AUTHS.map((auth, index) => ( + {appState?.authRequests?.map((auth, index) => ( @@ -298,7 +366,7 @@ export default function Home() { - {CLAIMS.map((claim, index) => ( + {appState?.claimRequests?.map((claim, index) => (
{AuthType[auth.authType]} @@ -141,7 +210,7 @@ export default function Home() { )}
{/* Table for Verified Claims */} - {verifiedClaims && ( + {appState.verifiedClaims && ( <>

Verified Claims

@@ -153,7 +222,7 @@ export default function Home() { - {verifiedClaims.map((claim, index) => ( + {appState.verifiedClaims.map((claim, index) => ( - {verifiedAuths ? ( - + {appState.verifiedAuths ? ( + ) : ( )} @@ -228,11 +299,11 @@ export default function Home() { - {verifiedClaims ? ( + {appState.verifiedClaims ? ( - +
{AuthType[auth.authType]} {auth.userId || "No userId requested"} {auth.isOptional ? "optional" : "required"}{getProofDataForAuth(verifiedAuths, auth.authType)!.toString()} + {getProofDataForAuth(appState.verifiedAuths, auth.authType)!.toString()} + ZK proof not generated yet {claim.value ? claim.value : "1"} {claim.isSelectableByUser ? "yes" : "no"} {claim.isOptional ? "optional" : "required"} { getProofDataForClaim( - verifiedClaims!, + appState.verifiedClaims!, claim.claimType || 0, claim.groupId!, claim.value || 1 @@ -258,7 +329,11 @@ export default function Home() {
{{ message: signMessage(address!) }.message}{verifiedSignedMessage ? verifiedSignedMessage : "ZK Proof not verified"} + {appState.verifiedSignedMessage + ? appState.verifiedSignedMessage + : "ZK Proof not verified"} +
diff --git a/front/src/utils/useContract.tsx b/front/src/utils/useContract.tsx index 21a7576..c94c148 100644 --- a/front/src/utils/useContract.tsx +++ b/front/src/utils/useContract.tsx @@ -1,37 +1,25 @@ import { useEffect, useState } from "react"; import { Chain, + GetContractReturnType, + PublicClient, TransactionReceipt, WalletClient, - createWalletClient, - custom, - formatEther, getContract, } from "viem"; -import { - useAccount, - useNetwork, - usePrepareContractWrite, - useSwitchNetwork, - useWalletClient, -} from "wagmi"; +import { useAccount, useNetwork, useSwitchNetwork, useWalletClient } from "wagmi"; import { waitForTransaction, getPublicClient } from "@wagmi/core"; import { abi as AirdropABI } from "../../../abi/Airdrop.json"; import { errorsABI } from "./errorsABI"; import { formatError } from "./misc"; -import { VerifiedAuth, VerifiedClaim } from "@/app/sismo-connect-config"; import { fundMyAccountOnLocalFork } from "./fundMyAccountOnLocalFork"; import { transactions } from "../../../broadcast/Airdrop.s.sol/5151111/run-latest.json"; export type ContractClaim = { - claimAirdrop: () => Promise; - reset: () => void; + airdropContract: GetContractReturnType; + switchNetworkAsync: ((chainId?: number | undefined) => Promise) | undefined; + waitingForTransaction: (hash: `0x${string}`) => Promise; error: string; - amountClaimed: string; - pageState: string; - verifiedClaims: VerifiedClaim[] | undefined; - verifiedAuths: VerifiedAuth[] | undefined; - verifiedSignedMessage: string | undefined; }; export default function useContract({ @@ -42,31 +30,21 @@ export default function useContract({ chain: Chain; }): ContractClaim { const [error, setError] = useState(""); - const [pageState, setPageState] = useState("init"); - const [amountClaimed, setAmountClaimed] = useState(""); - const [verifiedClaims, setVerifiedClaims] = useState(); - const [verifiedAuths, setVerifiedAuths] = useState(); - const [verifiedSignedMessage, setVerifiedSignedMessage] = useState(); const { chain: currentChain } = useNetwork(); - const { isConnected, address } = useAccount({ - onConnect: async ({ address }) => address && (await fundMyAccountOnLocalFork(address)), - }); const { switchNetworkAsync } = useSwitchNetwork(); const publicClient = getPublicClient(); const { data: walletClient } = useWalletClient(); + const { isConnected, address } = useAccount({ + onConnect: async ({ address }) => address && (await fundMyAccountOnLocalFork(address)), + }); - const contract = getContract({ + const airdropContract = getContract({ address: transactions[0].contractAddress as `0x${string}`, abi: [...AirdropABI, ...errorsABI], publicClient, walletClient: walletClient as WalletClient, }); - useEffect(() => { - if (!responseBytes) return; - setPageState("responseReceived"); - }, [responseBytes]); - /* ************* Handle simulateContract call & chain errors ************ */ useEffect(() => { if (currentChain?.id !== chain.id) return setError(`Please switch to ${chain.name} network`); @@ -74,78 +52,46 @@ export default function useContract({ }, [currentChain]); useEffect(() => { - if (!address) return; + if (!isConnected) return; if (!responseBytes) return; async function simulate() { try { - await contract.simulate.claimWithSismo([responseBytes, address]); + await airdropContract.simulate.claimWithSismo([responseBytes, address]); } catch (e: any) { return setError(formatError(e)); } } simulate(); - }, [address, responseBytes]); + }, [address, isConnected, responseBytes]); - /* ************ Handle the airdrop claim button click ******************* */ - async function claimAirdrop() { - if (!address) return; - setError(""); - try { - if (currentChain?.id !== chain.id) await switchNetworkAsync?.(chain.id); - setPageState("confirmingTransaction"); - const hash = await contract.write.claimWithSismo([responseBytes, address]); - setPageState("verifying"); - let txReceipt: TransactionReceipt | undefined; - if (chain.id === 5151111) { - const timeout = new Promise((_, reject) => - setTimeout(() => { - setPageState("responseReceived"); - reject( - new Error( - "Transaction timed-out: If you are running a local fork on Anvil please make sure to reset your wallet nonce. In metamask: Go to settings > advanced > clear activity and nonce data" - ) - ); - }, 10000) - ); - const txReceiptPromise = hash && waitForTransaction({ hash: hash }); - const race = await Promise.race([txReceiptPromise, timeout]); - txReceipt = race as TransactionReceipt; - } else { - txReceipt = hash && (await waitForTransaction({ hash: hash })); - } - if (txReceipt?.status === "success") { - setAmountClaimed( - formatEther((await contract.read.balanceOf([address])) as unknown as bigint) - ); - setVerifiedClaims((await contract.read.getVerifiedClaims()) as VerifiedClaim[]); - setVerifiedAuths((await contract.read.getVerifiedAuths()) as VerifiedAuth[]); - setVerifiedSignedMessage((await contract.read.getVerifiedSignedMessage()) as string); - setPageState("verified"); - } - } catch (e: any) { - console.error(e); - setError(formatError(e)); + async function waitingForTransaction( + hash: `0x${string}` + ): Promise { + let txReceipt: TransactionReceipt | undefined; + if (chain.id === 5151111) { + const timeout = new Promise((_, reject) => + setTimeout(() => { + reject( + new Error( + "Transaction timed-out: If you are running a local fork on Anvil please make sure to reset your wallet nonce. In metamask: Go to settings > advanced > clear activity and nonce data" + ) + ); + }, 10000) + ); + const txReceiptPromise = hash && waitForTransaction({ hash: hash }); + const race = await Promise.race([txReceiptPromise, timeout]); + txReceipt = race as TransactionReceipt; + } else { + txReceipt = hash && (await waitForTransaction({ hash: hash })); } - } - - function reset() { - setAmountClaimed(""); - setError(""); - setPageState("init"); - const url = new URL(window.location.href); - url.searchParams.delete("sismoConnectResponseCompressed"); - window.history.replaceState({}, "", url.toString()); + return txReceipt; } return { - claimAirdrop, - reset, + airdropContract, + switchNetworkAsync, + waitingForTransaction, error, - pageState, - amountClaimed, - verifiedClaims, - verifiedAuths, - verifiedSignedMessage, }; } From 58778a53b3cbfb4615a19122f45a025a2449de3a Mon Sep 17 00:00:00 2001 From: bigq Date: Mon, 17 Jul 2023 16:36:40 +0200 Subject: [PATCH 04/12] feat: remove already claimed error and claimed mapping --- abi/Airdrop.json | 24 ------------------------ src/Airdrop.sol | 2 -- 2 files changed, 26 deletions(-) diff --git a/abi/Airdrop.json b/abi/Airdrop.json index c3ee870..b784908 100644 --- a/abi/Airdrop.json +++ b/abi/Airdrop.json @@ -105,11 +105,6 @@ "stateMutability": "nonpayable", "type": "constructor" }, - { - "inputs": [], - "name": "AlreadyClaimed", - "type": "error" - }, { "anonymous": false, "inputs": [ @@ -400,25 +395,6 @@ "stateMutability": "nonpayable", "type": "function" }, - { - "inputs": [ - { - "internalType": "uint256", - "name": "", - "type": "uint256" - } - ], - "name": "claimed", - "outputs": [ - { - "internalType": "bool", - "name": "", - "type": "bool" - } - ], - "stateMutability": "view", - "type": "function" - }, { "inputs": [], "name": "config", diff --git a/src/Airdrop.sol b/src/Airdrop.sol index 52dbe18..be097e7 100644 --- a/src/Airdrop.sol +++ b/src/Airdrop.sol @@ -13,12 +13,10 @@ import "sismo-connect-solidity/SismoLib.sol"; * The contract stores all verified results in storage */ contract Airdrop is ERC20, SismoConnect { - error AlreadyClaimed(); event AuthVerified(VerifiedAuth verifiedAuth); event ClaimVerified(VerifiedClaim verifiedClaim); event SignedMessageVerified(bytes verifiedSignedMessage); using SismoConnectHelper for SismoConnectVerifiedResult; - mapping(uint256 => bool) public claimed; // must correspond to requests defined in the app frontend // Sismo Connect response's zk proofs will be checked against these requests. From 64184b686adb2e09d9e73cfc0252f06b76f5be65 Mon Sep 17 00:00:00 2001 From: bigq Date: Mon, 17 Jul 2023 16:57:15 +0200 Subject: [PATCH 05/12] feat: add tests --- script/Airdrop.s.sol | 7 ++-- tests/Airdrop.t.sol | 49 ++++++++++++++++++++++++++++ tests/sismo-connect-config.test.json | 33 +++++++++++++++++++ 3 files changed, 84 insertions(+), 5 deletions(-) create mode 100644 tests/Airdrop.t.sol create mode 100644 tests/sismo-connect-config.test.json diff --git a/script/Airdrop.s.sol b/script/Airdrop.s.sol index 016b282..2f83c9b 100644 --- a/script/Airdrop.s.sol +++ b/script/Airdrop.s.sol @@ -2,16 +2,13 @@ pragma solidity ^0.8.20; import "forge-std/Script.sol"; -import "forge-std/console.sol"; import "sismo-connect-solidity/SismoLib.sol"; -import {SismoConnectConfigReader} from "./utils/SismoConnectConfigReader.sol"; +import {SismoConnectConfigReader} from "script/utils/SismoConnectConfigReader.sol"; import {Airdrop} from "src/Airdrop.sol"; contract DeployAirdrop is Script, SismoConnectConfigReader { - using stdJson for string; - function run() public { - console.log("Deploying Airdrop contract"); + console.log("Deploying Airdrop contract..."); string memory json = vm.readFile(string.concat(vm.projectRoot(), "/sismo-connect-config.json")); ( bytes16 appId, diff --git a/tests/Airdrop.t.sol b/tests/Airdrop.t.sol new file mode 100644 index 0000000..273d4fd --- /dev/null +++ b/tests/Airdrop.t.sol @@ -0,0 +1,49 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.20; + +import "tests/base/BaseTest.t.sol"; +import "sismo-connect-solidity/SismoLib.sol"; +import {SismoConnectConfigReader} from "script/utils/SismoConnectConfigReader.sol"; +import {Airdrop} from "src/Airdrop.sol"; + +contract Airdroptest is BaseTest, SismoConnectConfigReader { + Airdrop airdrop; + address claimAddress = 0x061060a65146b3265C62fC8f3AE977c9B27260fF; + + function setUp() public { + string memory json = vm.readFile( + string.concat(vm.projectRoot(), "/tests/sismo-connect-config.test.json") + ); + ( + bytes16 appId, + AuthRequest[] memory authRequests, + ClaimRequest[] memory claimRequests, + bool isImpersonationMode + ) = readSismoConnectRequest(json); + + airdrop = new Airdrop( + "my Airdrop", + "AIR", + appId, + isImpersonationMode, + authRequests, + claimRequests + ); + } + + function testAirdrop() public { + _registerTreeRoot(0x04f0ace60fdf560415b93173156e67c6735946e9889973bfd56f1bcbe6fc5bcf); + + bytes + memory response = hex"000000000000000000000000000000000000000000000000000000000000002032403ced4b65f2079eda77c84e7d2be600000000000000000000000000000000b8e2054f8a912367e38a22ce773328ff000000000000000000000000000000007369736d6f2d636f6e6e6563742d76312e31000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000000000e00000000000000000000000000000000000000000000000000000000000000020000000000000000000000000061060a65146b3265c62fc8f3ae977c9b27260ff000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000c000000000000000000000000000000000000000000000000000000000000005800000000000000000000000000000000000000000000000000000000000000a400000000000000000000000000000000000000000000000000000000000000f0000000000000000000000000000000000000000000000000000000000000013e000000000000000000000000000000000000000000000000000000000000018c000000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000000001a068796472612d73332e310000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001c000000000000000000000000000000000000000000000000000000000000004a00000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000117bf88d1838c2d751d4024ecfb1380715632ca591ce68f8067d85d2c8d8939f200000000000000000000000000000000000000000000000000000000000000a00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002c0047544dc2f35dc1c4b885d8a39cb82608ebf83e3feed1f436171709d37d51a8a2526935b27d929b4b064636ece29905178ce4981e5f7b01f970d1cf8a6226b8c2ad15a49879fae99e9e00062b8ea9a660b1cb2579665f4358b6253bbc5a7be4c0fcea2b209a292c511231b684633fa201aee9def4d3b4fb137ba3910239316f9073bdd0d5e951032122e1ad1e07abc04c6600bf304115d03ffdc29b2a2caf15f2054b55ae9609f928b3d93ce05cc82d6e29d0d9cab1ccbc24f59331630803cd4063e68ccf7a9a27348e46123914d2333734340acaa9dab114376f18e4b865fab15c93376b31f2736c6372123d2ea212c7a3567f978a9302722c2a8aee359192b0000000000000000000000000000000000000000000000000000000000000000214649c394fbf6e29bf4e81a343902ccc2f24ba2f2b04a2a3277ca2f7b5aa4091801b584700a740f9576cc7e83745895452edc518a9ce60b430e1272fc4eb93b057cf80de4f8dd3e4c56f948f40c28c3acbeca71ef9f825597bf8cc059f1238b00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000017bf88d1838c2d751d4024ecfb1380715632ca591ce68f8067d85d2c8d8939f20fe61c3a8c717465050c08081b2efe7d66020da96e00293df4c354c783d06b6b00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000000001a068796472612d73332e310000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001c000000000000000000000000000000000000000000000000000000000000004a0000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000003000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000010000000000000000000000008ab1760889f26cbbf33a75fd2cf1696bfccdc9e600000000000000000000000000000000000000000000000000000000000000a00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002c02f50b16bfc61dc9c2f0c09fd67e91db58f2239be60dd0f9602dd3d8a8a15c603089fd93bba0dad603da177c2f11ac78412ee19bd5c7aefdd8b4f431a2ab5b5721eb5eb10c05df8bdcc3b7f97e8030aea630f698d62b840a34e61811f6a118cc726f6d0e2c7ea2341b3ff70a1f76df109feed6ef759423f041488cb2d951344cb1a9fc57246776ad917aec9ee1416540b23c40b8ba979da8b3c9eeb9f84bfde5510f3d0cce20b5e39b8633b3d6078b5f635e6aa200874aee8fa893c47104c3ec80a00a15e66a3e899111de783b97f43cfd7b0123e09ced51df86c390deb2a1786180eb8a60bcb504f1643e2ae2da2e016708cf5851524db2c1aa93a15656c39300000000000000000000000008ab1760889f26cbbf33a75fd2cf1696bfccdc9e6214649c394fbf6e29bf4e81a343902ccc2f24ba2f2b04a2a3277ca2f7b5aa4091801b584700a740f9576cc7e83745895452edc518a9ce60b430e1272fc4eb93b057cf80de4f8dd3e4c56f948f40c28c3acbeca71ef9f825597bf8cc059f1238b00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000017bf88d1838c2d751d4024ecfb1380715632ca591ce68f8067d85d2c8d8939f20fe61c3a8c717465050c08081b2efe7d66020da96e00293df4c354c783d06b6b00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000000001a068796472612d73332e310000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001c000000000000000000000000000000000000000000000000000000000000004a000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001000000000000000000000000100100000000000000000000000000003577409700000000000000000000000000000000000000000000000000000000000000a00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002c00ed0c2b835363cf36bdfd3e557fce01eacfcf727275e17f33c6f0e406a75b2900e25d818d02c196094ecf6ba71e2a6f69ad14835602211a9de1cfcc1041fb14027498765b2b63ba02c0f26a189b93b17e243edb8e8f3195b6f97c61f47d78bf411c24f20aa363bacbd83d5ccf8f820601fc2970e55ddd7d403ef50efc60f4ebf19a3bd29e3aa810f5b87f54f64c85871c49fa35fa806205c547de02088702c1b03bb38c075a924ae8824e4b7828c1d254a70397c5b82ea7c2fc3632dcbe1140a16efdc2de7bda8929db7627b39ab8ae925c4fa36eb5f3488de5776a2c05b4e040358247d87f7a94afcbd5470b2eb635268ab8400d5cc90bb66183fe0e8605ff50000000000000000000000001001000000000000000000000000000035774097214649c394fbf6e29bf4e81a343902ccc2f24ba2f2b04a2a3277ca2f7b5aa4091801b584700a740f9576cc7e83745895452edc518a9ce60b430e1272fc4eb93b057cf80de4f8dd3e4c56f948f40c28c3acbeca71ef9f825597bf8cc059f1238b00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000017bf88d1838c2d751d4024ecfb1380715632ca591ce68f8067d85d2c8d8939f20fe61c3a8c717465050c08081b2efe7d66020da96e00293df4c354c783d06b6b00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000000000c068796472612d73332e310000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001e000000000000000000000000000000000000000000000000000000000000004c00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000000da1c3726426d5639f4c6352c2c976b87000000000000000000000000000000006c617465737400000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000c0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002c0200b870b6b98208d64a6a4a05b1e4bfb9f22922c115ae5dd7ff17a6a299acb152ec097dd3db6c9926960112a5320d3a65a0d12944c90719f0516125323879271057355333193b52f7423c4d13509425d9293e0cf27cddce4667a4e07da41ad6b2ee11ca5311ca4bcc0b7de512c868b5cb3daa27105beaa93ca759b80153d3df80fba9733a0e267e24de218f2723153eec9bf31f8a6fc51ad91b3b2effa6a6b350008859dc9c8eb5e7e9fa813bfc0ef9ac2b55d3bdba00f4e4be2d7c1ea13ee2d05e9098974f5741855eeb36a5d8921f915acdffbd7df6f7f40345598495f118d29acbb12432bc8d88957aead179cb8255c6852182308083ede772059510911100000000000000000000000000000000000000000000000000000000000000000214649c394fbf6e29bf4e81a343902ccc2f24ba2f2b04a2a3277ca2f7b5aa4091801b584700a740f9576cc7e83745895452edc518a9ce60b430e1272fc4eb93b057cf80de4f8dd3e4c56f948f40c28c3acbeca71ef9f825597bf8cc059f1238b04f0ace60fdf560415b93173156e67c6735946e9889973bfd56f1bcbe6fc5bcf047893723e9a7bacfb1a78d3cd7a46475d5235d4e6539843243601e011ea612c297e95db0e54e9c86eaeee09512f6206998a6f3bd1b071767c582f637f1131350000000000000000000000000000000000000000000000000000000000000001188afd5abda6d59313851e5226920a12cb91d3438c8e3dbaf07829b03ffffffc000000000000000000000000000000000000000000000000000000000000000017bf88d1838c2d751d4024ecfb1380715632ca591ce68f8067d85d2c8d8939f20fe61c3a8c717465050c08081b2efe7d66020da96e00293df4c354c783d06b6b00000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000000000c068796472612d73332e310000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001e000000000000000000000000000000000000000000000000000000000000004c0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000085c7ee90829de70d0d51f52336ea4722000000000000000000000000000000006c617465737400000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000c0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002c022bea6b85886dee008027530930cc26baf788b020b9401f114baab7ed8abaf2d2749fdcdb221b31bb9362905af39e36eadc9da9df12afbc302cefa3013387ca12ffa0f366307c0efb87d025ae1aae93a14b216e0af8b70ce0aa8ca0fde2ec85627cbe663d8f8a14ac1f26608740b0dff356872b44a070eaff87346978657e3cd043dfbd4adfc118a40614b28f92efef78b75731203220cb0b512c2e1f791f7bc28f4c97b27a44e2fd95a2697a7c5d9b57b8a136f99537da68e27346908590a9018abf5491106cecbed78655fa49410233167f4ac53f126074166172965de31dd20155e5b0433e1a672476a09510a3df44eb9875b6fc6a0b4dbeb4d25c5ebd49f0000000000000000000000000000000000000000000000000000000000000000214649c394fbf6e29bf4e81a343902ccc2f24ba2f2b04a2a3277ca2f7b5aa4091801b584700a740f9576cc7e83745895452edc518a9ce60b430e1272fc4eb93b057cf80de4f8dd3e4c56f948f40c28c3acbeca71ef9f825597bf8cc059f1238b04f0ace60fdf560415b93173156e67c6735946e9889973bfd56f1bcbe6fc5bcf26fe397125972c6a5376a768ad125e677b231ecd6aba4f42b2ba35563ecf7f1d05b412ea09364d5333c4bf39518baeec82596273ee6351b0a4ce42429cdb1ec1000000000000000000000000000000000000000000000000000000000000000424ff51aac03aa6b99cb169b633e796681bf9a3d480011edd783c14d81ffffffe000000000000000000000000000000000000000000000000000000000000000017bf88d1838c2d751d4024ecfb1380715632ca591ce68f8067d85d2c8d8939f20fe61c3a8c717465050c08081b2efe7d66020da96e00293df4c354c783d06b6b00000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000000000c068796472612d73332e310000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001e000000000000000000000000000000000000000000000000000000000000004c00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000002fae674b6cba3ff2f8ce2114defb200b1000000000000000000000000000000006c617465737400000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000a00000000000000000000000000000000000000000000000000000000000000c0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002c001198ba630cf788a0ed3cb2f1ddc11c8baff54595185d9065899967a228046fd0786997fbfa50284fb978fd9f2e4ebb511a473bd14573090a4614b283d8c49230ac7248891c0d967b50b52f090ed9fc5f05b93a3473d5fd667d84f878fa82c701e8e6811710d8fbd247f9a91e9ed2797028f9726bc96fa89687f8dee79d526001220876eeb625ce5cab9d088058a94e7d70eba9899fa7ff8f2d67217136d788313c1e2bff0cf8f09f2c6bc3342ead78b1b1d499d79b34c52a18077b5674d44332076f464760de54fc68a8e49040af7f02d5cc90fe8fb75e1eaab6d3279a0aa87110b7635aae4f499c80aa0af433d83c10fdce8c969e480a19542360d134cee790000000000000000000000000000000000000000000000000000000000000000214649c394fbf6e29bf4e81a343902ccc2f24ba2f2b04a2a3277ca2f7b5aa4091801b584700a740f9576cc7e83745895452edc518a9ce60b430e1272fc4eb93b057cf80de4f8dd3e4c56f948f40c28c3acbeca71ef9f825597bf8cc059f1238b04f0ace60fdf560415b93173156e67c6735946e9889973bfd56f1bcbe6fc5bcf223889be50de6309c4a51edd039aa1fb882c9964a746581ff319bbe982debcf82efc4cc7c03398856dbf08377861fb15549f5a9137456a615d4da264e7d69cd4000000000000000000000000000000000000000000000000000000000000000a08f0ec7865abde5ef350b4bd682b46dfa35deafb12d4cd29ac96341c4ffffffb000000000000000000000000000000000000000000000000000000000000000117bf88d1838c2d751d4024ecfb1380715632ca591ce68f8067d85d2c8d8939f20fe61c3a8c717465050c08081b2efe7d66020da96e00293df4c354c783d06b6b000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"; + + // balance should be zero before airdrop claiming + assertEq(airdrop.balanceOf(claimAddress), 0); + + // verify ZK proofs contained in response + airdrop.claimWithSismo({response: response, to: claimAddress}); + + // balance should be updated witht the right amount of tokens after proofs verification + assertEq(airdrop.balanceOf(claimAddress), 6 * 10 ** 18); + } +} diff --git a/tests/sismo-connect-config.test.json b/tests/sismo-connect-config.test.json new file mode 100644 index 0000000..f816faa --- /dev/null +++ b/tests/sismo-connect-config.test.json @@ -0,0 +1,33 @@ +{ + "appId": "0x32403ced4b65f2079eda77c84e7d2be6", + "isImpersonationMode": true, + "authRequests": [ + { + "authType": 0 + }, + { + "authType": 3 + }, + { + "authType": 1, + "isOptional": true + } + ], + "claimRequests": [ + { + "groupId": "0xda1c3726426d5639f4c6352c2c976b87" + }, + { + "groupId": "0x85c7ee90829de70d0d51f52336ea4722", + "claimType": 0, + "value": 4, + "isSelectableByUser": true + }, + { + "groupId": "0xfae674b6cba3ff2f8ce2114defb200b1", + "claimType": 2, + "value": 10, + "isOptional": true + } + ] +} From 6253f86381007306497a0b5118a68bd797cb22f5 Mon Sep 17 00:00:00 2001 From: bigq Date: Mon, 17 Jul 2023 23:04:33 +0200 Subject: [PATCH 06/12] feat: remove switch network competition, hide claim button with errors, add error support when contract not deployed --- front/src/app/page.tsx | 55 ++++++++++++++++++++------------- front/src/utils/useContract.tsx | 7 ++--- 2 files changed, 37 insertions(+), 25 deletions(-) diff --git a/front/src/app/page.tsx b/front/src/app/page.tsx index 4ff93a1..89d9c04 100644 --- a/front/src/app/page.tsx +++ b/front/src/app/page.tsx @@ -2,7 +2,7 @@ import { useEffect, useState } from "react"; import Header from "./components/Header"; -import { useAccount, useNetwork, useSwitchNetwork } from "wagmi"; +import { useAccount, useNetwork } from "wagmi"; import { useConnectModal } from "@rainbow-me/rainbowkit"; import { formatError, @@ -45,7 +45,6 @@ export default function Home() { onConnect: async ({ address }) => address && (await fundMyAccountOnLocalFork(address)), }); const { chain } = useNetwork(); - const { switchNetwork } = useSwitchNetwork(); const { openConnectModal, connectModalOpen } = useConnectModal(); const { airdropContract, switchNetworkAsync, waitingForTransaction, error } = useContract({ @@ -58,7 +57,8 @@ export default function Home() { setAppState((prev) => { return { ...prev, pageState: "responseReceived" }; }); - }, [responseBytes]); + setClaimError(error); + }, [responseBytes, error, claimError]); /* ************************* Reset state **************************** */ function resetApp() { @@ -111,6 +111,10 @@ export default function Home() { } } catch (e: any) { setClaimError(formatError(e)); + } finally { + setAppState((prev) => { + return { ...prev, pageState: "responseReceived" }; + }); } } @@ -156,25 +160,34 @@ export default function Home() { /> )} -
- {appState.pageState == "responseReceived" && ( - - )} - {appState.pageState == "confirmingTransaction" && ( - - )} - {appState.pageState == "verifying" && ( - Verifying ZK Proofs onchain... - )} - {appState.pageState == "verified" && ( - ZK Proofs Verified! - )} -
- {isConnected && !appState.amountClaimed && (error || claimError) && ( + {claimError !== null && ( +
+ {appState.pageState == "responseReceived" && ( + + )} + {appState.pageState == "confirmingTransaction" && ( + + )} + {appState.pageState == "verifying" && ( + Verifying ZK Proofs onchain... + )} + {appState.pageState == "verified" && ( + ZK Proofs Verified! + )} +
+ )} + {isConnected && !appState.amountClaimed && claimError && ( <> -

{error}

- {error.slice(0, 16) === "Please switch to" && ( - +

{claimError}

+ {claimError.slice(0, 50) === + 'The contract function "balanceOf" returned no data' && ( +

+ Please restart your frontend with "yarn dev" command and try again, it will + automatically deploy a new contract for you! +

+ )} + {claimError.slice(0, 16) === "Please switch to" && ( + )} )} diff --git a/front/src/utils/useContract.tsx b/front/src/utils/useContract.tsx index c94c148..08c481f 100644 --- a/front/src/utils/useContract.tsx +++ b/front/src/utils/useContract.tsx @@ -57,6 +57,7 @@ export default function useContract({ async function simulate() { try { await airdropContract.simulate.claimWithSismo([responseBytes, address]); + await airdropContract.simulate.balanceOf([address]); } catch (e: any) { return setError(formatError(e)); } @@ -72,10 +73,8 @@ export default function useContract({ if (chain.id === 5151111) { const timeout = new Promise((_, reject) => setTimeout(() => { - reject( - new Error( - "Transaction timed-out: If you are running a local fork on Anvil please make sure to reset your wallet nonce. In metamask: Go to settings > advanced > clear activity and nonce data" - ) + setError( + "Transaction timed-out: If you are running a local fork on Anvil please make sure to reset your wallet nonce. In metamask: Go to settings > advanced > clear activity and nonce data" ); }, 10000) ); From 66d0351bb7f2e8bd666e0b891c6d953edb59783b Mon Sep 17 00:00:00 2001 From: bigq Date: Tue, 18 Jul 2023 10:35:33 +0200 Subject: [PATCH 07/12] feat: requests source of truth comes from the smart contract instead of the frontend --- abi/Airdrop.json | 174 ++++++++++++---------- front/src/app/page.tsx | 82 +++++++++- front/src/app/sismo-connect-config.ts | 52 ------- script/Airdrop.s.sol | 12 +- script/utils/SismoConnectConfigReader.sol | 19 ++- src/Airdrop.sol | 92 +++++++++++- tests/Airdrop.t.sol | 10 +- 7 files changed, 286 insertions(+), 155 deletions(-) diff --git a/abi/Airdrop.json b/abi/Airdrop.json index b784908..6c38bd6 100644 --- a/abi/Airdrop.json +++ b/abi/Airdrop.json @@ -21,85 +21,6 @@ "internalType": "bool", "name": "isImpersonationMode", "type": "bool" - }, - { - "components": [ - { - "internalType": "enum AuthType", - "name": "authType", - "type": "uint8" - }, - { - "internalType": "uint256", - "name": "userId", - "type": "uint256" - }, - { - "internalType": "bool", - "name": "isAnon", - "type": "bool" - }, - { - "internalType": "bool", - "name": "isOptional", - "type": "bool" - }, - { - "internalType": "bool", - "name": "isSelectableByUser", - "type": "bool" - }, - { - "internalType": "bytes", - "name": "extraData", - "type": "bytes" - } - ], - "internalType": "struct AuthRequest[]", - "name": "authRequests", - "type": "tuple[]" - }, - { - "components": [ - { - "internalType": "enum ClaimType", - "name": "claimType", - "type": "uint8" - }, - { - "internalType": "bytes16", - "name": "groupId", - "type": "bytes16" - }, - { - "internalType": "bytes16", - "name": "groupTimestamp", - "type": "bytes16" - }, - { - "internalType": "uint256", - "name": "value", - "type": "uint256" - }, - { - "internalType": "bool", - "name": "isOptional", - "type": "bool" - }, - { - "internalType": "bool", - "name": "isSelectableByUser", - "type": "bool" - }, - { - "internalType": "bytes", - "name": "extraData", - "type": "bytes" - } - ], - "internalType": "struct ClaimRequest[]", - "name": "claimRequests", - "type": "tuple[]" } ], "stateMutability": "nonpayable", @@ -464,6 +385,101 @@ "stateMutability": "nonpayable", "type": "function" }, + { + "inputs": [], + "name": "getAuthRequests", + "outputs": [ + { + "components": [ + { + "internalType": "enum AuthType", + "name": "authType", + "type": "uint8" + }, + { + "internalType": "uint256", + "name": "userId", + "type": "uint256" + }, + { + "internalType": "bool", + "name": "isAnon", + "type": "bool" + }, + { + "internalType": "bool", + "name": "isOptional", + "type": "bool" + }, + { + "internalType": "bool", + "name": "isSelectableByUser", + "type": "bool" + }, + { + "internalType": "bytes", + "name": "extraData", + "type": "bytes" + } + ], + "internalType": "struct AuthRequest[]", + "name": "", + "type": "tuple[]" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "getClaimRequests", + "outputs": [ + { + "components": [ + { + "internalType": "enum ClaimType", + "name": "claimType", + "type": "uint8" + }, + { + "internalType": "bytes16", + "name": "groupId", + "type": "bytes16" + }, + { + "internalType": "bytes16", + "name": "groupTimestamp", + "type": "bytes16" + }, + { + "internalType": "uint256", + "name": "value", + "type": "uint256" + }, + { + "internalType": "bool", + "name": "isOptional", + "type": "bool" + }, + { + "internalType": "bool", + "name": "isSelectableByUser", + "type": "bool" + }, + { + "internalType": "bytes", + "name": "extraData", + "type": "bytes" + } + ], + "internalType": "struct ClaimRequest[]", + "name": "", + "type": "tuple[]" + } + ], + "stateMutability": "view", + "type": "function" + }, { "inputs": [], "name": "getVerifiedAuths", diff --git a/front/src/app/page.tsx b/front/src/app/page.tsx index 89d9c04..f00602d 100644 --- a/front/src/app/page.tsx +++ b/front/src/app/page.tsx @@ -13,14 +13,19 @@ import { } from "@/utils/misc"; import { mumbaiFork } from "@/utils/wagmi"; import { + AuthRequest, + ClaimRequest, + RequestBuilder, SismoConnectButton, + VaultConfig, VerifiedAuth, VerifiedClaim, // the Sismo Connect React button displayed below } from "@sismo-core/sismo-connect-react"; import { fundMyAccountOnLocalFork } from "@/utils/fundMyAccountOnLocalFork"; -import { AUTHS, CLAIMS, CONFIG, AuthType, ClaimType } from "@/app/sismo-connect-config"; +import { CONFIG, AuthType, ClaimType } from "@/app/sismo-connect-config"; import useContract from "@/utils/useContract"; -import { formatEther } from "viem"; +import { decodeAbiParameters, formatEther } from "viem"; +import { get } from "http"; /* ******************** Defines the chain to use *************************** */ const CHAIN = mumbaiFork; @@ -29,12 +34,21 @@ export default function Home() { const [appState, setAppState] = useState<{ pageState: string; amountClaimed: string; + sismoConnectConfig: { appId: string | undefined; vault: VaultConfig | {} }; + claimRequests: ClaimRequest[] | undefined; + authRequests: AuthRequest[] | undefined; verifiedClaims: VerifiedClaim[] | undefined; verifiedAuths: VerifiedAuth[] | undefined; verifiedSignedMessage: string | undefined; }>({ pageState: "init", amountClaimed: "", + sismoConnectConfig: { + appId: undefined, + vault: {}, + }, + claimRequests: undefined, + authRequests: undefined, verifiedClaims: undefined, verifiedAuths: undefined, verifiedSignedMessage: undefined, @@ -52,6 +66,52 @@ export default function Home() { chain: CHAIN, }); + useEffect(() => { + if (!isConnected) return; + async function getRequests() { + const appId = (await airdropContract.read.APP_ID()) as string; + const isImpersonationMode = (await airdropContract.read.IS_IMPERSONATION_MODE()) as boolean; + const authRequests = ((await airdropContract.read.getAuthRequests()) as AuthRequest[]).map( + (authRequest: AuthRequest) => { + return { + ...authRequest, + userId: authRequest.userId?.toString() ?? "0", + }; + } + ) as AuthRequest[]; + const claimRequests = ((await airdropContract.read.getClaimRequests()) as ClaimRequest[]).map( + (claimRequest: ClaimRequest) => { + return { + ...claimRequest, + groupTimestamp: + (claimRequest.groupTimestamp as string) === "0x6c617465737400000000000000000000" + ? "latest" + : (decodeAbiParameters( + ["bytes16"], + claimRequest.groupTimestamp as `0x${string}` + )[0] as number), + value: parseInt(claimRequest.value?.toString() ?? "1"), + }; + } + ) as ClaimRequest[]; + + setAppState((prev) => { + return { + ...prev, + // we impersonate accounts if the impersonation mode is set to true in the contract + sismoConnectConfig: { + appId, + vault: (isImpersonationMode === true ? CONFIG.vault : {}) as VaultConfig, + }, + authRequests, + claimRequests, + }; + }); + } + + getRequests(); + }, [appState.pageState]); + useEffect(() => { if (!responseBytes) return; setAppState((prev) => { @@ -65,6 +125,12 @@ export default function Home() { setAppState({ pageState: "init", amountClaimed: "", + sismoConnectConfig: { + appId: undefined, + vault: {}, + }, + claimRequests: undefined, + authRequests: undefined, verifiedClaims: undefined, verifiedAuths: undefined, verifiedSignedMessage: undefined, @@ -141,14 +207,16 @@ export default function Home() { {appState.pageState == "init" && ( <>
{AuthType[auth.authType]} {auth.userId || "No userId requested"}
= 17 - groupId: "0x85c7ee90829de70d0d51f52336ea4722", - claimType: ClaimType.GTE, - value: 4, // impersonated dhadrien.sismo.eth has 17 votes, eligible - isSelectableByUser: true, - }, - { - // claim on Stand with Crypto NFT Minters Data Group membership: https://factory.sismo.io/groups-explorer?search=0xfae674b6cba3ff2f8ce2114defb200b1 - // Data Group members = minters of the Stand with Crypto NFT - // value for each group member = number of NFT minted - // request user to prove membership in the group with value = 10 - groupId: "0xfae674b6cba3ff2f8ce2114defb200b1", - claimType: ClaimType.EQ, - value: 10, // dhadrien.sismo.eth minted exactly 10, eligible - isOptional: true, - }, -]; - -// Request users to sign a message -export const SIGNATURE_REQUEST: SignatureRequest = { - message: "I love Sismo!", - isSelectableByUser: true, -}; diff --git a/script/Airdrop.s.sol b/script/Airdrop.s.sol index 2f83c9b..491b2cd 100644 --- a/script/Airdrop.s.sol +++ b/script/Airdrop.s.sol @@ -12,13 +12,19 @@ contract DeployAirdrop is Script, SismoConnectConfigReader { string memory json = vm.readFile(string.concat(vm.projectRoot(), "/sismo-connect-config.json")); ( bytes16 appId, - AuthRequest[] memory authRequests, - ClaimRequest[] memory claimRequests, + // AuthRequest[] memory authRequests, + // ClaimRequest[] memory claimRequests, bool isImpersonationMode ) = readSismoConnectRequest(json); vm.startBroadcast(); - new Airdrop("my Airdrop", "AIR", appId, isImpersonationMode, authRequests, claimRequests); + new Airdrop( + "my Airdrop", + "AIR", + appId, + isImpersonationMode + // authRequests, claimRequests + ); vm.stopBroadcast(); } } diff --git a/script/utils/SismoConnectConfigReader.sol b/script/utils/SismoConnectConfigReader.sol index 3a13e8b..01b0d26 100644 --- a/script/utils/SismoConnectConfigReader.sol +++ b/script/utils/SismoConnectConfigReader.sol @@ -15,13 +15,24 @@ contract SismoConnectConfigReader is Script { function readSismoConnectRequest( string memory json - ) public returns (bytes16, AuthRequest[] memory, ClaimRequest[] memory, bool) { + ) + public + returns ( + bytes16, + // AuthRequest[] memory, ClaimRequest[] memory, + bool + ) + { bytes16 appId = bytes16(json.readBytes(string.concat(".appId"))); - AuthRequest[] memory authRequests = readAuthRequests(json); - ClaimRequest[] memory claimRequests = readClaimRequests(json); + // AuthRequest[] memory authRequests = readAuthRequests(json); + // ClaimRequest[] memory claimRequests = readClaimRequests(json); bool isImpersonationMode = _tryReadBool(json, ".isImpersonationMode"); - return (appId, authRequests, claimRequests, isImpersonationMode); + return ( + appId, + //authRequests, claimRequests, + isImpersonationMode + ); } function readAuthRequests(string memory json) public virtual returns (AuthRequest[] memory) { diff --git a/src/Airdrop.sol b/src/Airdrop.sol index be097e7..f7e3277 100644 --- a/src/Airdrop.sol +++ b/src/Airdrop.sol @@ -18,9 +18,9 @@ contract Airdrop is ERC20, SismoConnect { event SignedMessageVerified(bytes verifiedSignedMessage); using SismoConnectHelper for SismoConnectVerifiedResult; - // must correspond to requests defined in the app frontend + // these requests should be queried by the app frontend // Sismo Connect response's zk proofs will be checked against these requests. - // check Airdrop.s.sol to see how these requests are built and passed to the constructor + // check _setRequests function to see how these requests are built AuthRequest[] private _authRequests; ClaimRequest[] private _claimRequests; @@ -33,10 +33,84 @@ contract Airdrop is ERC20, SismoConnect { string memory name, string memory symbol, bytes16 appId, - bool isImpersonationMode, - AuthRequest[] memory authRequests, - ClaimRequest[] memory claimRequests + bool isImpersonationMode ) ERC20(name, symbol) SismoConnect(buildConfig(appId, isImpersonationMode)) { + // defining requests + // Request users to prove ownership of a Data Source (Wallet, Twitter, Github, Telegram, etc.) + AuthRequest[] memory authRequests = new AuthRequest[](3); + // Anonymous identifier of the vault for this app + // vaultId = hash(vaultSecret, appId). + // full docs: https://docs.sismo.io/sismo-docs/build-with-sismo-connect/technical-documentation/vault-and-proof-identifiers + authRequests[0] = AuthRequest({ + authType: AuthType.VAULT, + userId: 0, + isAnon: false, + isOptional: false, + isSelectableByUser: true, + extraData: "" + }); + // Request users to prove ownership of an EVM account + authRequests[1] = AuthRequest({ + authType: AuthType.EVM_ACCOUNT, + userId: 0, + isAnon: false, + isOptional: false, + isSelectableByUser: true, + extraData: "" + }); + // Request users to prove ownership of a Github account + authRequests[2] = AuthRequest({ + authType: AuthType.GITHUB, + userId: 0, + isAnon: false, + isOptional: true, + isSelectableByUser: true, + extraData: "" + }); + + // Request users to prove membership in a Data Group (e.g I own a wallet that is part of a DAO, owns an NFT, etc.) + ClaimRequest[] memory claimRequests = new ClaimRequest[](3); + // claim on Sismo Hub GitHub Contributors Data Group membership: https://factory.sismo.io/groups-explorer?search=0xda1c3726426d5639f4c6352c2c976b87 + // Data Group members = contributors to sismo-core/sismo-hub + // value for each group member = number of contributions + // request user to prove membership in the group + claimRequests[0] = ClaimRequest({ + groupId: bytes16(0xda1c3726426d5639f4c6352c2c976b87), + groupTimestamp: bytes16("latest"), + isOptional: false, + value: 1, + isSelectableByUser: true, + claimType: ClaimType.GTE, + extraData: "" + }); + // claim ENS DAO Voters Data Group membership: https://factory.sismo.io/groups-explorer?search=0x85c7ee90829de70d0d51f52336ea4722 + // Data Group members = voters in ENS DAO + // value for each group member = number of votes in ENS DAO + // request user to prove membership in the group with value >= 17 + claimRequests[1] = ClaimRequest({ + groupId: bytes16(0x85c7ee90829de70d0d51f52336ea4722), + groupTimestamp: bytes16("latest"), + isOptional: false, + value: 4, + isSelectableByUser: true, + claimType: ClaimType.GTE, + extraData: "" + }); + // claim on Stand with Crypto NFT Minters Data Group membership: https://factory.sismo.io/groups-explorer?search=0xfae674b6cba3ff2f8ce2114defb200b1 + // Data Group members = minters of the Stand with Crypto NFT + // value for each group member = number of NFT minted + // request user to prove membership in the group with value = 10 + claimRequests[2] = ClaimRequest({ + groupId: bytes16(0xfae674b6cba3ff2f8ce2114defb200b1), + groupTimestamp: bytes16("latest"), + isOptional: true, + value: 10, + isSelectableByUser: true, + claimType: ClaimType.EQ, + extraData: "" + }); + + // setting requests in storage _setAuths(authRequests); _setClaims(claimRequests); } @@ -73,6 +147,14 @@ contract Airdrop is ERC20, SismoConnect { emit SignedMessageVerified(result.signedMessage); } + function getClaimRequests() external view returns (ClaimRequest[] memory) { + return _claimRequests; + } + + function getAuthRequests() external view returns (AuthRequest[] memory) { + return _authRequests; + } + function getVerifiedClaims() external view returns (VerifiedClaim[] memory) { return _verifiedClaims; } diff --git a/tests/Airdrop.t.sol b/tests/Airdrop.t.sol index 273d4fd..51e65c0 100644 --- a/tests/Airdrop.t.sol +++ b/tests/Airdrop.t.sol @@ -16,8 +16,8 @@ contract Airdroptest is BaseTest, SismoConnectConfigReader { ); ( bytes16 appId, - AuthRequest[] memory authRequests, - ClaimRequest[] memory claimRequests, + // AuthRequest[] memory authRequests, + // ClaimRequest[] memory claimRequests, bool isImpersonationMode ) = readSismoConnectRequest(json); @@ -25,9 +25,9 @@ contract Airdroptest is BaseTest, SismoConnectConfigReader { "my Airdrop", "AIR", appId, - isImpersonationMode, - authRequests, - claimRequests + isImpersonationMode + // authRequests, + // claimRequests ); } From ac30692d995d67a3e7fcdc3325f87e17bdb45c2e Mon Sep 17 00:00:00 2001 From: bigq Date: Tue, 18 Jul 2023 15:53:01 +0200 Subject: [PATCH 08/12] feat: add new Sismo Solidity library + remove config reader --- .gitmodules | 6 +- lib/sismo-connect-packages | 1 - lib/sismo-connect-solidity | 1 + remappings.txt | 2 +- script/Airdrop.s.sol | 20 +-- script/utils/SismoConnectConfigReader.sol | 179 ---------------------- src/Airdrop.sol | 14 +- tests/Airdrop.t.sol | 24 +-- tests/base/BaseTest.t.sol | 2 +- 9 files changed, 21 insertions(+), 228 deletions(-) delete mode 160000 lib/sismo-connect-packages create mode 160000 lib/sismo-connect-solidity delete mode 100644 script/utils/SismoConnectConfigReader.sol diff --git a/.gitmodules b/.gitmodules index 96de28d..d08fd58 100644 --- a/.gitmodules +++ b/.gitmodules @@ -4,6 +4,6 @@ [submodule "lib/openzeppelin-contracts"] path = lib/openzeppelin-contracts url = https://github.com/OpenZeppelin/openzeppelin-contracts -[submodule "lib/sismo-connect-packages"] - path = lib/sismo-connect-packages - url = https://github.com/sismo-core/sismo-connect-packages +[submodule "lib/sismo-connect-solidity"] + path = lib/sismo-connect-solidity + url = https://github.com/sismo-core/sismo-connect-solidity diff --git a/lib/sismo-connect-packages b/lib/sismo-connect-packages deleted file mode 160000 index d1de041..0000000 --- a/lib/sismo-connect-packages +++ /dev/null @@ -1 +0,0 @@ -Subproject commit d1de04182f20e9e1a2526aaab8659252982e8500 diff --git a/lib/sismo-connect-solidity b/lib/sismo-connect-solidity new file mode 160000 index 0000000..5825929 --- /dev/null +++ b/lib/sismo-connect-solidity @@ -0,0 +1 @@ +Subproject commit 58259290afd09c9cc15b65b5365f35e19ccf34a9 diff --git a/remappings.txt b/remappings.txt index fbe0825..8062183 100644 --- a/remappings.txt +++ b/remappings.txt @@ -1,3 +1,3 @@ forge-std/=lib/forge-std/src/ @openzeppelin/contracts/=lib/openzeppelin-contracts/contracts/ -sismo-connect-solidity/=lib/sismo-connect-packages/packages/sismo-connect-solidity/src/ \ No newline at end of file +sismo-connect-solidity/=lib/sismo-connect-solidity/src/ \ No newline at end of file diff --git a/script/Airdrop.s.sol b/script/Airdrop.s.sol index 491b2cd..0d8b7e3 100644 --- a/script/Airdrop.s.sol +++ b/script/Airdrop.s.sol @@ -2,29 +2,15 @@ pragma solidity ^0.8.20; import "forge-std/Script.sol"; -import "sismo-connect-solidity/SismoLib.sol"; -import {SismoConnectConfigReader} from "script/utils/SismoConnectConfigReader.sol"; +import "sismo-connect-solidity/SismoConnectLib.sol"; import {Airdrop} from "src/Airdrop.sol"; -contract DeployAirdrop is Script, SismoConnectConfigReader { +contract DeployAirdrop is Script { function run() public { console.log("Deploying Airdrop contract..."); - string memory json = vm.readFile(string.concat(vm.projectRoot(), "/sismo-connect-config.json")); - ( - bytes16 appId, - // AuthRequest[] memory authRequests, - // ClaimRequest[] memory claimRequests, - bool isImpersonationMode - ) = readSismoConnectRequest(json); vm.startBroadcast(); - new Airdrop( - "my Airdrop", - "AIR", - appId, - isImpersonationMode - // authRequests, claimRequests - ); + new Airdrop("my Airdrop", "AIR"); vm.stopBroadcast(); } } diff --git a/script/utils/SismoConnectConfigReader.sol b/script/utils/SismoConnectConfigReader.sol deleted file mode 100644 index 01b0d26..0000000 --- a/script/utils/SismoConnectConfigReader.sol +++ /dev/null @@ -1,179 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity ^0.8.17; - -import "forge-std/Script.sol"; -import "forge-std/StdJson.sol"; -import "sismo-connect-solidity/SismoLib.sol"; -import {Strings} from "@openzeppelin/contracts/utils/Strings.sol"; - -contract SismoConnectConfigReader is Script { - using stdJson for string; - AuthRequestBuilder public authrequestBuilder = new AuthRequestBuilder(); - ClaimRequestBuilder public claimrequestBuilder = new ClaimRequestBuilder(); - - address public constant PROXY_ADMIN = 0x2110475dfbB8d331b300178A867372991ff35fA3; - - function readSismoConnectRequest( - string memory json - ) - public - returns ( - bytes16, - // AuthRequest[] memory, ClaimRequest[] memory, - bool - ) - { - bytes16 appId = bytes16(json.readBytes(string.concat(".appId"))); - // AuthRequest[] memory authRequests = readAuthRequests(json); - // ClaimRequest[] memory claimRequests = readClaimRequests(json); - bool isImpersonationMode = _tryReadBool(json, ".isImpersonationMode"); - - return ( - appId, - //authRequests, claimRequests, - isImpersonationMode - ); - } - - function readAuthRequests(string memory json) public virtual returns (AuthRequest[] memory) { - uint256 nbOfAuthRequests = json.readStringArray(".authRequests").length; - AuthRequest[] memory authRequests = new AuthRequest[](nbOfAuthRequests); - for (uint256 i = 0; i < nbOfAuthRequests; i++) { - string memory baseKey = string.concat(".authRequests[", Strings.toString(i), "]."); - AuthType authType = AuthType(_tryReadUint8(json, string.concat(baseKey, "authType"))); - uint256 userId = _tryReadUint(json, string.concat(baseKey, "userId")); - authRequests[i] = authrequestBuilder.build({ - authType: authType, - userId: userId, - isAnon: _tryReadBool(json, string.concat(baseKey, "isAnon")), - isOptional: _tryReadBool(json, string.concat(baseKey, "isOptional")), - isSelectableByUser: _tryIsSelectableByUserForAuthRequest( - json, - string.concat(baseKey, "isSelectableByUser"), - authType, - userId - ), - extraData: _tryReadBytes(json, string.concat(baseKey, "extraData")) - }); - } - return authRequests; - } - - function readClaimRequests(string memory json) public returns (ClaimRequest[] memory) { - uint256 nbOfClaims = json.readStringArray(".claimRequests").length; - ClaimRequest[] memory claimRequests = new ClaimRequest[](nbOfClaims); - for (uint256 i = 0; i < nbOfClaims; i++) { - string memory baseKey = string.concat(".claimRequests[", Strings.toString(i), "]."); - claimRequests[i] = claimrequestBuilder.build({ - groupId: bytes16(json.readBytes(string.concat(baseKey, "groupId"))), - claimType: ClaimType(_tryReadUint8(json, string.concat(baseKey, "claimType"))), - groupTimestamp: _tryReadUint(json, string.concat(baseKey, "groupTimestamp")) == 0 - ? bytes16("latest") - : bytes16(uint128(_tryReadUint(json, string.concat(baseKey, "groupTimestamp")))), - // default value to 1 if not specified - value: _tryReadUint(json, string.concat(baseKey, "value")) == 0 - ? 1 - : _tryReadUint(json, string.concat(baseKey, "value")), - isOptional: _tryReadBool(json, string.concat(baseKey, "isOptional")), - isSelectableByUser: _tryIsSelectableByUserForClaimRequest( - json, - string.concat(baseKey, "isSelectableByUser") - ), - extraData: _tryReadBytes(json, string.concat(baseKey, "extraData")) - }); - } - return claimRequests; - } - - // default value is true for isSelectableByUser field in ClaimRequest, that is why we need this fucntion instead of using _tryReadBool - function _tryIsSelectableByUserForClaimRequest( - string memory json, - string memory key - ) internal returns (bool) { - try vm.parseJsonBool(json, key) returns (bool boolean) { - return boolean; - } catch { - return true; - } - } - - function _tryIsSelectableByUserForAuthRequest( - string memory json, - string memory key, - AuthType authType, - uint256 requestedUserId - ) internal returns (bool) { - try vm.parseJsonBool(json, key) returns (bool boolean) { - return boolean; - } catch { - return _defaultIsSelectableByUserForAuthRequest(authType, requestedUserId); - } - } - - function _defaultIsSelectableByUserForAuthRequest( - AuthType authType, - uint256 requestedUserId - ) internal pure returns (bool) { - // isSelectableByUser value should always be false in case of VAULT authType. - // This is because the user can't select the account they want to use for the app. - // the userId = Hash(VaultSecret, AppId) in the case of VAULT authType. - if (authType == AuthType.VAULT) { - return false; - } - // When `requestedUserId` is 0, it means no specific auth account is requested by the app, - // so we want the default value for `isSelectableByUser` to be `true`. - if (requestedUserId == 0) { - return true; - } - // When `requestedUserId` is not 0, it means a specific auth account is requested by the app, - // so we want the default value for `isSelectableByUser` to be `false`. - else { - return false; - } - // However, the dev can still override this default value by setting `isSelectableByUser` to `true`. - } - - function _tryReadUint8(string memory json, string memory key) internal returns (uint8) { - try vm.parseJsonUint(json, key) returns (uint256 value) { - return uint8(value); - } catch { - return 0; - } - } - - function _tryReadUint(string memory json, string memory key) internal returns (uint256) { - try vm.parseJsonUint(json, key) returns (uint256 value) { - return value; - } catch { - return 0; - } - } - - function _tryReadBool(string memory json, string memory key) internal returns (bool) { - try vm.parseJsonBool(json, key) returns (bool boolean) { - return boolean; - } catch { - return false; - } - } - - function _tryReadString(string memory json, string memory key) internal returns (string memory) { - try vm.parseJsonString(json, key) returns (string memory value) { - return value; - } catch { - return ""; - } - } - - function _tryReadBytes(string memory json, string memory key) internal returns (bytes memory) { - try vm.parseJsonBytes(json, key) returns (bytes memory extraData) { - return extraData; - } catch { - return ""; - } - } - - function _compareStrings(string memory a, string memory b) internal pure returns (bool) { - return (keccak256(abi.encodePacked((a))) == keccak256(abi.encodePacked((b)))); - } -} diff --git a/src/Airdrop.sol b/src/Airdrop.sol index f7e3277..1a7e0a9 100644 --- a/src/Airdrop.sol +++ b/src/Airdrop.sol @@ -3,7 +3,7 @@ pragma solidity ^0.8.20; import {ERC20} from "@openzeppelin/contracts/token/ERC20/ERC20.sol"; import "forge-std/console.sol"; -import "sismo-connect-solidity/SismoLib.sol"; +import "sismo-connect-solidity/SismoConnectLib.sol"; /* * @title Airdrop @@ -18,6 +18,12 @@ contract Airdrop is ERC20, SismoConnect { event SignedMessageVerified(bytes verifiedSignedMessage); using SismoConnectHelper for SismoConnectVerifiedResult; + // appId of the Sismo Connect app + bytes16 private _appId = 0x32403ced4b65f2079eda77c84e7d2be6; + // allow proofs made from impersonating accounts to be verified + // it should be set to false for production + bool private _isImpersonationMode = true; + // these requests should be queried by the app frontend // Sismo Connect response's zk proofs will be checked against these requests. // check _setRequests function to see how these requests are built @@ -31,10 +37,8 @@ contract Airdrop is ERC20, SismoConnect { constructor( string memory name, - string memory symbol, - bytes16 appId, - bool isImpersonationMode - ) ERC20(name, symbol) SismoConnect(buildConfig(appId, isImpersonationMode)) { + string memory symbol + ) ERC20(name, symbol) SismoConnect(buildConfig(_appId, _isImpersonationMode)) { // defining requests // Request users to prove ownership of a Data Source (Wallet, Twitter, Github, Telegram, etc.) AuthRequest[] memory authRequests = new AuthRequest[](3); diff --git a/tests/Airdrop.t.sol b/tests/Airdrop.t.sol index 51e65c0..581dfdc 100644 --- a/tests/Airdrop.t.sol +++ b/tests/Airdrop.t.sol @@ -2,33 +2,15 @@ pragma solidity ^0.8.20; import "tests/base/BaseTest.t.sol"; -import "sismo-connect-solidity/SismoLib.sol"; -import {SismoConnectConfigReader} from "script/utils/SismoConnectConfigReader.sol"; +import "sismo-connect-solidity/SismoConnectLib.sol"; import {Airdrop} from "src/Airdrop.sol"; -contract Airdroptest is BaseTest, SismoConnectConfigReader { +contract Airdroptest is BaseTest { Airdrop airdrop; address claimAddress = 0x061060a65146b3265C62fC8f3AE977c9B27260fF; function setUp() public { - string memory json = vm.readFile( - string.concat(vm.projectRoot(), "/tests/sismo-connect-config.test.json") - ); - ( - bytes16 appId, - // AuthRequest[] memory authRequests, - // ClaimRequest[] memory claimRequests, - bool isImpersonationMode - ) = readSismoConnectRequest(json); - - airdrop = new Airdrop( - "my Airdrop", - "AIR", - appId, - isImpersonationMode - // authRequests, - // claimRequests - ); + airdrop = new Airdrop("my Airdrop", "AIR"); } function testAirdrop() public { diff --git a/tests/base/BaseTest.t.sol b/tests/base/BaseTest.t.sol index 504f647..48861b4 100644 --- a/tests/base/BaseTest.t.sol +++ b/tests/base/BaseTest.t.sol @@ -3,7 +3,7 @@ pragma solidity ^0.8.17; import "forge-std/Test.sol"; import "forge-std/console.sol"; -import {IAddressesProvider} from "sismo-connect-solidity/SismoLib.sol"; +import {IAddressesProvider} from "sismo-connect-solidity/SismoConnectLib.sol"; interface IAvailableRootsRegistry { event RegisteredRoot(uint256 root); From eadc24635cc1a6c09eecdc79f1ffcaa9bec61307 Mon Sep 17 00:00:00 2001 From: bigq Date: Tue, 18 Jul 2023 17:20:55 +0200 Subject: [PATCH 09/12] feat: simplify reading sismoConnectRequest from contract --- .gitmodules | 6 +-- abi/Airdrop.json | 22 +------- front/src/app/page.tsx | 30 +++-------- front/src/utils/misc.ts | 31 +++++++++++- lib/sismo-connect-packages | 1 + lib/sismo-connect-solidity | 1 - remappings.txt | 2 +- script/Airdrop.s.sol | 2 +- src/Airdrop.sol | 75 ++++++++-------------------- tests/Airdrop.t.sol | 2 +- tests/base/BaseTest.t.sol | 4 +- tests/sismo-connect-config.test.json | 33 ------------ 12 files changed, 70 insertions(+), 139 deletions(-) create mode 160000 lib/sismo-connect-packages delete mode 160000 lib/sismo-connect-solidity delete mode 100644 tests/sismo-connect-config.test.json diff --git a/.gitmodules b/.gitmodules index d08fd58..96de28d 100644 --- a/.gitmodules +++ b/.gitmodules @@ -4,6 +4,6 @@ [submodule "lib/openzeppelin-contracts"] path = lib/openzeppelin-contracts url = https://github.com/OpenZeppelin/openzeppelin-contracts -[submodule "lib/sismo-connect-solidity"] - path = lib/sismo-connect-solidity - url = https://github.com/sismo-core/sismo-connect-solidity +[submodule "lib/sismo-connect-packages"] + path = lib/sismo-connect-packages + url = https://github.com/sismo-core/sismo-connect-packages diff --git a/abi/Airdrop.json b/abi/Airdrop.json index 6c38bd6..aa23e7e 100644 --- a/abi/Airdrop.json +++ b/abi/Airdrop.json @@ -11,16 +11,6 @@ "internalType": "string", "name": "symbol", "type": "string" - }, - { - "internalType": "bytes16", - "name": "appId", - "type": "bytes16" - }, - { - "internalType": "bool", - "name": "isImpersonationMode", - "type": "bool" } ], "stateMutability": "nonpayable", @@ -387,7 +377,7 @@ }, { "inputs": [], - "name": "getAuthRequests", + "name": "getSismoConnectRequest", "outputs": [ { "components": [ @@ -425,15 +415,7 @@ "internalType": "struct AuthRequest[]", "name": "", "type": "tuple[]" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [], - "name": "getClaimRequests", - "outputs": [ + }, { "components": [ { diff --git a/front/src/app/page.tsx b/front/src/app/page.tsx index f00602d..33b37ca 100644 --- a/front/src/app/page.tsx +++ b/front/src/app/page.tsx @@ -6,6 +6,7 @@ import { useAccount, useNetwork } from "wagmi"; import { useConnectModal } from "@rainbow-me/rainbowkit"; import { formatError, + getAuthRequestsAndClaimRequestsFromSismoConnectRequest, getProofDataForAuth, getProofDataForClaim, getUserIdFromHex, @@ -71,29 +72,12 @@ export default function Home() { async function getRequests() { const appId = (await airdropContract.read.APP_ID()) as string; const isImpersonationMode = (await airdropContract.read.IS_IMPERSONATION_MODE()) as boolean; - const authRequests = ((await airdropContract.read.getAuthRequests()) as AuthRequest[]).map( - (authRequest: AuthRequest) => { - return { - ...authRequest, - userId: authRequest.userId?.toString() ?? "0", - }; - } - ) as AuthRequest[]; - const claimRequests = ((await airdropContract.read.getClaimRequests()) as ClaimRequest[]).map( - (claimRequest: ClaimRequest) => { - return { - ...claimRequest, - groupTimestamp: - (claimRequest.groupTimestamp as string) === "0x6c617465737400000000000000000000" - ? "latest" - : (decodeAbiParameters( - ["bytes16"], - claimRequest.groupTimestamp as `0x${string}` - )[0] as number), - value: parseInt(claimRequest.value?.toString() ?? "1"), - }; - } - ) as ClaimRequest[]; + const sismoConnectRequest = (await airdropContract.read.getSismoConnectRequest()) as [ + AuthRequest[], + ClaimRequest[] + ]; + const { authRequests, claimRequests } = + getAuthRequestsAndClaimRequestsFromSismoConnectRequest(sismoConnectRequest); setAppState((prev) => { return { diff --git a/front/src/utils/misc.ts b/front/src/utils/misc.ts index 22fe392..f67a600 100644 --- a/front/src/utils/misc.ts +++ b/front/src/utils/misc.ts @@ -1,7 +1,8 @@ -import { encodeAbiParameters } from "viem"; +import { decodeAbiParameters, encodeAbiParameters } from "viem"; import { abi as AirdropABI } from "../../../abi/Airdrop.json"; import { errorsABI } from "./errorsABI"; import { AuthType, VerifiedAuth, VerifiedClaim } from "@/app/sismo-connect-config"; +import { AuthRequest, ClaimRequest } from "@sismo-core/sismo-connect-react"; declare global { interface Window { @@ -74,3 +75,31 @@ export function getUserIdFromHex(hexUserId: string) { return hexUserId; // returns the original string if '00' is not found } } + +export function getAuthRequestsAndClaimRequestsFromSismoConnectRequest( + sismoConnectRequest: [AuthRequest[], ClaimRequest[]] +): { authRequests: AuthRequest[]; claimRequests: ClaimRequest[] } { + const authRequests = (sismoConnectRequest[0] as AuthRequest[]).map((authRequest: AuthRequest) => { + return { + ...authRequest, + userId: authRequest.userId?.toString() ?? "0", + }; + }) as AuthRequest[]; + const claimRequests = (sismoConnectRequest[1] as ClaimRequest[]).map( + (claimRequest: ClaimRequest) => { + return { + ...claimRequest, + groupTimestamp: + (claimRequest.groupTimestamp as string) === "0x6c617465737400000000000000000000" + ? "latest" + : (decodeAbiParameters( + ["bytes16"], + claimRequest.groupTimestamp as `0x${string}` + )[0] as number), + value: parseInt(claimRequest.value?.toString() ?? "1"), + }; + } + ) as ClaimRequest[]; + + return { authRequests, claimRequests }; +} diff --git a/lib/sismo-connect-packages b/lib/sismo-connect-packages new file mode 160000 index 0000000..d1de041 --- /dev/null +++ b/lib/sismo-connect-packages @@ -0,0 +1 @@ +Subproject commit d1de04182f20e9e1a2526aaab8659252982e8500 diff --git a/lib/sismo-connect-solidity b/lib/sismo-connect-solidity deleted file mode 160000 index 5825929..0000000 --- a/lib/sismo-connect-solidity +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 58259290afd09c9cc15b65b5365f35e19ccf34a9 diff --git a/remappings.txt b/remappings.txt index 8062183..fbe0825 100644 --- a/remappings.txt +++ b/remappings.txt @@ -1,3 +1,3 @@ forge-std/=lib/forge-std/src/ @openzeppelin/contracts/=lib/openzeppelin-contracts/contracts/ -sismo-connect-solidity/=lib/sismo-connect-solidity/src/ \ No newline at end of file +sismo-connect-solidity/=lib/sismo-connect-packages/packages/sismo-connect-solidity/src/ \ No newline at end of file diff --git a/script/Airdrop.s.sol b/script/Airdrop.s.sol index 0d8b7e3..cc45522 100644 --- a/script/Airdrop.s.sol +++ b/script/Airdrop.s.sol @@ -2,7 +2,7 @@ pragma solidity ^0.8.20; import "forge-std/Script.sol"; -import "sismo-connect-solidity/SismoConnectLib.sol"; +import "sismo-connect-solidity/SismoLib.sol"; import {Airdrop} from "src/Airdrop.sol"; contract DeployAirdrop is Script { diff --git a/src/Airdrop.sol b/src/Airdrop.sol index 1a7e0a9..fde1885 100644 --- a/src/Airdrop.sol +++ b/src/Airdrop.sol @@ -2,15 +2,15 @@ pragma solidity ^0.8.20; import {ERC20} from "@openzeppelin/contracts/token/ERC20/ERC20.sol"; -import "forge-std/console.sol"; -import "sismo-connect-solidity/SismoConnectLib.sol"; +import "forge-std/console2.sol"; +import "sismo-connect-solidity/SismoLib.sol"; /* * @title Airdrop * @author Sismo * @dev Simple Airdrop contract gated by Sismo Connect * Application requests multiple zk proofs (auths and claims) and verify them - * The contract stores all verified results in storage + * The contract stores all requests and verified results in storage */ contract Airdrop is ERC20, SismoConnect { event AuthVerified(VerifiedAuth verifiedAuth); @@ -45,31 +45,14 @@ contract Airdrop is ERC20, SismoConnect { // Anonymous identifier of the vault for this app // vaultId = hash(vaultSecret, appId). // full docs: https://docs.sismo.io/sismo-docs/build-with-sismo-connect/technical-documentation/vault-and-proof-identifiers - authRequests[0] = AuthRequest({ - authType: AuthType.VAULT, - userId: 0, - isAnon: false, - isOptional: false, - isSelectableByUser: true, - extraData: "" - }); + authRequests[0] = buildAuth({authType: AuthType.VAULT}); // Request users to prove ownership of an EVM account - authRequests[1] = AuthRequest({ - authType: AuthType.EVM_ACCOUNT, - userId: 0, - isAnon: false, - isOptional: false, - isSelectableByUser: true, - extraData: "" - }); + authRequests[1] = buildAuth({authType: AuthType.EVM_ACCOUNT}); // Request users to prove ownership of a Github account - authRequests[2] = AuthRequest({ + authRequests[2] = buildAuth({ authType: AuthType.GITHUB, - userId: 0, - isAnon: false, isOptional: true, - isSelectableByUser: true, - extraData: "" + isSelectableByUser: true }); // Request users to prove membership in a Data Group (e.g I own a wallet that is part of a DAO, owns an NFT, etc.) @@ -78,43 +61,26 @@ contract Airdrop is ERC20, SismoConnect { // Data Group members = contributors to sismo-core/sismo-hub // value for each group member = number of contributions // request user to prove membership in the group - claimRequests[0] = ClaimRequest({ - groupId: bytes16(0xda1c3726426d5639f4c6352c2c976b87), - groupTimestamp: bytes16("latest"), - isOptional: false, - value: 1, - isSelectableByUser: true, - claimType: ClaimType.GTE, - extraData: "" - }); + claimRequests[0] = buildClaim({groupId: bytes16(0xda1c3726426d5639f4c6352c2c976b87)}); // claim ENS DAO Voters Data Group membership: https://factory.sismo.io/groups-explorer?search=0x85c7ee90829de70d0d51f52336ea4722 // Data Group members = voters in ENS DAO // value for each group member = number of votes in ENS DAO // request user to prove membership in the group with value >= 17 - claimRequests[1] = ClaimRequest({ - groupId: bytes16(0x85c7ee90829de70d0d51f52336ea4722), - groupTimestamp: bytes16("latest"), - isOptional: false, - value: 4, - isSelectableByUser: true, - claimType: ClaimType.GTE, - extraData: "" - }); + claimRequests[1] = buildClaim({groupId: bytes16(0x85c7ee90829de70d0d51f52336ea4722), value: 4}); // claim on Stand with Crypto NFT Minters Data Group membership: https://factory.sismo.io/groups-explorer?search=0xfae674b6cba3ff2f8ce2114defb200b1 // Data Group members = minters of the Stand with Crypto NFT // value for each group member = number of NFT minted // request user to prove membership in the group with value = 10 - claimRequests[2] = ClaimRequest({ + claimRequests[2] = buildClaim({ groupId: bytes16(0xfae674b6cba3ff2f8ce2114defb200b1), - groupTimestamp: bytes16("latest"), - isOptional: true, value: 10, - isSelectableByUser: true, claimType: ClaimType.EQ, - extraData: "" + isOptional: true, + isSelectableByUser: true }); // setting requests in storage + // the frontend will query these requests _setAuths(authRequests); _setClaims(claimRequests); } @@ -151,12 +117,15 @@ contract Airdrop is ERC20, SismoConnect { emit SignedMessageVerified(result.signedMessage); } - function getClaimRequests() external view returns (ClaimRequest[] memory) { - return _claimRequests; - } - - function getAuthRequests() external view returns (AuthRequest[] memory) { - return _authRequests; + /** + * @dev Get the Sismo Connect request that was defined in the constructor + */ + function getSismoConnectRequest() + external + view + returns (AuthRequest[] memory, ClaimRequest[] memory) + { + return (_authRequests, _claimRequests); } function getVerifiedClaims() external view returns (VerifiedClaim[] memory) { diff --git a/tests/Airdrop.t.sol b/tests/Airdrop.t.sol index 581dfdc..abccc39 100644 --- a/tests/Airdrop.t.sol +++ b/tests/Airdrop.t.sol @@ -2,7 +2,7 @@ pragma solidity ^0.8.20; import "tests/base/BaseTest.t.sol"; -import "sismo-connect-solidity/SismoConnectLib.sol"; +import "sismo-connect-solidity/SismoLib.sol"; import {Airdrop} from "src/Airdrop.sol"; contract Airdroptest is BaseTest { diff --git a/tests/base/BaseTest.t.sol b/tests/base/BaseTest.t.sol index 48861b4..e591d25 100644 --- a/tests/base/BaseTest.t.sol +++ b/tests/base/BaseTest.t.sol @@ -3,7 +3,7 @@ pragma solidity ^0.8.17; import "forge-std/Test.sol"; import "forge-std/console.sol"; -import {IAddressesProvider} from "sismo-connect-solidity/SismoConnectLib.sol"; +import {IAddressesProvider} from "sismo-connect-solidity/SismoLib.sol"; interface IAvailableRootsRegistry { event RegisteredRoot(uint256 root); @@ -15,7 +15,7 @@ interface IAvailableRootsRegistry { contract BaseTest is Test { IAddressesProvider sismoAddressesProvider = - IAddressesProvider(0x3340Ac0CaFB3ae34dDD53dba0d7344C1Cf3EFE05); + IAddressesProvider(0x3Cd5334eB64ebBd4003b72022CC25465f1BFcEe6); IAvailableRootsRegistry availableRootsRegistry; function _registerTreeRoot(uint256 root) internal { diff --git a/tests/sismo-connect-config.test.json b/tests/sismo-connect-config.test.json deleted file mode 100644 index f816faa..0000000 --- a/tests/sismo-connect-config.test.json +++ /dev/null @@ -1,33 +0,0 @@ -{ - "appId": "0x32403ced4b65f2079eda77c84e7d2be6", - "isImpersonationMode": true, - "authRequests": [ - { - "authType": 0 - }, - { - "authType": 3 - }, - { - "authType": 1, - "isOptional": true - } - ], - "claimRequests": [ - { - "groupId": "0xda1c3726426d5639f4c6352c2c976b87" - }, - { - "groupId": "0x85c7ee90829de70d0d51f52336ea4722", - "claimType": 0, - "value": 4, - "isSelectableByUser": true - }, - { - "groupId": "0xfae674b6cba3ff2f8ce2114defb200b1", - "claimType": 2, - "value": 10, - "isOptional": true - } - ] -} From 44f670485421c22b37a9116c2faddd2c46c8a49b Mon Sep 17 00:00:00 2001 From: bigq Date: Tue, 18 Jul 2023 17:43:26 +0200 Subject: [PATCH 10/12] feat: remove config into json logic + add impersonation in page.tsx + add new solidity lib --- .gitmodules | 6 +-- front/package.json | 3 +- front/script/sismo-config-to-json.ts | 19 --------- front/src/app/page.tsx | 58 ++++++++++++++++++++++----- front/src/app/sismo-connect-config.ts | 33 --------------- front/src/utils/index.ts | 7 ++++ front/src/utils/misc.ts | 9 ++++- front/src/utils/wagmi.tsx | 14 +++++++ lib/sismo-connect-packages | 1 - lib/sismo-connect-solidity | 1 + remappings.txt | 2 +- script/Airdrop.s.sol | 2 +- src/Airdrop.sol | 2 +- tests/Airdrop.t.sol | 2 +- tests/base/BaseTest.t.sol | 2 +- 15 files changed, 87 insertions(+), 74 deletions(-) delete mode 100644 front/script/sismo-config-to-json.ts delete mode 100644 front/src/app/sismo-connect-config.ts create mode 100644 front/src/utils/index.ts delete mode 160000 lib/sismo-connect-packages create mode 160000 lib/sismo-connect-solidity diff --git a/.gitmodules b/.gitmodules index 96de28d..d08fd58 100644 --- a/.gitmodules +++ b/.gitmodules @@ -4,6 +4,6 @@ [submodule "lib/openzeppelin-contracts"] path = lib/openzeppelin-contracts url = https://github.com/OpenZeppelin/openzeppelin-contracts -[submodule "lib/sismo-connect-packages"] - path = lib/sismo-connect-packages - url = https://github.com/sismo-core/sismo-connect-packages +[submodule "lib/sismo-connect-solidity"] + path = lib/sismo-connect-solidity + url = https://github.com/sismo-core/sismo-connect-solidity diff --git a/front/package.json b/front/package.json index 227edbe..21e9e0c 100644 --- a/front/package.json +++ b/front/package.json @@ -3,11 +3,10 @@ "version": "0.1.0", "private": true, "scripts": { - "dev": "NODE_OPTIONS='--max-http-header-size=24576' next dev -p 3000 & nodemon --watch ./src/app/sismo-connect-config.ts --watch ../src/Airdrop.sol --exec 'npm run config-to-json && npm run deploy-airdrop && sleep 3 && ./script/log.sh'", + "dev": "NODE_OPTIONS='--max-http-header-size=24576' next dev -p 3000 & nodemon --watch ../src/Airdrop.sol --exec 'npm run deploy-airdrop && sleep 3 && ./script/log.sh'", "build": "next build", "start": "NODE_OPTIONS='--max-http-header-size=24576' next start", "lint": "next lint", - "config-to-json": "npx tsx ./script/sismo-config-to-json.ts > ../sismo-connect-config.json", "deploy-airdrop": "forge script DeployAirdrop --rpc-url http://localhost:8545 -vv --mnemonics 'test test test test test test test test test test test junk' --sender '0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266' --broadcast && ./script/generate-abi-from-front.sh" }, "browser": { diff --git a/front/script/sismo-config-to-json.ts b/front/script/sismo-config-to-json.ts deleted file mode 100644 index 5e171be..0000000 --- a/front/script/sismo-config-to-json.ts +++ /dev/null @@ -1,19 +0,0 @@ -import { CONFIG, AUTHS, CLAIMS } from "../src/app/sismo-connect-config"; - -const printSismoConfigJson = (): void => { - const impersonate = CONFIG?.vault?.impersonate; - console.log( - JSON.stringify( - { - appId: CONFIG.appId, - isImpersonationMode: impersonate ? impersonate.length > 0 : false, - authRequests: AUTHS, - claimRequests: CLAIMS, - // signatureRequest: SIGNATURE_REQUEST, - }, - null, - 2 - ) - ); -}; -printSismoConfigJson(); diff --git a/front/src/app/page.tsx b/front/src/app/page.tsx index 33b37ca..aab3d88 100644 --- a/front/src/app/page.tsx +++ b/front/src/app/page.tsx @@ -4,6 +4,7 @@ import { useEffect, useState } from "react"; import Header from "./components/Header"; import { useAccount, useNetwork } from "wagmi"; import { useConnectModal } from "@rainbow-me/rainbowkit"; +import { formatEther } from "viem"; import { formatError, getAuthRequestsAndClaimRequestsFromSismoConnectRequest, @@ -11,22 +12,60 @@ import { getProofDataForClaim, getUserIdFromHex, signMessage, -} from "@/utils/misc"; -import { mumbaiFork } from "@/utils/wagmi"; + fundMyAccountOnLocalFork, + useContract, + // chains + mumbaiFork, + mainnet, + goerli, + sepolia, + optimism, + optimismGoerli, + arbitrum, + arbitrumGoerli, + scrollTestnet, + gnosis, + polygon, + polygonMumbai, +} from "@/utils"; import { AuthRequest, ClaimRequest, - RequestBuilder, SismoConnectButton, VaultConfig, VerifiedAuth, VerifiedClaim, // the Sismo Connect React button displayed below + AuthType, + ClaimType, + SismoConnectConfig, } from "@sismo-core/sismo-connect-react"; -import { fundMyAccountOnLocalFork } from "@/utils/fundMyAccountOnLocalFork"; -import { CONFIG, AuthType, ClaimType } from "@/app/sismo-connect-config"; -import useContract from "@/utils/useContract"; -import { decodeAbiParameters, formatEther } from "viem"; -import { get } from "http"; + +/* ********************** Sismo Connect Config *************************** */ +// For development purposes insert the Data Sources that you want to impersonate +// Never use this in production +// the appId is not referenced here as it is set directly in the contract +export const CONFIG: Omit = { + vault: { + // For development purposes insert the Data Sources that you want to impersonate + // Never use this in production + impersonate: [ + // EVM Data Sources + "dhadrien.sismo.eth", + "0xA4C94A6091545e40fc9c3E0982AEc8942E282F38", + "0x1b9424ed517f7700e7368e34a9743295a225d889", + "0x82fbed074f62386ed43bb816f748e8817bf46ff7", + "0xc281bd4db5bf94f02a8525dca954db3895685700", + // Github Data Source + "github:dhadrien", + // Twitter Data Source + "twitter:dhadrien_", + // Telegram Data Source + "telegram:dhadrien", + ], + }, + // displayRawResponse: true, // this enables you to get access directly to the + // Sismo Connect Response in the vault instead of redirecting back to the app +}; /* ******************** Defines the chain to use *************************** */ const CHAIN = mumbaiFork; @@ -61,12 +100,13 @@ export default function Home() { }); const { chain } = useNetwork(); const { openConnectModal, connectModalOpen } = useConnectModal(); - const { airdropContract, switchNetworkAsync, waitingForTransaction, error } = useContract({ responseBytes, chain: CHAIN, }); + // Get the SismoConnectConfig and Sismo Connect Request from the contract + // Set react state accordingly to display the Sismo Connect Button useEffect(() => { if (!isConnected) return; async function getRequests() { diff --git a/front/src/app/sismo-connect-config.ts b/front/src/app/sismo-connect-config.ts deleted file mode 100644 index dcc93fd..0000000 --- a/front/src/app/sismo-connect-config.ts +++ /dev/null @@ -1,33 +0,0 @@ -import { - ClaimType, - AuthType, - SismoConnectConfig, - VerifiedAuth, - VerifiedClaim, -} from "@sismo-core/sismo-connect-client"; - -export { ClaimType, AuthType }; -export type { VerifiedAuth, VerifiedClaim }; -export const CONFIG: SismoConnectConfig = { - appId: "0x32403ced4b65f2079eda77c84e7d2be6", - vault: { - // For development purposes insert the Data Sources that you want to impersonate - // Never use this in production - impersonate: [ - // EVM Data Sources - "dhadrien.sismo.eth", - "0xA4C94A6091545e40fc9c3E0982AEc8942E282F38", - "0x1b9424ed517f7700e7368e34a9743295a225d889", - "0x82fbed074f62386ed43bb816f748e8817bf46ff7", - "0xc281bd4db5bf94f02a8525dca954db3895685700", - // Github Data Source - "github:dhadrien", - // Twitter Data Source - "twitter:dhadrien_", - // Telegram Data Source - "telegram:dhadrien", - ], - }, - // displayRawResponse: true, // this enables you to get access directly to the - // Sismo Connect Response in the vault instead of redirecting back to the app -}; diff --git a/front/src/utils/index.ts b/front/src/utils/index.ts new file mode 100644 index 0000000..00a8987 --- /dev/null +++ b/front/src/utils/index.ts @@ -0,0 +1,7 @@ +import useContract from "@/utils/useContract"; + +export * from "./errorsABI"; +export * from "./fundMyAccountOnLocalFork"; +export * from "./misc"; +export { useContract }; +export * from "./wagmi"; diff --git a/front/src/utils/misc.ts b/front/src/utils/misc.ts index f67a600..0411ebb 100644 --- a/front/src/utils/misc.ts +++ b/front/src/utils/misc.ts @@ -1,8 +1,13 @@ import { decodeAbiParameters, encodeAbiParameters } from "viem"; import { abi as AirdropABI } from "../../../abi/Airdrop.json"; import { errorsABI } from "./errorsABI"; -import { AuthType, VerifiedAuth, VerifiedClaim } from "@/app/sismo-connect-config"; -import { AuthRequest, ClaimRequest } from "@sismo-core/sismo-connect-react"; +import { + AuthRequest, + AuthType, + ClaimRequest, + VerifiedAuth, + VerifiedClaim, +} from "@sismo-core/sismo-connect-react"; declare global { interface Window { diff --git a/front/src/utils/wagmi.tsx b/front/src/utils/wagmi.tsx index 0dc8a12..6435385 100644 --- a/front/src/utils/wagmi.tsx +++ b/front/src/utils/wagmi.tsx @@ -21,6 +21,20 @@ import { WagmiConfig } from "wagmi"; import { publicProvider } from "wagmi/providers/public"; import { getDefaultWallets, RainbowKitProvider } from "@rainbow-me/rainbowkit"; +export { + mainnet, + goerli, + sepolia, + optimism, + optimismGoerli, + arbitrum, + arbitrumGoerli, + scrollTestnet, + gnosis, + polygon, + polygonMumbai, +}; + export const mumbaiFork = { id: 5151111, name: "Local Fork Mumbai", diff --git a/lib/sismo-connect-packages b/lib/sismo-connect-packages deleted file mode 160000 index d1de041..0000000 --- a/lib/sismo-connect-packages +++ /dev/null @@ -1 +0,0 @@ -Subproject commit d1de04182f20e9e1a2526aaab8659252982e8500 diff --git a/lib/sismo-connect-solidity b/lib/sismo-connect-solidity new file mode 160000 index 0000000..52441ae --- /dev/null +++ b/lib/sismo-connect-solidity @@ -0,0 +1 @@ +Subproject commit 52441ae8f880071944db8ecba6d1dc1202016182 diff --git a/remappings.txt b/remappings.txt index fbe0825..8062183 100644 --- a/remappings.txt +++ b/remappings.txt @@ -1,3 +1,3 @@ forge-std/=lib/forge-std/src/ @openzeppelin/contracts/=lib/openzeppelin-contracts/contracts/ -sismo-connect-solidity/=lib/sismo-connect-packages/packages/sismo-connect-solidity/src/ \ No newline at end of file +sismo-connect-solidity/=lib/sismo-connect-solidity/src/ \ No newline at end of file diff --git a/script/Airdrop.s.sol b/script/Airdrop.s.sol index cc45522..0d8b7e3 100644 --- a/script/Airdrop.s.sol +++ b/script/Airdrop.s.sol @@ -2,7 +2,7 @@ pragma solidity ^0.8.20; import "forge-std/Script.sol"; -import "sismo-connect-solidity/SismoLib.sol"; +import "sismo-connect-solidity/SismoConnectLib.sol"; import {Airdrop} from "src/Airdrop.sol"; contract DeployAirdrop is Script { diff --git a/src/Airdrop.sol b/src/Airdrop.sol index fde1885..567942f 100644 --- a/src/Airdrop.sol +++ b/src/Airdrop.sol @@ -3,7 +3,7 @@ pragma solidity ^0.8.20; import {ERC20} from "@openzeppelin/contracts/token/ERC20/ERC20.sol"; import "forge-std/console2.sol"; -import "sismo-connect-solidity/SismoLib.sol"; +import "sismo-connect-solidity/SismoConnectLib.sol"; /* * @title Airdrop diff --git a/tests/Airdrop.t.sol b/tests/Airdrop.t.sol index abccc39..581dfdc 100644 --- a/tests/Airdrop.t.sol +++ b/tests/Airdrop.t.sol @@ -2,7 +2,7 @@ pragma solidity ^0.8.20; import "tests/base/BaseTest.t.sol"; -import "sismo-connect-solidity/SismoLib.sol"; +import "sismo-connect-solidity/SismoConnectLib.sol"; import {Airdrop} from "src/Airdrop.sol"; contract Airdroptest is BaseTest { diff --git a/tests/base/BaseTest.t.sol b/tests/base/BaseTest.t.sol index e591d25..12829da 100644 --- a/tests/base/BaseTest.t.sol +++ b/tests/base/BaseTest.t.sol @@ -3,7 +3,7 @@ pragma solidity ^0.8.17; import "forge-std/Test.sol"; import "forge-std/console.sol"; -import {IAddressesProvider} from "sismo-connect-solidity/SismoLib.sol"; +import {IAddressesProvider} from "sismo-connect-solidity/SismoConnectLib.sol"; interface IAvailableRootsRegistry { event RegisteredRoot(uint256 root); From fb7f84fb47bc96aa80315b679530a2cce1238112 Mon Sep 17 00:00:00 2001 From: bigq Date: Tue, 18 Jul 2023 20:54:56 +0200 Subject: [PATCH 11/12] feat: clean wording in frontend to make button behavior more evident --- front/src/app/page.tsx | 163 +++++++++++++++++++---------------------- 1 file changed, 74 insertions(+), 89 deletions(-) diff --git a/front/src/app/page.tsx b/front/src/app/page.tsx index aab3d88..5d238be 100644 --- a/front/src/app/page.tsx +++ b/front/src/app/page.tsx @@ -71,27 +71,27 @@ export const CONFIG: Omit = { const CHAIN = mumbaiFork; export default function Home() { - const [appState, setAppState] = useState<{ - pageState: string; - amountClaimed: string; - sismoConnectConfig: { appId: string | undefined; vault: VaultConfig | {} }; - claimRequests: ClaimRequest[] | undefined; - authRequests: AuthRequest[] | undefined; + const [pageState, setPageState] = useState("init"); + const [sismoConnectConfig, setSismoConnectConfig] = useState({ + appId: "", + }); + const [sismoConnectRequest, setSismoConnectRequest] = useState<{ + auths: AuthRequest[] | undefined; + claims: ClaimRequest[] | undefined; + }>({ + auths: undefined, + claims: undefined, + }); + const [sismoConnectVerifiedResult, setSismoConnectVerifiedResult] = useState<{ verifiedClaims: VerifiedClaim[] | undefined; verifiedAuths: VerifiedAuth[] | undefined; verifiedSignedMessage: string | undefined; + amountClaimed: string; }>({ - pageState: "init", - amountClaimed: "", - sismoConnectConfig: { - appId: undefined, - vault: {}, - }, - claimRequests: undefined, - authRequests: undefined, verifiedClaims: undefined, verifiedAuths: undefined, verifiedSignedMessage: undefined, + amountClaimed: "", }); const [responseBytes, setResponseBytes] = useState(null); const [claimError, setClaimError] = useState(null); @@ -119,45 +119,40 @@ export default function Home() { const { authRequests, claimRequests } = getAuthRequestsAndClaimRequestsFromSismoConnectRequest(sismoConnectRequest); - setAppState((prev) => { - return { - ...prev, - // we impersonate accounts if the impersonation mode is set to true in the contract - sismoConnectConfig: { - appId, - vault: (isImpersonationMode === true ? CONFIG.vault : {}) as VaultConfig, - }, - authRequests, - claimRequests, - }; + setSismoConnectConfig({ + appId, + // we impersonate accounts if the impersonation mode is set to true in the contract + vault: (isImpersonationMode === true ? CONFIG.vault : {}) as VaultConfig, + }); + setSismoConnectRequest({ + auths: authRequests, + claims: claimRequests, }); } - getRequests(); - }, [appState.pageState]); + }, [pageState]); useEffect(() => { if (!responseBytes) return; - setAppState((prev) => { - return { ...prev, pageState: "responseReceived" }; - }); + setPageState("responseReceived"); setClaimError(error); }, [responseBytes, error, claimError]); /* ************************* Reset state **************************** */ function resetApp() { - setAppState({ - pageState: "init", - amountClaimed: "", - sismoConnectConfig: { - appId: undefined, - vault: {}, - }, - claimRequests: undefined, - authRequests: undefined, + setPageState("init"); + setSismoConnectConfig({ + appId: "", + }); + setSismoConnectRequest({ + auths: undefined, + claims: undefined, + }); + setSismoConnectVerifiedResult({ verifiedClaims: undefined, verifiedAuths: undefined, verifiedSignedMessage: undefined, + amountClaimed: "", }); setClaimError(""); const url = new URL(window.location.href); @@ -172,13 +167,9 @@ export default function Home() { setClaimError(""); try { if (chain?.id !== CHAIN.id) await switchNetworkAsync?.(CHAIN.id); - setAppState((prev) => { - return { ...prev, pageState: "confirmingTransaction" }; - }); + setPageState("confirmingTransaction"); const hash = await airdropContract.write.claimWithSismo([responseBytes, address]); - setAppState((prev) => { - return { ...prev, pageState: "verifying" }; - }); + setPageState("verifying"); let txReceipt = await waitingForTransaction(hash); if (txReceipt?.status === "success") { const amountClaimed = formatEther( @@ -188,23 +179,18 @@ export default function Home() { const verifiedAuths = (await airdropContract.read.getVerifiedAuths()) as VerifiedAuth[]; const verifiedSignedMessage = (await airdropContract.read.getVerifiedSignedMessage()) as string; - setAppState((prev) => { - return { - ...prev, - amountClaimed, - verifiedClaims, - verifiedAuths, - verifiedSignedMessage, - pageState: "verified", - }; + setPageState("verified"); + setSismoConnectVerifiedResult({ + verifiedClaims, + verifiedAuths, + verifiedSignedMessage, + amountClaimed, }); } } catch (e: any) { setClaimError(formatError(e)); } finally { - setAppState((prev) => { - return { ...prev, pageState: "responseReceived" }; - }); + setPageState("responseReceived"); } } @@ -221,26 +207,23 @@ export default function Home() { <> <> {" "} - {appState.pageState != "init" && } + {pageState != "init" && }

{`Chain: ${chain?.name} [${chain?.id}]`}
Your airdrop destination address is: {address}

- {appState.pageState == "init" && ( + {pageState == "init" && ( <> - {appState.pageState == "responseReceived" && ( + {pageState == "responseReceived" && ( )} - {appState.pageState == "confirmingTransaction" && ( + {pageState == "confirmingTransaction" && ( )} - {appState.pageState == "verifying" && ( + {pageState == "verifying" && ( Verifying ZK Proofs onchain... )} - {appState.pageState == "verified" && ( - ZK Proofs Verified! - )} + {pageState == "verified" && ZK Proofs Verified! } )} - {isConnected && !appState.amountClaimed && claimError && ( + {isConnected && !sismoConnectVerifiedResult.amountClaimed && claimError && ( <>

{claimError}

{claimError.slice(0, 50) === @@ -285,10 +266,11 @@ export default function Home() { )} {/* Table of the Sismo Connect requests and verified result */} {/* Table for Verified Auths */} - {appState.verifiedAuths && ( + {sismoConnectVerifiedResult.verifiedAuths && ( <>

- {appState.amountClaimed} tokens were claimed in total on {address}. + {sismoConnectVerifiedResult.amountClaimed} tokens were claimed in total on{" "} + {address}.

Verified Auths

@@ -299,7 +281,7 @@ export default function Home() { - {appState.verifiedAuths.map((auth, index) => ( + {sismoConnectVerifiedResult.verifiedAuths.map((auth, index) => (
{AuthType[auth.authType]} @@ -315,7 +297,7 @@ export default function Home() { )}
{/* Table for Verified Claims */} - {appState.verifiedClaims && ( + {sismoConnectVerifiedResult.verifiedClaims && ( <>

Verified Claims

@@ -327,7 +309,7 @@ export default function Home() { - {appState.verifiedClaims.map((claim, index) => ( + {sismoConnectVerifiedResult.verifiedClaims.map((claim, index) => ( - {appState?.authRequests?.map((auth, index) => ( + {sismoConnectRequest?.auths?.map((auth, index) => ( - {appState.verifiedAuths ? ( + {sismoConnectVerifiedResult.verifiedAuths ? ( ) : ( @@ -390,7 +375,7 @@ export default function Home() { - {appState?.claimRequests?.map((claim, index) => ( + {sismoConnectRequest?.claims?.map((claim, index) => ( - {appState.verifiedClaims ? ( + {sismoConnectVerifiedResult.verifiedClaims ? ( From b0efc58c7ce232726dca5bf12852681d8687daa5 Mon Sep 17 00:00:00 2001 From: bigq Date: Tue, 18 Jul 2023 21:27:56 +0200 Subject: [PATCH 12/12] feat: simplify storage setup by having null as default --- abi/Airdrop.json | 22 ++---------- front/src/app/page.tsx | 77 ++++++++++++++++------------------------- front/src/utils/misc.ts | 12 +++++++ src/Airdrop.sol | 59 +++++++++++++++++++++---------- 4 files changed, 86 insertions(+), 84 deletions(-) diff --git a/abi/Airdrop.json b/abi/Airdrop.json index aa23e7e..5c4491d 100644 --- a/abi/Airdrop.json +++ b/abi/Airdrop.json @@ -464,7 +464,7 @@ }, { "inputs": [], - "name": "getVerifiedAuths", + "name": "getSismoConnectVerifiedResult", "outputs": [ { "components": [ @@ -497,15 +497,7 @@ "internalType": "struct VerifiedAuth[]", "name": "", "type": "tuple[]" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [], - "name": "getVerifiedClaims", - "outputs": [ + }, { "components": [ { @@ -547,15 +539,7 @@ "internalType": "struct VerifiedClaim[]", "name": "", "type": "tuple[]" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [], - "name": "getVerifiedSignedMessage", - "outputs": [ + }, { "internalType": "bytes", "name": "", diff --git a/front/src/app/page.tsx b/front/src/app/page.tsx index 5d238be..4bf49a8 100644 --- a/front/src/app/page.tsx +++ b/front/src/app/page.tsx @@ -14,6 +14,7 @@ import { signMessage, fundMyAccountOnLocalFork, useContract, + getResults, // chains mumbaiFork, mainnet, @@ -76,23 +77,15 @@ export default function Home() { appId: "", }); const [sismoConnectRequest, setSismoConnectRequest] = useState<{ - auths: AuthRequest[] | undefined; - claims: ClaimRequest[] | undefined; - }>({ - auths: undefined, - claims: undefined, - }); + auths: AuthRequest[]; + claims: ClaimRequest[]; + } | null>(null); const [sismoConnectVerifiedResult, setSismoConnectVerifiedResult] = useState<{ - verifiedClaims: VerifiedClaim[] | undefined; - verifiedAuths: VerifiedAuth[] | undefined; - verifiedSignedMessage: string | undefined; + verifiedClaims: VerifiedClaim[]; + verifiedAuths: VerifiedAuth[]; + verifiedSignedMessage: string; amountClaimed: string; - }>({ - verifiedClaims: undefined, - verifiedAuths: undefined, - verifiedSignedMessage: undefined, - amountClaimed: "", - }); + } | null>(null); const [responseBytes, setResponseBytes] = useState(null); const [claimError, setClaimError] = useState(null); const { isConnected, address } = useAccount({ @@ -141,19 +134,9 @@ export default function Home() { /* ************************* Reset state **************************** */ function resetApp() { setPageState("init"); - setSismoConnectConfig({ - appId: "", - }); - setSismoConnectRequest({ - auths: undefined, - claims: undefined, - }); - setSismoConnectVerifiedResult({ - verifiedClaims: undefined, - verifiedAuths: undefined, - verifiedSignedMessage: undefined, - amountClaimed: "", - }); + setSismoConnectConfig({ appId: "" }); + setSismoConnectRequest(null); + setSismoConnectVerifiedResult(null); setClaimError(""); const url = new URL(window.location.href); url.searchParams.delete("sismoConnectResponseCompressed"); @@ -172,20 +155,20 @@ export default function Home() { setPageState("verifying"); let txReceipt = await waitingForTransaction(hash); if (txReceipt?.status === "success") { - const amountClaimed = formatEther( - (await airdropContract.read.balanceOf([address])) as bigint + const sismoConnectVerifiedResult = getResults( + (await airdropContract.read.getSismoConnectVerifiedResult()) as [ + VerifiedAuth[], + VerifiedClaim[], + string + ] ); - const verifiedClaims = (await airdropContract.read.getVerifiedClaims()) as VerifiedClaim[]; - const verifiedAuths = (await airdropContract.read.getVerifiedAuths()) as VerifiedAuth[]; - const verifiedSignedMessage = - (await airdropContract.read.getVerifiedSignedMessage()) as string; - setPageState("verified"); setSismoConnectVerifiedResult({ - verifiedClaims, - verifiedAuths, - verifiedSignedMessage, - amountClaimed, + verifiedClaims: sismoConnectVerifiedResult.verifiedClaims, + verifiedAuths: sismoConnectVerifiedResult.verifiedAuths, + verifiedSignedMessage: sismoConnectVerifiedResult.verifiedSignedMessage, + amountClaimed: formatEther((await airdropContract.read.balanceOf([address])) as bigint), }); + setPageState("verified"); } } catch (e: any) { setClaimError(formatError(e)); @@ -214,7 +197,7 @@ export default function Home() { Your airdrop destination address is: {address}

- {pageState == "init" && ( + {pageState == "init" && sismoConnectRequest && ( <> ZK Proofs Verified! } )} - {isConnected && !sismoConnectVerifiedResult.amountClaimed && claimError && ( + {isConnected && !sismoConnectVerifiedResult?.amountClaimed && claimError && ( <>

{claimError}

{claimError.slice(0, 50) === @@ -266,7 +249,7 @@ export default function Home() { )} {/* Table of the Sismo Connect requests and verified result */} {/* Table for Verified Auths */} - {sismoConnectVerifiedResult.verifiedAuths && ( + {sismoConnectVerifiedResult?.verifiedAuths && ( <>

{sismoConnectVerifiedResult.amountClaimed} tokens were claimed in total on{" "} @@ -297,7 +280,7 @@ export default function Home() { )}
{/* Table for Verified Claims */} - {sismoConnectVerifiedResult.verifiedClaims && ( + {sismoConnectVerifiedResult?.verifiedClaims && ( <>

Verified Claims

{AuthType[auth.authType]} {auth.userId || "No userId requested"} {auth.isOptional ? "optional" : "required"} - {getProofDataForAuth(appState.verifiedAuths, auth.authType)!.toString()} + {getProofDataForAuth( + sismoConnectVerifiedResult.verifiedAuths, + auth.authType + )!.toString()} ZK proof not generated yet
{claim.value ? claim.value : "1"} {claim.isSelectableByUser ? "yes" : "no"} {claim.isOptional ? "optional" : "required"} { getProofDataForClaim( - appState.verifiedClaims!, + sismoConnectVerifiedResult.verifiedClaims!, claim.claimType || 0, claim.groupId!, claim.value || 1 @@ -435,8 +420,8 @@ export default function Home() {
{{ message: signMessage(address!) }.message} - {appState.verifiedSignedMessage - ? appState.verifiedSignedMessage + {sismoConnectVerifiedResult.verifiedSignedMessage + ? sismoConnectVerifiedResult.verifiedSignedMessage : "ZK Proof not verified"}
@@ -346,10 +329,10 @@ export default function Home() { - {sismoConnectVerifiedResult.verifiedAuths ? ( + {sismoConnectVerifiedResult?.verifiedAuths ? ( @@ -389,7 +372,7 @@ export default function Home() { - {sismoConnectVerifiedResult.verifiedClaims ? ( + {sismoConnectVerifiedResult?.verifiedClaims ? ( diff --git a/front/src/utils/misc.ts b/front/src/utils/misc.ts index 0411ebb..d40a88d 100644 --- a/front/src/utils/misc.ts +++ b/front/src/utils/misc.ts @@ -108,3 +108,15 @@ export function getAuthRequestsAndClaimRequestsFromSismoConnectRequest( return { authRequests, claimRequests }; } + +export function getResults(sismoConnectVerifiedResult: [VerifiedAuth[], VerifiedClaim[], string]): { + verifiedAuths: VerifiedAuth[]; + verifiedClaims: VerifiedClaim[]; + verifiedSignedMessage: string; +} { + return { + verifiedAuths: sismoConnectVerifiedResult[0] as VerifiedAuth[], + verifiedClaims: sismoConnectVerifiedResult[1] as VerifiedClaim[], + verifiedSignedMessage: sismoConnectVerifiedResult[2] as string, + }; +} diff --git a/src/Airdrop.sol b/src/Airdrop.sol index 567942f..cbea912 100644 --- a/src/Airdrop.sol +++ b/src/Airdrop.sol @@ -9,7 +9,7 @@ import "sismo-connect-solidity/SismoConnectLib.sol"; * @title Airdrop * @author Sismo * @dev Simple Airdrop contract gated by Sismo Connect - * Application requests multiple zk proofs (auths and claims) and verify them + * Application requests multiple zk proofs (with auths and claims requests) and verify them * The contract stores all requests and verified results in storage */ contract Airdrop is ERC20, SismoConnect { @@ -39,7 +39,9 @@ contract Airdrop is ERC20, SismoConnect { string memory name, string memory symbol ) ERC20(name, symbol) SismoConnect(buildConfig(_appId, _isImpersonationMode)) { - // defining requests + // Defining requests that will be queried by the app frontend to allow users to generate a Sismo Connect response in their Sismo Vault + // The Sismo Connect Response holding the zk proofs will be checked against these requests in the claimWithSismo function below + // Request users to prove ownership of a Data Source (Wallet, Twitter, Github, Telegram, etc.) AuthRequest[] memory authRequests = new AuthRequest[](3); // Anonymous identifier of the vault for this app @@ -49,6 +51,7 @@ contract Airdrop is ERC20, SismoConnect { // Request users to prove ownership of an EVM account authRequests[1] = buildAuth({authType: AuthType.EVM_ACCOUNT}); // Request users to prove ownership of a Github account + // this request is optional authRequests[2] = buildAuth({ authType: AuthType.GITHUB, isOptional: true, @@ -65,7 +68,7 @@ contract Airdrop is ERC20, SismoConnect { // claim ENS DAO Voters Data Group membership: https://factory.sismo.io/groups-explorer?search=0x85c7ee90829de70d0d51f52336ea4722 // Data Group members = voters in ENS DAO // value for each group member = number of votes in ENS DAO - // request user to prove membership in the group with value >= 17 + // request user to prove membership in the group with value >= 4 claimRequests[1] = buildClaim({groupId: bytes16(0x85c7ee90829de70d0d51f52336ea4722), value: 4}); // claim on Stand with Crypto NFT Minters Data Group membership: https://factory.sismo.io/groups-explorer?search=0xfae674b6cba3ff2f8ce2114defb200b1 // Data Group members = minters of the Stand with Crypto NFT @@ -79,12 +82,17 @@ contract Airdrop is ERC20, SismoConnect { isSelectableByUser: true }); - // setting requests in storage - // the frontend will query these requests - _setAuths(authRequests); - _setClaims(claimRequests); + // storing auth and claim requests in storage + // the frontend will query the Sismo Connect request from the contract + _setSismoConnectRequest({auths: authRequests, claims: claimRequests}); } + /** + * @dev Claim the airdrop with a Sismo Connect response + * Sismo Connect response's zk proofs will be checked against the requests defined in the constructor above + * @param response Sismo Connect response + * @param to address to mint the airdrop to + */ function claimWithSismo(bytes memory response, address to) public { SismoConnectVerifiedResult memory result = verify({ responseBytes: response, @@ -92,7 +100,9 @@ contract Airdrop is ERC20, SismoConnect { auths: _authRequests, // checking response against requested claims claims: _claimRequests, - // checking response against requested message signature + // checking response against a message signature + // the message is the address to mint the airdrop to + // this signature prevents front-running attacks signature: buildSignature({message: abi.encode(to)}) }); @@ -100,9 +110,8 @@ contract Airdrop is ERC20, SismoConnect { uint256 airdropAmount = (result.auths.length + result.claims.length) * 10 ** 18; _mint(to, airdropAmount); - // cleaning previous results of the verification - _cleanVerifiedAuths(); - _cleanVerifiedClaims(); + // remove previous verified results from the verification + _removePreviousVerifiedResults(); // storing the result of the verification for (uint256 i = 0; i < result.auths.length; i++) { @@ -128,16 +137,25 @@ contract Airdrop is ERC20, SismoConnect { return (_authRequests, _claimRequests); } - function getVerifiedClaims() external view returns (VerifiedClaim[] memory) { - return _verifiedClaims; + /** + * @dev Get the verified auths, claims and the verified signature that was verified in the claimWithSismo function + */ + function getSismoConnectVerifiedResult() + external + view + returns (VerifiedAuth[] memory, VerifiedClaim[] memory, bytes memory) + { + return (_verifiedAuths, _verifiedClaims, _verifiedSignedMessage); } - function getVerifiedAuths() external view returns (VerifiedAuth[] memory) { - return _verifiedAuths; - } + // helpers - function getVerifiedSignedMessage() external view returns (bytes memory) { - return _verifiedSignedMessage; + function _setSismoConnectRequest( + AuthRequest[] memory auths, + ClaimRequest[] memory claims + ) private { + _setAuths(auths); + _setClaims(claims); } function _setAuths(AuthRequest[] memory auths) private { @@ -152,6 +170,11 @@ contract Airdrop is ERC20, SismoConnect { } } + function _removePreviousVerifiedResults() private { + _cleanVerifiedAuths(); + _cleanVerifiedClaims(); + } + function _cleanVerifiedAuths() private { uint256 verifiedAuthsLength = _verifiedAuths.length; if (verifiedAuthsLength != 0) {
{AuthType[auth.authType]} {auth.userId || "No userId requested"} {auth.isOptional ? "optional" : "required"} {getProofDataForAuth( - sismoConnectVerifiedResult.verifiedAuths, + sismoConnectVerifiedResult?.verifiedAuths, auth.authType )!.toString()} {claim.value ? claim.value : "1"} {claim.isSelectableByUser ? "yes" : "no"} {claim.isOptional ? "optional" : "required"} { getProofDataForClaim( @@ -420,7 +403,7 @@ export default function Home() {
{{ message: signMessage(address!) }.message} - {sismoConnectVerifiedResult.verifiedSignedMessage + {sismoConnectVerifiedResult?.verifiedSignedMessage ? sismoConnectVerifiedResult.verifiedSignedMessage : "ZK Proof not verified"}