Skip to content

Commit

Permalink
fix: quorum calculation (#2158)
Browse files Browse the repository at this point in the history
enhancement: add project quorum

Co-authored-by: Tuditi <[email protected]>
  • Loading branch information
nicole-obrien and Tuditi authored Mar 18, 2024
1 parent e9ed34c commit 891ec7f
Show file tree
Hide file tree
Showing 5 changed files with 77 additions and 32 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@
export let statusLoaded: boolean = false
export let overviewLoaded: boolean = false
export let projected: boolean = false
let selectedAnswerValues: number[] = []
let votedAnswerValues: number[] = []
Expand All @@ -37,7 +38,6 @@
let openedQuestionIndex: number = -1
let isUpdatingVotedAnswerValues: boolean = false
let lastAction: 'vote' | 'stopVote'
let projected: boolean = false
$: selectedProposalOverview = $participationOverviewForSelectedAccount?.participations?.[$selectedProposal?.id]
$: trackedParticipations = Object.values(selectedProposalOverview ?? {})
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,16 +10,22 @@
import { getSignificantDigitsAndRound } from '@core/utils'
export let proposal: IProposal
export let projected: boolean = false
const QUORUM_PERCENTAGE_DECIMAL = 0.05
const currentMilestone = $networkStatus.currentMilestone
$: circulatingSupplyVotedPercentage =
getCirculatingSupplyVotedPercentage($selectedParticipationEventStatus, proposal, currentMilestone) ?? 0
$: ({ actualPercentage, projectedPercentage } = getCirculatingSupplyVotedPercentage(
$selectedParticipationEventStatus,
proposal,
currentMilestone
))
$: percentage = projected ? projectedPercentage : actualPercentage
function formatPercentage(percentage: number): string {
const percentageWithSignificantDigits = getSignificantDigitsAndRound(Number(percentage))
const percentageWithSignificantDigits = getSignificantDigitsAndRound(Number(percentage) * 100)
const percentageString = String(percentageWithSignificantDigits).replace(/[,.]/g, getDecimalSeparator()) + '%'
return percentageString
}
Expand All @@ -33,9 +39,9 @@
</div>
<div class="flex gap-1">
<Text align="center" fontWeight="medium" textColor="brand">
{formatPercentage(circulatingSupplyVotedPercentage)} / {QUORUM_PERCENTAGE_DECIMAL * 100}%
{formatPercentage(percentage)} / {QUORUM_PERCENTAGE_DECIMAL * 100}%
</Text>
</div>
</div>
<Progress size="sm" progress={circulatingSupplyVotedPercentage / QUORUM_PERCENTAGE_DECIMAL} />
<Progress size="sm" progress={(percentage / QUORUM_PERCENTAGE_DECIMAL) * 100} />
</div>
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
let statusLoaded: boolean = false
let overviewLoaded: boolean = false
let projected: boolean = false
onMount(() => {
// Callbacks used, because we don't want to await the resolution of the promises.
Expand All @@ -41,8 +42,8 @@
>
<div class="w-2/5 flex flex-col p-6 space-y-6 relative overflow-y-scroll">
<ProposalDetailsPane proposal={$selectedProposal} />
<QuorumProgress proposal={$selectedProposal} />
<QuorumProgress proposal={$selectedProposal} {projected} />
<ProposalInformationPane />
</div>
<ProposalQuestionListPane {statusLoaded} {overviewLoaded} />
<ProposalQuestionListPane bind:projected {statusLoaded} {overviewLoaded} />
</Pane>
Original file line number Diff line number Diff line change
Expand Up @@ -10,48 +10,80 @@ export function getCirculatingSupplyVotedPercentage(
proposal: IProposal,
currentMilestone: number,
profile: IProfile = get(activeProfile)
): number {
): { actualPercentage: number; projectedPercentage: number } {
const circulatingSupply = profile.network.protocol?.circulatingSupply
if (!circulatingSupply || !participationEventStatus?.questions || !proposal?.milestones) {
return 0
return { actualPercentage: 0, projectedPercentage: 0 }
}
const { totalCurrentVotes, totalAccumulatedVotes } = getTotalEventVotes(participationEventStatus)
const { elapsedMilestones, remainingMilestones } = getMilestoneCount(
participationEventStatus,
proposal.milestones,
currentMilestone
)

const totalEventVotes = getTotalEventVotes(participationEventStatus)
const milestoneCount = getMilestoneCount(participationEventStatus, proposal.milestones, currentMilestone)

const maximumVotes = BigInt(circulatingSupply) * BigInt(milestoneCount)
const percentage = calculatePercentageOfBigInt(totalEventVotes, maximumVotes, 6)
const maximumAccumulatedVotes = (BigInt(circulatingSupply) * BigInt(elapsedMilestones)) / BigInt(1000) // Divide by 1000 because 1000 microns = 1 vote
const maximumRemainingVotes = (BigInt(circulatingSupply) * BigInt(remainingMilestones)) / BigInt(1000) // Divide by 1000 because 1000 microns = 1 vote

return percentage
const actualPercentage = calculatePercentageOfBigInt(totalAccumulatedVotes, maximumAccumulatedVotes, 6)
const projectedPercentage = calculatePercentageOfBigInt(
totalAccumulatedVotes + totalCurrentVotes * BigInt(remainingMilestones),
maximumAccumulatedVotes + maximumRemainingVotes,
6
)
return { actualPercentage, projectedPercentage }
}

function getTotalEventVotes(participationEventStatus: ParticipationEventStatus): bigint {
const isCommencing = participationEventStatus.status === EventStatus.Commencing
function getTotalEventVotes(participationEventStatus: ParticipationEventStatus): {
totalCurrentVotes: bigint
totalAccumulatedVotes: bigint
} {
const participationQuestion = participationEventStatus.questions?.[0]

if (!participationQuestion) {
return BigInt(0)
return { totalCurrentVotes: BigInt(0), totalAccumulatedVotes: BigInt(0) }
}

const totalEventVotes = participationQuestion.answers.reduce(
(total, answer) => (total += BigInt(answer[isCommencing ? 'current' : 'accumulated'])),
const totalCurrentVotes = participationQuestion.answers.reduce(
(total, answer) => (total += BigInt(answer.current)),
BigInt(0)
)

const totalAccumulatedVotes = participationQuestion.answers.reduce(
(total, answer) => (total += BigInt(answer.accumulated)),
BigInt(0)
)

return totalEventVotes
return { totalCurrentVotes, totalAccumulatedVotes }
}

function getMilestoneCount(
participationEventStatus: ParticipationEventStatus,
proposalMilestones: Record<EventStatus, number>,
currentMilestone: number
): number {
): { elapsedMilestones: number; remainingMilestones: number } {
let elapsedMilestones = 0
let remainingMilestones = 0
switch (participationEventStatus.status) {
case EventStatus.Holding:
return Math.max(currentMilestone - proposalMilestones.holding, 1)
case EventStatus.Ended:
return Math.max(proposalMilestones.ended - proposalMilestones.holding, 1)
case EventStatus.Upcoming: // Announcement
elapsedMilestones = 0
remainingMilestones = Math.max(proposalMilestones.ended - proposalMilestones.holding, 0)
break
case EventStatus.Commencing: // Voting Open
elapsedMilestones = 0
remainingMilestones = Math.max(proposalMilestones.ended - proposalMilestones.holding, 0)
break
case EventStatus.Holding: // Counting
elapsedMilestones = Math.max(currentMilestone - proposalMilestones.holding, 1)
remainingMilestones = Math.max(proposalMilestones.ended - currentMilestone, 0)
break
case EventStatus.Ended: // Results
elapsedMilestones = Math.max(proposalMilestones.ended - proposalMilestones.holding, 1)
remainingMilestones = 0
break
default:
return 1
elapsedMilestones = 1
break
}
return { elapsedMilestones, remainingMilestones }
}
14 changes: 10 additions & 4 deletions packages/shared/src/lib/core/utils/number.ts
Original file line number Diff line number Diff line change
Expand Up @@ -60,10 +60,12 @@ export function BigIntAbs(bigInt: bigint | string): bigint {
export function getSignificantDigitsAndRound(num: number, significantDigits: number = 2): number {
if (num === 0) {
return 0
} else if (num >= 1 || num <= 0) {
throw new Error('Number must be less than 1 and greater than 0.')
} else if (num <= 0) {
throw new Error('Number must be greater than 0.')
}

const parts = num.toString().split('.')

const numStr = num.toString()
let indexOfFirstSignificantDigit = numStr.indexOf('.') + 1

Expand All @@ -86,11 +88,15 @@ export function getSignificantDigitsAndRound(num: number, significantDigits: num
}

const zeros = '0'.repeat(indexOfFirstSignificantDigit - numStr.indexOf('.') - 1)
const resultStr = `0.${zeros}${digitsForRounding}`
const resultStr = `${parts[0]}.${zeros}${digitsForRounding}`

return parseFloat(resultStr)
}

export function calculatePercentageOfBigInt(total: bigint, max: bigint, precision: number): number {
return Number((total * BigInt(100) * BigInt(10 ** precision)) / max) / 10 ** precision
if (max === BigInt(0)) {
return 0
} else {
return Number((total * BigInt(10 ** precision)) / max) / 10 ** precision
}
}

0 comments on commit 891ec7f

Please sign in to comment.