Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add a standard Multisig UI #120

Merged
merged 6 commits into from
Oct 1, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,8 @@ NEXT_PUBLIC_TOKEN_ADDRESS=0x9A3218197C77F54BB2510FBBcc7Da3A4D2bE0DeE
# Plugin addresses
NEXT_PUBLIC_TOKEN_VOTING_PLUGIN_ADDRESS=0x7AdF2545e746E014887916e476DfCB3Fb57D78b0
NEXT_PUBLIC_LOCK_TO_VOTE_PLUGIN_ADDRESS=
NEXT_PUBLIC_MULTISIG_PLUGIN_ADDRESS=0xf49d54D40A331441536BDF74C44FFb527cf113c9
NEXT_PUBLIC_MULTISIG_PLUGIN_ADDRESS=0xA4371a239D08bfBA6E8894eccf8466C6323A52C3
NEXT_PUBLIC_OPT_MULTISIG_PLUGIN_ADDRESS=0xf49d54D40A331441536BDF74C44FFb527cf113c9
NEXT_PUBLIC_EMERGENCY_MULTISIG_PLUGIN_ADDRESS=0x3abd07A24a39eCEB2a701f3C4A5BBbcb7069460D
NEXT_PUBLIC_DUAL_GOVERNANCE_PLUGIN_ADDRESS=0x31df2Cf73f36732c10523E4F228a458292B8F6DF
NEXT_PUBLIC_PUBLIC_KEY_REGISTRY_CONTRACT_ADDRESS=0x4BA2de07E5B7FB284d363DBb4c481F330c25b2A5
Expand Down
1 change: 1 addition & 0 deletions constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ export const PUB_DAO_ADDRESS = (process.env.NEXT_PUBLIC_DAO_ADDRESS ?? "") as Ad
export const PUB_TOKEN_ADDRESS = (process.env.NEXT_PUBLIC_TOKEN_ADDRESS ?? "") as Address;

export const PUB_MULTISIG_PLUGIN_ADDRESS = (process.env.NEXT_PUBLIC_MULTISIG_PLUGIN_ADDRESS ?? "") as Address;
export const PUB_OPT_MULTISIG_PLUGIN_ADDRESS = (process.env.NEXT_PUBLIC_OPT_MULTISIG_PLUGIN_ADDRESS ?? "") as Address;
export const PUB_EMERGENCY_MULTISIG_PLUGIN_ADDRESS = (process.env.NEXT_PUBLIC_EMERGENCY_MULTISIG_PLUGIN_ADDRESS ??
"") as Address;
export const PUB_DUAL_GOVERNANCE_PLUGIN_ADDRESS = (process.env.NEXT_PUBLIC_DUAL_GOVERNANCE_PLUGIN_ADDRESS ??
Expand Down
6 changes: 3 additions & 3 deletions plugins/emergency-multisig/hooks/useProposalApprovals.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { useState, useEffect } from "react";
import { Address, getAbiItem } from "viem";
import { usePublicClient } from "wagmi";
import { ApprovedEvent, ApprovedEventResponse, EmergencyProposal } from "../utils/types";
import { ApprovedEvent, EmergencyProposal } from "../utils/types";
import { EmergencyMultisigPluginAbi } from "../artifacts/EmergencyMultisigPlugin";
import { PUB_CHAIN } from "@/constants";

Expand All @@ -17,15 +17,15 @@ export function useProposalApprovals(pluginAddress: Address, proposalId: string,
async function getLogs() {
if (!publicClient || !proposal?.parameters?.snapshotBlock) return;

const logs: ApprovedEventResponse[] = (await publicClient.getLogs({
const logs = await publicClient.getLogs({
address: pluginAddress,
event: event,
args: {
proposalId: BigInt(proposalId),
},
fromBlock: proposal.parameters.snapshotBlock,
toBlock: "latest", // TODO: Make this variable between 'latest' and proposal last block
})) as any;
});

const newLogs = logs.flatMap((log) => log.args);
if (newLogs.length > proposalLogs.length) setLogs(newLogs);
Expand Down
49 changes: 29 additions & 20 deletions plugins/emergency-multisig/hooks/useProposalVariantStatus.ts
Original file line number Diff line number Diff line change
@@ -1,22 +1,26 @@
import { useState, useEffect } from "react";
import { EmergencyProposal } from "@/plugins/emergency-multisig/utils/types";
import { ProposalStatus } from "@aragon/ods";
import dayjs from "dayjs";
import { EmergencyProposal } from "../utils/types";

export const useProposalVariantStatus = (proposal: EmergencyProposal) => {
const [status, setStatus] = useState({ variant: "", label: "" });

useEffect(() => {
if (!proposal || !proposal?.parameters) return;
setStatus(
proposal?.approvals >= proposal?.parameters?.minApprovals
? proposal?.executed
? { variant: "success", label: "Executed" }
: { variant: "success", label: "Executable" }
: dayjs().isAfter(dayjs(Number(proposal?.parameters.expirationDate) * 1000))
? { variant: "critical", label: "Failed" }
: { variant: "info", label: "Active" }
);

if (proposal?.executed) {
setStatus({ variant: "primary", label: "Executed" });
} else if (Math.floor(Date.now() / 1000) >= proposal.parameters.expirationDate) {
if (proposal.approvals < proposal.parameters.minApprovals) {
setStatus({ variant: "critical", label: "Defeated" });
} else {
setStatus({ variant: "critical", label: "Expired" });
}
} else if (proposal.approvals >= proposal.parameters.minApprovals) {
setStatus({ variant: "success", label: "Executable" });
} else {
setStatus({ variant: "info", label: "Active" });
}
}, [proposal, proposal?.approvals, proposal?.executed, proposal?.parameters?.minApprovals]);

return status;
Expand All @@ -27,15 +31,20 @@ export const useProposalStatus = (proposal: EmergencyProposal) => {

useEffect(() => {
if (!proposal || !proposal?.parameters) return;
setStatus(
proposal?.approvals >= proposal?.parameters?.minApprovals
? proposal?.executed
? ProposalStatus.EXECUTED
: ProposalStatus.ACCEPTED
: dayjs().isAfter(dayjs(Number(proposal?.parameters.expirationDate) * 1000))
? ProposalStatus.FAILED
: ProposalStatus.ACTIVE
);

if (proposal?.executed) {
setStatus(ProposalStatus.EXECUTED);
} else if (Math.floor(Date.now() / 1000) >= proposal.parameters.expirationDate) {
if (proposal.approvals < proposal.parameters.minApprovals) {
setStatus(ProposalStatus.REJECTED);
} else {
setStatus(ProposalStatus.EXPIRED);
}
} else if (proposal.approvals >= proposal.parameters.minApprovals) {
setStatus(ProposalStatus.EXECUTABLE);
} else {
setStatus(ProposalStatus.ACTIVE);
}
}, [proposal, proposal?.approvals, proposal?.executed, proposal?.parameters?.minApprovals]);

return status;
Expand Down
8 changes: 2 additions & 6 deletions plugins/emergency-multisig/utils/types.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -43,11 +43,7 @@ export type EmergencyProposal = {
resources: IProposalResource[];
};

export type ApprovedEventResponse = {
args: ApprovedEvent[];
};

export type ApprovedEvent = {
proposalId: bigint;
approver: Address;
proposalId?: bigint;
approver?: Address;
};
16 changes: 12 additions & 4 deletions plugins/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import {
PUB_EMERGENCY_MULTISIG_PLUGIN_ADDRESS,
PUB_TOKEN_VOTING_PLUGIN_ADDRESS,
PUB_LOCK_TO_VOTE_PLUGIN_ADDRESS,
PUB_OPT_MULTISIG_PLUGIN_ADDRESS,
} from "@/constants";
import { IconType } from "@aragon/ods";

Expand All @@ -20,6 +21,13 @@ type PluginItem = {
};

export const plugins: PluginItem[] = [
{
id: "multisig",
folderName: "multisig",
title: "Multisig",
// icon: IconType.BLOCKCHAIN_BLOCKCHAIN,
pluginAddress: PUB_MULTISIG_PLUGIN_ADDRESS,
},
{
id: "token-voting",
folderName: "tokenVoting",
Expand All @@ -42,11 +50,11 @@ export const plugins: PluginItem[] = [
pluginAddress: PUB_DUAL_GOVERNANCE_PLUGIN_ADDRESS,
},
{
id: "multisig",
folderName: "multisig",
title: "Multisig",
id: "opt-multisig",
folderName: "opt-multisig",
title: "Multisig (Optimistic)",
// icon: IconType.BLOCKCHAIN_BLOCKCHAIN,
pluginAddress: PUB_MULTISIG_PLUGIN_ADDRESS,
pluginAddress: PUB_OPT_MULTISIG_PLUGIN_ADDRESS,
},
{
id: "emergency",
Expand Down
6 changes: 4 additions & 2 deletions plugins/lockToVote/components/vote/vetoes-section.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -28,10 +28,12 @@ const VetoCard = function ({ veto }: { veto: VetoCastEvent }) {
<Card className="p-3">
<div className="space-between flex flex-row">
<div className="flex flex-grow">
<Blockies className="rounded-3xl" size={9} seed={veto?.voter} />
<Blockies className="rounded-3xl" size={9} seed={veto?.voter ?? ""} />
<div className="px-2">
<AddressText>{veto?.voter}</AddressText>
<p className="text-sm text-neutral-600">{compactNumber(formatUnits(veto.votingPower, 18))} votes</p>
<p className="text-sm text-neutral-600">
{compactNumber(formatUnits(veto.votingPower ?? BigInt(0), 18))} votes
</p>
</div>
</div>
</div>
Expand Down
39 changes: 19 additions & 20 deletions plugins/lockToVote/hooks/useProposalVariantStatus.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { useState, useEffect } from "react";
import { Proposal } from "@/plugins/lockToVote/utils/types";
import { Proposal } from "../utils/types";
import { ProposalStatus } from "@aragon/ods";
import { useToken } from "./useToken";

Expand All @@ -9,18 +9,17 @@ export const useProposalVariantStatus = (proposal: Proposal) => {

useEffect(() => {
if (!proposal || !proposal?.parameters || !totalSupply) return;

const minVetoVotingPower = (totalSupply * BigInt(proposal.parameters.minVetoVotingPower)) / BigInt(1_000_000);

setStatus(
proposal?.vetoTally >= minVetoVotingPower
? { variant: "critical", label: "Defeated" }
: proposal?.active
? { variant: "info", label: "Active" }
: proposal?.executed
? { variant: "primary", label: "Executed" }
: { variant: "success", label: "Executable" }
);
if (proposal?.active) {
setStatus({ variant: "info", label: "Active" });
} else if (proposal?.executed) {
setStatus({ variant: "primary", label: "Executed" });
} else if (proposal?.vetoTally >= minVetoVotingPower) {
setStatus({ variant: "critical", label: "Defeated" });
} else {
setStatus({ variant: "success", label: "Executable" });
}
}, [
proposal?.vetoTally,
proposal?.active,
Expand All @@ -40,15 +39,15 @@ export const useProposalStatus = (proposal: Proposal) => {

const minVetoVotingPower = (totalSupply * BigInt(proposal.parameters.minVetoVotingPower)) / BigInt(1_000_000);

setStatus(
proposal?.vetoTally >= minVetoVotingPower
? ProposalStatus.VETOED
: proposal?.active
? ProposalStatus.ACTIVE
: proposal?.executed
? ProposalStatus.EXECUTED
: ProposalStatus.ACCEPTED
);
if (proposal?.active) {
setStatus(ProposalStatus.ACTIVE);
} else if (proposal?.executed) {
setStatus(ProposalStatus.EXECUTED);
} else if (proposal?.vetoTally >= minVetoVotingPower) {
setStatus(ProposalStatus.VETOED);
} else {
setStatus(ProposalStatus.ACCEPTED);
}
}, [
proposal?.vetoTally,
proposal?.active,
Expand Down
6 changes: 3 additions & 3 deletions plugins/lockToVote/hooks/useProposalVetoes.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { useState, useEffect } from "react";
import { Address, getAbiItem } from "viem";
import { LockToVetoPluginAbi } from "../artifacts/LockToVetoPlugin.sol";
import { Proposal, VetoCastEvent, VoteCastResponse } from "../utils/types";
import { Proposal, VetoCastEvent } from "../utils/types";
import { usePublicClient } from "wagmi";
import { PUB_CHAIN } from "@/constants";

Expand All @@ -17,15 +17,15 @@ export function useProposalVetoes(pluginAddress: Address, proposalId: number, pr
async function getLogs() {
if (!publicClient || !proposal?.parameters?.snapshotBlock) return;

const logs: VoteCastResponse[] = (await publicClient.getLogs({
const logs = await publicClient.getLogs({
address: pluginAddress,
event,
args: {
proposalId: BigInt(proposalId),
},
fromBlock: proposal.parameters.snapshotBlock,
toBlock: "latest", // TODO: Make this variable between 'latest' and proposal last block
})) as any;
});

const newLogs = logs.flatMap((log) => log.args);
if (newLogs.length > proposalLogs.length) setLogs(newLogs);
Expand Down
6 changes: 3 additions & 3 deletions plugins/lockToVote/utils/types.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ export type VoteCastResponse = {
};

export type VetoCastEvent = {
voter: Address;
proposalId: bigint;
votingPower: bigint;
voter?: Address;
proposalId?: bigint;
votingPower?: bigint;
};
33 changes: 17 additions & 16 deletions plugins/members/artifacts/MultisigPlugin.sol.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,14 @@ export const MultisigPluginAbi = [
name: "DaoUnauthorized",
type: "error",
},
{
inputs: [
{ internalType: "uint64", name: "limit", type: "uint64" },
{ internalType: "uint64", name: "actual", type: "uint64" },
],
name: "DateOutOfBounds",
type: "error",
},
{
inputs: [{ internalType: "address", name: "member", type: "address" }],
name: "InvalidAddresslistUpdate",
Expand Down Expand Up @@ -72,12 +80,6 @@ export const MultisigPluginAbi = [
name: "BeaconUpgraded",
type: "event",
},
{
anonymous: false,
inputs: [{ indexed: true, internalType: "uint256", name: "proposalId", type: "uint256" }],
name: "Executed",
type: "event",
},
{
anonymous: false,
inputs: [{ indexed: false, internalType: "uint8", name: "version", type: "uint8" }],
Expand Down Expand Up @@ -107,7 +109,6 @@ export const MultisigPluginAbi = [
inputs: [
{ indexed: false, internalType: "bool", name: "onlyListed", type: "bool" },
{ indexed: true, internalType: "uint16", name: "minApprovals", type: "uint16" },
{ indexed: false, internalType: "uint64", name: "destinationProposalDuration", type: "uint64" },
],
name: "MultisigSettingsUpdated",
type: "event",
Expand Down Expand Up @@ -220,11 +221,14 @@ export const MultisigPluginAbi = [
{ internalType: "bytes", name: "data", type: "bytes" },
],
internalType: "struct IDAO.Action[]",
name: "_destinationActions",
name: "_actions",
type: "tuple[]",
},
{ internalType: "contract OptimisticTokenVotingPlugin", name: "_destinationPlugin", type: "address" },
{ internalType: "uint256", name: "_allowFailureMap", type: "uint256" },
{ internalType: "bool", name: "_approveProposal", type: "bool" },
{ internalType: "bool", name: "_tryExecution", type: "bool" },
{ internalType: "uint64", name: "_startDate", type: "uint64" },
{ internalType: "uint64", name: "_endDate", type: "uint64" },
],
name: "createProposal",
outputs: [{ internalType: "uint256", name: "proposalId", type: "uint256" }],
Expand Down Expand Up @@ -255,24 +259,24 @@ export const MultisigPluginAbi = [
components: [
{ internalType: "uint16", name: "minApprovals", type: "uint16" },
{ internalType: "uint64", name: "snapshotBlock", type: "uint64" },
{ internalType: "uint64", name: "expirationDate", type: "uint64" },
{ internalType: "uint64", name: "startDate", type: "uint64" },
{ internalType: "uint64", name: "endDate", type: "uint64" },
],
internalType: "struct Multisig.ProposalParameters",
name: "parameters",
type: "tuple",
},
{ internalType: "bytes", name: "metadataURI", type: "bytes" },
{
components: [
{ internalType: "address", name: "to", type: "address" },
{ internalType: "uint256", name: "value", type: "uint256" },
{ internalType: "bytes", name: "data", type: "bytes" },
],
internalType: "struct IDAO.Action[]",
name: "destinationActions",
name: "actions",
type: "tuple[]",
},
{ internalType: "contract OptimisticTokenVotingPlugin", name: "destinationPlugin", type: "address" },
{ internalType: "uint256", name: "allowFailureMap", type: "uint256" },
],
stateMutability: "view",
type: "function",
Expand Down Expand Up @@ -302,7 +306,6 @@ export const MultisigPluginAbi = [
components: [
{ internalType: "bool", name: "onlyListed", type: "bool" },
{ internalType: "uint16", name: "minApprovals", type: "uint16" },
{ internalType: "uint64", name: "destinationProposalDuration", type: "uint64" },
],
internalType: "struct Multisig.MultisigSettings",
name: "_multisigSettings",
Expand Down Expand Up @@ -351,7 +354,6 @@ export const MultisigPluginAbi = [
outputs: [
{ internalType: "bool", name: "onlyListed", type: "bool" },
{ internalType: "uint16", name: "minApprovals", type: "uint16" },
{ internalType: "uint64", name: "destinationProposalDuration", type: "uint64" },
],
stateMutability: "view",
type: "function",
Expand Down Expand Up @@ -397,7 +399,6 @@ export const MultisigPluginAbi = [
components: [
{ internalType: "bool", name: "onlyListed", type: "bool" },
{ internalType: "uint16", name: "minApprovals", type: "uint16" },
{ internalType: "uint64", name: "destinationProposalDuration", type: "uint64" },
],
internalType: "struct Multisig.MultisigSettings",
name: "_multisigSettings",
Expand Down
Loading
Loading