Feat/1512: equity like share vote (#1848)

* Feat/1512: Proposal type as enum, hoisted proposal type and min voting balance

* Feat/1512: Setup and new LP tally for update market proposals

* Feat/1512: Added LP data (if update market) to proposal-votes-table.tsx

* Feat/1512: Fixing some broken tests and added unit tests for proposal-votes-table

* Feat/1512: Tests for use-vote-information hook

* Feat/1512: Tests for use-network-params hook

* Feat/1512: Fixed some regenerated types

* Feat/1512: Addressed comments from PR

* Feat/1512: Condensed all useMemos in use-vote-information into a single one

* Feat/1512: Small tweak to Thumbs element
This commit is contained in:
Sam Keen 2022-10-26 13:44:10 +01:00 committed by GitHub
parent ab64cf69be
commit 068381d620
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
49 changed files with 1324 additions and 732 deletions

View File

@ -188,7 +188,8 @@
"errorDetails": "Error details", "errorDetails": "Error details",
"proposedNewValue": "Proposed new value:", "proposedNewValue": "Proposed new value:",
"proposalChange": "Change <code>{{key}}</code> to <code>{{value}}</code>", "proposalChange": "Change <code>{{key}}</code> to <code>{{value}}</code>",
"votes": "Votes", "tokenVotes": "Token votes",
"liquidityVotes": "Liquidity votes",
"yourVote": "Your vote", "yourVote": "Your vote",
"for": "For", "for": "For",
"against": "Against", "against": "Against",
@ -201,6 +202,7 @@
"toVote": "to vote", "toVote": "to vote",
"voteFor": "Vote for", "voteFor": "Vote for",
"voteAgainst": "Vote against", "voteAgainst": "Vote against",
"votingThresholdInfo": "If the token vote passes the participation threshold it will be the deciding vote. If not, the outcome will be determined by liquidity providers on this market.",
"noGovernanceTokens": "You need some VEGA tokens to participate in governance", "noGovernanceTokens": "You need some VEGA tokens to participate in governance",
"youVoted": "You voted", "youVoted": "You voted",
"changeVote": "Change vote", "changeVote": "Change vote",
@ -208,6 +210,8 @@
"votePending": "Casting vote", "votePending": "Casting vote",
"voteError": "Something went wrong, and your vote was not seen by the network", "voteError": "Something went wrong, and your vote was not seen by the network",
"back": "back", "back": "back",
"byTokenVote": "by Token vote",
"byLiquidityVote": "by Liquidity vote",
"youDidNotVote": "Voting has ended. You did not vote", "youDidNotVote": "Voting has ended. You did not vote",
"voteState_Yes": "For", "voteState_Yes": "For",
"voteState_No": "Against", "voteState_No": "Against",
@ -552,9 +556,12 @@
"reference": "Reference", "reference": "Reference",
"voteBreakdown": "Vote breakdown", "voteBreakdown": "Vote breakdown",
"willPass": "Will pass", "willPass": "Will pass",
"majorityMet": "Majority met", "majorityMet": "Token majority met",
"participationMet": "Participation met", "majorityLPMet": "Liquidity majority met",
"participationMet": "Token participation met",
"participationLPMet": "Liquidity participation met",
"tokenForProposal": "Tokens for proposal", "tokenForProposal": "Tokens for proposal",
"tokenLPForProposal": "Liquidity shares for proposal",
"tokensAgainstProposal": "Tokens against proposal", "tokensAgainstProposal": "Tokens against proposal",
"participationRequired": "Participation required", "participationRequired": "Participation required",
"numberOfVotingParties": "Number of voting parties", "numberOfVotingParties": "Number of voting parties",

View File

@ -64,6 +64,7 @@ fragment ProposalFields on Proposal {
yes { yes {
totalTokens totalTokens
totalNumber totalNumber
totalEquityLikeShareWeight
votes { votes {
value value
party { party {
@ -78,6 +79,7 @@ fragment ProposalFields on Proposal {
no { no {
totalTokens totalTokens
totalNumber totalNumber
totalEquityLikeShareWeight
votes { votes {
value value
party { party {

View File

@ -1,345 +0,0 @@
/* tslint:disable */
/* eslint-disable */
// @generated
// This file was automatically generated and should not be edited.
import { ProposalState, ProposalRejectionReason, VoteValue } from "@vegaprotocol/types";
// ====================================================
// GraphQL fragment: ProposalFields
// ====================================================
export interface ProposalFields_rationale {
__typename: "ProposalRationale";
/**
* Title to be used to give a short description of the proposal in lists.
* This is to be between 0 and 100 unicode characters.
* This is mandatory for all proposals.
*/
title: string;
/**
* Description to show a short title / something in case the link goes offline.
* This is to be between 0 and 20k unicode characters.
* This is mandatory for all proposals.
*/
description: string;
}
export interface ProposalFields_party {
__typename: "Party";
/**
* Party identifier
*/
id: string;
}
export interface ProposalFields_terms_change_NewFreeform {
__typename: "NewFreeform";
}
export interface ProposalFields_terms_change_NewMarket_instrument_futureProduct_settlementAsset {
__typename: "Asset";
/**
* The symbol of the asset (e.g: GBP)
*/
symbol: string;
}
export interface ProposalFields_terms_change_NewMarket_instrument_futureProduct {
__typename: "FutureProduct";
/**
* Product asset
*/
settlementAsset: ProposalFields_terms_change_NewMarket_instrument_futureProduct_settlementAsset;
}
export interface ProposalFields_terms_change_NewMarket_instrument {
__typename: "InstrumentConfiguration";
/**
* Full and fairly descriptive name for the instrument
*/
name: string;
/**
* A short non necessarily unique code used to easily describe the instrument (e.g: FX:BTCUSD/DEC18)
*/
code: string;
/**
* Future product specification
*/
futureProduct: ProposalFields_terms_change_NewMarket_instrument_futureProduct | null;
}
export interface ProposalFields_terms_change_NewMarket {
__typename: "NewMarket";
/**
* New market instrument configuration
*/
instrument: ProposalFields_terms_change_NewMarket_instrument;
}
export interface ProposalFields_terms_change_UpdateMarket {
__typename: "UpdateMarket";
marketId: string;
}
export interface ProposalFields_terms_change_NewAsset_source_BuiltinAsset {
__typename: "BuiltinAsset";
/**
* Maximum amount that can be requested by a party through the built-in asset faucet at a time
*/
maxFaucetAmountMint: string;
}
export interface ProposalFields_terms_change_NewAsset_source_ERC20 {
__typename: "ERC20";
/**
* The address of the ERC20 contract
*/
contractAddress: string;
}
export type ProposalFields_terms_change_NewAsset_source = ProposalFields_terms_change_NewAsset_source_BuiltinAsset | ProposalFields_terms_change_NewAsset_source_ERC20;
export interface ProposalFields_terms_change_NewAsset {
__typename: "NewAsset";
/**
* The full name of the asset (e.g: Great British Pound)
*/
name: string;
/**
* The symbol of the asset (e.g: GBP)
*/
symbol: string;
/**
* The source of the new asset
*/
source: ProposalFields_terms_change_NewAsset_source;
}
export interface ProposalFields_terms_change_UpdateNetworkParameter_networkParameter {
__typename: "NetworkParameter";
/**
* The name of the network parameter
*/
key: string;
/**
* The value of the network parameter
*/
value: string;
}
export interface ProposalFields_terms_change_UpdateNetworkParameter {
__typename: "UpdateNetworkParameter";
networkParameter: ProposalFields_terms_change_UpdateNetworkParameter_networkParameter;
}
export interface ProposalFields_terms_change_UpdateAsset_source {
__typename: "UpdateERC20";
/**
* The lifetime limits deposit per address
* Note: this is a temporary measure for alpha mainnet
*/
lifetimeLimit: string;
/**
* The maximum allowed per withdrawal
* Note: this is a temporary measure for alpha mainnet
*/
withdrawThreshold: string;
}
export interface ProposalFields_terms_change_UpdateAsset {
__typename: "UpdateAsset";
/**
* The minimum economically meaningful amount of this specific asset
*/
quantum: string;
/**
* The asset to update
*/
assetId: string;
/**
* The source of the updated asset
*/
source: ProposalFields_terms_change_UpdateAsset_source;
}
export type ProposalFields_terms_change = ProposalFields_terms_change_NewFreeform | ProposalFields_terms_change_NewMarket | ProposalFields_terms_change_UpdateMarket | ProposalFields_terms_change_NewAsset | ProposalFields_terms_change_UpdateNetworkParameter | ProposalFields_terms_change_UpdateAsset;
export interface ProposalFields_terms {
__typename: "ProposalTerms";
/**
* RFC3339Nano time and date when voting closes for this proposal.
* Constrained by "minClose" and "maxClose" network parameters.
*/
closingDatetime: string;
/**
* RFC3339Nano time and date when this proposal is executed (if passed). Note that it has to be after closing date time.
* Constrained by "minEnactInSeconds" and "maxEnactInSeconds" network parameters.
* Note: Optional as free form proposals do not require it.
*/
enactmentDatetime: string | null;
/**
* Actual change being introduced by the proposal - action the proposal triggers if passed and enacted.
*/
change: ProposalFields_terms_change;
}
export interface ProposalFields_votes_yes_votes_party_stakingSummary {
__typename: "StakingSummary";
/**
* The stake currently available for the party
*/
currentStakeAvailable: string;
}
export interface ProposalFields_votes_yes_votes_party {
__typename: "Party";
/**
* Party identifier
*/
id: string;
/**
* The staking information for this Party
*/
stakingSummary: ProposalFields_votes_yes_votes_party_stakingSummary;
}
export interface ProposalFields_votes_yes_votes {
__typename: "Vote";
/**
* The vote value cast
*/
value: VoteValue;
/**
* The party casting the vote
*/
party: ProposalFields_votes_yes_votes_party;
/**
* RFC3339Nano time and date when the vote reached Vega network
*/
datetime: string;
}
export interface ProposalFields_votes_yes {
__typename: "ProposalVoteSide";
/**
* Total number of governance tokens from the votes cast for this side
*/
totalTokens: string;
/**
* Total number of votes cast for this side
*/
totalNumber: string;
/**
* All votes cast for this side
*/
votes: ProposalFields_votes_yes_votes[] | null;
}
export interface ProposalFields_votes_no_votes_party_stakingSummary {
__typename: "StakingSummary";
/**
* The stake currently available for the party
*/
currentStakeAvailable: string;
}
export interface ProposalFields_votes_no_votes_party {
__typename: "Party";
/**
* Party identifier
*/
id: string;
/**
* The staking information for this Party
*/
stakingSummary: ProposalFields_votes_no_votes_party_stakingSummary;
}
export interface ProposalFields_votes_no_votes {
__typename: "Vote";
/**
* The vote value cast
*/
value: VoteValue;
/**
* The party casting the vote
*/
party: ProposalFields_votes_no_votes_party;
/**
* RFC3339Nano time and date when the vote reached Vega network
*/
datetime: string;
}
export interface ProposalFields_votes_no {
__typename: "ProposalVoteSide";
/**
* Total number of governance tokens from the votes cast for this side
*/
totalTokens: string;
/**
* Total number of votes cast for this side
*/
totalNumber: string;
/**
* All votes cast for this side
*/
votes: ProposalFields_votes_no_votes[] | null;
}
export interface ProposalFields_votes {
__typename: "ProposalVotes";
/**
* Yes votes cast for this proposal
*/
yes: ProposalFields_votes_yes;
/**
* No votes cast for this proposal
*/
no: ProposalFields_votes_no;
}
export interface ProposalFields {
__typename: "Proposal";
/**
* Proposal ID that is filled by Vega once proposal reaches the network
*/
id: string | null;
/**
* Rationale behind the proposal
*/
rationale: ProposalFields_rationale;
/**
* A UUID reference to aid tracking proposals on Vega
*/
reference: string;
/**
* State of the proposal
*/
state: ProposalState;
/**
* RFC3339Nano time and date when the proposal reached Vega network
*/
datetime: string;
/**
* Why the proposal was rejected by the core
*/
rejectionReason: ProposalRejectionReason | null;
/**
* Party that prepared the proposal
*/
party: ProposalFields_party;
/**
* Error details of the rejectionReason
*/
errorDetails: string | null;
/**
* Terms of the proposal
*/
terms: ProposalFields_terms;
/**
* Votes cast for this proposal
*/
votes: ProposalFields_votes;
}

View File

@ -3,19 +3,19 @@ import { Schema as Types } from '@vegaprotocol/types';
import { gql } from '@apollo/client'; import { gql } from '@apollo/client';
import * as Apollo from '@apollo/client'; import * as Apollo from '@apollo/client';
const defaultOptions = {} as const; const defaultOptions = {} as const;
export type ProposalFieldsFragment = { __typename?: 'Proposal', id?: string | null, reference: string, state: Types.ProposalState, datetime: string, rejectionReason?: Types.ProposalRejectionReason | null, errorDetails?: string | null, party: { __typename?: 'Party', id: string }, terms: { __typename?: 'ProposalTerms', closingDatetime: string, enactmentDatetime?: string | null, change: { __typename: 'NewAsset', name: string, symbol: string, source: { __typename: 'BuiltinAsset', maxFaucetAmountMint: string } | { __typename: 'ERC20', contractAddress: string } } | { __typename?: 'NewFreeform' } | { __typename?: 'NewMarket', decimalPlaces: number, metadata?: Array<string> | null, instrument: { __typename?: 'InstrumentConfiguration', name: string, code: string, futureProduct?: { __typename?: 'FutureProduct', settlementAsset: { __typename?: 'Asset', symbol: string } } | null } } | { __typename?: 'UpdateAsset', quantum: string, source: { __typename?: 'UpdateERC20', lifetimeLimit: string, withdrawThreshold: string } } | { __typename?: 'UpdateMarket', marketId: string } | { __typename?: 'UpdateNetworkParameter', networkParameter: { __typename?: 'NetworkParameter', key: string, value: string } } }, votes: { __typename?: 'ProposalVotes', yes: { __typename?: 'ProposalVoteSide', totalTokens: string, totalNumber: string, votes?: Array<{ __typename?: 'Vote', value: Types.VoteValue, datetime: string, party: { __typename?: 'Party', id: string, stake: { __typename?: 'PartyStake', currentStakeAvailable: string } } }> | null }, no: { __typename?: 'ProposalVoteSide', totalTokens: string, totalNumber: string, votes?: Array<{ __typename?: 'Vote', value: Types.VoteValue, datetime: string, party: { __typename?: 'Party', id: string, stake: { __typename?: 'PartyStake', currentStakeAvailable: string } } }> | null } } }; export type ProposalFieldsFragment = { __typename?: 'Proposal', id?: string | null, reference: string, state: Types.ProposalState, datetime: string, rejectionReason?: Types.ProposalRejectionReason | null, errorDetails?: string | null, party: { __typename?: 'Party', id: string }, terms: { __typename?: 'ProposalTerms', closingDatetime: string, enactmentDatetime?: string | null, change: { __typename: 'NewAsset', name: string, symbol: string, source: { __typename: 'BuiltinAsset', maxFaucetAmountMint: string } | { __typename: 'ERC20', contractAddress: string } } | { __typename?: 'NewFreeform' } | { __typename?: 'NewMarket', decimalPlaces: number, metadata?: Array<string> | null, instrument: { __typename?: 'InstrumentConfiguration', name: string, code: string, futureProduct?: { __typename?: 'FutureProduct', settlementAsset: { __typename?: 'Asset', symbol: string } } | null } } | { __typename?: 'UpdateAsset', quantum: string, source: { __typename?: 'UpdateERC20', lifetimeLimit: string, withdrawThreshold: string } } | { __typename?: 'UpdateMarket', marketId: string } | { __typename?: 'UpdateNetworkParameter', networkParameter: { __typename?: 'NetworkParameter', key: string, value: string } } }, votes: { __typename?: 'ProposalVotes', yes: { __typename?: 'ProposalVoteSide', totalTokens: string, totalNumber: string, totalEquityLikeShareWeight: string, votes?: Array<{ __typename?: 'Vote', value: Types.VoteValue, datetime: string, party: { __typename?: 'Party', id: string, stake: { __typename?: 'PartyStake', currentStakeAvailable: string } } }> | null }, no: { __typename?: 'ProposalVoteSide', totalTokens: string, totalNumber: string, totalEquityLikeShareWeight: string, votes?: Array<{ __typename?: 'Vote', value: Types.VoteValue, datetime: string, party: { __typename?: 'Party', id: string, stake: { __typename?: 'PartyStake', currentStakeAvailable: string } } }> | null } } };
export type ProposalQueryVariables = Types.Exact<{ export type ProposalQueryVariables = Types.Exact<{
proposalId: Types.Scalars['ID']; proposalId: Types.Scalars['ID'];
}>; }>;
export type ProposalQuery = { __typename?: 'Query', proposal?: { __typename?: 'Proposal', id?: string | null, reference: string, state: Types.ProposalState, datetime: string, rejectionReason?: Types.ProposalRejectionReason | null, errorDetails?: string | null, party: { __typename?: 'Party', id: string }, terms: { __typename?: 'ProposalTerms', closingDatetime: string, enactmentDatetime?: string | null, change: { __typename: 'NewAsset', name: string, symbol: string, source: { __typename: 'BuiltinAsset', maxFaucetAmountMint: string } | { __typename: 'ERC20', contractAddress: string } } | { __typename?: 'NewFreeform' } | { __typename?: 'NewMarket', decimalPlaces: number, metadata?: Array<string> | null, instrument: { __typename?: 'InstrumentConfiguration', name: string, code: string, futureProduct?: { __typename?: 'FutureProduct', settlementAsset: { __typename?: 'Asset', symbol: string } } | null } } | { __typename?: 'UpdateAsset', quantum: string, source: { __typename?: 'UpdateERC20', lifetimeLimit: string, withdrawThreshold: string } } | { __typename?: 'UpdateMarket', marketId: string } | { __typename?: 'UpdateNetworkParameter', networkParameter: { __typename?: 'NetworkParameter', key: string, value: string } } }, votes: { __typename?: 'ProposalVotes', yes: { __typename?: 'ProposalVoteSide', totalTokens: string, totalNumber: string, votes?: Array<{ __typename?: 'Vote', value: Types.VoteValue, datetime: string, party: { __typename?: 'Party', id: string, stake: { __typename?: 'PartyStake', currentStakeAvailable: string } } }> | null }, no: { __typename?: 'ProposalVoteSide', totalTokens: string, totalNumber: string, votes?: Array<{ __typename?: 'Vote', value: Types.VoteValue, datetime: string, party: { __typename?: 'Party', id: string, stake: { __typename?: 'PartyStake', currentStakeAvailable: string } } }> | null } } } | null }; export type ProposalQuery = { __typename?: 'Query', proposal?: { __typename?: 'Proposal', id?: string | null, reference: string, state: Types.ProposalState, datetime: string, rejectionReason?: Types.ProposalRejectionReason | null, errorDetails?: string | null, party: { __typename?: 'Party', id: string }, terms: { __typename?: 'ProposalTerms', closingDatetime: string, enactmentDatetime?: string | null, change: { __typename: 'NewAsset', name: string, symbol: string, source: { __typename: 'BuiltinAsset', maxFaucetAmountMint: string } | { __typename: 'ERC20', contractAddress: string } } | { __typename?: 'NewFreeform' } | { __typename?: 'NewMarket', decimalPlaces: number, metadata?: Array<string> | null, instrument: { __typename?: 'InstrumentConfiguration', name: string, code: string, futureProduct?: { __typename?: 'FutureProduct', settlementAsset: { __typename?: 'Asset', symbol: string } } | null } } | { __typename?: 'UpdateAsset', quantum: string, source: { __typename?: 'UpdateERC20', lifetimeLimit: string, withdrawThreshold: string } } | { __typename?: 'UpdateMarket', marketId: string } | { __typename?: 'UpdateNetworkParameter', networkParameter: { __typename?: 'NetworkParameter', key: string, value: string } } }, votes: { __typename?: 'ProposalVotes', yes: { __typename?: 'ProposalVoteSide', totalTokens: string, totalNumber: string, totalEquityLikeShareWeight: string, votes?: Array<{ __typename?: 'Vote', value: Types.VoteValue, datetime: string, party: { __typename?: 'Party', id: string, stake: { __typename?: 'PartyStake', currentStakeAvailable: string } } }> | null }, no: { __typename?: 'ProposalVoteSide', totalTokens: string, totalNumber: string, totalEquityLikeShareWeight: string, votes?: Array<{ __typename?: 'Vote', value: Types.VoteValue, datetime: string, party: { __typename?: 'Party', id: string, stake: { __typename?: 'PartyStake', currentStakeAvailable: string } } }> | null } } } | null };
export type ProposalsQueryVariables = Types.Exact<{ [key: string]: never; }>; export type ProposalsQueryVariables = Types.Exact<{ [key: string]: never; }>;
export type ProposalsQuery = { __typename?: 'Query', proposals?: Array<{ __typename?: 'Proposal', id?: string | null, reference: string, state: Types.ProposalState, datetime: string, rejectionReason?: Types.ProposalRejectionReason | null, errorDetails?: string | null, party: { __typename?: 'Party', id: string }, terms: { __typename?: 'ProposalTerms', closingDatetime: string, enactmentDatetime?: string | null, change: { __typename: 'NewAsset', name: string, symbol: string, source: { __typename: 'BuiltinAsset', maxFaucetAmountMint: string } | { __typename: 'ERC20', contractAddress: string } } | { __typename?: 'NewFreeform' } | { __typename?: 'NewMarket', decimalPlaces: number, metadata?: Array<string> | null, instrument: { __typename?: 'InstrumentConfiguration', name: string, code: string, futureProduct?: { __typename?: 'FutureProduct', settlementAsset: { __typename?: 'Asset', symbol: string } } | null } } | { __typename?: 'UpdateAsset', quantum: string, source: { __typename?: 'UpdateERC20', lifetimeLimit: string, withdrawThreshold: string } } | { __typename?: 'UpdateMarket', marketId: string } | { __typename?: 'UpdateNetworkParameter', networkParameter: { __typename?: 'NetworkParameter', key: string, value: string } } }, votes: { __typename?: 'ProposalVotes', yes: { __typename?: 'ProposalVoteSide', totalTokens: string, totalNumber: string, votes?: Array<{ __typename?: 'Vote', value: Types.VoteValue, datetime: string, party: { __typename?: 'Party', id: string, stake: { __typename?: 'PartyStake', currentStakeAvailable: string } } }> | null }, no: { __typename?: 'ProposalVoteSide', totalTokens: string, totalNumber: string, votes?: Array<{ __typename?: 'Vote', value: Types.VoteValue, datetime: string, party: { __typename?: 'Party', id: string, stake: { __typename?: 'PartyStake', currentStakeAvailable: string } } }> | null } } }> | null }; export type ProposalsQuery = { __typename?: 'Query', proposals?: Array<{ __typename?: 'Proposal', id?: string | null, reference: string, state: Types.ProposalState, datetime: string, rejectionReason?: Types.ProposalRejectionReason | null, errorDetails?: string | null, party: { __typename?: 'Party', id: string }, terms: { __typename?: 'ProposalTerms', closingDatetime: string, enactmentDatetime?: string | null, change: { __typename: 'NewAsset', name: string, symbol: string, source: { __typename: 'BuiltinAsset', maxFaucetAmountMint: string } | { __typename: 'ERC20', contractAddress: string } } | { __typename?: 'NewFreeform' } | { __typename?: 'NewMarket', decimalPlaces: number, metadata?: Array<string> | null, instrument: { __typename?: 'InstrumentConfiguration', name: string, code: string, futureProduct?: { __typename?: 'FutureProduct', settlementAsset: { __typename?: 'Asset', symbol: string } } | null } } | { __typename?: 'UpdateAsset', quantum: string, source: { __typename?: 'UpdateERC20', lifetimeLimit: string, withdrawThreshold: string } } | { __typename?: 'UpdateMarket', marketId: string } | { __typename?: 'UpdateNetworkParameter', networkParameter: { __typename?: 'NetworkParameter', key: string, value: string } } }, votes: { __typename?: 'ProposalVotes', yes: { __typename?: 'ProposalVoteSide', totalTokens: string, totalNumber: string, totalEquityLikeShareWeight: string, votes?: Array<{ __typename?: 'Vote', value: Types.VoteValue, datetime: string, party: { __typename?: 'Party', id: string, stake: { __typename?: 'PartyStake', currentStakeAvailable: string } } }> | null }, no: { __typename?: 'ProposalVoteSide', totalTokens: string, totalNumber: string, totalEquityLikeShareWeight: string, votes?: Array<{ __typename?: 'Vote', value: Types.VoteValue, datetime: string, party: { __typename?: 'Party', id: string, stake: { __typename?: 'PartyStake', currentStakeAvailable: string } } }> | null } } }> | null };
export const ProposalFieldsFragmentDoc = gql` export const ProposalFieldsFragmentDoc = gql`
fragment ProposalFields on Proposal { fragment ProposalFields on Proposal {
@ -84,6 +84,7 @@ export const ProposalFieldsFragmentDoc = gql`
yes { yes {
totalTokens totalTokens
totalNumber totalNumber
totalEquityLikeShareWeight
votes { votes {
value value
party { party {
@ -98,6 +99,7 @@ export const ProposalFieldsFragmentDoc = gql`
no { no {
totalTokens totalTokens
totalNumber totalNumber
totalEquityLikeShareWeight
votes { votes {
value value
party { party {

View File

@ -1,10 +1,10 @@
import { ProposalState } from '@vegaprotocol/types'; import { ProposalState } from '@vegaprotocol/types';
import type { ProposalFields } from '../../__generated__/ProposalFields'; import type { Proposal_proposal } from '../../proposal/__generated__/Proposal';
export const CurrentProposalState = ({ export const CurrentProposalState = ({
proposal, proposal,
}: { }: {
proposal: ProposalFields; proposal: Proposal_proposal;
}) => { }) => {
const { state } = proposal; const { state } = proposal;
let className = 'text-white'; let className = 'text-white';

View File

@ -6,8 +6,8 @@ import { ProposalRejectionReason, ProposalState } from '@vegaprotocol/types';
import type { NetworkParamsQuery } from '@vegaprotocol/web3'; import type { NetworkParamsQuery } from '@vegaprotocol/web3';
import { AppStateProvider } from '../../../../contexts/app-state/app-state-provider'; import { AppStateProvider } from '../../../../contexts/app-state/app-state-provider';
import { generateProposal } from '../../test-helpers/generate-proposals'; import { generateProposal } from '../../test-helpers/generate-proposals';
import type { ProposalFields } from '../../__generated__/ProposalFields';
import { CurrentProposalStatus } from './current-proposal-status'; import { CurrentProposalStatus } from './current-proposal-status';
import type { Proposal_proposal } from '../../proposal/__generated__/Proposal';
const networkParamsQueryMock: MockedResponse<NetworkParamsQuery> = { const networkParamsQueryMock: MockedResponse<NetworkParamsQuery> = {
request: { request: {
@ -31,7 +31,7 @@ const networkParamsQueryMock: MockedResponse<NetworkParamsQuery> = {
}, },
}; };
const renderComponent = ({ proposal }: { proposal: ProposalFields }) => { const renderComponent = ({ proposal }: { proposal: Proposal_proposal }) => {
render( render(
<AppStateProvider> <AppStateProvider>
<MockedProvider mocks={[networkParamsQueryMock]}> <MockedProvider mocks={[networkParamsQueryMock]}>
@ -52,7 +52,7 @@ afterEach(() => {
it('Proposal open - renders will fail state if the proposal will fail', async () => { it('Proposal open - renders will fail state if the proposal will fail', async () => {
const proposal = generateProposal(); const proposal = generateProposal();
const failedProposal: ProposalFields = { const failedProposal: Proposal_proposal = {
...proposal, ...proposal,
votes: { votes: {
__typename: 'ProposalVotes', __typename: 'ProposalVotes',
@ -61,12 +61,14 @@ it('Proposal open - renders will fail state if the proposal will fail', async ()
totalNumber: '0', totalNumber: '0',
totalTokens: '0', totalTokens: '0',
votes: null, votes: null,
totalEquityLikeShareWeight: '0',
}, },
no: { no: {
__typename: 'ProposalVoteSide', __typename: 'ProposalVoteSide',
totalNumber: '0', totalNumber: '0',
totalTokens: '0', totalTokens: '0',
votes: null, votes: null,
totalEquityLikeShareWeight: '0',
}, },
}, },
}; };
@ -119,7 +121,7 @@ it('Proposal passed - renders vote passed and time since vote closed', async ()
it('Proposal waiting for node vote - will pass - renders if the vote will pass and status', async () => { it('Proposal waiting for node vote - will pass - renders if the vote will pass and status', async () => {
const proposal = generateProposal(); const proposal = generateProposal();
const failedProposal: ProposalFields = { const failedProposal: Proposal_proposal = {
...proposal, ...proposal,
state: ProposalState.STATE_WAITING_FOR_NODE_VOTE, state: ProposalState.STATE_WAITING_FOR_NODE_VOTE,
votes: { votes: {
@ -129,12 +131,14 @@ it('Proposal waiting for node vote - will pass - renders if the vote will pass
totalNumber: '0', totalNumber: '0',
totalTokens: '0', totalTokens: '0',
votes: null, votes: null,
totalEquityLikeShareWeight: '0',
}, },
no: { no: {
__typename: 'ProposalVoteSide', __typename: 'ProposalVoteSide',
totalNumber: '0', totalNumber: '0',
totalTokens: '0', totalTokens: '0',
votes: null, votes: null,
totalEquityLikeShareWeight: '0',
}, },
}, },
}; };
@ -245,12 +249,14 @@ it('Proposal failed - renders participation not met if participation is not met'
totalNumber: '0', totalNumber: '0',
totalTokens: '0', totalTokens: '0',
votes: null, votes: null,
totalEquityLikeShareWeight: '0',
}, },
no: { no: {
__typename: 'ProposalVoteSide', __typename: 'ProposalVoteSide',
totalNumber: '0', totalNumber: '0',
totalTokens: '0', totalTokens: '0',
votes: null, votes: null,
totalEquityLikeShareWeight: '0',
}, },
}, },
}, },
@ -280,12 +286,14 @@ it('Proposal failed - renders majority not met if majority is not met', async ()
totalNumber: '0', totalNumber: '0',
totalTokens: '0', totalTokens: '0',
votes: null, votes: null,
totalEquityLikeShareWeight: '0',
}, },
no: { no: {
__typename: 'ProposalVoteSide', __typename: 'ProposalVoteSide',
totalNumber: '1', totalNumber: '1',
totalTokens: '25242474195500835440000', totalTokens: '25242474195500835440000',
votes: null, votes: null,
totalEquityLikeShareWeight: '0',
}, },
}, },
}, },

View File

@ -4,7 +4,7 @@ import { useTranslation } from 'react-i18next';
import { ProposalState } from '@vegaprotocol/types'; import { ProposalState } from '@vegaprotocol/types';
import { useVoteInformation } from '../../hooks'; import { useVoteInformation } from '../../hooks';
import type { ProposalFields } from '../../__generated__/ProposalFields'; import type { Proposal_proposal } from '../../proposal/__generated__/Proposal';
export const StatusPass = ({ children }: { children: ReactNode }) => ( export const StatusPass = ({ children }: { children: ReactNode }) => (
<span className="text-vega-green">{children}</span> <span className="text-vega-green">{children}</span>
@ -42,9 +42,10 @@ const WillPass = ({
export const CurrentProposalStatus = ({ export const CurrentProposalStatus = ({
proposal, proposal,
}: { }: {
proposal: ProposalFields; proposal: Proposal_proposal;
}) => { }) => {
const { willPass, majorityMet, participationMet } = useVoteInformation({ const { willPassByTokenVote, majorityMet, participationMet } =
useVoteInformation({
proposal, proposal,
}); });
const { t } = useTranslation(); const { t } = useTranslation();
@ -61,7 +62,9 @@ export const CurrentProposalStatus = ({
}); });
if (proposal.state === ProposalState.STATE_OPEN) { if (proposal.state === ProposalState.STATE_OPEN) {
return <WillPass willPass={willPass}>{t('currentlySetTo')}</WillPass>; return (
<WillPass willPass={willPassByTokenVote}>{t('currentlySetTo')}</WillPass>
);
} }
if ( if (
@ -108,7 +111,12 @@ export const CurrentProposalStatus = ({
return ( return (
<> <>
<span>{t('votePassed')}</span> <span>{t('votePassed')}</span>
<StatusPass>&nbsp;{proposal.state}</StatusPass> <StatusPass>
&nbsp;
{proposal.state === ProposalState.STATE_ENACTED
? t('Enacted')
: t('Passed')}
</StatusPass>
<span> <span>
&nbsp; &nbsp;
{proposal.state === ProposalState.STATE_ENACTED {proposal.state === ProposalState.STATE_ENACTED
@ -121,7 +129,7 @@ export const CurrentProposalStatus = ({
if (proposal.state === ProposalState.STATE_WAITING_FOR_NODE_VOTE) { if (proposal.state === ProposalState.STATE_WAITING_FOR_NODE_VOTE) {
return ( return (
<WillPass willPass={willPass}> <WillPass willPass={willPassByTokenVote}>
<span>{t('WaitingForNodeVote')}</span>{' '} <span>{t('WaitingForNodeVote')}</span>{' '}
<span>{t('currentlySetTo')}</span> <span>{t('currentlySetTo')}</span>
</WillPass> </WillPass>

View File

@ -4,10 +4,10 @@ import { useTranslation } from 'react-i18next';
import { KeyValueTable, KeyValueTableRow } from '@vegaprotocol/ui-toolkit'; import { KeyValueTable, KeyValueTableRow } from '@vegaprotocol/ui-toolkit';
import { DATE_FORMAT_DETAILED } from '../../../../lib/date-formats'; import { DATE_FORMAT_DETAILED } from '../../../../lib/date-formats';
import { CurrentProposalState } from '../current-proposal-state'; import { CurrentProposalState } from '../current-proposal-state';
import type { ProposalFields } from '../../__generated__/ProposalFields'; import type { Proposal_proposal } from '../../proposal/__generated__/Proposal';
interface ProposalChangeTableProps { interface ProposalChangeTableProps {
proposal: ProposalFields; proposal: Proposal_proposal;
} }
export const ProposalChangeTable = ({ proposal }: ProposalChangeTableProps) => { export const ProposalChangeTable = ({ proposal }: ProposalChangeTableProps) => {

View File

@ -1,9 +1,9 @@
import { render, screen } from '@testing-library/react'; import { render, screen } from '@testing-library/react';
import { generateProposal } from '../../test-helpers/generate-proposals'; import { generateProposal } from '../../test-helpers/generate-proposals';
import { ProposalHeader } from './proposal-header'; import { ProposalHeader } from './proposal-header';
import type { ProposalFields } from '../../__generated__/ProposalFields'; import type { Proposal_proposal } from '../../proposal/__generated__/Proposal';
const renderComponent = (proposal: ProposalFields) => ( const renderComponent = (proposal: Proposal_proposal) => (
<ProposalHeader proposal={proposal} /> <ProposalHeader proposal={proposal} />
); );

View File

@ -2,9 +2,13 @@ import { useTranslation } from 'react-i18next';
import { Lozenge } from '@vegaprotocol/ui-toolkit'; import { Lozenge } from '@vegaprotocol/ui-toolkit';
import type { ReactNode } from 'react'; import type { ReactNode } from 'react';
import { shorten } from '@vegaprotocol/react-helpers'; import { shorten } from '@vegaprotocol/react-helpers';
import type { ProposalFields } from '../../__generated__/ProposalFields'; import type { Proposal_proposal } from '../../proposal/__generated__/Proposal';
export const ProposalHeader = ({ proposal }: { proposal: ProposalFields }) => { export const ProposalHeader = ({
proposal,
}: {
proposal: Proposal_proposal;
}) => {
const { t } = useTranslation(); const { t } = useTranslation();
const { change } = proposal.terms; const { change } = proposal.terms;

View File

@ -1,11 +1,11 @@
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import { SyntaxHighlighter } from '@vegaprotocol/ui-toolkit'; import { SyntaxHighlighter } from '@vegaprotocol/ui-toolkit';
import type { ProposalFields_terms } from '../../__generated__/ProposalFields'; import type { Proposal_proposal_terms } from '../../proposal/__generated__/Proposal';
export const ProposalTermsJson = ({ export const ProposalTermsJson = ({
terms, terms,
}: { }: {
terms: ProposalFields_terms; terms: Proposal_proposal_terms;
}) => { }) => {
const { t } = useTranslation(); const { t } = useTranslation();
return ( return (

View File

@ -0,0 +1,115 @@
import { render, screen } from '@testing-library/react';
import { MockedProvider } from '@apollo/client/testing';
import { AppStateProvider } from '../../../../contexts/app-state/app-state-provider';
import { ProposalVotesTable } from './proposal-votes-table';
import { ProposalType } from '../proposal/proposal';
import {
generateNoVotes,
generateProposal,
generateYesVotes,
} from '../../test-helpers/generate-proposals';
const defaultProposal = generateProposal();
const defaultProposalType = ProposalType.PROPOSAL_NETWORK_PARAMETER;
const updateMarketProposal = generateProposal({
terms: {
change: {
__typename: 'UpdateMarket',
marketId: '12345',
},
},
votes: {
__typename: 'ProposalVotes',
yes: generateYesVotes(10),
no: generateNoVotes(0),
},
});
const updateMarketProposalType = ProposalType.PROPOSAL_UPDATE_MARKET;
const renderComponent = (
proposal = defaultProposal,
proposalType = defaultProposalType
) =>
render(
<MockedProvider>
<AppStateProvider>
<ProposalVotesTable proposal={proposal} proposalType={proposalType} />
</AppStateProvider>
</MockedProvider>
);
describe('Proposal Votes Table', () => {
it('should render successfully', () => {
const { baseElement } = renderComponent();
expect(baseElement).toBeTruthy();
});
it('should show vote breakdown fields, excluding custom update market fields', () => {
renderComponent();
expect(screen.getByText('Will pass')).toBeInTheDocument();
expect(screen.getByText('Token majority met')).toBeInTheDocument();
expect(screen.getByText('Token participation met')).toBeInTheDocument();
expect(screen.getByText('Tokens for proposal')).toBeInTheDocument();
expect(screen.getByText('Total Supply')).toBeInTheDocument();
expect(screen.getByText('Tokens against proposal')).toBeInTheDocument();
expect(screen.getByText('Participation required')).toBeInTheDocument();
expect(screen.getByText('Majority Required')).toBeInTheDocument();
expect(screen.getByText('Number of voting parties')).toBeInTheDocument();
expect(screen.getByText('Total yes tokens')).toBeInTheDocument();
expect(
screen.getByText('Total tokens voted percentage')
).toBeInTheDocument();
expect(screen.getByText('Number of votes for')).toBeInTheDocument();
expect(screen.getByText('Number of votes against')).toBeInTheDocument();
expect(screen.getByText('Yes percentage')).toBeInTheDocument();
expect(screen.getByText('No percentage')).toBeInTheDocument();
expect(screen.queryByText('Liquidity majority met')).toBeNull();
expect(screen.queryByText('Liquidity participation met')).toBeNull();
expect(screen.queryByText('Liquidity shares for proposal')).toBeNull();
});
it('displays different breakdown fields for update market proposal', () => {
renderComponent(updateMarketProposal, updateMarketProposalType);
expect(screen.getByText('Liquidity majority met')).toBeInTheDocument();
expect(screen.getByText('Liquidity participation met')).toBeInTheDocument();
expect(
screen.getByText('Liquidity shares for proposal')
).toBeInTheDocument();
expect(screen.queryByText('Number of voting parties')).toBeNull();
expect(screen.queryByText('Total yes tokens')).toBeNull();
expect(screen.queryByText('Total tokens voted percentage')).toBeNull();
expect(screen.queryByText('Number of votes for')).toBeNull();
expect(screen.queryByText('Number of votes against')).toBeNull();
expect(screen.queryByText('Yes percentage')).toBeNull();
expect(screen.queryByText('No percentage')).toBeNull();
});
it('displays if an update market proposal will pass by token vote', () => {
renderComponent(updateMarketProposal, updateMarketProposalType);
expect(screen.getByText('👍 by Token vote')).toBeInTheDocument();
});
it('displays if an update market proposal will pass by LP vote', () => {
renderComponent(
generateProposal({
terms: {
change: {
__typename: 'UpdateMarket',
marketId: '12345',
},
},
votes: {
__typename: 'ProposalVotes',
yes: {
...generateYesVotes(0, 1, '10'),
},
no: {
...generateNoVotes(0, 1, '0'),
},
},
}),
updateMarketProposalType
);
expect(screen.getByText('👍 by Liquidity vote')).toBeInTheDocument();
});
});

View File

@ -1,40 +1,58 @@
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import {
import { KeyValueTable, KeyValueTableRow } from '@vegaprotocol/ui-toolkit'; KeyValueTable,
KeyValueTableRow,
Thumbs,
} from '@vegaprotocol/ui-toolkit';
import { import {
formatNumber, formatNumber,
formatNumberPercentage, formatNumberPercentage,
} from '@vegaprotocol/react-helpers'; } from '@vegaprotocol/react-helpers';
import { useVoteInformation } from '../../hooks'; import { useVoteInformation } from '../../hooks';
import { useAppState } from '../../../../contexts/app-state/app-state-context'; import { useAppState } from '../../../../contexts/app-state/app-state-context';
import type { ProposalFields } from '../../__generated__/ProposalFields'; import type { Proposal_proposal } from '../../proposal/__generated__/Proposal';
import { ProposalType } from '../proposal/proposal';
interface ProposalVotesTableProps { interface ProposalVotesTableProps {
proposal: ProposalFields; proposal: Proposal_proposal;
proposalType: ProposalType | null;
} }
export const ProposalVotesTable = ({ proposal }: ProposalVotesTableProps) => { export const ProposalVotesTable = ({
proposal,
proposalType,
}: ProposalVotesTableProps) => {
const { t } = useTranslation(); const { t } = useTranslation();
const { const {
appState: { totalSupply }, appState: { totalSupply },
} = useAppState(); } = useAppState();
const { const {
willPass, willPassByTokenVote,
willPassByLPVote,
totalTokensPercentage, totalTokensPercentage,
participationMet, participationMet,
participationLPMet,
totalTokensVoted, totalTokensVoted,
noPercentage, noPercentage,
yesPercentage, yesPercentage,
noTokens, noTokens,
yesTokens, yesTokens,
yesEquityLikeShareWeight,
yesVotes, yesVotes,
noVotes, noVotes,
totalVotes, totalVotes,
requiredMajorityPercentage, requiredMajorityPercentage,
requiredParticipation, requiredParticipation,
majorityMet, majorityMet,
majorityLPMet,
} = useVoteInformation({ proposal }); } = useVoteInformation({ proposal });
const isUpdateMarket = proposalType === ProposalType.PROPOSAL_UPDATE_MARKET;
const updateMarketWillPass = willPassByTokenVote || willPassByLPVote;
const updateMarketVotePassMethod = willPassByTokenVote
? t('byTokenVote')
: t('byLiquidityVote');
return ( return (
<KeyValueTable <KeyValueTable
title={t('voteBreakdown')} title={t('voteBreakdown')}
@ -44,20 +62,48 @@ export const ProposalVotesTable = ({ proposal }: ProposalVotesTableProps) => {
> >
<KeyValueTableRow> <KeyValueTableRow>
{t('willPass')} {t('willPass')}
{willPass ? '👍' : '👎'} {isUpdateMarket ? (
updateMarketWillPass ? (
<Thumbs up={true} text={updateMarketVotePassMethod} />
) : (
<Thumbs up={false} />
)
) : willPassByTokenVote ? (
<Thumbs up={true} />
) : (
<Thumbs up={false} />
)}
</KeyValueTableRow> </KeyValueTableRow>
<KeyValueTableRow> <KeyValueTableRow>
{t('majorityMet')} {t('majorityMet')}
{majorityMet ? '👍' : '👎'} {majorityMet ? <Thumbs up={true} /> : <Thumbs up={false} />}
</KeyValueTableRow> </KeyValueTableRow>
{isUpdateMarket && (
<KeyValueTableRow>
{t('majorityLPMet')}
{majorityLPMet ? <Thumbs up={true} /> : <Thumbs up={false} />}
</KeyValueTableRow>
)}
<KeyValueTableRow> <KeyValueTableRow>
{t('participationMet')} {t('participationMet')}
{participationMet ? '👍' : '👎'} {participationMet ? <Thumbs up={true} /> : <Thumbs up={false} />}
</KeyValueTableRow> </KeyValueTableRow>
{isUpdateMarket && (
<KeyValueTableRow>
{t('participationLPMet')}
{participationLPMet ? <Thumbs up={true} /> : <Thumbs up={false} />}
</KeyValueTableRow>
)}
<KeyValueTableRow> <KeyValueTableRow>
{t('tokenForProposal')} {t('tokenForProposal')}
{formatNumber(yesTokens, 2)} {formatNumber(yesTokens, 2)}
</KeyValueTableRow> </KeyValueTableRow>
{isUpdateMarket && (
<KeyValueTableRow>
{t('tokenLPForProposal')}
{formatNumber(yesEquityLikeShareWeight, 2)}
</KeyValueTableRow>
)}
<KeyValueTableRow> <KeyValueTableRow>
{t('totalSupply')} {t('totalSupply')}
{formatNumber(totalSupply, 2)} {formatNumber(totalSupply, 2)}
@ -74,6 +120,8 @@ export const ProposalVotesTable = ({ proposal }: ProposalVotesTableProps) => {
{t('majorityRequired')} {t('majorityRequired')}
{formatNumberPercentage(requiredMajorityPercentage)} {formatNumberPercentage(requiredMajorityPercentage)}
</KeyValueTableRow> </KeyValueTableRow>
{!isUpdateMarket && (
<>
<KeyValueTableRow> <KeyValueTableRow>
{t('numberOfVotingParties')} {t('numberOfVotingParties')}
{formatNumber(totalVotes, 0)} {formatNumber(totalVotes, 0)}
@ -102,6 +150,8 @@ export const ProposalVotesTable = ({ proposal }: ProposalVotesTableProps) => {
{t('noPercentage')} {t('noPercentage')}
{formatNumberPercentage(noPercentage, 2)} {formatNumberPercentage(noPercentage, 2)}
</KeyValueTableRow> </KeyValueTableRow>
</>
)}
</KeyValueTable> </KeyValueTable>
); );
}; };

View File

@ -3,6 +3,22 @@ import { generateProposal } from '../../test-helpers/generate-proposals';
import { Proposal } from './proposal'; import { Proposal } from './proposal';
import type { Proposal_proposal } from '../../proposal/__generated__/Proposal'; import type { Proposal_proposal } from '../../proposal/__generated__/Proposal';
jest.mock('@vegaprotocol/react-helpers', () => ({
...jest.requireActual('@vegaprotocol/react-helpers'),
useNetworkParams: jest.fn(() => ({
params: {
governance_proposal_asset_minVoterBalance: '1',
governance_proposal_freeform_minVoterBalance: '0',
governance_proposal_market_minVoterBalance: '1',
governance_proposal_updateAsset_minVoterBalance: '0',
governance_proposal_updateMarket_minVoterBalance: '1',
governance_proposal_updateNetParam_minVoterBalance: '1',
spam_protection_voting_min_tokens: '1000000000000000000',
},
loading: false,
error: null,
})),
}));
jest.mock('../proposal-detail-header/proposal-header', () => ({ jest.mock('../proposal-detail-header/proposal-header', () => ({
ProposalHeader: () => <div data-testid="proposal-header"></div>, ProposalHeader: () => <div data-testid="proposal-header"></div>,
})); }));
@ -19,16 +35,16 @@ jest.mock('../vote-details', () => ({
VoteDetails: () => <div data-testid="proposal-vote-details"></div>, VoteDetails: () => <div data-testid="proposal-vote-details"></div>,
})); }));
it('Renders with data-testid', () => { it('Renders with data-testid', async () => {
const proposal = generateProposal(); const proposal = generateProposal();
render(<Proposal proposal={proposal as Proposal_proposal} />); render(<Proposal proposal={proposal as Proposal_proposal} />);
expect(screen.getByTestId('proposal')).toBeInTheDocument(); expect(await screen.findByTestId('proposal')).toBeInTheDocument();
}); });
it('renders each section', () => { it('renders each section', async () => {
const proposal = generateProposal(); const proposal = generateProposal();
render(<Proposal proposal={proposal as Proposal_proposal} />); render(<Proposal proposal={proposal as Proposal_proposal} />);
expect(screen.getByTestId('proposal-header')).toBeInTheDocument(); expect(await screen.findByTestId('proposal-header')).toBeInTheDocument();
expect(screen.getByTestId('proposal-change-table')).toBeInTheDocument(); expect(screen.getByTestId('proposal-change-table')).toBeInTheDocument();
expect(screen.getByTestId('proposal-terms-json')).toBeInTheDocument(); expect(screen.getByTestId('proposal-terms-json')).toBeInTheDocument();
expect(screen.getByTestId('proposal-votes-table')).toBeInTheDocument(); expect(screen.getByTestId('proposal-votes-table')).toBeInTheDocument();

View File

@ -1,3 +1,5 @@
import { NetworkParams, useNetworkParams } from '@vegaprotocol/react-helpers';
import { AsyncRenderer } from '@vegaprotocol/ui-toolkit';
import { ProposalHeader } from '../proposal-detail-header/proposal-header'; import { ProposalHeader } from '../proposal-detail-header/proposal-header';
import type { Proposal_proposal } from '../../proposal/__generated__/Proposal'; import type { Proposal_proposal } from '../../proposal/__generated__/Proposal';
import { ProposalChangeTable } from '../proposal-change-table'; import { ProposalChangeTable } from '../proposal-change-table';
@ -5,28 +7,89 @@ import { ProposalTermsJson } from '../proposal-terms-json';
import { ProposalVotesTable } from '../proposal-votes-table'; import { ProposalVotesTable } from '../proposal-votes-table';
import { VoteDetails } from '../vote-details'; import { VoteDetails } from '../vote-details';
export enum ProposalType {
PROPOSAL_NEW_MARKET = 'PROPOSAL_NEW_MARKET',
PROPOSAL_UPDATE_MARKET = 'PROPOSAL_UPDATE_MARKET',
PROPOSAL_NEW_ASSET = 'PROPOSAL_NEW_ASSET',
PROPOSAL_UPDATE_ASSET = 'PROPOSAL_UPDATE_ASSET',
PROPOSAL_NETWORK_PARAMETER = 'PROPOSAL_NETWORK_PARAMETER',
PROPOSAL_FREEFORM = 'PROPOSAL_FREEFORM',
}
interface ProposalProps { interface ProposalProps {
proposal: Proposal_proposal; proposal: Proposal_proposal;
} }
export const Proposal = ({ proposal }: ProposalProps) => { export const Proposal = ({ proposal }: ProposalProps) => {
const { params, loading, error } = useNetworkParams([
NetworkParams.governance_proposal_market_minVoterBalance,
NetworkParams.governance_proposal_updateMarket_minVoterBalance,
NetworkParams.governance_proposal_asset_minVoterBalance,
NetworkParams.governance_proposal_updateAsset_minVoterBalance,
NetworkParams.governance_proposal_updateNetParam_minVoterBalance,
NetworkParams.governance_proposal_freeform_minVoterBalance,
NetworkParams.spam_protection_voting_min_tokens,
]);
if (!proposal) { if (!proposal) {
return null; return null;
} }
let minVoterBalance = null;
let proposalType = null;
if (params) {
switch (proposal.terms.change.__typename) {
case 'NewMarket':
minVoterBalance = params.governance_proposal_market_minVoterBalance;
proposalType = ProposalType.PROPOSAL_NEW_MARKET;
break;
case 'UpdateMarket':
minVoterBalance =
params.governance_proposal_updateMarket_minVoterBalance;
proposalType = ProposalType.PROPOSAL_UPDATE_MARKET;
break;
case 'NewAsset':
minVoterBalance = params.governance_proposal_asset_minVoterBalance;
proposalType = ProposalType.PROPOSAL_NEW_ASSET;
break;
case 'UpdateAsset':
minVoterBalance =
params.governance_proposal_updateAsset_minVoterBalance;
proposalType = ProposalType.PROPOSAL_UPDATE_ASSET;
break;
case 'UpdateNetworkParameter':
minVoterBalance =
params.governance_proposal_updateNetParam_minVoterBalance;
proposalType = ProposalType.PROPOSAL_NETWORK_PARAMETER;
break;
case 'NewFreeform':
minVoterBalance = params.governance_proposal_freeform_minVoterBalance;
proposalType = ProposalType.PROPOSAL_FREEFORM;
break;
}
}
return ( return (
<AsyncRenderer data={params} loading={loading} error={error}>
<section data-testid="proposal"> <section data-testid="proposal">
<ProposalHeader proposal={proposal} /> <ProposalHeader proposal={proposal} />
<div className="mb-8"> <div className="mb-8">
<ProposalChangeTable proposal={proposal} /> <ProposalChangeTable proposal={proposal} />
</div> </div>
<div className="mb-8"> <div className="mb-8">
<VoteDetails proposal={proposal} /> <VoteDetails
proposal={proposal}
proposalType={proposalType}
minVoterBalance={minVoterBalance}
spamProtectionMinTokens={params?.spam_protection_voting_min_tokens}
/>
</div> </div>
<div className="mb-8"> <div className="mb-8">
<ProposalVotesTable proposal={proposal} /> <ProposalVotesTable proposal={proposal} proposalType={proposalType} />
</div> </div>
<ProposalTermsJson terms={proposal.terms} /> <ProposalTermsJson terms={proposal.terms} />
</section> </section>
</AsyncRenderer>
); );
}; };

View File

@ -26,10 +26,10 @@ import {
lastWeek, lastWeek,
nextWeek, nextWeek,
} from '../../test-helpers/mocks'; } from '../../test-helpers/mocks';
import type { Proposals_proposalsConnection_edges_node as ProposalNode } from '../../proposals/__generated__/Proposals'; import type { Proposal_proposal } from '../../proposal/__generated__/Proposal';
const renderComponent = ( const renderComponent = (
proposal: ProposalNode, proposal: Proposal_proposal,
mock = networkParamsQueryMock mock = networkParamsQueryMock
) => ) =>
render( render(

View File

@ -15,7 +15,7 @@ import {
ProposalState, ProposalState,
} from '@vegaprotocol/types'; } from '@vegaprotocol/types';
import Routes from '../../../routes'; import Routes from '../../../routes';
import type { ProposalFields } from '../../__generated__/ProposalFields'; import type { Proposal_proposal } from '../../proposal/__generated__/Proposal';
const MajorityNotReached = () => { const MajorityNotReached = () => {
const { t } = useTranslation(); const { t } = useTranslation();
@ -37,10 +37,11 @@ const ParticipationNotReached = () => {
export const ProposalsListItemDetails = ({ export const ProposalsListItemDetails = ({
proposal, proposal,
}: { }: {
proposal: ProposalFields; proposal: Proposal_proposal;
}) => { }) => {
const { state } = proposal; const { state } = proposal;
const { willPass, majorityMet, participationMet } = useVoteInformation({ const { willPassByTokenVote, majorityMet, participationMet } =
useVoteInformation({
proposal, proposal,
}); });
const { t } = useTranslation(); const { t } = useTranslation();
@ -133,7 +134,7 @@ export const ProposalsListItemDetails = ({
voteStatus = voteStatus =
(!participationMet && <ParticipationNotReached />) || (!participationMet && <ParticipationNotReached />) ||
(!majorityMet && <MajorityNotReached />) || (!majorityMet && <MajorityNotReached />) ||
(willPass ? ( (willPassByTokenVote ? (
<> <>
{t('Set to')} <StatusPass>{t('pass')}</StatusPass> {t('Set to')} <StatusPass>{t('pass')}</StatusPass>
</> </>

View File

@ -1,9 +1,9 @@
import type { ProposalFields } from '../../__generated__/ProposalFields';
import { ProposalHeader } from '../proposal-detail-header/proposal-header'; import { ProposalHeader } from '../proposal-detail-header/proposal-header';
import { ProposalsListItemDetails } from './proposals-list-item-details'; import { ProposalsListItemDetails } from './proposals-list-item-details';
import type { Proposal_proposal } from '../../proposal/__generated__/Proposal';
interface ProposalsListItemProps { interface ProposalsListItemProps {
proposal: ProposalFields; proposal: Proposal_proposal;
} }
export const ProposalsListItem = ({ proposal }: ProposalsListItemProps) => { export const ProposalsListItem = ({ proposal }: ProposalsListItemProps) => {

View File

@ -14,7 +14,7 @@ import {
lastMonth, lastMonth,
nextMonth, nextMonth,
} from '../../test-helpers/mocks'; } from '../../test-helpers/mocks';
import type { ProposalFields } from '../../__generated__/ProposalFields'; import type { Proposal_proposal } from '../../proposal/__generated__/Proposal';
const openProposalClosesNextMonth = generateProposal({ const openProposalClosesNextMonth = generateProposal({
id: 'proposal1', id: 'proposal1',
@ -58,7 +58,7 @@ const failedProposalClosedLastMonth = generateProposal({
}, },
}); });
const renderComponent = (proposals: ProposalFields[]) => ( const renderComponent = (proposals: Proposal_proposal[]) => (
<Router> <Router>
<MockedProvider mocks={[networkParamsQueryMock]}> <MockedProvider mocks={[networkParamsQueryMock]}>
<AppStateProvider> <AppStateProvider>

View File

@ -7,17 +7,17 @@ import { ProposalsListFilter } from '../proposals-list-filter';
import Routes from '../../../routes'; import Routes from '../../../routes';
import { Button } from '@vegaprotocol/ui-toolkit'; import { Button } from '@vegaprotocol/ui-toolkit';
import { Link } from 'react-router-dom'; import { Link } from 'react-router-dom';
import type { ProposalFields } from '../../__generated__/ProposalFields'; import type { Proposal_proposal } from '../../proposal/__generated__/Proposal';
import { Links } from '../../../../config'; import { Links } from '../../../../config';
import { ExternalLink } from '@vegaprotocol/ui-toolkit'; import { ExternalLink } from '@vegaprotocol/ui-toolkit';
interface ProposalsListProps { interface ProposalsListProps {
proposals: ProposalFields[]; proposals: Proposal_proposal[];
} }
interface SortedProposalsProps { interface SortedProposalsProps {
open: ProposalFields[]; open: Proposal_proposal[];
closed: ProposalFields[]; closed: Proposal_proposal[];
} }
export const ProposalsList = ({ proposals }: ProposalsListProps) => { export const ProposalsList = ({ proposals }: ProposalsListProps) => {
@ -39,7 +39,7 @@ export const ProposalsList = ({ proposals }: ProposalsListProps) => {
} }
); );
const filterPredicate = (p: ProposalFields) => const filterPredicate = (p: Proposal_proposal) =>
p.id?.includes(filterString) || p.id?.includes(filterString) ||
p.party?.id?.toString().includes(filterString); p.party?.id?.toString().includes(filterString);

View File

@ -12,7 +12,7 @@ import {
nextWeek, nextWeek,
lastMonth, lastMonth,
} from '../../test-helpers/mocks'; } from '../../test-helpers/mocks';
import type { ProposalFields } from '../../__generated__/ProposalFields'; import type { Proposal_proposal } from '../../proposal/__generated__/Proposal';
const rejectedProposalClosesNextWeek = generateProposal({ const rejectedProposalClosesNextWeek = generateProposal({
id: 'rejected1', id: 'rejected1',
@ -35,7 +35,7 @@ const rejectedProposalClosedLastMonth = generateProposal({
}, },
}); });
const renderComponent = (proposals: ProposalFields[]) => ( const renderComponent = (proposals: Proposal_proposal[]) => (
<Router> <Router>
<MockedProvider mocks={[networkParamsQueryMock]}> <MockedProvider mocks={[networkParamsQueryMock]}>
<AppStateProvider> <AppStateProvider>

View File

@ -3,17 +3,17 @@ import { useTranslation } from 'react-i18next';
import { Heading } from '../../../../components/heading'; import { Heading } from '../../../../components/heading';
import { ProposalsListItem } from '../proposals-list-item'; import { ProposalsListItem } from '../proposals-list-item';
import { ProposalsListFilter } from '../proposals-list-filter'; import { ProposalsListFilter } from '../proposals-list-filter';
import type { Proposals_proposalsConnection_edges_node } from '../../proposals/__generated__/Proposals'; import type { Proposal_proposal } from '../../proposal/__generated__/Proposal';
interface ProposalsListProps { interface ProposalsListProps {
proposals: Proposals_proposalsConnection_edges_node[]; proposals: Proposal_proposal[];
} }
export const RejectedProposalsList = ({ proposals }: ProposalsListProps) => { export const RejectedProposalsList = ({ proposals }: ProposalsListProps) => {
const { t } = useTranslation(); const { t } = useTranslation();
const [filterString, setFilterString] = useState(''); const [filterString, setFilterString] = useState('');
const filterPredicate = (p: Proposals_proposalsConnection_edges_node) => const filterPredicate = (p: Proposal_proposal) =>
p.id?.includes(filterString) || p.id?.includes(filterString) ||
p.party?.id?.toString().includes(filterString); p.party?.id?.toString().includes(filterString);

View File

@ -1,6 +1,6 @@
import { render } from '@testing-library/react'; import { render } from '@testing-library/react';
import { ProposalMinRequirements } from './proposal-min-requirements'; import { ProposalMinRequirements } from './proposal-min-requirements';
import { ProposalUserAction } from '@vegaprotocol/types'; import { ProposalUserAction } from '../../components/shared';
describe('ProposalFormMinRequirements', () => { describe('ProposalFormMinRequirements', () => {
it('should render successfully with spam protection value, if larger', () => { it('should render successfully with spam protection value, if larger', () => {

View File

@ -1,7 +1,11 @@
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import BigNumber from 'bignumber.js'; import BigNumber from 'bignumber.js';
import { addDecimal } from '@vegaprotocol/react-helpers'; import { addDecimal } from '@vegaprotocol/react-helpers';
import { ProposalUserAction } from '@vegaprotocol/types';
export enum ProposalUserAction {
CREATE = 'CREATE',
VOTE = 'VOTE',
}
interface ProposalFormMinRequirementsProps { interface ProposalFormMinRequirementsProps {
minProposalBalance: string; minProposalBalance: string;

View File

@ -15,11 +15,8 @@ import type {
} from './__generated__/VoteButtonsQuery'; } from './__generated__/VoteButtonsQuery';
import { VoteState } from './use-user-vote'; import { VoteState } from './use-user-vote';
import { useVegaWallet, useVegaWalletDialogStore } from '@vegaprotocol/wallet'; import { useVegaWallet, useVegaWalletDialogStore } from '@vegaprotocol/wallet';
import { import { ProposalState, VoteValue } from '@vegaprotocol/types';
ProposalState, import { ProposalUserAction } from '../shared';
ProposalUserAction,
VoteValue,
} from '@vegaprotocol/types';
import { AsyncRenderer, Button, ButtonLink } from '@vegaprotocol/ui-toolkit'; import { AsyncRenderer, Button, ButtonLink } from '@vegaprotocol/ui-toolkit';
import { ProposalMinRequirements } from '../shared'; import { ProposalMinRequirements } from '../shared';
import { addDecimal } from '@vegaprotocol/react-helpers'; import { addDecimal } from '@vegaprotocol/react-helpers';

View File

@ -1,72 +1,43 @@
import { formatDistanceToNow } from 'date-fns';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import { formatNumber } from '../../../../lib/format-number'; import { formatNumber } from '../../../../lib/format-number';
import { ConnectToVega } from '../../../../components/connect-to-vega/connect-to-vega'; import { ConnectToVega } from '../../../../components/connect-to-vega';
import { useVoteInformation } from '../../hooks'; import { useVoteInformation } from '../../hooks';
import { CurrentProposalStatus } from '../current-proposal-status';
import { useUserVote } from './use-user-vote'; import { useUserVote } from './use-user-vote';
import { VoteButtonsContainer } from './vote-buttons'; import { VoteButtonsContainer } from './vote-buttons';
import { VoteProgress } from './vote-progress'; import { VoteProgress } from './vote-progress';
import { useVegaWallet } from '@vegaprotocol/wallet'; import { useVegaWallet } from '@vegaprotocol/wallet';
import { ProposalState } from '@vegaprotocol/types'; import { ProposalType } from '../proposal/proposal';
import type { Proposal_proposal } from '../../proposal/__generated__/Proposal'; import type { Proposal_proposal } from '../../proposal/__generated__/Proposal';
import { NetworkParams, useNetworkParams } from '@vegaprotocol/react-helpers';
interface VoteDetailsProps { interface VoteDetailsProps {
proposal: Proposal_proposal; proposal: Proposal_proposal;
minVoterBalance: string | null;
spamProtectionMinTokens: string | null;
proposalType: ProposalType | null;
} }
export const VoteDetails = ({ proposal }: VoteDetailsProps) => { export const VoteDetails = ({
proposal,
minVoterBalance,
spamProtectionMinTokens,
proposalType,
}: VoteDetailsProps) => {
const { pubKey } = useVegaWallet(); const { pubKey } = useVegaWallet();
const { const {
totalTokensPercentage, totalTokensPercentage,
participationMet, participationMet,
totalTokensVoted, totalTokensVoted,
noPercentage, noPercentage,
noLPPercentage,
yesPercentage, yesPercentage,
yesLPPercentage,
yesTokens, yesTokens,
noTokens, noTokens,
requiredMajorityPercentage, requiredMajorityPercentage,
requiredMajorityLPPercentage,
requiredParticipation, requiredParticipation,
} = useVoteInformation({ proposal }); } = useVoteInformation({ proposal });
let minVoterBalance = null;
const { params } = useNetworkParams([
NetworkParams.governance_proposal_market_minVoterBalance,
NetworkParams.governance_proposal_updateMarket_minVoterBalance,
NetworkParams.governance_proposal_asset_minVoterBalance,
NetworkParams.governance_proposal_updateAsset_minVoterBalance,
NetworkParams.governance_proposal_updateNetParam_minVoterBalance,
NetworkParams.governance_proposal_freeform_minVoterBalance,
NetworkParams.spam_protection_voting_min_tokens,
]);
if (params) {
switch (proposal.terms.change.__typename) {
case 'NewMarket':
minVoterBalance = params.governance_proposal_market_minVoterBalance;
break;
case 'UpdateMarket':
minVoterBalance =
params.governance_proposal_updateMarket_minVoterBalance;
break;
case 'NewAsset':
minVoterBalance = params.governance_proposal_asset_minVoterBalance;
break;
case 'UpdateAsset':
minVoterBalance =
params.governance_proposal_updateAsset_minVoterBalance;
break;
case 'UpdateNetworkParameter':
minVoterBalance =
params.governance_proposal_updateNetParam_minVoterBalance;
break;
case 'NewFreeform':
minVoterBalance = params.governance_proposal_freeform_minVoterBalance;
break;
}
}
const { t } = useTranslation(); const { t } = useTranslation();
const { voteState, voteDatetime, castVote } = useUserVote( const { voteState, voteDatetime, castVote } = useUserVote(
proposal.id, proposal.id,
@ -75,21 +46,55 @@ export const VoteDetails = ({ proposal }: VoteDetailsProps) => {
); );
const defaultDecimals = 2; const defaultDecimals = 2;
const daysLeft = t('daysLeft', {
daysLeft: formatDistanceToNow(new Date(proposal.terms.closingDatetime)),
});
return ( return (
<>
{proposalType === ProposalType.PROPOSAL_UPDATE_MARKET && (
<section> <section>
<h3 className="text-xl mb-2">{t('votes')}</h3> <h3 className="text-xl mb-2">{t('liquidityVotes')}</h3>
<p> <table className="w-full mb-8">
<span> <thead>
<CurrentProposalStatus proposal={proposal} /> <tr>
</span> <th className="text-vega-green w-[18%] text-left">
{'. '} {t('for')}
{proposal.state === ProposalState.STATE_OPEN ? daysLeft : null} </th>
</p> <th>
<table className="w-full"> <VoteProgress
threshold={requiredMajorityLPPercentage}
progress={yesLPPercentage}
/>
</th>
<th className="text-danger w-[18%] text-right">
{t('against')}
</th>
</tr>
</thead>
<tbody>
<tr>
<td
className="text-left"
data-testid="vote-progress-indicator-percentage-for"
>
{yesLPPercentage.toFixed(defaultDecimals)}%
</td>
<td className="text-center text-white">
{t('majorityRequired')}{' '}
{requiredMajorityLPPercentage.toFixed(defaultDecimals)}%
</td>
<td
className="text-right"
data-testid="vote-progress-indicator-percentage-against"
>
{noLPPercentage.toFixed(defaultDecimals)}%
</td>
</tr>
</tbody>
</table>
</section>
)}
<section>
<h3 className="text-xl mb-2">{t('tokenVotes')}</h3>
<table className="w-full mb-4">
<thead> <thead>
<tr> <tr>
<th className="text-vega-green w-[18%] text-left">{t('for')}</th> <th className="text-vega-green w-[18%] text-left">{t('for')}</th>
@ -136,7 +141,7 @@ export const VoteDetails = ({ proposal }: VoteDetailsProps) => {
</tr> </tr>
</tbody> </tbody>
</table> </table>
<p> <p className="mb-6">
{t('participation')} {t('participation')}
{': '} {': '}
{participationMet ? ( {participationMet ? (
@ -151,6 +156,9 @@ export const VoteDetails = ({ proposal }: VoteDetailsProps) => {
{t('governanceRequired')}) {t('governanceRequired')})
</span> </span>
</p> </p>
{proposalType === ProposalType.PROPOSAL_UPDATE_MARKET && (
<p>{t('votingThresholdInfo')}</p>
)}
{pubKey ? ( {pubKey ? (
<> <>
<h3 className="text-xl mb-2">{t('yourVote')}</h3> <h3 className="text-xl mb-2">{t('yourVote')}</h3>
@ -160,7 +168,7 @@ export const VoteDetails = ({ proposal }: VoteDetailsProps) => {
voteDatetime={voteDatetime} voteDatetime={voteDatetime}
proposalState={proposal.state} proposalState={proposal.state}
minVoterBalance={minVoterBalance} minVoterBalance={minVoterBalance}
spamProtectionMinTokens={params.spam_protection_voting_min_tokens} spamProtectionMinTokens={spamProtectionMinTokens}
className="flex" className="flex"
/> />
</> </>
@ -168,5 +176,6 @@ export const VoteDetails = ({ proposal }: VoteDetailsProps) => {
<ConnectToVega /> <ConnectToVega />
)} )}
</section> </section>
</>
); );
}; };

View File

@ -1 +1,2 @@
export { useVoteInformation } from './use-vote-information'; export { useVoteInformation } from './use-vote-information';
export { useProposalNetworkParams } from './use-proposal-network-params';

View File

@ -0,0 +1,146 @@
import { renderHook } from '@testing-library/react';
import { BigNumber } from '../../../lib/bignumber';
import { useProposalNetworkParams } from './use-proposal-network-params';
import { generateProposal } from '../test-helpers/generate-proposals';
jest.mock('@vegaprotocol/react-helpers', () => ({
...jest.requireActual('@vegaprotocol/react-helpers'),
useNetworkParams: jest.fn(() => ({
params: {
governance_proposal_updateMarket_requiredMajority: '0.1',
governance_proposal_updateMarket_requiredParticipation: '0.15',
governance_proposal_updateMarket_requiredMajorityLP: '0.2',
governance_proposal_updateMarket_requiredParticipationLP: '0.25',
governance_proposal_market_requiredMajority: '0.3',
governance_proposal_market_requiredParticipation: '0.35',
governance_proposal_asset_requiredMajority: '0.4',
governance_proposal_asset_requiredParticipation: '0.45',
governance_proposal_updateAsset_requiredMajority: '0.5',
governance_proposal_updateAsset_requiredParticipation: '0.55',
governance_proposal_updateNetParam_requiredMajority: '0.6',
governance_proposal_updateNetParam_requiredParticipation: '0.65',
governance_proposal_freeform_requiredMajority: '0.7',
governance_proposal_freeform_requiredParticipation: '0.75',
},
loading: false,
error: null,
})),
}));
describe('use-proposal-network-params', () => {
it('returns the correct params for an update market proposal', () => {
const proposal = generateProposal({
terms: {
change: {
__typename: 'UpdateMarket',
},
},
});
const {
result: { current },
} = renderHook(() => useProposalNetworkParams({ proposal }));
expect(current).toEqual({
requiredMajority: '0.1',
requiredMajorityLP: '0.2',
requiredParticipation: new BigNumber(0.15),
requiredParticipationLP: new BigNumber(0.25),
});
});
it('returns the correct params for a market proposal', () => {
const proposal = generateProposal({
terms: {
change: {
__typename: 'NewMarket',
},
},
});
const {
result: { current },
} = renderHook(() => useProposalNetworkParams({ proposal }));
expect(current).toEqual({
requiredMajority: '0.3',
requiredParticipation: new BigNumber(0.35),
});
});
it('returns the correct params for an asset proposal', () => {
const proposal = generateProposal({
terms: {
change: {
__typename: 'NewAsset',
},
},
});
const {
result: { current },
} = renderHook(() => useProposalNetworkParams({ proposal }));
expect(current).toEqual({
requiredMajority: '0.4',
requiredParticipation: new BigNumber(0.45),
});
});
it('returns the correct params for an update asset proposal', () => {
const proposal = generateProposal({
terms: {
change: {
__typename: 'UpdateAsset',
},
},
});
const {
result: { current },
} = renderHook(() => useProposalNetworkParams({ proposal }));
expect(current).toEqual({
requiredMajority: '0.5',
requiredParticipation: new BigNumber(0.55),
});
});
it('returns the correct params for a network params proposal', () => {
const proposal = generateProposal({
terms: {
change: {
__typename: 'UpdateNetworkParameter',
},
},
});
const {
result: { current },
} = renderHook(() => useProposalNetworkParams({ proposal }));
expect(current).toEqual({
requiredMajority: '0.6',
requiredParticipation: new BigNumber(0.65),
});
});
it('returns the correct params for a freeform proposal', () => {
const proposal = generateProposal({
terms: {
change: {
__typename: 'NewFreeform',
},
},
});
const {
result: { current },
} = renderHook(() => useProposalNetworkParams({ proposal }));
expect(current).toEqual({
requiredMajority: '0.7',
requiredParticipation: new BigNumber(0.75),
});
});
});

View File

@ -0,0 +1,90 @@
import { NetworkParams, useNetworkParams } from '@vegaprotocol/react-helpers';
import { BigNumber } from '../../../lib/bignumber';
import type { Proposal_proposal } from '../proposal/__generated__/Proposal';
export const useProposalNetworkParams = ({
proposal,
}: {
proposal: Proposal_proposal;
}) => {
const { params } = useNetworkParams([
NetworkParams.governance_proposal_updateMarket_requiredMajority,
NetworkParams.governance_proposal_updateMarket_requiredMajorityLP,
NetworkParams.governance_proposal_updateMarket_requiredParticipation,
NetworkParams.governance_proposal_updateMarket_requiredParticipationLP,
NetworkParams.governance_proposal_market_requiredMajority,
NetworkParams.governance_proposal_market_requiredParticipation,
NetworkParams.governance_proposal_updateAsset_requiredMajority,
NetworkParams.governance_proposal_updateAsset_requiredParticipation,
NetworkParams.governance_proposal_asset_requiredMajority,
NetworkParams.governance_proposal_asset_requiredParticipation,
NetworkParams.governance_proposal_updateNetParam_requiredMajority,
NetworkParams.governance_proposal_updateNetParam_requiredParticipation,
NetworkParams.governance_proposal_freeform_requiredMajority,
NetworkParams.governance_proposal_freeform_requiredParticipation,
]);
if (!params) {
return {
requiredMajority: new BigNumber(1),
requiredMajorityLP: new BigNumber(0),
requiredParticipation: new BigNumber(1),
requiredParticipationLP: new BigNumber(0),
};
}
switch (proposal.terms.change.__typename) {
case 'UpdateMarket':
return {
requiredMajority:
params.governance_proposal_updateMarket_requiredMajority,
requiredMajorityLP:
params.governance_proposal_updateMarket_requiredMajorityLP,
requiredParticipation: new BigNumber(
params.governance_proposal_updateMarket_requiredParticipation
),
requiredParticipationLP: new BigNumber(
params.governance_proposal_updateMarket_requiredParticipationLP
),
};
case 'UpdateNetworkParameter':
return {
requiredMajority:
params.governance_proposal_updateNetParam_requiredMajority,
requiredParticipation: new BigNumber(
params.governance_proposal_updateNetParam_requiredParticipation
),
};
case 'NewAsset':
return {
requiredMajority: params.governance_proposal_asset_requiredMajority,
requiredParticipation: new BigNumber(
params.governance_proposal_asset_requiredParticipation
),
};
case 'UpdateAsset':
return {
requiredMajority:
params.governance_proposal_updateAsset_requiredMajority,
requiredParticipation: new BigNumber(
params.governance_proposal_updateAsset_requiredParticipation
),
};
case 'NewMarket':
return {
requiredMajority: params.governance_proposal_market_requiredMajority,
requiredParticipation: new BigNumber(
params.governance_proposal_market_requiredParticipation
),
};
case 'NewFreeform':
return {
requiredMajority: params.governance_proposal_freeform_requiredMajority,
requiredParticipation: new BigNumber(
params.governance_proposal_freeform_requiredParticipation
),
};
default:
throw new Error('Unknown proposal type');
}
};

View File

@ -0,0 +1,279 @@
import { renderHook } from '@testing-library/react';
import { BigNumber } from '../../../lib/bignumber';
import { useVoteInformation } from './use-vote-information';
import {
generateProposal,
generateYesVotes,
generateNoVotes,
} from '../test-helpers/generate-proposals';
import type { AppState } from '../../../contexts/app-state/app-state-context';
const mockTotalSupply = new BigNumber(100);
const mockAppState: AppState = {
totalAssociated: new BigNumber('50063005'),
decimals: 18,
totalSupply: mockTotalSupply,
balanceFormatted: new BigNumber(0),
walletBalance: new BigNumber(0),
lien: new BigNumber(0),
allowance: new BigNumber(0),
tranches: null,
vegaWalletOverlay: false,
vegaWalletManageOverlay: false,
ethConnectOverlay: false,
walletAssociatedBalance: null,
vestingAssociatedBalance: null,
trancheBalances: [],
totalLockedBalance: new BigNumber(0),
totalVestedBalance: new BigNumber(0),
trancheError: null,
drawerOpen: false,
associationBreakdown: {
vestingAssociations: {},
stakingAssociations: {},
},
transactionOverlay: false,
bannerMessage: '',
};
jest.mock('../../../contexts/app-state/app-state-context', () => ({
useAppState: () => ({
appState: mockAppState,
}),
}));
jest.mock('@vegaprotocol/react-helpers', () => ({
...jest.requireActual('@vegaprotocol/react-helpers'),
useNetworkParams: jest.fn(() => ({
params: {
governance_proposal_updateMarket_requiredMajority: '0.5',
governance_proposal_updateMarket_requiredMajorityLP: '0.5',
governance_proposal_updateMarket_requiredParticipation: '0.5',
governance_proposal_updateMarket_requiredParticipationLP: '0.5',
governance_proposal_market_requiredMajority: '0.5',
governance_proposal_market_requiredParticipation: '0.5',
governance_proposal_asset_requiredMajority: '0.5',
governance_proposal_asset_requiredParticipation: '0.5',
governance_proposal_updateAsset_requiredMajority: '0.5',
governance_proposal_updateAsset_requiredParticipation: '0.5',
governance_proposal_updateNetParam_requiredMajority: '0.5',
governance_proposal_updateNetParam_requiredParticipation: '0.5',
governance_proposal_freeform_requiredMajority: '0.5',
governance_proposal_freeform_requiredParticipation: '0.5',
},
loading: false,
error: null,
})),
}));
describe('use-vote-information', () => {
it('returns all required vote information', () => {
const yesVotes = 40;
const noVotes = 60;
const yesEquityLikeShareWeight = '30';
const noEquityLikeShareWeight = '70';
// Note - giving a fixedTokenValue of 1 means a ratio of 1:1 votes to tokens, making sums easier :)
const fixedTokenValue = 1;
const proposal = generateProposal({
terms: {
change: {
__typename: 'UpdateMarket',
marketId: '12345',
},
},
votes: {
__typename: 'ProposalVotes',
yes: generateYesVotes(
yesVotes,
fixedTokenValue,
yesEquityLikeShareWeight
),
no: generateNoVotes(noVotes, fixedTokenValue, noEquityLikeShareWeight),
},
});
const {
result: { current },
} = renderHook(() => useVoteInformation({ proposal }));
expect(current.requiredMajorityPercentage).toEqual(new BigNumber(50));
expect(current.requiredMajorityLPPercentage).toEqual(new BigNumber(50));
expect(current.noTokens).toEqual(new BigNumber(60));
expect(current.noVotes).toEqual(new BigNumber(60));
expect(current.noEquityLikeShareWeight).toEqual(new BigNumber(70));
expect(current.yesTokens).toEqual(new BigNumber(40));
expect(current.yesVotes).toEqual(new BigNumber(40));
expect(current.yesEquityLikeShareWeight).toEqual(new BigNumber(30));
expect(current.totalTokensVoted).toEqual(new BigNumber(100));
expect(current.totalVotes).toEqual(new BigNumber(100));
expect(current.totalEquityLikeShareWeight).toEqual(new BigNumber(100));
expect(current.yesPercentage).toEqual(new BigNumber(40));
expect(current.yesLPPercentage).toEqual(new BigNumber(30));
expect(current.noPercentage).toEqual(new BigNumber(60));
expect(current.noLPPercentage).toEqual(new BigNumber(70));
expect(current.requiredParticipation).toEqual(new BigNumber(50));
expect(current.participationMet).toEqual(true);
expect(current.requiredParticipationLP).toEqual(new BigNumber(50));
expect(current.participationLPMet).toEqual(true);
expect(current.majorityMet).toEqual(false);
expect(current.majorityLPMet).toEqual(false);
expect(current.totalTokensPercentage).toEqual(new BigNumber(100));
expect(current.totalLPTokensPercentage).toEqual(new BigNumber(100));
expect(current.willPassByTokenVote).toEqual(false);
expect(current.willPassByLPVote).toEqual(false);
});
it('correctly returns majority, participation and will-pass status for a proposal with no votes', () => {
const yesVotes = 0;
const noVotes = 0;
const fixedTokenValue = 1;
const proposal = generateProposal({
votes: {
__typename: 'ProposalVotes',
yes: generateYesVotes(yesVotes, fixedTokenValue),
no: generateNoVotes(noVotes, fixedTokenValue),
},
});
const {
result: { current },
} = renderHook(() => useVoteInformation({ proposal }));
expect(current.participationMet).toEqual(false);
expect(current.majorityMet).toEqual(false);
expect(current.willPassByTokenVote).toEqual(false);
});
it('correctly shows lack of participation for a failing proposal lacking votes', () => {
const yesVotes = 20;
const noVotes = 10;
const fixedTokenValue = 1;
const proposal = generateProposal({
votes: {
__typename: 'ProposalVotes',
yes: generateYesVotes(yesVotes, fixedTokenValue),
no: generateNoVotes(noVotes, fixedTokenValue),
},
});
const {
result: { current },
} = renderHook(() => useVoteInformation({ proposal }));
expect(current.participationMet).toEqual(false);
});
it('correctly shows participation but lack of majority for a failing proposal with enough votes but not enough majority', () => {
const yesVotes = 20;
const noVotes = 70;
const fixedTokenValue = 1;
const proposal = generateProposal({
votes: {
__typename: 'ProposalVotes',
yes: generateYesVotes(yesVotes, fixedTokenValue),
no: generateNoVotes(noVotes, fixedTokenValue),
},
});
const {
result: { current },
} = renderHook(() => useVoteInformation({ proposal }));
expect(current.participationMet).toEqual(true);
expect(current.majorityMet).toEqual(false);
expect(current.willPassByTokenVote).toEqual(false);
});
it('correctly shows participation, majority and will-pass data for successful proposal', () => {
const yesVotes = 70;
const noVotes = 20;
const fixedTokenValue = 1;
const proposal = generateProposal({
votes: {
__typename: 'ProposalVotes',
yes: generateYesVotes(yesVotes, fixedTokenValue),
no: generateNoVotes(noVotes, fixedTokenValue),
},
});
const {
result: { current },
} = renderHook(() => useVoteInformation({ proposal }));
expect(current.participationMet).toEqual(true);
expect(current.majorityMet).toEqual(true);
expect(current.willPassByTokenVote).toEqual(true);
});
it('correctly shows whether an update market proposal will pass by token or LP vote - both failing', () => {
const yesVotes = 20;
const noVotes = 70;
const yesEquityLikeShareWeight = '30';
const noEquityLikeShareWeight = '60';
const fixedTokenValue = 1;
const proposal = generateProposal({
terms: {
change: {
__typename: 'UpdateMarket',
marketId: '12345',
},
},
votes: {
__typename: 'ProposalVotes',
yes: generateYesVotes(
yesVotes,
fixedTokenValue,
yesEquityLikeShareWeight
),
no: generateNoVotes(noVotes, fixedTokenValue, noEquityLikeShareWeight),
},
});
const {
result: { current },
} = renderHook(() => useVoteInformation({ proposal }));
expect(current.willPassByTokenVote).toEqual(false);
expect(current.willPassByLPVote).toEqual(false);
});
it('correctly shows whether an update market proposal failing token but passing LP voting', () => {
const yesVotes = 0;
const noVotes = 70;
const yesEquityLikeShareWeight = '80';
const noEquityLikeShareWeight = '20';
const fixedTokenValue = 1;
const proposal = generateProposal({
terms: {
change: {
__typename: 'UpdateMarket',
marketId: '12345',
},
},
votes: {
__typename: 'ProposalVotes',
yes: generateYesVotes(
yesVotes,
fixedTokenValue,
yesEquityLikeShareWeight
),
no: generateNoVotes(noVotes, fixedTokenValue, noEquityLikeShareWeight),
},
});
const {
result: { current },
} = renderHook(() => useVoteInformation({ proposal }));
expect(current.willPassByTokenVote).toEqual(false);
expect(current.willPassByLPVote).toEqual(true);
});
});

View File

@ -1,162 +1,198 @@
import { useNetworkParams, NetworkParams } from '@vegaprotocol/react-helpers'; import { useMemo } from 'react';
import React from 'react';
import { useAppState } from '../../../contexts/app-state/app-state-context'; import { useAppState } from '../../../contexts/app-state/app-state-context';
import { BigNumber } from '../../../lib/bignumber'; import { BigNumber } from '../../../lib/bignumber';
import type { ProposalFields } from '../__generated__/ProposalFields'; import { useProposalNetworkParams } from './use-proposal-network-params';
import type { Proposal_proposal } from '../proposal/__generated__/Proposal';
const useProposalNetworkParams = ({
proposal,
}: {
proposal: ProposalFields;
}) => {
const { params } = useNetworkParams([
NetworkParams.governance_proposal_updateMarket_requiredMajority,
NetworkParams.governance_proposal_updateMarket_requiredParticipation,
NetworkParams.governance_proposal_market_requiredMajority,
NetworkParams.governance_proposal_market_requiredParticipation,
NetworkParams.governance_proposal_asset_requiredMajority,
NetworkParams.governance_proposal_asset_requiredParticipation,
NetworkParams.governance_proposal_updateNetParam_requiredMajority,
NetworkParams.governance_proposal_updateNetParam_requiredParticipation,
NetworkParams.governance_proposal_freeform_requiredMajority,
NetworkParams.governance_proposal_freeform_requiredParticipation,
]);
if (!params) {
return {
requiredMajority: new BigNumber(1),
requiredParticipation: new BigNumber(1),
};
}
switch (proposal.terms.change.__typename) {
case 'UpdateMarket':
return {
requiredMajority:
params.governance_proposal_updateMarket_requiredMajority,
requiredParticipation: new BigNumber(
params.governance_proposal_updateMarket_requiredParticipation
),
};
case 'UpdateNetworkParameter':
return {
requiredMajority:
params.governance_proposal_updateNetParam_requiredMajority,
requiredParticipation: new BigNumber(
params.governance_proposal_updateNetParam_requiredParticipation
),
};
case 'NewAsset':
return {
requiredMajority: params.governance_proposal_asset_requiredMajority,
requiredParticipation: new BigNumber(
params.governance_proposal_asset_requiredParticipation
),
};
case 'NewMarket':
return {
requiredMajority: params.governance_proposal_market_requiredMajority,
requiredParticipation: new BigNumber(
params.governance_proposal_market_requiredParticipation
),
};
case 'NewFreeform':
return {
requiredMajority: params.governance_proposal_freeform_requiredMajority,
requiredParticipation: new BigNumber(
params.governance_proposal_freeform_requiredParticipation
),
};
default:
throw new Error('Unknown proposal type');
}
};
export const useVoteInformation = ({ export const useVoteInformation = ({
proposal, proposal,
}: { }: {
proposal: ProposalFields; proposal: Proposal_proposal;
}) => { }) => {
const { const {
appState: { totalSupply }, appState: { totalSupply },
} = useAppState(); } = useAppState();
const { requiredMajority, requiredParticipation } = useProposalNetworkParams({ const {
requiredMajority,
requiredParticipation,
requiredMajorityLP,
requiredParticipationLP,
} = useProposalNetworkParams({
proposal, proposal,
}); });
const requiredMajorityPercentage = React.useMemo( const {
() => requiredMajorityPercentage,
requiredMajority requiredMajorityLPPercentage,
noTokens,
noEquityLikeShareWeight,
yesTokens,
yesEquityLikeShareWeight,
totalTokensVoted,
totalEquityLikeShareWeight,
yesPercentage,
yesLPPercentage,
noPercentage,
noLPPercentage,
participationMet,
participationLPMet,
majorityMet,
majorityLPMet,
totalTokensPercentage,
totalLPTokensPercentage,
willPassByTokenVote,
willPassByLPVote,
} = useMemo(() => {
const requiredMajorityPercentage = requiredMajority
? new BigNumber(requiredMajority).times(100) ? new BigNumber(requiredMajority).times(100)
: new BigNumber(100), : new BigNumber(100);
[requiredMajority]
);
const noTokens = React.useMemo(() => { const requiredMajorityLPPercentage = requiredMajorityLP
return new BigNumber(proposal.votes.no.totalTokens); ? new BigNumber(requiredMajorityLP).times(100)
}, [proposal.votes.no.totalTokens]); : new BigNumber(100);
const yesTokens = React.useMemo(() => { const noTokens = new BigNumber(proposal.votes.no.totalTokens);
return new BigNumber(proposal.votes.yes.totalTokens);
}, [proposal.votes.yes.totalTokens]);
const totalTokensVoted = React.useMemo( const noEquityLikeShareWeight = !proposal.votes.no
() => yesTokens.plus(noTokens), .totalEquityLikeShareWeight
[yesTokens, noTokens]
);
const yesPercentage = React.useMemo(
() =>
totalTokensVoted.isZero()
? new BigNumber(0) ? new BigNumber(0)
: yesTokens.multipliedBy(100).dividedBy(totalTokensVoted), : new BigNumber(proposal.votes.no.totalEquityLikeShareWeight);
[totalTokensVoted, yesTokens]
); const yesTokens = new BigNumber(proposal.votes.yes.totalTokens);
const noPercentage = React.useMemo(
() => const yesEquityLikeShareWeight = !proposal.votes.yes
totalTokensVoted.isZero() .totalEquityLikeShareWeight
? new BigNumber(0) ? new BigNumber(0)
: noTokens.multipliedBy(100).dividedBy(totalTokensVoted), : new BigNumber(proposal.votes.yes.totalEquityLikeShareWeight);
[noTokens, totalTokensVoted]
const totalTokensVoted = yesTokens.plus(noTokens);
const totalEquityLikeShareWeight = yesEquityLikeShareWeight.plus(
noEquityLikeShareWeight
); );
const participationMet = React.useMemo(() => {
const tokensNeeded = totalSupply.multipliedBy(requiredParticipation);
return totalTokensVoted.isGreaterThan(tokensNeeded);
}, [requiredParticipation, totalTokensVoted, totalSupply]);
const majorityMet = React.useMemo(() => { const yesPercentage = totalTokensVoted.isZero()
return yesPercentage.isGreaterThanOrEqualTo(requiredMajorityPercentage); ? new BigNumber(0)
}, [yesPercentage, requiredMajorityPercentage]); : yesTokens.multipliedBy(100).dividedBy(totalTokensVoted);
const totalTokensPercentage = React.useMemo(() => { const yesLPPercentage = totalEquityLikeShareWeight.isZero()
return totalTokensVoted.multipliedBy(100).dividedBy(totalSupply); ? new BigNumber(0)
}, [totalTokensVoted, totalSupply]); : yesEquityLikeShareWeight
.multipliedBy(100)
.dividedBy(totalEquityLikeShareWeight);
const willPass = React.useMemo( const noPercentage = totalTokensVoted.isZero()
() => ? new BigNumber(0)
: noTokens.multipliedBy(100).dividedBy(totalTokensVoted);
const noLPPercentage = totalEquityLikeShareWeight.isZero()
? new BigNumber(0)
: noEquityLikeShareWeight
.multipliedBy(100)
.dividedBy(totalEquityLikeShareWeight);
const participationMet = totalTokensVoted.isGreaterThan(
totalSupply.multipliedBy(requiredParticipation)
);
const participationLPMet = requiredParticipationLP
? totalEquityLikeShareWeight.isGreaterThan(
totalSupply.multipliedBy(requiredParticipationLP)
)
: false;
const majorityMet = yesPercentage.isGreaterThanOrEqualTo(
requiredMajorityPercentage
);
const majorityLPMet = yesLPPercentage.isGreaterThanOrEqualTo(
requiredMajorityLPPercentage
);
const totalTokensPercentage = totalTokensVoted
.multipliedBy(100)
.dividedBy(totalSupply);
const totalLPTokensPercentage = totalEquityLikeShareWeight
.multipliedBy(100)
.dividedBy(totalSupply);
const willPassByTokenVote =
participationMet && participationMet &&
new BigNumber(yesPercentage).isGreaterThanOrEqualTo( new BigNumber(yesPercentage).isGreaterThanOrEqualTo(
requiredMajorityPercentage requiredMajorityPercentage
),
[participationMet, requiredMajorityPercentage, yesPercentage]
); );
const willPassByLPVote =
participationLPMet &&
new BigNumber(yesLPPercentage).isGreaterThanOrEqualTo(
requiredMajorityLPPercentage
);
return { return {
willPass, requiredMajorityPercentage,
totalTokensPercentage, requiredMajorityLPPercentage,
participationMet,
totalTokensVoted,
noPercentage,
yesPercentage,
noTokens, noTokens,
noEquityLikeShareWeight,
yesTokens, yesTokens,
yesEquityLikeShareWeight,
totalTokensVoted,
totalEquityLikeShareWeight,
yesPercentage,
yesLPPercentage,
noPercentage,
noLPPercentage,
participationMet,
participationLPMet,
majorityMet,
majorityLPMet,
totalTokensPercentage,
totalLPTokensPercentage,
willPassByTokenVote,
willPassByLPVote,
};
}, [
proposal.votes.no.totalEquityLikeShareWeight,
proposal.votes.no.totalTokens,
proposal.votes.yes.totalEquityLikeShareWeight,
proposal.votes.yes.totalTokens,
requiredMajority,
requiredMajorityLP,
requiredParticipation,
requiredParticipationLP,
totalSupply,
]);
return {
willPassByTokenVote,
willPassByLPVote,
totalTokensPercentage,
totalLPTokensPercentage,
participationMet,
participationLPMet,
totalTokensVoted,
totalEquityLikeShareWeight,
noPercentage,
noLPPercentage,
yesPercentage,
yesLPPercentage,
noTokens,
noEquityLikeShareWeight,
yesTokens,
yesEquityLikeShareWeight,
yesVotes: new BigNumber(proposal.votes.yes.totalNumber), yesVotes: new BigNumber(proposal.votes.yes.totalNumber),
noVotes: new BigNumber(proposal.votes.no.totalNumber), noVotes: new BigNumber(proposal.votes.no.totalNumber),
totalVotes: new BigNumber(proposal.votes.yes.totalNumber).plus( totalVotes: new BigNumber(proposal.votes.yes.totalNumber).plus(
proposal.votes.no.totalNumber proposal.votes.no.totalNumber
), ),
requiredMajorityPercentage, requiredMajorityPercentage,
requiredMajorityLPPercentage,
requiredParticipation: new BigNumber(requiredParticipation).times(100), requiredParticipation: new BigNumber(requiredParticipation).times(100),
requiredParticipationLP:
requiredParticipationLP &&
new BigNumber(requiredParticipationLP).times(100),
majorityMet, majorityMet,
majorityLPMet,
}; };
}; };

View File

@ -645,6 +645,10 @@ export interface Proposal_proposal_votes_yes {
* Total number of votes cast for this side * Total number of votes cast for this side
*/ */
totalNumber: string; totalNumber: string;
/**
* Total equity like share weight for this side (only for UpdateMarket Proposals)
*/
totalEquityLikeShareWeight: string;
/** /**
* All votes cast for this side * All votes cast for this side
*/ */
@ -697,6 +701,10 @@ export interface Proposal_proposal_votes_no {
* Total number of votes cast for this side * Total number of votes cast for this side
*/ */
totalNumber: string; totalNumber: string;
/**
* Total equity like share weight for this side (only for UpdateMarket Proposals)
*/
totalEquityLikeShareWeight: string;
/** /**
* All votes cast for this side * All votes cast for this side
*/ */

View File

@ -192,6 +192,7 @@ export const PROPOSAL_QUERY = gql`
yes { yes {
totalTokens totalTokens
totalNumber totalNumber
totalEquityLikeShareWeight
votes { votes {
value value
party { party {
@ -206,6 +207,7 @@ export const PROPOSAL_QUERY = gql`
no { no {
totalTokens totalTokens
totalNumber totalNumber
totalEquityLikeShareWeight
votes { votes {
value value
party { party {

View File

@ -231,6 +231,10 @@ export interface ProposalFields_votes_yes {
* Total number of votes cast for this side * Total number of votes cast for this side
*/ */
totalNumber: string; totalNumber: string;
/**
* Total equity like share weight for this side (only for UpdateMarket Proposals)
*/
totalEquityLikeShareWeight: string;
/** /**
* All votes cast for this side * All votes cast for this side
*/ */
@ -283,6 +287,10 @@ export interface ProposalFields_votes_no {
* Total number of votes cast for this side * Total number of votes cast for this side
*/ */
totalNumber: string; totalNumber: string;
/**
* Total equity like share weight for this side (only for UpdateMarket Proposals)
*/
totalEquityLikeShareWeight: string;
/** /**
* All votes cast for this side * All votes cast for this side
*/ */

View File

@ -231,6 +231,10 @@ export interface Proposals_proposalsConnection_edges_node_votes_yes {
* Total number of votes cast for this side * Total number of votes cast for this side
*/ */
totalNumber: string; totalNumber: string;
/**
* Total equity like share weight for this side (only for UpdateMarket Proposals)
*/
totalEquityLikeShareWeight: string;
/** /**
* All votes cast for this side * All votes cast for this side
*/ */
@ -283,6 +287,10 @@ export interface Proposals_proposalsConnection_edges_node_votes_no {
* Total number of votes cast for this side * Total number of votes cast for this side
*/ */
totalNumber: string; totalNumber: string;
/**
* Total equity like share weight for this side (only for UpdateMarket Proposals)
*/
totalEquityLikeShareWeight: string;
/** /**
* All votes cast for this side * All votes cast for this side
*/ */

View File

@ -76,6 +76,7 @@ export const PROPOSAL_FRAGMENT = gql`
yes { yes {
totalTokens totalTokens
totalNumber totalNumber
totalEquityLikeShareWeight
votes { votes {
value value
party { party {
@ -90,6 +91,7 @@ export const PROPOSAL_FRAGMENT = gql`
no { no {
totalTokens totalTokens
totalNumber totalNumber
totalEquityLikeShareWeight
votes { votes {
value value
party { party {

View File

@ -19,7 +19,7 @@ import { AsyncRenderer, Link } from '@vegaprotocol/ui-toolkit';
import { Heading } from '../../../../components/heading'; import { Heading } from '../../../../components/heading';
import { VegaWalletContainer } from '../../../../components/vega-wallet-container'; import { VegaWalletContainer } from '../../../../components/vega-wallet-container';
import { NetworkParams, useNetworkParams } from '@vegaprotocol/react-helpers'; import { NetworkParams, useNetworkParams } from '@vegaprotocol/react-helpers';
import { ProposalUserAction } from '@vegaprotocol/types'; import { ProposalUserAction } from '../../components/shared';
export interface FreeformProposalFormFields { export interface FreeformProposalFormFields {
proposalVoteDeadline: string; proposalVoteDeadline: string;

View File

@ -33,7 +33,7 @@ import {
} from '@vegaprotocol/ui-toolkit'; } from '@vegaprotocol/ui-toolkit';
import { Heading } from '../../../../components/heading'; import { Heading } from '../../../../components/heading';
import { VegaWalletContainer } from '../../../../components/vega-wallet-container'; import { VegaWalletContainer } from '../../../../components/vega-wallet-container';
import { ProposalUserAction } from '@vegaprotocol/types'; import { ProposalUserAction } from '../../components/shared';
interface SelectedNetworkParamCurrentValueProps { interface SelectedNetworkParamCurrentValueProps {
value: string; value: string;

View File

@ -26,7 +26,7 @@ import { ProposalMinRequirements } from '../../components/shared';
import { AsyncRenderer, Link } from '@vegaprotocol/ui-toolkit'; import { AsyncRenderer, Link } from '@vegaprotocol/ui-toolkit';
import { Heading } from '../../../../components/heading'; import { Heading } from '../../../../components/heading';
import { VegaWalletContainer } from '../../../../components/vega-wallet-container'; import { VegaWalletContainer } from '../../../../components/vega-wallet-container';
import { ProposalUserAction } from '@vegaprotocol/types'; import { ProposalUserAction } from '../../components/shared';
export interface NewAssetProposalFormFields { export interface NewAssetProposalFormFields {
proposalVoteDeadline: string; proposalVoteDeadline: string;

View File

@ -25,7 +25,7 @@ import { ProposalMinRequirements } from '../../components/shared';
import { AsyncRenderer, Link } from '@vegaprotocol/ui-toolkit'; import { AsyncRenderer, Link } from '@vegaprotocol/ui-toolkit';
import { Heading } from '../../../../components/heading'; import { Heading } from '../../../../components/heading';
import { VegaWalletContainer } from '../../../../components/vega-wallet-container'; import { VegaWalletContainer } from '../../../../components/vega-wallet-container';
import { ProposalUserAction } from '@vegaprotocol/types'; import { ProposalUserAction } from '../../components/shared';
export interface NewMarketProposalFormFields { export interface NewMarketProposalFormFields {
proposalVoteDeadline: string; proposalVoteDeadline: string;

View File

@ -25,7 +25,7 @@ import { ProposalMinRequirements } from '../../components/shared';
import { AsyncRenderer, Link } from '@vegaprotocol/ui-toolkit'; import { AsyncRenderer, Link } from '@vegaprotocol/ui-toolkit';
import { Heading } from '../../../../components/heading'; import { Heading } from '../../../../components/heading';
import { VegaWalletContainer } from '../../../../components/vega-wallet-container'; import { VegaWalletContainer } from '../../../../components/vega-wallet-container';
import { ProposalUserAction } from '@vegaprotocol/types'; import { ProposalUserAction } from '../../components/shared';
export interface UpdateAssetProposalFormFields { export interface UpdateAssetProposalFormFields {
proposalVoteDeadline: string; proposalVoteDeadline: string;

View File

@ -36,7 +36,7 @@ import {
import { Heading } from '../../../../components/heading'; import { Heading } from '../../../../components/heading';
import { VegaWalletContainer } from '../../../../components/vega-wallet-container'; import { VegaWalletContainer } from '../../../../components/vega-wallet-container';
import type { ProposalMarketsQuery } from './__generated__/ProposalMarketsQuery'; import type { ProposalMarketsQuery } from './__generated__/ProposalMarketsQuery';
import { ProposalUserAction } from '@vegaprotocol/types'; import { ProposalUserAction } from '../../components/shared';
export const MARKETS_QUERY = gql` export const MARKETS_QUERY = gql`
query ProposalMarketsQuery { query ProposalMarketsQuery {

View File

@ -6,17 +6,17 @@ import mergeWith from 'lodash/mergeWith';
import type { DeepPartial } from '../../../lib/type-helpers'; import type { DeepPartial } from '../../../lib/type-helpers';
import type { import type {
ProposalFields, Proposal_proposal,
ProposalFields_votes_no, Proposal_proposal_votes_yes,
ProposalFields_votes_no_votes, Proposal_proposal_votes_yes_votes,
ProposalFields_votes_yes, Proposal_proposal_votes_no,
ProposalFields_votes_yes_votes, Proposal_proposal_votes_no_votes,
} from '../__generated__/ProposalFields'; } from '../proposal/__generated__/Proposal';
export function generateProposal( export function generateProposal(
override: DeepPartial<ProposalFields> = {} override: DeepPartial<Proposal_proposal> = {}
): ProposalFields { ): Proposal_proposal {
const defaultProposal: ProposalFields = { const defaultProposal: Proposal_proposal = {
__typename: 'Proposal', __typename: 'Proposal',
id: faker.datatype.uuid(), id: faker.datatype.uuid(),
rationale: { rationale: {
@ -63,7 +63,7 @@ export function generateProposal(
}, },
}; };
return mergeWith<ProposalFields, DeepPartial<ProposalFields>>( return mergeWith<Proposal_proposal, DeepPartial<Proposal_proposal>>(
defaultProposal, defaultProposal,
override, override,
(objValue, srcValue) => { (objValue, srcValue) => {
@ -77,10 +77,11 @@ export function generateProposal(
export const generateYesVotes = ( export const generateYesVotes = (
numberOfVotes = 5, numberOfVotes = 5,
fixedTokenValue?: number fixedTokenValue?: number,
): ProposalFields_votes_yes => { totalEquityLikeShareWeight?: string
): Proposal_proposal_votes_yes => {
const votes = Array.from(Array(numberOfVotes)).map(() => { const votes = Array.from(Array(numberOfVotes)).map(() => {
const vote: ProposalFields_votes_yes_votes = { const vote: Proposal_proposal_votes_yes_votes = {
__typename: 'Vote', __typename: 'Vote',
value: VoteValue.VALUE_YES, value: VoteValue.VALUE_YES,
party: { party: {
@ -112,15 +113,17 @@ export const generateYesVotes = (
}, new BigNumber(0)) }, new BigNumber(0))
.toString(), .toString(),
votes, votes,
totalEquityLikeShareWeight: totalEquityLikeShareWeight || '0',
}; };
}; };
export const generateNoVotes = ( export const generateNoVotes = (
numberOfVotes = 5, numberOfVotes = 5,
fixedTokenValue?: number fixedTokenValue?: number,
): ProposalFields_votes_no => { totalEquityLikeShareWeight?: string
): Proposal_proposal_votes_no => {
const votes = Array.from(Array(numberOfVotes)).map(() => { const votes = Array.from(Array(numberOfVotes)).map(() => {
const vote: ProposalFields_votes_no_votes = { const vote: Proposal_proposal_votes_no_votes = {
__typename: 'Vote', __typename: 'Vote',
value: VoteValue.VALUE_NO, value: VoteValue.VALUE_NO,
party: { party: {
@ -151,5 +154,6 @@ export const generateNoVotes = (
}, new BigNumber(0)) }, new BigNumber(0))
.toString(), .toString(),
votes, votes,
totalEquityLikeShareWeight: totalEquityLikeShareWeight || '0',
}; };
}; };

View File

@ -35,6 +35,8 @@ export const NetworkParams = {
'governance_proposal_updateMarket_minVoterBalance', 'governance_proposal_updateMarket_minVoterBalance',
governance_proposal_updateMarket_requiredMajority: governance_proposal_updateMarket_requiredMajority:
'governance_proposal_updateMarket_requiredMajority', 'governance_proposal_updateMarket_requiredMajority',
governance_proposal_updateMarket_requiredMajorityLP:
'governance_proposal_updateMarket_requiredMajorityLP',
governance_proposal_updateMarket_minClose: governance_proposal_updateMarket_minClose:
'governance_proposal_updateMarket_minClose', 'governance_proposal_updateMarket_minClose',
governance_proposal_updateMarket_maxClose: governance_proposal_updateMarket_maxClose:
@ -77,6 +79,8 @@ export const NetworkParams = {
'governance_proposal_freeform_maxClose', 'governance_proposal_freeform_maxClose',
governance_proposal_updateMarket_requiredParticipation: governance_proposal_updateMarket_requiredParticipation:
'governance_proposal_updateMarket_requiredParticipation', 'governance_proposal_updateMarket_requiredParticipation',
governance_proposal_updateMarket_requiredParticipationLP:
'governance_proposal_updateMarket_requiredParticipationLP',
governance_proposal_updateMarket_minProposerBalance: governance_proposal_updateMarket_minProposerBalance:
'governance_proposal_updateMarket_minProposerBalance', 'governance_proposal_updateMarket_minProposerBalance',
governance_proposal_market_requiredMajority: governance_proposal_market_requiredMajority:
@ -89,6 +93,10 @@ export const NetworkParams = {
'governance_proposal_asset_requiredMajority', 'governance_proposal_asset_requiredMajority',
governance_proposal_asset_requiredParticipation: governance_proposal_asset_requiredParticipation:
'governance_proposal_asset_requiredParticipation', 'governance_proposal_asset_requiredParticipation',
governance_proposal_updateAsset_requiredMajority:
'governance_proposal_updateAsset_requiredMajority',
governance_proposal_updateAsset_requiredParticipation:
'governance_proposal_updateAsset_requiredParticipation',
governance_proposal_asset_minProposerBalance: governance_proposal_asset_minProposerBalance:
'governance_proposal_asset_minProposerBalance', 'governance_proposal_asset_minProposerBalance',
governance_proposal_updateAsset_minProposerBalance: governance_proposal_updateAsset_minProposerBalance:

View File

@ -31,6 +31,7 @@ export * from './syntax-highlighter';
export * from './tabs'; export * from './tabs';
export * from './text-area'; export * from './text-area';
export * from './theme-switcher'; export * from './theme-switcher';
export * from './thumbs';
export * from './toggle'; export * from './toggle';
export * from './tooltip'; export * from './tooltip';
export * from './vega-icons'; export * from './vega-icons';

View File

@ -0,0 +1 @@
export * from './thumbs';

View File

@ -0,0 +1,19 @@
import { render, screen } from '@testing-library/react';
import { Thumbs } from './thumbs';
describe('Thumbs', () => {
it('renders up', () => {
render(<Thumbs up={true} />);
expect(screen.getByText('👍')).toBeInTheDocument();
});
it('renders down', () => {
render(<Thumbs up={false} />);
expect(screen.getByText('👎')).toBeInTheDocument();
});
it('renders text', () => {
render(<Thumbs up={true} text="test" />);
expect(screen.getByText('👍 test')).toBeInTheDocument();
});
});

View File

@ -0,0 +1,26 @@
import { Thumbs } from './thumbs';
import type { ThumbsProps } from './thumbs';
import type { Meta, Story } from '@storybook/react';
export default {
component: Thumbs,
title: 'Thumbs',
} as Meta;
const Template: Story<ThumbsProps> = (args) => <Thumbs {...args} />;
export const Up = Template.bind({});
Up.args = {
up: true,
};
export const Down = Template.bind({});
Down.args = {
up: false,
};
export const WithText = Template.bind({});
WithText.args = {
up: true,
text: 'description text',
};

View File

@ -0,0 +1,12 @@
export interface ThumbsProps {
up: boolean;
text?: string;
}
export const Thumbs = ({ up, text }: ThumbsProps) => {
return (
<span>
{up ? '👍' : '👎'} {text}
</span>
);
};