Skip to content

Commit

Permalink
Add check if governance power is enough to vote.
Browse files Browse the repository at this point in the history
If the user hasn't selected a delegate wallet, it will automatically pick the one with most governance power.
  • Loading branch information
ppsimatikas committed Sep 24, 2024
1 parent 9396e6e commit 73ab429
Show file tree
Hide file tree
Showing 4 changed files with 138 additions and 39 deletions.
26 changes: 17 additions & 9 deletions hooks/queries/addresses/tokenOwnerRecord.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,17 @@ import { useQuery } from '@tanstack/react-query'
import { useRealmQuery } from '../realm'
import { useSelectedDelegatorStore } from 'stores/useSelectedDelegatorStore'

export const useAddressQuery_CouncilTokenOwner = () => {
export const useAddressQuery_CouncilTokenOwnerByPK = (owner: PublicKey | undefined) => {
const realm = useRealmQuery().data?.result
return useAddressQuery_TokenOwnerRecord(
realm?.owner,
realm?.pubkey,
realm?.account.config.councilMint,
owner
)
}

export const useAddressQuery_CouncilTokenOwner = () => {
const wallet = useWalletOnePointOh()
const selectedCouncilDelegator = useSelectedDelegatorStore(
(s) => s.councilDelegator
Expand All @@ -18,16 +27,20 @@ export const useAddressQuery_CouncilTokenOwner = () => {
? selectedCouncilDelegator
: wallet?.publicKey ?? undefined

return useAddressQuery_CouncilTokenOwnerByPK(owner)
}

export const useAddressQuery_CommunityTokenOwnerByPK = (owner: PublicKey | undefined) => {
const realm = useRealmQuery().data?.result
return useAddressQuery_TokenOwnerRecord(
realm?.owner,
realm?.pubkey,
realm?.account.config.councilMint,
realm?.account.communityMint,
owner
)
}

export const useAddressQuery_CommunityTokenOwner = () => {
const realm = useRealmQuery().data?.result
const wallet = useWalletOnePointOh()
const selectedCommunityDelegator = useSelectedDelegatorStore(
(s) => s.communityDelegator
Expand All @@ -40,12 +53,7 @@ export const useAddressQuery_CommunityTokenOwner = () => {
: // I wanted to eliminate `null` as a possible type
wallet?.publicKey ?? undefined

return useAddressQuery_TokenOwnerRecord(
realm?.owner,
realm?.pubkey,
realm?.account.communityMint,
owner
)
return useAddressQuery_CommunityTokenOwnerByPK(owner)
}

export const useAddressQuery_TokenOwnerRecord = (
Expand Down
12 changes: 12 additions & 0 deletions hooks/queries/tokenOwnerRecord.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@ import asFindable from '@utils/queries/asFindable'
import {
useAddressQuery_CommunityTokenOwner,
useAddressQuery_CouncilTokenOwner,
useAddressQuery_CommunityTokenOwnerByPK,
useAddressQuery_CouncilTokenOwnerByPK
} from './addresses/tokenOwnerRecord'
import { useRealmQuery } from './realm'
import useLegacyConnectionContext from '@hooks/useLegacyConnectionContext'
Expand Down Expand Up @@ -238,3 +240,13 @@ export const useUserCouncilTokenOwnerRecord = () => {
const { data: tokenOwnerRecordPubkey } = useAddressQuery_CouncilTokenOwner()
return useTokenOwnerRecordByPubkeyQuery(tokenOwnerRecordPubkey)
}

export const useUserCommunityTokenOwnerRecordByPK = (pk: PublicKey | undefined) => {
const { data: tokenOwnerRecordPubkey } = useAddressQuery_CommunityTokenOwnerByPK(pk)
return useTokenOwnerRecordByPubkeyQuery(tokenOwnerRecordPubkey)
}

export const useUserCouncilTokenOwnerRecordByPK = (pk: PublicKey | undefined) => {
const { data: tokenOwnerRecordPubkey } = useAddressQuery_CouncilTokenOwnerByPK(pk)
return useTokenOwnerRecordByPubkeyQuery(tokenOwnerRecordPubkey)
}
136 changes: 107 additions & 29 deletions hooks/useCreateProposal.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,23 +15,41 @@ import useLegacyConnectionContext from './useLegacyConnectionContext'
import queryClient from './queries/queryClient'
import { proposalQueryKeys } from './queries/proposal'
import { createLUTProposal } from 'actions/createLUTproposal'
import { useLegacyVoterWeight } from './queries/governancePower'
import {useVotingClients} from "@hooks/useVotingClients";
import { useRealmVoterWeightPlugins } from '@hooks/useRealmVoterWeightPlugins'
import useRealm from '@hooks/useRealm'
import useWalletOnePointOh from '@hooks/useWalletOnePointOh'
import BN from 'bn.js'
import { formatNumber } from '@utils/formatNumber'
import { BigNumber } from 'bignumber.js'
import { Governance, ProgramAccount } from '@solana/spl-governance'
import {
useTokenOwnerRecordsDelegatedToUser,
useUserCommunityTokenOwnerRecordByPK, useUserCouncilTokenOwnerRecordByPK
} from '@hooks/queries/tokenOwnerRecord'
import { useSelectedDelegatorStore } from '../stores/useSelectedDelegatorStore'
import { shortenAddress } from '@utils/address'

export default function useCreateProposal() {
const connection = useLegacyConnectionContext()
const realm = useRealmQuery().data?.result
const config = useRealmConfigQuery().data?.result
const mint = useRealmCommunityMintInfoQuery().data?.result
const councilMint = useRealmCouncilMintInfoQuery().data?.result
const { result: ownVoterWeight } = useLegacyVoterWeight()
// const { result: ownVoterWeight } = useLegacyVoterWeight()
const {
power: communityPower,
proposer: communityProposer,
} = useProposeAs('community')
const { data: communityProposerData } = useUserCommunityTokenOwnerRecordByPK(communityProposer)
const communityProposerTokenRecord = communityProposerData?.result

const {
power: councilPower,
proposer: councilProposer,
} = useProposeAs('council')

const { data: councilProposerData } = useUserCouncilTokenOwnerRecordByPK(councilProposer)
const councilProposerTokenRecord = councilProposerData?.result

const { getRpcContext } = useRpcContext()
const votingClients = useVotingClients();
Expand Down Expand Up @@ -59,21 +77,26 @@ export default function useCreateProposal() {
governance.pubkey
)
const minCouncilTokensToCreateProposal = selectedGovernance?.account.config.minCouncilTokensToCreateProposal
const councilPower = ownVoterWeight?.councilTokenRecord?.account.governingTokenDepositAmount
const minCommunityTokensToCreateProposal = selectedGovernance?.account.config.minCommunityTokensToCreateProposal

const ownTokenRecord =
minCouncilTokensToCreateProposal && councilPower && councilPower >= minCouncilTokensToCreateProposal ?
ownVoterWeight?.councilTokenRecord :
ownVoterWeight?.communityTokenRecord
const useCouncilPower = minCouncilTokensToCreateProposal && councilPower && councilPower.gte(minCouncilTokensToCreateProposal)
const ownTokenRecord =
useCouncilPower ?
councilProposerTokenRecord :
communityProposerTokenRecord

if (!ownTokenRecord) throw new Error('token owner record does not exist')
if (!selectedGovernance) throw new Error('governance not found')
if (!realm) throw new Error()

if (!useCouncilPower && communityPower && minCommunityTokensToCreateProposal && communityPower.lt(minCommunityTokensToCreateProposal)) {
throw new Error('Not enough voting power')
}

// this is somewhat confusing - the basic idea is:
// although a vote may be by community vote, the proposer may create it with their council token
// The choice of which token to use is made when the token record is selected
const proposeByCouncil = ownVoterWeight?.councilTokenRecord?.pubkey.toBase58() === (ownTokenRecord?.pubkey.toBase58() ?? "");
const proposeByCouncil = councilProposer?.toBase58() === (ownTokenRecord?.pubkey.toBase58() ?? "");
// now we can we identify whether we are using the community or council voting client (to decide which (if any) plugins to use)
const votingClient = votingClients(proposeByCouncil ? 'council' : 'community');

Expand Down Expand Up @@ -150,12 +173,17 @@ export default function useCreateProposal() {
)

const minCouncilTokensToCreateProposal = selectedGovernance?.account.config.minCouncilTokensToCreateProposal
const councilPower = ownVoterWeight?.councilTokenRecord?.account.governingTokenDepositAmount
const minCommunityTokensToCreateProposal = selectedGovernance?.account.config.minCommunityTokensToCreateProposal

const ownTokenRecord =
minCouncilTokensToCreateProposal && councilPower && councilPower >= minCouncilTokensToCreateProposal ?
ownVoterWeight?.councilTokenRecord :
ownVoterWeight?.communityTokenRecord
const useCouncilPower = minCouncilTokensToCreateProposal && councilPower && councilPower.gte(minCouncilTokensToCreateProposal)
const ownTokenRecord =
useCouncilPower ?
councilProposerTokenRecord :
communityProposerTokenRecord

if (!useCouncilPower && communityPower && minCommunityTokensToCreateProposal && communityPower.lt(minCommunityTokensToCreateProposal)) {
throw new Error('Not enough voting power')
}

if (!ownTokenRecord) throw new Error('token owner record does not exist')
if (!selectedGovernance) throw new Error('governance not found')
Expand All @@ -164,7 +192,7 @@ export default function useCreateProposal() {
// this is somewhat confusing - the basic idea is:
// although a vote may be by community vote, the proposer may create it with their council token
// The choice of which token to use is made when the token record is selected
const proposeByCouncil = ownVoterWeight?.councilTokenRecord?.pubkey.toBase58() === (ownTokenRecord?.pubkey.toBase58() ?? "");
const proposeByCouncil = councilProposer?.toBase58() === (ownTokenRecord?.pubkey.toBase58() ?? "");
// now we can we identify whether we are using the community or council voting client (to decide which (if any) plugins to use)
const votingClient = votingClients(proposeByCouncil ? 'council' : 'community');

Expand Down Expand Up @@ -210,6 +238,42 @@ export default function useCreateProposal() {
return { handleCreateProposal, propose, proposeMultiChoice }
}

const useProposeAs = (
role: 'community' | 'council',
) => {
const wallet = useWalletOnePointOh()
const { voterWeightForWallet, isReady } = useRealmVoterWeightPlugins(role)

const { councilDelegator, communityDelegator} = useSelectedDelegatorStore()
const { data: delegatesArray } = useTokenOwnerRecordsDelegatedToUser()


let proposer = councilDelegator
? councilDelegator
: communityDelegator
? communityDelegator
: wallet?.publicKey || undefined

let maxPower = proposer ? voterWeightForWallet(proposer)?.value : new BN(0)
// The user hasn't selected a specific delegator to perform actions as
// We will use the delegator with the maximum power, or the user's wallet
if (!councilDelegator && !communityDelegator && delegatesArray) {
for (const delegator of delegatesArray) {
const p = voterWeightForWallet(delegator.account.governingTokenOwner)?.value
if (p && maxPower && p.gt(maxPower)) {
maxPower = p
proposer = delegator.account.governingTokenOwner
}
}
}

return {
power: maxPower,
proposer,
isReady
}
}

export const useCanCreateProposal = (
governance?: ProgramAccount<Governance> | null
) => {
Expand All @@ -219,48 +283,62 @@ export const useCanCreateProposal = (
const realm = useRealmQuery().data?.result

const {
ownVoterWeight: communityOwnVoterWeight,
} = useRealmVoterWeightPlugins('community')
power: communityPower,
proposer: communityProposer,
isReady: communityReady
} = useProposeAs('community')

const {
isReady,
ownVoterWeight: councilOwnVoterWeight,
} = useRealmVoterWeightPlugins('council')
power: councilPower,
proposer: councilProposer,
isReady: councilReady
} = useProposeAs('council')

const power = communityPower || councilPower
const proposer = communityPower ? communityProposer : councilProposer
const isReady = communityReady && councilReady

const {
toManyCommunityOutstandingProposalsForUser,
toManyCouncilOutstandingProposalsForUse,
} = useRealm()

const communityOwnVoterWeightValue = communityOwnVoterWeight && communityOwnVoterWeight.value
const councilOwnVoterWeightValue = councilOwnVoterWeight && councilOwnVoterWeight.value

const minWeightToCreateProposal = (governance?.pubkey == realm?.account.communityMint ?
governance?.account.config.minCommunityTokensToCreateProposal :
governance?.account.config.minCouncilTokensToCreateProposal) || new BN(1)

const votingPower = communityOwnVoterWeightValue || councilOwnVoterWeightValue
governance?.account.config.minCouncilTokensToCreateProposal) || undefined

const hasEnoughVotingPower = votingPower?.gt(minWeightToCreateProposal)
const hasEnoughVotingPower = power?.gt(minWeightToCreateProposal || new BN(1))

const canCreateProposal =
realm &&
hasEnoughVotingPower &&
!toManyCommunityOutstandingProposalsForUser &&
!toManyCouncilOutstandingProposalsForUse

const minWeightToCreateProposalS = minWeightToCreateProposal
? new BigNumber(minWeightToCreateProposal.toString()).toString()
: "1"

const error = !connected
? 'Connect your wallet to create new proposal'
: isReady && !communityOwnVoterWeight && !councilOwnVoterWeight
: isReady && !communityPower && !councilPower
? 'There is no governance configuration to create a new proposal'
: !hasEnoughVotingPower
? `Please select only one account with at least ${formatNumber(new BigNumber(minWeightToCreateProposal.toString()))} governance power to create a new proposal.`
? `Please select only one account with at least ${minWeightToCreateProposalS} governance power to create a new proposal.`
: toManyCommunityOutstandingProposalsForUser
? 'Too many community outstanding proposals. You need to finalize them before creating a new one.'
: toManyCouncilOutstandingProposalsForUse
? 'Too many council outstanding proposals. You need to finalize them before creating a new one.'
: ''

const warning = proposer
? `Add a proposal as: ${shortenAddress(proposer.toString())}.`
: ''

return {
canCreateProposal,
error
error,
warning
}
}
3 changes: 2 additions & 1 deletion pages/dao/[symbol]/proposal/new.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -635,7 +635,7 @@ const New = () => {
[governance?.pubkey?.toBase58()]
)

const { canCreateProposal, error } = useCanCreateProposal(isMulti ? multiChoiceForm.governance : governance)
const { canCreateProposal, error, warning } = useCanCreateProposal(isMulti ? multiChoiceForm.governance : governance)

return (
<div className="grid grid-cols-12 gap-4">
Expand Down Expand Up @@ -849,6 +849,7 @@ const New = () => {
Add proposal
</Button>
</div>
{warning && <p className="p-2 text-right text-gray-400">{warning}</p>}
{error && <p className="p-2 text-right text-red-400">{error}</p>}
</div>
</>
Expand Down

0 comments on commit 73ab429

Please sign in to comment.