Skip to content

Commit

Permalink
Merge pull request #69 from oasisprotocol/CedarMist/weighting
Browse files Browse the repository at this point in the history
move to flags & add poll weighting
  • Loading branch information
csillag authored Oct 3, 2024
2 parents 9c0ce2f + efbab4c commit 49ba763
Show file tree
Hide file tree
Showing 14 changed files with 223 additions and 125 deletions.
1 change: 1 addition & 0 deletions frontend/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@
"@oasisprotocol/sapphire-ethers-v6": "^6.0.1",
"@oasisprotocol/sapphire-paratime": "^2.0.1",
"@phosphor-icons/core": "^2.0.8",
"cbor-web": "^9.0.2",
"ethers": "6.10.0",
"framer-motion": "^11.5.4",
"lru-cache": "10.1.0",
Expand Down
6 changes: 4 additions & 2 deletions frontend/src/components/PollCard/index.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { Proposal } from '../../types'
import { isPollActive, Proposal } from '../../types'
import { FC, MouseEventHandler, useEffect } from 'react'
import { Link } from 'react-router-dom'
import classes from './index.module.css'
Expand Down Expand Up @@ -94,7 +94,7 @@ export const PollCard: FC<{

const {
id: pollId,
proposal: { active },
proposal: { params },
ipfsParams: {
name,
description,
Expand All @@ -111,6 +111,8 @@ export const PollCard: FC<{
},
}

const active = isPollActive(params)

const renderedDescription = description

const textMatches = searchPatterns.length
Expand Down
18 changes: 13 additions & 5 deletions frontend/src/constants/config.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
// EIP-3085: wallet_addEthereumChain RPC Method
import { ExtendedPoll } from '../types'
import { ExtendedPoll, FLAG_ACTIVE } from '../types'
import { randomchoice, chain_info } from '@oasisprotocol/blockvote-contracts'

export const METAMASK_HOME_PAGE_URL = 'https://metamask.io/'
Expand Down Expand Up @@ -56,7 +56,9 @@ export const demoPoll1 = {
id: 'demo',
proposal: {
id: '0xdemo',
active: true,
params: {
flags: FLAG_ACTIVE,
},
},
ipfsParams: {
creator: 'demo',
Expand All @@ -73,7 +75,9 @@ export const demoPoll2 = {
id: 'demo',
proposal: {
id: '0xdemo',
active: true,
params: {
flags: FLAG_ACTIVE,
},
},
ipfsParams: {
creator: 'demo',
Expand All @@ -90,7 +94,9 @@ export const demoPoll3 = {
id: 'demo',
proposal: {
id: '0xdemo',
active: true,
params: {
flags: FLAG_ACTIVE,
},
},
ipfsParams: {
creator: 'demo',
Expand All @@ -107,7 +113,9 @@ export const demoPoll4 = {
id: 'demo',
proposal: {
id: '0xdemo',
active: true,
params: {
flags: FLAG_ACTIVE,
},
},
ipfsParams: {
creator: 'demo',
Expand Down
41 changes: 28 additions & 13 deletions frontend/src/hooks/useExtendedPoll.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,22 @@
import { ListOfVotes, ExtendedPoll, PollResults, Proposal, ListOfVoters } from '../types'
import {
ListOfVotes,
ExtendedPoll,
PollResults,
Proposal,
ListOfVoters,
isPollActive,
shouldPublishVotes,
shouldPublishVoters,
inactivatePoll,
Poll,
} from '../types'
import { useEffect, useMemo, useState } from 'react'
import { dashboard, demoSettings, getDemoPoll } from '../constants/config'
import { usePollGaslessStatus } from './usePollGaslessStatus'
import { usePollPermissions } from './usePollPermissions'
import { useEthereum } from './useEthereum'
import { useContracts } from './useContracts'
import { decodeBase64, toUtf8String } from 'ethers'
import { decodePollMetadata } from '../utils/poll.utils'
import { getVerdict } from '../components/InputFields'

const noVoters: ListOfVoters = { out_count: 0n, out_voters: [] }
Expand Down Expand Up @@ -36,10 +47,14 @@ export const useExtendedPoll = (

let correctiveAction: (() => void) | undefined

const ipfsParams = useMemo(
() => (metadata ? JSON.parse(toUtf8String(decodeBase64(metadata))) : undefined),
[metadata],
)
const [ipfsParams, ipfsError] = useMemo((): [Poll | undefined, string | undefined] => {
try {
return metadata ? [decodePollMetadata(metadata), undefined] : [undefined, undefined]
} catch (e) {
console.log('metadata problem on poll', proposal?.id, e)
return [undefined, "Invalid metadata, poll can't be displayed."]
}
}, [metadata])

useEffect(
// Update poll object
Expand Down Expand Up @@ -77,10 +92,10 @@ export const useExtendedPoll = (
correctiveAction = checkPermissions
}

const isActive = !!proposal?.active
const isActive = isPollActive(proposal?.params)

const loadVotes = async () => {
if (isDemo || !proposal || !pollManager || !ipfsParams || proposal.active) return
if (isDemo || !proposal || !pollManager || !ipfsParams || isActive) return

if (params.onDashboard && !dashboard.showResults) return

Expand All @@ -90,7 +105,7 @@ export const useExtendedPoll = (
setVoteCounts(voteCounts)
setWinningChoice(proposal.topChoice)

if (proposal.params.publishVotes) {
if (shouldPublishVotes(proposal.params)) {
const loadedVotes: ListOfVotes = {
out_count: 1000n, // Fake number, will be updated when the first batch is loaded
out_voters: [],
Expand All @@ -103,7 +118,7 @@ export const useExtendedPoll = (
loadedVotes.out_choices.push(...newVotes.out_choices)
}
setVotes(loadedVotes)
} else if (proposal.params.publishVoters) {
} else if (shouldPublishVoters(proposal.params)) {
const loadedVoters: ListOfVoters = {
out_count: 1000n, // Fake number, will be updated when the first batch is loaded
out_voters: [],
Expand All @@ -124,7 +139,7 @@ export const useExtendedPoll = (
useEffect(
// Load votes, when the stars are right
() => void loadVotes(),
[proposal, proposal?.active, params.onDashboard, dashboard.showResults, pollManager, ipfsParams, isDemo],
[proposal, isActive, params.onDashboard, dashboard.showResults, pollManager, ipfsParams, isDemo],
)

const pollResults = useMemo(() => {
Expand Down Expand Up @@ -155,7 +170,7 @@ export const useExtendedPoll = (
...poll,
proposal: {
...poll.proposal,
active: false,
params: inactivatePoll(poll.proposal.params),
topChoice: 0n,
},
})
Expand All @@ -178,7 +193,7 @@ export const useExtendedPoll = (
isActive,
isDemo,
isLoading: false,
error: undefined,
error: ipfsError,
correctiveAction,
poll,
voteCounts,
Expand Down
3 changes: 1 addition & 2 deletions frontend/src/hooks/useProposalFromChain.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ export const useProposalFromChain = (proposalId: string) => {
try {
setIsLoading(true)
const data = await pollManager.PROPOSALS(proposalId)
const [active, topChoice, params] = data
const [topChoice, params] = data
const acl = data?.params?.acl
if (!acl || acl === ZeroAddress) {
// setError('Found proposal with invalid ACL.')
Expand All @@ -39,7 +39,6 @@ export const useProposalFromChain = (proposalId: string) => {
} else {
setProposal({
id: proposalId,
active,
topChoice,
params,
})
Expand Down
15 changes: 6 additions & 9 deletions frontend/src/pages/DashboardPage/useDashboardData.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { useContracts } from '../../hooks/useContracts'
import { useEffect, useMemo, useState } from 'react'
import { PollManager, Proposal } from '../../types'
import { isPollActive, PollManager, Proposal } from '../../types'
import { useEthereum } from '../../hooks/useEthereum'
import { useBooleanField, useOneOfField, useTextField } from '../../components/InputFields'
import { useNavigate } from 'react-router-dom'
Expand All @@ -22,9 +22,9 @@ export type WantedStatus = 'active' | 'completed' | 'all'
export const isPollStatusAcceptable = (proposal: Proposal, wantedStatus: WantedStatus): boolean => {
switch (wantedStatus) {
case 'active':
return proposal.active
return isPollActive(proposal.params)
case 'completed':
return !proposal.active
return !isPollActive(proposal.params)
case 'all':
return true
}
Expand Down Expand Up @@ -135,8 +135,8 @@ async function fetchProposals(
}

result.out_proposals.forEach(({ id, proposal }) => {
const [active, topChoice, params] = proposal
proposalList.push({ id, active, topChoice, params })
const [topChoice, params] = proposal
proposalList.push({ id, topChoice, params })
})

if (result.out_proposals.length < FETCH_BATCH_SIZE) {
Expand Down Expand Up @@ -204,10 +204,7 @@ export const useDashboardData = () => {
}

const allProposals = useMemo(
() => [
...activeProposals.map((p): Proposal => ({ ...p, active: true })),
...pastProposals.map((p): Proposal => ({ ...p, active: false })),
],
() => [...activeProposals, ...pastProposals],
[activeProposals, pastProposals, userAddress],
)

Expand Down
7 changes: 4 additions & 3 deletions frontend/src/pages/PollPage/hook.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import { getVerdict } from '../../components/InputFields'
import { useExtendedPoll } from '../../hooks/useExtendedPoll'
import { useProposalFromChain } from '../../hooks/useProposalFromChain'
import { useNavigate } from 'react-router-dom'
import { isPollActive } from '../../types'

export const usePollData = (pollId: string) => {
const navigate = useNavigate()
Expand Down Expand Up @@ -362,7 +363,7 @@ export const usePollData = (pollId: string) => {
() => {
if (
isDemo &&
poll?.proposal.active &&
isPollActive(poll?.proposal.params) &&
remainingTime?.isPastDue &&
remainingTime.totalSeconds < demoSettings.waitSecondsBeforeFormallyCompleting + 5 &&
remainingTime.totalSeconds >= demoSettings.waitSecondsBeforeFormallyCompleting
Expand All @@ -380,7 +381,7 @@ export const usePollData = (pollId: string) => {
if (hasCompleted) {
if (!poll) {
// console.log("No poll loaded, waiting to load")
} else if (poll.proposal.active) {
} else if (isPollActive(poll.proposal.params)) {
// console.log("Apparently, we have completed a poll, but we still perceive it as active, so scheduling a reload...")
setTimeout(() => {
// console.log("Reloading now")
Expand All @@ -400,7 +401,7 @@ export const usePollData = (pollId: string) => {
isLoading: isProposalLoading || isLoading,
error: proposalError ?? error,
poll,
active: !!poll?.proposal?.active,
active: isPollActive(poll?.proposal?.params),

selectedChoice: winningChoice ?? selectedChoice,
canSelect,
Expand Down
1 change: 1 addition & 0 deletions frontend/src/types/index.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
export type { DataEntry } from './data-entry'
export type * from './poll'
export * from './poll-flags'
export type { IconSize } from './icon-size'
export type { IconProps } from './icon-props'
23 changes: 23 additions & 0 deletions frontend/src/types/poll-flags.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import { PollManager } from './poll'

export const FLAG_ACTIVE = 1n << 0n
export const FLAG_PUBLISH_VOTERS = 1n << 1n
export const FLAG_PUBLISH_VOTES = 1n << 2n
export const FLAG_HIDDEN = 1n << 3n
export const FLAG_WEIGHT_LOG10 = 1n << 4n
export const FLAG_WEIGHT_ONE = 1n << 5n

type Params = PollManager.ProposalParamsStructOutput

export const isPollActive = (params: Params | undefined): boolean => !!((params?.flags ?? 0n) & FLAG_ACTIVE)

export const inactivatePoll = (params: Params): Params => ({
...params,
flags: isPollActive(params) ? params.flags ^ FLAG_ACTIVE : params.flags,
})

export const shouldPublishVotes = (params: Params | undefined): boolean =>
!!((params?.flags ?? 0n) & FLAG_PUBLISH_VOTES)

export const shouldPublishVoters = (params: Params | undefined): boolean =>
!!((params?.flags ?? 0n) & FLAG_PUBLISH_VOTERS)
1 change: 0 additions & 1 deletion frontend/src/types/poll.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,6 @@ export type {

export type Proposal = {
id: string
active: boolean
topChoice: bigint
params: PollManager.ProposalParamsStructOutput
}
Expand Down
47 changes: 33 additions & 14 deletions frontend/src/utils/poll.utils.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,6 @@
import {
AbiCoder,
BytesLike,
encodeBase64,
getAddress,
JsonRpcProvider,
ParamType,
toUtf8Bytes,
} from 'ethers'
import { AbiCoder, BytesLike, getAddress, JsonRpcProvider, ParamType } from 'ethers'

import { encode as cborEncode, decode as cborDecode } from 'cbor-web'

import {
chain_info,
Expand All @@ -25,7 +19,7 @@ import {
} from '@oasisprotocol/blockvote-contracts'
export type { ContractType, NftType } from '@oasisprotocol/blockvote-contracts'
export { isToken } from '@oasisprotocol/blockvote-contracts'
import { Poll, PollManager } from '../types'
import { FLAG_HIDDEN, FLAG_PUBLISH_VOTERS, FLAG_PUBLISH_VOTES, Poll, PollManager } from '../types'
import { EthereumContext } from '../providers/EthereumContext'
import { DecisionWithReason, denyWithReason } from '../components/InputFields'
import { FetcherFetchOptions } from './StoredLRUCache'
Expand Down Expand Up @@ -141,6 +135,27 @@ export type CreatePollProps = {
completionTime: Date | undefined
}

const CURRENT_ENCODING_VERSION = 0

const encodePollMetadata = (poll: Poll): Uint8Array => {
const encoded = cborEncode({ v: CURRENT_ENCODING_VERSION, data: poll })
// console.log('Encoded poll data', encoded)
return encoded
}

export const decodePollMetadata = (metadata: string): Poll => {
const { v, data } = cborDecode(metadata.substring(2), { preferWeb: true, encoding: 'hex' })

if (typeof v !== 'number') throw new Error('Unknown poll data format')

switch (v as number) {
case CURRENT_ENCODING_VERSION:
return data as Poll
default:
throw new Error(`Unrecognized poll data format version: ${v}`)
}
}

export const createPoll = async (
pollManager: PollManager,
creator: string,
Expand Down Expand Up @@ -176,14 +191,18 @@ export const createPoll = async (

// console.log('Compiling poll', poll)

let pollFlags: bigint = 0n

if (poll.options.publishVoters) pollFlags |= FLAG_PUBLISH_VOTERS
if (poll.options.publishVotes) pollFlags |= FLAG_PUBLISH_VOTES
if (isHidden) pollFlags |= FLAG_HIDDEN

const proposalParams: PollManager.ProposalParamsStruct = {
metadata: encodeBase64(toUtf8Bytes(JSON.stringify(poll))),
metadata: encodePollMetadata(poll),
numChoices: answers.length,
publishVotes: poll.options.publishVotes,
publishVoters: poll.options.publishVoters,
closeTimestamp: poll.options.closeTimestamp,
acl: aclOptions.address,
isHidden,
flags: pollFlags,
}

console.log('params are', proposalParams)
Expand Down
Loading

0 comments on commit 49ba763

Please sign in to comment.