From 26515ae0fb9d579aae71196c73d1e9f8354b269f Mon Sep 17 00:00:00 2001 From: xavikh Date: Thu, 14 Mar 2024 11:14:09 +0100 Subject: [PATCH 1/6] Add permit to veto --- .../artifacts/ERC20withPermit.sol.ts | 526 ++++++++++++++++++ plugins/lockToVote/hooks/useProposalVeto.tsx | 96 +++- 2 files changed, 613 insertions(+), 9 deletions(-) create mode 100644 plugins/lockToVote/artifacts/ERC20withPermit.sol.ts diff --git a/plugins/lockToVote/artifacts/ERC20withPermit.sol.ts b/plugins/lockToVote/artifacts/ERC20withPermit.sol.ts new file mode 100644 index 00000000..7c10533c --- /dev/null +++ b/plugins/lockToVote/artifacts/ERC20withPermit.sol.ts @@ -0,0 +1,526 @@ +import { Abi } from "viem"; +export const ERC20Abi: Abi = [ + { + "inputs": [], + "stateMutability": "nonpayable", + "type": "constructor" + }, + { + "inputs": [], + "name": "ECDSAInvalidSignature", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "length", + "type": "uint256" + } + ], + "name": "ECDSAInvalidSignatureLength", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "bytes32", + "name": "s", + "type": "bytes32" + } + ], + "name": "ECDSAInvalidSignatureS", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "spender", + "type": "address" + }, + { + "internalType": "uint256", + "name": "allowance", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "needed", + "type": "uint256" + } + ], + "name": "ERC20InsufficientAllowance", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "sender", + "type": "address" + }, + { + "internalType": "uint256", + "name": "balance", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "needed", + "type": "uint256" + } + ], + "name": "ERC20InsufficientBalance", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "approver", + "type": "address" + } + ], + "name": "ERC20InvalidApprover", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "receiver", + "type": "address" + } + ], + "name": "ERC20InvalidReceiver", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "sender", + "type": "address" + } + ], + "name": "ERC20InvalidSender", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "spender", + "type": "address" + } + ], + "name": "ERC20InvalidSpender", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "deadline", + "type": "uint256" + } + ], + "name": "ERC2612ExpiredSignature", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "signer", + "type": "address" + }, + { + "internalType": "address", + "name": "owner", + "type": "address" + } + ], + "name": "ERC2612InvalidSigner", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "account", + "type": "address" + }, + { + "internalType": "uint256", + "name": "currentNonce", + "type": "uint256" + } + ], + "name": "InvalidAccountNonce", + "type": "error" + }, + { + "inputs": [], + "name": "InvalidShortString", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "string", + "name": "str", + "type": "string" + } + ], + "name": "StringTooLong", + "type": "error" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "owner", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "spender", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "value", + "type": "uint256" + } + ], + "name": "Approval", + "type": "event" + }, + { + "anonymous": false, + "inputs": [], + "name": "EIP712DomainChanged", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "from", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "to", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "value", + "type": "uint256" + } + ], + "name": "Transfer", + "type": "event" + }, + { + "inputs": [], + "name": "DOMAIN_SEPARATOR", + "outputs": [ + { + "internalType": "bytes32", + "name": "", + "type": "bytes32" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "owner", + "type": "address" + }, + { + "internalType": "address", + "name": "spender", + "type": "address" + } + ], + "name": "allowance", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "spender", + "type": "address" + }, + { + "internalType": "uint256", + "name": "value", + "type": "uint256" + } + ], + "name": "approve", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "account", + "type": "address" + } + ], + "name": "balanceOf", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "decimals", + "outputs": [ + { + "internalType": "uint8", + "name": "", + "type": "uint8" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "eip712Domain", + "outputs": [ + { + "internalType": "bytes1", + "name": "fields", + "type": "bytes1" + }, + { + "internalType": "string", + "name": "name", + "type": "string" + }, + { + "internalType": "string", + "name": "version", + "type": "string" + }, + { + "internalType": "uint256", + "name": "chainId", + "type": "uint256" + }, + { + "internalType": "address", + "name": "verifyingContract", + "type": "address" + }, + { + "internalType": "bytes32", + "name": "salt", + "type": "bytes32" + }, + { + "internalType": "uint256[]", + "name": "extensions", + "type": "uint256[]" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "name", + "outputs": [ + { + "internalType": "string", + "name": "", + "type": "string" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "owner", + "type": "address" + } + ], + "name": "nonces", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "owner", + "type": "address" + }, + { + "internalType": "address", + "name": "spender", + "type": "address" + }, + { + "internalType": "uint256", + "name": "value", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "deadline", + "type": "uint256" + }, + { + "internalType": "uint8", + "name": "v", + "type": "uint8" + }, + { + "internalType": "bytes32", + "name": "r", + "type": "bytes32" + }, + { + "internalType": "bytes32", + "name": "s", + "type": "bytes32" + } + ], + "name": "permit", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "symbol", + "outputs": [ + { + "internalType": "string", + "name": "", + "type": "string" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "totalSupply", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "to", + "type": "address" + }, + { + "internalType": "uint256", + "name": "value", + "type": "uint256" + } + ], + "name": "transfer", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "from", + "type": "address" + }, + { + "internalType": "address", + "name": "to", + "type": "address" + }, + { + "internalType": "uint256", + "name": "value", + "type": "uint256" + } + ], + "name": "transferFrom", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "nonpayable", + "type": "function" + } +] \ No newline at end of file diff --git a/plugins/lockToVote/hooks/useProposalVeto.tsx b/plugins/lockToVote/hooks/useProposalVeto.tsx index 35bd317d..398dd55a 100644 --- a/plugins/lockToVote/hooks/useProposalVeto.tsx +++ b/plugins/lockToVote/hooks/useProposalVeto.tsx @@ -3,13 +3,18 @@ import { usePublicClient, useWaitForTransactionReceipt, useWriteContract, + useReadContracts, + useSignTypedData, + useAccount, } from "wagmi"; +import { hexToSignature } from "viem"; import { useProposal } from "./useProposal"; import { useProposalVetoes } from "@/plugins/dualGovernance/hooks/useProposalVetoes"; import { useUserCanVeto } from "@/plugins/dualGovernance/hooks/useUserCanVeto"; -import { OptimisticTokenVotingPluginAbi } from "@/plugins/dualGovernance/artifacts/OptimisticTokenVotingPlugin.sol"; +import { LockToVetoPluginAbi } from "@/plugins/lockToVote/artifacts/LockToVetoPlugin.sol"; +import { ERC20Abi } from "@/plugins/lockToVote/artifacts/ERC20withPermit.sol"; import { useAlertContext, AlertContextProps } from "@/context/AlertContext"; -import { PUB_CHAIN, PUB_LOCK_TO_VOTE_PLUGIN_ADDRESS } from "@/constants"; +import { PUB_CHAIN, PUB_TOKEN_ADDRESS, PUB_LOCK_TO_VOTE_PLUGIN_ADDRESS } from "@/constants"; export function useProposalVeto(proposalId: string) { const publicClient = usePublicClient({ chainId: PUB_CHAIN.id }); @@ -27,6 +32,38 @@ export function useProposalVeto(proposalId: string) { ); const { addAlert } = useAlertContext() as AlertContextProps; + const account_address = useAccount().address!; + + const erc20Contract = { + address: PUB_TOKEN_ADDRESS, + abi: ERC20Abi, + }; + const { data: erc20data } = useReadContracts({ + contracts: [{ + ...erc20Contract, + functionName: "balanceOf", + args: [account_address], + },{ + ...erc20Contract, + functionName: "nonces", + args: [account_address], + },{ + ...erc20Contract, + functionName: "name", + },{ + ...erc20Contract, + functionName: "version", + }] + }); + + const [balanceResult, nonceResult, nameResult, versionResult] = erc20data || []; + + const balance = BigInt(Number(balanceResult?.result)); + const nonce = BigInt(Number(nonceResult?.result)); + const erc20_name = nameResult?.result; + const versionFromContract = versionResult?.result; + + const { signTypedData: vetoPermitSign } = useSignTypedData(); const { writeContract: vetoWrite, data: vetoTxHash, @@ -47,7 +84,8 @@ export function useProposalVeto(proposalId: string) { timeout: 4 * 1000, }); } else { - addAlert("Could not create the proposal", { type: "error" }); + console.error(vetoingError); + addAlert("Could not veto", { type: "error" }); } return; } @@ -72,12 +110,52 @@ export function useProposalVeto(proposalId: string) { }, [vetoingStatus, vetoTxHash, isConfirming, isConfirmed]); const vetoProposal = () => { - vetoWrite({ - abi: OptimisticTokenVotingPluginAbi, - address: PUB_LOCK_TO_VOTE_PLUGIN_ADDRESS, - functionName: "veto", - args: [proposalId], - }); + let deadline = BigInt(Math.floor(Date.now() / 1000) + 60 * 60); + let value = balance; + let destination: `0x${string}` = PUB_LOCK_TO_VOTE_PLUGIN_ADDRESS; + + const domain = { + chainId: PUB_CHAIN.id, + name: String(erc20_name), + /* We assume 1 if permit version is not specified */ + version: String(versionFromContract ?? '1'), + verifyingContract: PUB_TOKEN_ADDRESS, + }; + + const types = { + Permit: [ + { name: 'owner', type: 'address' }, + { name: 'spender', type: 'address' }, + { name: 'value', type: 'uint256' }, + { name: 'nonce', type: 'uint256' }, + { name: 'deadline', type: 'uint256' }, + ], + }; + + const message = { + owner: account_address, + spender: destination, + value, + nonce, + deadline, + }; + + vetoPermitSign({ + account: account_address, + types, + domain, + primaryType: 'Permit', + message, + }, { onSuccess: (hexSignature) => { + let signature = hexToSignature(hexSignature); + + vetoWrite({ + abi: LockToVetoPluginAbi, + address: destination, + functionName: "vetoPermit", + args: [proposalId, value, deadline, signature.v, signature.r, signature.s], + }); + }}); }; return { From 0e763b59259f0ae65b4902e38d7d76cbac3fab58 Mon Sep 17 00:00:00 2001 From: xavikh Date: Thu, 14 Mar 2024 11:44:39 +0100 Subject: [PATCH 2/6] Fix null values --- plugins/lockToVote/hooks/useProposalVeto.tsx | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/plugins/lockToVote/hooks/useProposalVeto.tsx b/plugins/lockToVote/hooks/useProposalVeto.tsx index 2ae301ed..c06df574 100644 --- a/plugins/lockToVote/hooks/useProposalVeto.tsx +++ b/plugins/lockToVote/hooks/useProposalVeto.tsx @@ -58,11 +58,6 @@ export function useProposalVeto(proposalId: string) { const [balanceResult, nonceResult, nameResult, versionResult] = erc20data || []; - const balance = BigInt(Number(balanceResult?.result)); - const nonce = BigInt(Number(nonceResult?.result)); - const erc20_name = nameResult?.result; - const versionFromContract = versionResult?.result; - const { signTypedData: vetoPermitSign } = useSignTypedData(); const { writeContract: vetoWrite, @@ -109,6 +104,12 @@ export function useProposalVeto(proposalId: string) { }, [vetoingStatus, vetoTxHash, isConfirming, isConfirmed]); const vetoProposal = () => { + if (!proposal || !balanceResult || !nonceResult || !nameResult || !versionResult) return; + const balance = BigInt(Number(balanceResult?.result)); + const nonce = BigInt(Number(nonceResult?.result)); + const erc20_name = nameResult?.result; + const versionFromContract = versionResult?.result; + let deadline = BigInt(Math.floor(Date.now() / 1000) + 60 * 60); let value = balance; let destination: `0x${string}` = PUB_LOCK_TO_VOTE_PLUGIN_ADDRESS; From f1081bafa433c7fd58e542ac60e10027a95d6984 Mon Sep 17 00:00:00 2001 From: Carlos Juarez Date: Thu, 14 Mar 2024 14:37:50 +0100 Subject: [PATCH 3/6] feat: fixing the statuses and changes in the namings --- .../components/proposal/details.tsx | 30 +++--- plugins/lockToVote/components/vote/tally.tsx | 4 +- .../lockToVote/hooks/useProposalExecute.tsx | 93 +++++++++++++++++++ .../hooks/useProposalVariantStatus.tsx | 8 +- plugins/lockToVote/pages/proposal.tsx | 4 +- 5 files changed, 117 insertions(+), 22 deletions(-) create mode 100644 plugins/lockToVote/hooks/useProposalExecute.tsx diff --git a/plugins/lockToVote/components/proposal/details.tsx b/plugins/lockToVote/components/proposal/details.tsx index 7bbdd917..61f20a0a 100644 --- a/plugins/lockToVote/components/proposal/details.tsx +++ b/plugins/lockToVote/components/proposal/details.tsx @@ -1,19 +1,31 @@ import dayjs from "dayjs"; +import { compactNumber } from "@/utils/numbers"; import { ReactNode } from "react"; +import { formatUnits } from "viem"; interface ProposalDetailsProps { minVetoVotingPower?: bigint; endDate?: bigint; - snapshotBlock?: bigint; } const ProposalDetails: React.FC = ({ /** Timestamp */ endDate, - snapshotBlock, + minVetoVotingPower, }) => { return ( <> + +

+ Threshold +

+
+

Min. Quorum

+ + {compactNumber(formatUnits(minVetoVotingPower || BigInt(0), 18))} + +
+

Ending @@ -27,23 +39,13 @@ const ProposalDetails: React.FC = ({

- -

- Snapshot -

-
-

Taken at block

- - {snapshotBlock?.toLocaleString()} - -
-
+ ); }; // This should be encapsulated as soon as ODS exports this widget -const Card = function ({ children }: { children: ReactNode }) { +const Card = function({ children }: { children: ReactNode }) { return (

- Vetoed + For

{compactNumber(formatUnits(voteCount || BigInt(0), 18))} @@ -27,7 +27,7 @@ const VetoTally: FC = ({ voteCount, votePercentage }) => ( ); // This should be encapsulated as soon as ODS exports this widget -const Card = function ({ children }: { children: ReactNode }) { +const Card = function({ children }: { children: ReactNode }) { return (

{ + if (!canExecute) return; + + executeWrite({ + chainId: PUB_CHAIN.id, + abi: OptimisticTokenVotingPluginAbi, + address: PUB_LOCK_TO_VOTE_PLUGIN_ADDRESS, + functionName: "execute", + args: [proposalId], + }); + }; + + useEffect(() => { + if (executingStatus === "idle" || executingStatus === "pending") return; + else if (executingStatus === "error") { + if (executingError?.message?.startsWith("User rejected the request")) { + addAlert("Transaction rejected by the user", { + timeout: 4 * 1000, + }); + } else { + console.error(executingError); + addAlert("Could not execute the proposal", { + type: "error", + description: + "The proposal may contain actions with invalid operations", + }); + } + return; + } + + // success + if (!executeTxHash) return; + else if (isConfirming) { + addAlert("Proposal submitted", { + description: "Waiting for the transaction to be validated", + type: "info", + txHash: executeTxHash, + }); + return; + } else if (!isConfirmed) return; + + addAlert("Proposal executed", { + description: "The transaction has been validated", + type: "success", + txHash: executeTxHash, + }); + + setTimeout(() => reload(), 1000 * 2); + }, [executingStatus, executeTxHash, isConfirming, isConfirmed]); + + return { + executeProposal, + canExecute: + !isCanVoteError && !isCanVoteLoading && !isConfirmed && !!canExecute, + isConfirming, + isConfirmed, + }; +} diff --git a/plugins/lockToVote/hooks/useProposalVariantStatus.tsx b/plugins/lockToVote/hooks/useProposalVariantStatus.tsx index 174d1259..d7008907 100644 --- a/plugins/lockToVote/hooks/useProposalVariantStatus.tsx +++ b/plugins/lockToVote/hooks/useProposalVariantStatus.tsx @@ -7,13 +7,13 @@ export const useProposalVariantStatus = (proposal: Proposal) => { useEffect(() => { if (!proposal || !proposal?.parameters) return; setStatus( - proposal?.vetoTally >= proposal?.parameters?.minVetoVotingPower - ? { variant: 'critical', label: 'Defeated' } + proposal?.vetoTally >= proposal?.parameters?.minVetoVotingPower + ? { variant: 'success', label: 'Executable' } : proposal?.active ? { variant: 'primary', label: 'Active' } - : proposal?.executed + : proposal?.executed ? { variant: 'success', label: 'Executed' } - : { variant: 'success', label: 'Executable' } + : { variant: 'critical', label: 'Defeated' } ); }, [proposal?.vetoTally, proposal?.active, proposal?.executed, proposal?.parameters?.minVetoVotingPower]); diff --git a/plugins/lockToVote/pages/proposal.tsx b/plugins/lockToVote/pages/proposal.tsx index 9c429782..5c1bfb8d 100644 --- a/plugins/lockToVote/pages/proposal.tsx +++ b/plugins/lockToVote/pages/proposal.tsx @@ -10,7 +10,7 @@ import { PleaseWaitSpinner } from "@/components/please-wait"; import { useSkipFirstRender } from "@/hooks/useSkipFirstRender"; import { useState } from "react"; import { useProposalVeto } from "@/plugins/lockToVote/hooks/useProposalVeto"; -import { useProposalExecute } from "@/plugins/dualGovernance/hooks/useProposalExecute"; +import { useProposalExecute } from "@/plugins/lockToVote/hooks/useProposalExecute"; import { useProposalClaimLock } from "@/plugins/lockToVote/hooks/useProposalClaimLock"; import { useAccount } from "wagmi"; @@ -86,7 +86,7 @@ export default function ProposalDetail({ id: proposalId }: { id: string }) { />
From 3ef3c7edc0e77fb2a712615f1faba2b979a17b97 Mon Sep 17 00:00:00 2001 From: xavikh Date: Thu, 14 Mar 2024 19:01:02 +0100 Subject: [PATCH 4/6] Move permit our of veto hook --- .../ERC20Permit.sol.ts | 2 +- hooks/usePermit.ts | 99 +++++++++++++++++++ plugins/lockToVote/hooks/useProposalVeto.tsx | 90 ++++------------- 3 files changed, 118 insertions(+), 73 deletions(-) rename plugins/lockToVote/artifacts/ERC20withPermit.sol.ts => artifacts/ERC20Permit.sol.ts (99%) create mode 100644 hooks/usePermit.ts diff --git a/plugins/lockToVote/artifacts/ERC20withPermit.sol.ts b/artifacts/ERC20Permit.sol.ts similarity index 99% rename from plugins/lockToVote/artifacts/ERC20withPermit.sol.ts rename to artifacts/ERC20Permit.sol.ts index 7c10533c..c6b8ddca 100644 --- a/plugins/lockToVote/artifacts/ERC20withPermit.sol.ts +++ b/artifacts/ERC20Permit.sol.ts @@ -1,5 +1,5 @@ import { Abi } from "viem"; -export const ERC20Abi: Abi = [ +export const ERC20PermitAbi: Abi = [ { "inputs": [], "stateMutability": "nonpayable", diff --git a/hooks/usePermit.ts b/hooks/usePermit.ts new file mode 100644 index 00000000..f4b8124f --- /dev/null +++ b/hooks/usePermit.ts @@ -0,0 +1,99 @@ +import { useEffect } from "react"; +import { + useReadContracts, + useSignTypedData, + useAccount, +} from "wagmi"; +import { hexToSignature } from "viem"; +import { ERC20PermitAbi } from "@/artifacts/ERC20Permit.sol"; +import { useAlertContext, AlertContextProps } from "@/context/AlertContext"; +import { PUB_CHAIN, PUB_TOKEN_ADDRESS } from "@/constants"; + +export function usePermit() { + const { addAlert } = useAlertContext() as AlertContextProps; + + const account_address = useAccount().address!; + const erc20Contract = { + address: PUB_TOKEN_ADDRESS, + abi: ERC20PermitAbi, + }; + const { data: erc20data, refetch: erc20refetch } = useReadContracts({ + contracts: [{ + ...erc20Contract, + functionName: "nonces", + args: [account_address], + },{ + ...erc20Contract, + functionName: "name", + },{ + ...erc20Contract, + functionName: "version", + }] + }); + const [nonceResult, nameResult, versionResult] = erc20data || []; + + const { signTypedDataAsync: permitSign, status: permitSignStatus } = useSignTypedData(); + + useEffect(() => { + switch (permitSignStatus) { + case "idle": + case "pending": + return; + case "error": + addAlert("Could not sign the permit", { type: "error", timeout: 1500 }); + return; + case "success": + addAlert("Permit signed", { type: "success", timeout: 1500 }); + return; + } + }, [permitSignStatus]); + + const signPermit = async (dest: `0x${string}`, value: BigInt, deadline: BigInt = BigInt(Math.floor(Date.now() / 1000) + 60 * 60)) => { + if (!nonceResult || !nameResult || !versionResult) return; + + const nonce = BigInt(Number(nonceResult?.result)); + const erc20_name = String(nameResult?.result); + /* We assume 1 if permit version is not specified */ + const versionFromContract = String(versionResult?.result ?? '1'); + + const domain = { + chainId: PUB_CHAIN.id, + name: erc20_name, + version: versionFromContract, + verifyingContract: PUB_TOKEN_ADDRESS, + }; + + const types = { + Permit: [ + { name: 'owner', type: 'address' }, + { name: 'spender', type: 'address' }, + { name: 'value', type: 'uint256' }, + { name: 'nonce', type: 'uint256' }, + { name: 'deadline', type: 'uint256' }, + ], + }; + + const message = { + owner: account_address, + spender: dest, + value, + nonce, + deadline, + }; + + let sig = await permitSign({ + account: account_address, + types, + domain, + primaryType: 'Permit', + message, + }); + + return hexToSignature(sig); + }; + + return { + refetchPermitData: erc20refetch, + signPermit, + }; +} diff --git a/plugins/lockToVote/hooks/useProposalVeto.tsx b/plugins/lockToVote/hooks/useProposalVeto.tsx index c06df574..bd05aee5 100644 --- a/plugins/lockToVote/hooks/useProposalVeto.tsx +++ b/plugins/lockToVote/hooks/useProposalVeto.tsx @@ -3,16 +3,15 @@ import { usePublicClient, useWaitForTransactionReceipt, useWriteContract, - useReadContracts, - useSignTypedData, + useReadContract, useAccount, } from "wagmi"; -import { hexToSignature } from "viem"; +import { ERC20PermitAbi } from "@/artifacts/ERC20Permit.sol"; import { useProposal } from "./useProposal"; import { useProposalVetoes } from "@/plugins/lockToVote/hooks/useProposalVetoes"; import { useUserCanVeto } from "@/plugins/lockToVote/hooks/useUserCanVeto"; import { LockToVetoPluginAbi } from "@/plugins/lockToVote/artifacts/LockToVetoPlugin.sol"; -import { ERC20Abi } from "@/plugins/lockToVote/artifacts/ERC20withPermit.sol"; +import { usePermit } from "@/hooks/usePermit"; import { useAlertContext, AlertContextProps } from "@/context/AlertContext"; import { PUB_CHAIN, PUB_TOKEN_ADDRESS, PUB_LOCK_TO_VOTE_PLUGIN_ADDRESS } from "@/constants"; @@ -30,35 +29,18 @@ export function useProposalVeto(proposalId: string) { proposalId, proposal ); + const { signPermit, refetchPermitData } = usePermit(); const { addAlert } = useAlertContext() as AlertContextProps; const account_address = useAccount().address!; - const erc20Contract = { + const { data: balanceData } = useReadContract({ address: PUB_TOKEN_ADDRESS, - abi: ERC20Abi, - }; - const { data: erc20data } = useReadContracts({ - contracts: [{ - ...erc20Contract, - functionName: "balanceOf", - args: [account_address], - },{ - ...erc20Contract, - functionName: "nonces", - args: [account_address], - },{ - ...erc20Contract, - functionName: "name", - },{ - ...erc20Contract, - functionName: "version", - }] + abi: ERC20PermitAbi, + functionName: "balanceOf", + args: [account_address], }); - const [balanceResult, nonceResult, nameResult, versionResult] = erc20data || []; - - const { signTypedData: vetoPermitSign } = useSignTypedData(); const { writeContract: vetoWrite, data: vetoTxHash, @@ -79,6 +61,7 @@ export function useProposalVeto(proposalId: string) { timeout: 4 * 1000, }); } else { + console.error(vetoingError); addAlert("Could not create the veto", { type: "error" }); } return; @@ -101,61 +84,24 @@ export function useProposalVeto(proposalId: string) { }); refetchCanVeto(); refetchProposal(); + refetchPermitData(); }, [vetoingStatus, vetoTxHash, isConfirming, isConfirmed]); const vetoProposal = () => { - if (!proposal || !balanceResult || !nonceResult || !nameResult || !versionResult) return; - const balance = BigInt(Number(balanceResult?.result)); - const nonce = BigInt(Number(nonceResult?.result)); - const erc20_name = nameResult?.result; - const versionFromContract = versionResult?.result; - - let deadline = BigInt(Math.floor(Date.now() / 1000) + 60 * 60); - let value = balance; - let destination: `0x${string}` = PUB_LOCK_TO_VOTE_PLUGIN_ADDRESS; - - const domain = { - chainId: PUB_CHAIN.id, - name: String(erc20_name), - /* We assume 1 if permit version is not specified */ - version: String(versionFromContract ?? '1'), - verifyingContract: PUB_TOKEN_ADDRESS, - }; + let dest: `0x${string}` = PUB_LOCK_TO_VOTE_PLUGIN_ADDRESS; + let value = BigInt(Number(balanceData)); + let deadline = BigInt(Math.floor(Date.now() / 1000) + 60 * 60); // 1 hour from now - const types = { - Permit: [ - { name: 'owner', type: 'address' }, - { name: 'spender', type: 'address' }, - { name: 'value', type: 'uint256' }, - { name: 'nonce', type: 'uint256' }, - { name: 'deadline', type: 'uint256' }, - ], - }; - - const message = { - owner: account_address, - spender: destination, - value, - nonce, - deadline, - }; - - vetoPermitSign({ - account: account_address, - types, - domain, - primaryType: 'Permit', - message, - }, { onSuccess: (hexSignature) => { - let signature = hexToSignature(hexSignature); + signPermit(dest, value, deadline).then((sig) => { + if (!sig) return; vetoWrite({ abi: LockToVetoPluginAbi, - address: destination, + address: dest, functionName: "vetoPermit", - args: [proposalId, value, deadline, signature.v, signature.r, signature.s], + args: [proposalId, value, deadline, sig.v, sig.r, sig.s], }); - }}); + }); }; return { From d5e877fae4c229ae26bbcc13ef6a8adac49a7794 Mon Sep 17 00:00:00 2001 From: xavikh Date: Fri, 15 Mar 2024 11:57:09 +0100 Subject: [PATCH 5/6] Add user rejected alert --- hooks/usePermit.ts | 30 +++++++++++++------- plugins/lockToVote/hooks/useProposalVeto.tsx | 1 - 2 files changed, 20 insertions(+), 11 deletions(-) diff --git a/hooks/usePermit.ts b/hooks/usePermit.ts index f4b8124f..cd058884 100644 --- a/hooks/usePermit.ts +++ b/hooks/usePermit.ts @@ -32,7 +32,7 @@ export function usePermit() { }); const [nonceResult, nameResult, versionResult] = erc20data || []; - const { signTypedDataAsync: permitSign, status: permitSignStatus } = useSignTypedData(); + const { signTypedDataAsync: permitSign, status: permitSignStatus, error: permitSignError } = useSignTypedData(); useEffect(() => { switch (permitSignStatus) { @@ -40,7 +40,13 @@ export function usePermit() { case "pending": return; case "error": - addAlert("Could not sign the permit", { type: "error", timeout: 1500 }); + if (permitSignError?.message?.startsWith("User rejected the request")) { + addAlert("Transaction rejected by the user", { + timeout: 4 * 1000, + }); + } else { + addAlert("Could not sign the permit", { type: "error", timeout: 1500 }); + } return; case "success": addAlert("Permit signed", { type: "success", timeout: 1500 }); @@ -81,15 +87,19 @@ export function usePermit() { deadline, }; - let sig = await permitSign({ - account: account_address, - types, - domain, - primaryType: 'Permit', - message, - }); + try { + let sig = await permitSign({ + account: account_address, + types, + domain, + primaryType: 'Permit', + message, + }); - return hexToSignature(sig); + return hexToSignature(sig); + } catch (e) { + return; + } }; return { diff --git a/plugins/lockToVote/hooks/useProposalVeto.tsx b/plugins/lockToVote/hooks/useProposalVeto.tsx index bd05aee5..8406e0dd 100644 --- a/plugins/lockToVote/hooks/useProposalVeto.tsx +++ b/plugins/lockToVote/hooks/useProposalVeto.tsx @@ -61,7 +61,6 @@ export function useProposalVeto(proposalId: string) { timeout: 4 * 1000, }); } else { - console.error(vetoingError); addAlert("Could not create the veto", { type: "error" }); } return; From 599f45811616493eb27fb38a232aa325d0c4269c Mon Sep 17 00:00:00 2001 From: xavikh Date: Fri, 15 Mar 2024 17:09:48 +0100 Subject: [PATCH 6/6] Use proper type for addresses --- hooks/usePermit.ts | 4 ++-- plugins/lockToVote/hooks/useProposalVeto.tsx | 3 ++- utils/ipfs.ts | 4 ++-- 3 files changed, 6 insertions(+), 5 deletions(-) diff --git a/hooks/usePermit.ts b/hooks/usePermit.ts index cd058884..e33aa02e 100644 --- a/hooks/usePermit.ts +++ b/hooks/usePermit.ts @@ -4,7 +4,7 @@ import { useSignTypedData, useAccount, } from "wagmi"; -import { hexToSignature } from "viem"; +import { hexToSignature, Address } from "viem"; import { ERC20PermitAbi } from "@/artifacts/ERC20Permit.sol"; import { useAlertContext, AlertContextProps } from "@/context/AlertContext"; import { PUB_CHAIN, PUB_TOKEN_ADDRESS } from "@/constants"; @@ -54,7 +54,7 @@ export function usePermit() { } }, [permitSignStatus]); - const signPermit = async (dest: `0x${string}`, value: BigInt, deadline: BigInt = BigInt(Math.floor(Date.now() / 1000) + 60 * 60)) => { + const signPermit = async (dest: Address, value: BigInt, deadline: BigInt = BigInt(Math.floor(Date.now() / 1000) + 60 * 60)) => { if (!nonceResult || !nameResult || !versionResult) return; const nonce = BigInt(Number(nonceResult?.result)); diff --git a/plugins/lockToVote/hooks/useProposalVeto.tsx b/plugins/lockToVote/hooks/useProposalVeto.tsx index 8406e0dd..1968d9ec 100644 --- a/plugins/lockToVote/hooks/useProposalVeto.tsx +++ b/plugins/lockToVote/hooks/useProposalVeto.tsx @@ -6,6 +6,7 @@ import { useReadContract, useAccount, } from "wagmi"; +import { Address } from "viem"; import { ERC20PermitAbi } from "@/artifacts/ERC20Permit.sol"; import { useProposal } from "./useProposal"; import { useProposalVetoes } from "@/plugins/lockToVote/hooks/useProposalVetoes"; @@ -87,7 +88,7 @@ export function useProposalVeto(proposalId: string) { }, [vetoingStatus, vetoTxHash, isConfirming, isConfirmed]); const vetoProposal = () => { - let dest: `0x${string}` = PUB_LOCK_TO_VOTE_PLUGIN_ADDRESS; + let dest: Address = PUB_LOCK_TO_VOTE_PLUGIN_ADDRESS; let value = BigInt(Number(balanceData)); let deadline = BigInt(Math.floor(Date.now() / 1000) + 60 * 60); // 1 hour from now diff --git a/utils/ipfs.ts b/utils/ipfs.ts index 17b4611c..0c3d65a1 100644 --- a/utils/ipfs.ts +++ b/utils/ipfs.ts @@ -1,6 +1,6 @@ import { PUB_IPFS_ENDPOINT, PUB_IPFS_API_KEY } from "@/constants"; import { CID, IPFSHTTPClient } from "ipfs-http-client"; -import { fromHex } from "viem"; +import { fromHex, Address } from "viem"; export function fetchJsonFromIpfs(hexIpfsUri: string) { return fetchFromIPFS(hexIpfsUri).then((res) => res.json()); @@ -34,7 +34,7 @@ async function fetchFromIPFS(hexIpfsUri: string): Promise { } function getPath(hexIpfsUri: string) { - const decodedUri = fromHex(hexIpfsUri as `0x${string}`, "string"); + const decodedUri = fromHex(hexIpfsUri as Address, "string"); const path = decodedUri.includes("ipfs://") ? decodedUri.substring(7) : decodedUri;