Skip to content

Commit

Permalink
Governance: reset allocations when voting (#688)
Browse files Browse the repository at this point in the history
- Governance voting: reset allocations when voting
- Track allocated initiatives + reset on new allocations
  • Loading branch information
bpierre authored Jan 14, 2025
1 parent ee9a029 commit f7de49f
Show file tree
Hide file tree
Showing 7 changed files with 72 additions and 27 deletions.
10 changes: 5 additions & 5 deletions frontend/app/src/graphql/gql.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,6 @@ import * as types from './graphql';
* Learn more about it here: https://the-guild.dev/graphql/codegen/plugins/presets/preset-client#reducing-bundle-size
*/
const documents = {
"\n query TotalDeposited {\n collaterals {\n collIndex\n totalDeposited\n }\n }\n": types.TotalDepositedDocument,
"\n query TrovesCount($id: ID!) {\n borrowerInfo(id: $id) {\n troves\n trovesByCollateral\n }\n }\n": types.TrovesCountDocument,
"\n fragment FullTroveFragment on Trove {\n id\n borrower\n closedAt\n createdAt\n debt\n deposit\n interestRate\n mightBeLeveraged\n stake\n status\n troveId\n updatedAt\n collateral {\n id\n token {\n symbol\n name\n }\n minCollRatio\n collIndex\n }\n interestBatch {\n id\n annualInterestRate\n annualManagementFee\n batchManager\n }\n }\n": types.FullTroveFragmentFragmentDoc,
"\n query TrovesByAccount($account: Bytes!) {\n troves(\n where: {\n borrower: $account,\n status_in: [active,redeemed,liquidated],\n }\n orderBy: updatedAt\n orderDirection: desc\n ) {\n id\n borrower\n closedAt\n createdAt\n debt\n deposit\n interestRate\n mightBeLeveraged\n stake\n status\n troveId\n updatedAt\n collateral {\n id\n token {\n symbol\n name\n }\n minCollRatio\n collIndex\n }\n interestBatch {\n id\n annualInterestRate\n annualManagementFee\n batchManager\n }\n }\n }\n": types.TrovesByAccountDocument,
Expand All @@ -30,12 +29,9 @@ const documents = {
"\n query GovernanceInitiatives {\n governanceInitiatives {\n id\n }\n }\n": types.GovernanceInitiativesDocument,
"\n query GovernanceUser($id: ID!) {\n governanceUser(id: $id) {\n id\n allocatedLQTY\n stakedLQTY\n stakedOffset\n allocations {\n id\n atEpoch\n vetoLQTY\n voteLQTY\n initiative {\n id\n }\n }\n }\n }\n": types.GovernanceUserDocument,
"\n query GovernanceStats {\n governanceStats(id: \"stats\") {\n id\n totalLQTYStaked\n totalOffset\n totalInitiatives\n }\n }\n": types.GovernanceStatsDocument,
"\n query GovernanceUserAllocations($id: ID!) {\n governanceUser(id: $id) {\n allocated\n }\n }\n": types.GovernanceUserAllocationsDocument,
};

/**
* The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients.
*/
export function graphql(source: "\n query TotalDeposited {\n collaterals {\n collIndex\n totalDeposited\n }\n }\n"): typeof import('./graphql').TotalDepositedDocument;
/**
* The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients.
*/
Expand Down Expand Up @@ -92,6 +88,10 @@ export function graphql(source: "\n query GovernanceUser($id: ID!) {\n gover
* The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients.
*/
export function graphql(source: "\n query GovernanceStats {\n governanceStats(id: \"stats\") {\n id\n totalLQTYStaked\n totalOffset\n totalInitiatives\n }\n }\n"): typeof import('./graphql').GovernanceStatsDocument;
/**
* The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients.
*/
export function graphql(source: "\n query GovernanceUserAllocations($id: ID!) {\n governanceUser(id: $id) {\n allocated\n }\n }\n"): typeof import('./graphql').GovernanceUserAllocationsDocument;


export function graphql(source: string) {
Expand Down
37 changes: 23 additions & 14 deletions frontend/app/src/graphql/graphql.ts
Original file line number Diff line number Diff line change
Expand Up @@ -667,6 +667,7 @@ export enum GovernanceStats_OrderBy {

export type GovernanceUser = {
__typename?: 'GovernanceUser';
allocated: Array<Scalars['Bytes']['output']>;
allocatedLQTY: Scalars['BigInt']['output'];
allocations: Array<GovernanceAllocation>;
id: Scalars['ID']['output'];
Expand All @@ -686,6 +687,7 @@ export type GovernanceUserAllocationsArgs = {
export type GovernanceUser_Filter = {
/** Filter for the block changed event. */
_change_block?: InputMaybe<BlockChangedFilter>;
allocated?: InputMaybe<Array<Scalars['Bytes']['input']>>;
allocatedLQTY?: InputMaybe<Scalars['BigInt']['input']>;
allocatedLQTY_gt?: InputMaybe<Scalars['BigInt']['input']>;
allocatedLQTY_gte?: InputMaybe<Scalars['BigInt']['input']>;
Expand All @@ -694,6 +696,11 @@ export type GovernanceUser_Filter = {
allocatedLQTY_lte?: InputMaybe<Scalars['BigInt']['input']>;
allocatedLQTY_not?: InputMaybe<Scalars['BigInt']['input']>;
allocatedLQTY_not_in?: InputMaybe<Array<Scalars['BigInt']['input']>>;
allocated_contains?: InputMaybe<Array<Scalars['Bytes']['input']>>;
allocated_contains_nocase?: InputMaybe<Array<Scalars['Bytes']['input']>>;
allocated_not?: InputMaybe<Array<Scalars['Bytes']['input']>>;
allocated_not_contains?: InputMaybe<Array<Scalars['Bytes']['input']>>;
allocated_not_contains_nocase?: InputMaybe<Array<Scalars['Bytes']['input']>>;
allocations_?: InputMaybe<GovernanceAllocation_Filter>;
and?: InputMaybe<Array<InputMaybe<GovernanceUser_Filter>>>;
id?: InputMaybe<Scalars['ID']['input']>;
Expand Down Expand Up @@ -724,6 +731,7 @@ export type GovernanceUser_Filter = {
};

export enum GovernanceUser_OrderBy {
Allocated = 'allocated',
AllocatedLqty = 'allocatedLQTY',
Allocations = 'allocations',
Id = 'id',
Expand Down Expand Up @@ -2199,11 +2207,6 @@ export enum _SubgraphErrorPolicy_ {
Deny = 'deny'
}

export type TotalDepositedQueryVariables = Exact<{ [key: string]: never; }>;


export type TotalDepositedQuery = { __typename?: 'Query', collaterals: Array<{ __typename?: 'Collateral', collIndex: number, totalDeposited: bigint }> };

export type TrovesCountQueryVariables = Exact<{
id: Scalars['ID']['input'];
}>;
Expand Down Expand Up @@ -2288,6 +2291,13 @@ export type GovernanceStatsQueryVariables = Exact<{ [key: string]: never; }>;

export type GovernanceStatsQuery = { __typename?: 'Query', governanceStats?: { __typename?: 'GovernanceStats', id: string, totalLQTYStaked: bigint, totalOffset: bigint, totalInitiatives: number } | null };

export type GovernanceUserAllocationsQueryVariables = Exact<{
id: Scalars['ID']['input'];
}>;


export type GovernanceUserAllocationsQuery = { __typename?: 'Query', governanceUser?: { __typename?: 'GovernanceUser', allocated: Array<string> } | null };

export class TypedDocumentString<TResult, TVariables>
extends String
implements DocumentTypeDecoration<TResult, TVariables>
Expand Down Expand Up @@ -2350,14 +2360,6 @@ export const StabilityPoolDepositFragmentFragmentDoc = new TypedDocumentString(`
}
}
`, {"fragmentName":"StabilityPoolDepositFragment"}) as unknown as TypedDocumentString<StabilityPoolDepositFragmentFragment, unknown>;
export const TotalDepositedDocument = new TypedDocumentString(`
query TotalDeposited {
collaterals {
collIndex
totalDeposited
}
}
`) as unknown as TypedDocumentString<TotalDepositedQuery, TotalDepositedQueryVariables>;
export const TrovesCountDocument = new TypedDocumentString(`
query TrovesCount($id: ID!) {
borrowerInfo(id: $id) {
Expand Down Expand Up @@ -2548,4 +2550,11 @@ export const GovernanceStatsDocument = new TypedDocumentString(`
totalInitiatives
}
}
`) as unknown as TypedDocumentString<GovernanceStatsQuery, GovernanceStatsQueryVariables>;
`) as unknown as TypedDocumentString<GovernanceStatsQuery, GovernanceStatsQueryVariables>;
export const GovernanceUserAllocationsDocument = new TypedDocumentString(`
query GovernanceUserAllocations($id: ID!) {
governanceUser(id: $id) {
allocated
}
}
`) as unknown as TypedDocumentString<GovernanceUserAllocationsQuery, GovernanceUserAllocationsQueryVariables>;
6 changes: 6 additions & 0 deletions frontend/app/src/liquity-governance.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,9 @@ export function useGovernanceState() {
const Governance = getProtocolContract("Governance");
return useReadContracts({
contracts: [{
...Governance,
functionName: "epoch",
}, {
...Governance,
functionName: "epochStart",
}, {
Expand All @@ -31,12 +34,14 @@ export function useGovernanceState() {
}],
query: {
select: ([
epoch_,
epochStart_,
totalVotesAndState,
secondsWithinEpoch,
GOVERNANCE_EPOCH_DURATION,
GOVERNANCE_EPOCH_VOTING_CUTOFF,
]) => {
const epoch = epoch_.result ?? 0n;
const epochStart = epochStart_.result ?? 0n;
const epochDuration = GOVERNANCE_EPOCH_DURATION.result ?? 0n;
const epochVotingCutoff = GOVERNANCE_EPOCH_VOTING_CUTOFF.result ?? 0n;
Expand All @@ -54,6 +59,7 @@ export function useGovernanceState() {
countedVoteOffset: totalVotesAndState.result?.[1].countedVoteOffset,
daysLeft,
daysLeftRounded,
epoch,
epochEnd: epochStart + epochDuration,
epochStart,
period,
Expand Down
13 changes: 12 additions & 1 deletion frontend/app/src/subgraph-queries.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,10 @@ export async function graphQuery<TResult, TVariables>(
"Content-Type": "application/json",
Accept: "application/graphql-response+json",
},
body: JSON.stringify({ query, variables }),
body: JSON.stringify(
{ query, variables },
(_, value) => typeof value === "bigint" ? String(value) : value,
),
});

if (!response.ok) {
Expand Down Expand Up @@ -284,3 +287,11 @@ export const GovernanceStats = graphql(`
}
}
`);

export const GovernanceUserAllocated = graphql(`
query GovernanceUserAllocations($id: ID!) {
governanceUser(id: $id) {
allocated
}
}
`);
14 changes: 8 additions & 6 deletions frontend/app/src/tx-flows/allocateVotingPower.tsx
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
import type { FlowDeclaration } from "@/src/services/TransactionFlow";
import type { Address, Initiative, VoteAllocation } from "@/src/types";

import { AddressLink } from "@/src/comps/AddressLink/AddressLink";
import { Amount } from "@/src/comps/Amount/Amount";
import { getUserStates, useInitiatives } from "@/src/liquity-governance";
import { TransactionDetailsRow } from "@/src/screens/TransactionsScreen/TransactionsScreen";
import { TransactionStatus } from "@/src/screens/TransactionsScreen/TransactionStatus";
import { GovernanceUserAllocated, graphQuery } from "@/src/subgraph-queries";
import { vVoteAllocations } from "@/src/valibot-utils";
import { css } from "@/styled-system/css";
import { IconDownvote, IconUpvote } from "@liquity2/uikit";
Expand Down Expand Up @@ -181,23 +183,23 @@ export const allocateVotingPower: FlowDeclaration<AllocateVotingPowerRequest> =
let qty = dn.mul([unallocatedLQTY, 18], vote.value)[0];
remainingLqty -= qty;

// allocate any remaining LQTY to the last initiative
// if (index === initiativeAddresses.length - 1 && remainingLqty > 0n) {
// qty += remainingLqty;
// }

if (vote?.vote === "for") {
allocationArgs.votes[index] = qty;
} else if (vote?.vote === "against") {
allocationArgs.vetos[index] = qty;
}
}

const allocated = await graphQuery(
GovernanceUserAllocated,
{ id: account.toLowerCase() },
);

return writeContract(wagmiConfig, {
...contracts.Governance,
functionName: "allocateLQTY",
args: [
[],
(allocated.governanceUser?.allocated ?? []) as Address[],
allocationArgs.initiatives,
allocationArgs.votes,
allocationArgs.vetos,
Expand Down
1 change: 1 addition & 0 deletions subgraph/schema.graphql
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,7 @@ type GovernanceUser @entity {
stakedLQTY: BigInt!
stakedOffset: BigInt!
allocations: [GovernanceAllocation!]! @derivedFrom(field: "user")
allocated: [Bytes!]!
}

# Allocation of a user to a given initiative.
Expand Down
18 changes: 17 additions & 1 deletion subgraph/src/Governance.mapping.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { BigInt } from "@graphprotocol/graph-ts";
import { BigInt, Bytes } from "@graphprotocol/graph-ts";
import {
AllocateLQTY as AllocateLQTYEvent,
ClaimForInitiative as ClaimForInitiativeEvent,
Expand Down Expand Up @@ -51,6 +51,7 @@ export function handleDepositLQTY(event: DepositLQTYEvent): void {
let user = GovernanceUser.load(event.params.user.toHex());
if (user === null) {
user = new GovernanceUser(event.params.user.toHex());
user.allocated = [];
user.allocatedLQTY = BigInt.fromI32(0);
user.stakedLQTY = BigInt.fromI32(0);
user.stakedOffset = BigInt.fromI32(0);
Expand Down Expand Up @@ -112,6 +113,8 @@ export function handleAllocateLQTY(event: AllocateLQTYEvent): void {
allocation.vetoLQTY = BigInt.fromI32(0);
}

let wasAllocated = allocation.voteLQTY.gt(BigInt.fromI32(0)) || allocation.vetoLQTY.gt(BigInt.fromI32(0));

// votes
allocation.voteLQTY = allocation.voteLQTY.plus(event.params.deltaVoteLQTY);
user.allocatedLQTY = user.allocatedLQTY.plus(event.params.deltaVoteLQTY);
Expand All @@ -122,6 +125,19 @@ export function handleAllocateLQTY(event: AllocateLQTYEvent): void {

allocation.atEpoch = event.params.atEpoch;

let isAllocated = allocation.voteLQTY.gt(BigInt.fromI32(0)) || allocation.vetoLQTY.gt(BigInt.fromI32(0));

let allocated = user.allocated;
let initiativeAddress = Bytes.fromHexString(initiativeId);
if (!wasAllocated && isAllocated && !allocated.includes(initiativeAddress)) {
allocated.push(Bytes.fromHexString(initiativeId));
user.allocated = allocated;
} else if (wasAllocated && !isAllocated && allocated.includes(initiativeAddress)) {
let index = allocated.indexOf(initiativeAddress);
allocated.splice(index, 1);
user.allocated = allocated;
}

allocation.save();
user.save();
initiative.save();
Expand Down

0 comments on commit f7de49f

Please sign in to comment.