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

bug: governance and unbonding fixes #1044

Merged
merged 4 commits into from
Aug 29, 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
2 changes: 1 addition & 1 deletion apps/namadillo/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
"license": "MIT",
"private": true,
"dependencies": {
"@anomaorg/namada-indexer-client": "0.0.21",
"@anomaorg/namada-indexer-client": "0.0.23",
"@cosmjs/encoding": "^0.32.3",
"@tailwindcss/container-queries": "^0.1.1",
"@tanstack/react-query": "^5.40.0",
Expand Down
4 changes: 3 additions & 1 deletion apps/namadillo/src/App/Governance/ProposalHeader.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -296,7 +296,9 @@ const VoteButton: React.FC<{
}> = ({ proposal, voted, proposalId }) => {
const navigate = useNavigate();
const isExtensionConnected = useAtomValue(namadaExtensionConnectedAtom);
const canVote = useAtomValue(canVoteAtom);
const canVote = useAtomValue(
canVoteAtom(proposal.data?.startEpoch || BigInt(-1))
);

if (!isExtensionConnected) {
return null;
Expand Down
7 changes: 6 additions & 1 deletion apps/namadillo/src/App/Governance/ProposalStatusSummary.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import BigNumber from "bignumber.js";
import clsx from "clsx";
import { AnimatePresence, motion } from "framer-motion";
import { useState } from "react";
import { useEffect, useState } from "react";

import { PieChart, PieChartData, Stack } from "@namada/components";
import { formatPercentage } from "@namada/utils";
Expand Down Expand Up @@ -165,6 +165,11 @@ const Loaded: React.FC<{
highestVoteType
);

useEffect(() => {
// Reset the hovered vote type when the highest vote type changes(on data poll)
setHoveredVoteType(highestVoteType);
}, [highestVoteType]);

const votedProportion =
totalVotingPower.isEqualTo(0) ?
BigNumber(0)
Expand Down
3 changes: 2 additions & 1 deletion apps/namadillo/src/App/Governance/SubmitVote.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -73,11 +73,12 @@ export const WithProposalId: React.FC<{ proposalId: bigint }> = ({
const [selectedVoteType, setSelectedVoteType] = useState<VoteType>();

const proposalQueryResult = useAtomValue(proposalFamily(proposalId));
const canVote = useAtomValue(canVoteAtom);

const proposal =
proposalQueryResult.isSuccess ? proposalQueryResult.data : null;

const canVote = useAtomValue(canVoteAtom(proposal?.startEpoch || BigInt(-1)));

const onCloseModal = (): void => navigate(-1);

const dispatchPendingNotification = (txs: TxProps[]): void => {
Expand Down
6 changes: 4 additions & 2 deletions apps/namadillo/src/atoms/api.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { DefaultApi } from "@anomaorg/namada-indexer-client";
import { Configuration, DefaultApi } from "@anomaorg/namada-indexer-client";
import { Atom, atom, getDefaultStore } from "jotai";
import { indexerUrlAtom } from "./settings";

Expand All @@ -14,5 +14,7 @@ export const getIndexerApi = (): DefaultApi => {
// Helper function to use outside of hooks
const getApi = (get: <Value>(atom: Atom<Value>) => Value): DefaultApi => {
const indexerUrl = get(indexerUrlAtom);
return new DefaultApi({ basePath: indexerUrl });
const configuration = new Configuration({ basePath: indexerUrl });

return new DefaultApi(configuration);
};
1 change: 1 addition & 0 deletions apps/namadillo/src/atoms/chain/services.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ export const fetchChainParameters = async (
1,
minEpochDuration: Number(parameters.minDuration),
minNumOfBlocks: Number(parameters.minNumOfBlocks),
maxBlockTime: Number(parameters.maxBlockTime),
epochSwitchBlocksDelay: Number(parameters.epochSwitchBlocksDelay),
},
apr: BigNumber(parameters.apr),
Expand Down
9 changes: 8 additions & 1 deletion apps/namadillo/src/atoms/etc.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,15 @@ import { atomWithStorage } from "jotai/utils";

type ControlRoutineProps = {
shouldUpdateAmount: boolean;
shouldUpdateProposal: boolean;
lastBlockHeight: BigNumber | undefined;
};

export const controlRoutineAtom = atomWithStorage<ControlRoutineProps>(
"namadillo:etc",
{
shouldUpdateAmount: false,
shouldUpdateProposal: false,
lastBlockHeight: undefined,
}
);
Expand All @@ -29,7 +31,12 @@ export const shouldUpdateBalanceAtom = atom(
changeProps<boolean>("shouldUpdateAmount")
);

export const shouldUpdateProposalAtom = atom(
(get) => get(controlRoutineAtom).shouldUpdateProposal,
changeProps<boolean>("shouldUpdateProposal")
);

export const lastBlockHeightAtom = atom(
(get) => get(controlRoutineAtom).shouldUpdateAmount,
(get) => get(controlRoutineAtom).lastBlockHeight,
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actually not sure if this is correct, just seemed wrong :D

changeProps<BigNumber | undefined>("lastBlockHeight")
);
51 changes: 30 additions & 21 deletions apps/namadillo/src/atoms/proposals/atoms.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,15 +21,16 @@ import {
fetchVotedProposalIds,
} from "./functions";

import {
Bond as NamadaIndexerBond,
BondStatusEnum as NamadaIndexerBondStatusEnum,
} from "@anomaorg/namada-indexer-client";
import { Bond as NamadaIndexerBond } from "@anomaorg/namada-indexer-client";
import { shouldUpdateProposalAtom } from "atoms/etc";
export const proposalFamily = atomFamily((id: bigint) =>
atomWithQuery((get) => {
const api = get(indexerApiAtom);
const enablePolling = get(shouldUpdateProposalAtom);

return {
// TODO: subscribe to indexer events when it's done
refetchInterval: enablePolling ? 1000 : false,
queryKey: ["proposal", id.toString()],
queryFn: () => fetchProposalById(api, id),
};
Expand Down Expand Up @@ -96,7 +97,11 @@ export const paginatedProposalsFamily = atomFamily(
export const votedProposalIdsAtom = atomWithQuery((get) => {
const account = get(defaultAccountAtom);
const api = get(indexerApiAtom);
const enablePolling = get(shouldUpdateProposalAtom);

return {
// TODO: subscribe to indexer events when it's done
refetchInterval: enablePolling ? 1000 : false,
queryKey: ["voted-proposal-ids", account.data],
...queryDependentFn(async () => {
if (typeof account.data === "undefined") {
Expand All @@ -107,25 +112,29 @@ export const votedProposalIdsAtom = atomWithQuery((get) => {
};
});

export const canVoteAtom = atomWithQuery((get) => {
const account = get(defaultAccountAtom);
const api = get(indexerApiAtom);
export const canVoteAtom = atomFamily((proposalStartEpoch: bigint) =>
atomWithQuery((get) => {
const account = get(defaultAccountAtom);
const api = get(indexerApiAtom);

return {
queryKey: ["can-vote"],
enabled: account.isSuccess,
queryFn: async () => {
const all_bonds = await api.apiV1PosBondAddressGet(account.data!.address);
return {
queryKey: ["can-vote", account.data, api],
enabled: account.isSuccess,
queryFn: async () => {
const all_bonds = await api.apiV1PosBondAddressGet(
account.data!.address
);

return all_bonds.data.results.reduce(
(acc: boolean, current: NamadaIndexerBond) => {
return acc || current.status === NamadaIndexerBondStatusEnum.Active;
},
false
);
},
};
});
return all_bonds.data.results.reduce(
(acc: boolean, current: NamadaIndexerBond) => {
return acc || Number(current.startEpoch) <= proposalStartEpoch;
},
false
);
},
};
})
);

type CreateVoteTxArgs = {
proposalId: bigint;
Expand Down
11 changes: 6 additions & 5 deletions apps/namadillo/src/atoms/proposals/functions.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import {
ApiV1GovProposalGetStatusEnum as ApiIndexerProposalStatusEnum,
DefaultApi,
Proposal as IndexerProposal,
ProposalStatusEnum as IndexerProposalStatusEnum,
Expand Down Expand Up @@ -257,16 +258,16 @@ const fromIndexerStatus = (

const toIndexerStatus = (
proposalStatus: ProposalStatus
): IndexerProposalStatusEnum => {
): ApiIndexerProposalStatusEnum => {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What does this change do? It looks we are using ProposalStatusEnum in some cases but ApiV1GovProposalGetStatusEnum in others and I'm not sure what the difference is.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So there are two enums specified in swagger file. One is for status used in query params and other is used as a part of proposal response type. I think they should always be the same(kind of), but in rust we have two, because query params and response are two separate things.
FIrst:

        status:
          type: string
          enum: [pending, voting, passed, rejected]

Second:

          schema:
            type: string
            enum: [pending, votingPeriod, passed, rejected]

It's a bit confusing :D We can potentially leave two enums in rust but have only one described in swagger 🤔

switch (proposalStatus) {
case "pending":
return IndexerProposalStatusEnum.Pending;
return ApiIndexerProposalStatusEnum.Pending;
case "ongoing":
return IndexerProposalStatusEnum.Voting;
return ApiIndexerProposalStatusEnum.VotingPeriod;
case "passed":
return IndexerProposalStatusEnum.Passed;
return ApiIndexerProposalStatusEnum.Passed;
case "rejected":
return IndexerProposalStatusEnum.Rejected;
return ApiIndexerProposalStatusEnum.Rejected;
default:
return assertNever(proposalStatus);
}
Expand Down
5 changes: 3 additions & 2 deletions apps/namadillo/src/atoms/settings/services.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { DefaultApi } from "@anomaorg/namada-indexer-client";
import { Configuration, DefaultApi } from "@anomaorg/namada-indexer-client";
import { isUrlValid } from "@namada/utils";
import toml from "toml";
import { SettingsTomlOptions } from "types";
Expand All @@ -8,7 +8,8 @@ export const isIndexerAlive = async (url: string): Promise<boolean> => {
return false;
}
try {
const api = new DefaultApi({ basePath: url });
const configuration = new Configuration({ basePath: url });
const api = new DefaultApi(configuration);
const response = await api.healthGet();
return response.status === 200;
} catch {
Expand Down
8 changes: 4 additions & 4 deletions apps/namadillo/src/atoms/validators/functions.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import {
Bond as IndexerBond,
MergedBond as IndexerMergedBond,
Unbond as IndexerUnbond,
Validator as IndexerValidator,
VotingPower as IndexerVotingPower,
Expand All @@ -18,10 +19,9 @@ export const toValidator = (
const expectedApr = nominalApr.times(1 - commission.toNumber());

// Because epoch duration is in reality longer by epochSwitchBlocksDelay we have to account for that
const timePerBlock = epochInfo.minEpochDuration / epochInfo.minNumOfBlocks;
const realMinEpochDuration =
epochInfo.minEpochDuration +
timePerBlock * epochInfo.epochSwitchBlocksDelay;
epochInfo.maxBlockTime * epochInfo.epochSwitchBlocksDelay;

const unbondingPeriod = singleUnitDurationFromInterval(
0,
Expand Down Expand Up @@ -62,7 +62,7 @@ export const calculateUnbondingTimeLeft = (unbond: IndexerUnbond): string => {
* an array of MyValidators objects
*/
export const toMyValidators = (
indexerBonds: IndexerBond[],
indexerBonds: IndexerBond[] | IndexerMergedBond[],
indexerUnbonds: IndexerUnbond[],
totalVotingPower: IndexerVotingPower,
epochInfo: EpochInfo,
Expand All @@ -86,7 +86,7 @@ export const toMyValidators = (
const addBondToAddress = (
address: Address,
key: "bondItems" | "unbondItems",
bond: IndexerBond | IndexerUnbond
bond: IndexerBond | IndexerMergedBond | IndexerUnbond
): void => {
const { validator: _, ...bondsWithoutValidator } = bond;
myValidators[address]![key].push(bondsWithoutValidator);
Expand Down
13 changes: 12 additions & 1 deletion apps/namadillo/src/hooks/useTransactionCallbacks.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { accountBalanceAtom } from "atoms/accounts";
import { shouldUpdateBalanceAtom } from "atoms/etc";
import { shouldUpdateBalanceAtom, shouldUpdateProposalAtom } from "atoms/etc";
import { useAtomValue, useSetAtom } from "jotai";
import { useTransactionEventListener } from "utils";

Expand All @@ -19,4 +19,15 @@ export const useTransactionCallback = (): void => {
useTransactionEventListener("Bond.Success", onBalanceUpdate);
useTransactionEventListener("Unbond.Success", onBalanceUpdate);
useTransactionEventListener("Withdraw.Success", onBalanceUpdate);

const shouldUpdateProposal = useSetAtom(shouldUpdateProposalAtom);

useTransactionEventListener("VoteProposal.Success", () => {
shouldUpdateProposal(true);

// This does not guarantee that the proposal will be updated,
// but because this is temporary solution(don't quote me on this), it should be fine :)
const timePolling = 12 * 1000;
setTimeout(() => shouldUpdateProposal(false), timePolling);
});
};
1 change: 1 addition & 0 deletions apps/namadillo/src/types.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ export type EpochInfo = {
unbondingPeriodInEpochs: number;
minEpochDuration: number;
minNumOfBlocks: number;
maxBlockTime: number;
epochSwitchBlocksDelay: number;
};

Expand Down
33 changes: 27 additions & 6 deletions yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -45,12 +45,12 @@ __metadata:
languageName: node
linkType: hard

"@anomaorg/namada-indexer-client@npm:0.0.21":
version: 0.0.21
resolution: "@anomaorg/namada-indexer-client@npm:0.0.21"
"@anomaorg/namada-indexer-client@npm:0.0.23":
version: 0.0.23
resolution: "@anomaorg/namada-indexer-client@npm:0.0.23"
dependencies:
axios: "npm:^0.21.1"
checksum: 4b9632145eee1d20672ad65eef447ed90e3abfc2fee09a5c3e40fc5fd9321ff83aff03acf4be75abe1c74c83fc635216f46f5c58155c11a291656bb822f05465
axios: "npm:^1.6.1"
checksum: 1249a851ad5ad09daa88bc621fbc33fe520a3f5912ab02d472c4d7b52f26fb83cee2f7818c634a55abb137dfdf5226d0ecd057e24895573199da069a5111e1cd
languageName: node
linkType: hard

Expand Down Expand Up @@ -8448,7 +8448,7 @@ __metadata:
version: 0.0.0-use.local
resolution: "@namada/namadillo@workspace:apps/namadillo"
dependencies:
"@anomaorg/namada-indexer-client": "npm:0.0.21"
"@anomaorg/namada-indexer-client": "npm:0.0.23"
"@cosmjs/encoding": "npm:^0.32.3"
"@playwright/test": "npm:^1.24.1"
"@release-it/keep-a-changelog": "npm:^5.0.0"
Expand Down Expand Up @@ -12954,6 +12954,17 @@ __metadata:
languageName: node
linkType: hard

"axios@npm:^1.6.1":
version: 1.7.4
resolution: "axios@npm:1.7.4"
dependencies:
follow-redirects: "npm:^1.15.6"
form-data: "npm:^4.0.0"
proxy-from-env: "npm:^1.1.0"
checksum: 5ea1a93140ca1d49db25ef8e1bd8cfc59da6f9220159a944168860ad15a2743ea21c5df2967795acb15cbe81362f5b157fdebbea39d53117ca27658bab9f7f17
languageName: node
linkType: hard

"axobject-query@npm:^2.2.0":
version: 2.2.0
resolution: "axobject-query@npm:2.2.0"
Expand Down Expand Up @@ -19364,6 +19375,16 @@ __metadata:
languageName: node
linkType: hard

"follow-redirects@npm:^1.15.6":
version: 1.15.6
resolution: "follow-redirects@npm:1.15.6"
peerDependenciesMeta:
debug:
optional: true
checksum: 9ff767f0d7be6aa6870c82ac79cf0368cd73e01bbc00e9eb1c2a16fbb198ec105e3c9b6628bb98e9f3ac66fe29a957b9645bcb9a490bb7aa0d35f908b6b85071
languageName: node
linkType: hard

"for-each@npm:^0.3.3":
version: 0.3.3
resolution: "for-each@npm:0.3.3"
Expand Down
Loading