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;