From fa19c4095d04d28276ca66caba8d8b30eb954bb0 Mon Sep 17 00:00:00 2001 From: emmdim Date: Thu, 28 Sep 2023 18:45:03 +0200 Subject: [PATCH] Improves results calculation and exports utils function. Signed-off-by: emmdim --- packages/js-client/src/index.ts | 1 + .../src/internal/modules/decoding.ts | 25 +++-- packages/js-client/src/internal/utils.ts | 105 ++++++++++-------- packages/js-client/src/types.ts | 17 ++- 4 files changed, 87 insertions(+), 61 deletions(-) diff --git a/packages/js-client/src/index.ts b/packages/js-client/src/index.ts index dbad62e3..e73a89b6 100644 --- a/packages/js-client/src/index.ts +++ b/packages/js-client/src/index.ts @@ -1,3 +1,4 @@ export * from './client'; export * from './context'; export * from './types'; +export * from './internal/utils'; diff --git a/packages/js-client/src/internal/modules/decoding.ts b/packages/js-client/src/internal/modules/decoding.ts index 8c594e94..d1056fad 100644 --- a/packages/js-client/src/internal/modules/decoding.ts +++ b/packages/js-client/src/internal/modules/decoding.ts @@ -1,24 +1,29 @@ -import { InterfaceParams, getFunctionFragment } from "@aragon/sdk-client-common"; -import { OffchainVotingClientCore } from "../core"; -import { IOffchainVotingClientDecoding } from "../interfaces"; -import { AVAILABLE_FUNCTION_SIGNATURES } from "../constants"; -import { bytesToHex } from "@aragon/sdk-common"; +import { AVAILABLE_FUNCTION_SIGNATURES } from '../constants'; +import { OffchainVotingClientCore } from '../core'; +import { IOffchainVotingClientDecoding } from '../interfaces'; +import { + InterfaceParams, + getFunctionFragment, +} from '@aragon/sdk-client-common'; +import { bytesToHex } from '@aragon/sdk-common'; -export class OffchainVotingClientDecoding extends OffchainVotingClientCore - implements IOffchainVotingClientDecoding { +export class OffchainVotingClientDecoding + extends OffchainVotingClientCore + implements IOffchainVotingClientDecoding +{ // add your action decoders here - /** + /** * Returns the decoded function info given the encoded data of an action * * @param {Uint8Array} data * @return {*} {(InterfaceParams | null)} * @memberof OffchainVotingClientDecoding */ - public findInterface(data: Uint8Array): InterfaceParams | null { + public findInterface(data: Uint8Array): InterfaceParams | null { try { const func = getFunctionFragment(data, AVAILABLE_FUNCTION_SIGNATURES); return { - id: func.format("minimal"), + id: func.format('minimal'), functionName: func.name, hash: bytesToHex(data).substring(0, 10), }; diff --git a/packages/js-client/src/internal/utils.ts b/packages/js-client/src/internal/utils.ts index c458a520..5a4d9421 100644 --- a/packages/js-client/src/internal/utils.ts +++ b/packages/js-client/src/internal/utils.ts @@ -7,10 +7,13 @@ import { VoteOption, ProposalFromSC, GaslessProposalParametersStruct, - InvalidResults, GaslessVotingProposalFromSC, } from '../types'; -import { MintTokenParams, VoteValues } from '@aragon/sdk-client'; +import { + MintTokenParams, + TokenVotingProposalResult, + VoteValues, +} from '@aragon/sdk-client'; import { DaoAction, EMPTY_PROPOSAL_METADATA_LINK, @@ -23,7 +26,12 @@ import { AddressZero } from '@ethersproject/constants'; import { Contract } from '@ethersproject/contracts'; import { SignerWithAddress } from '@nomiclabs/hardhat-ethers/signers'; import { VocdoniVoting } from '@vocdoni/offchain-voting-ethers'; -import { ElectionStatus, PublishedElection } from '@vocdoni/sdk'; +import { + ElectionStatus, + IChoice, + IQuestion, + PublishedElection, +} from '@vocdoni/sdk'; // export function votingModeFromContracts(votingMode: number): VotingMode { // switch (votingMode) { @@ -221,66 +229,62 @@ export function toGaslessVotingProposal( }; } -function vochainVoteResultsToProposal(results: string[][]): { - [key: string]: number; -} { - let parsedResults: { [key: string]: number } = {}; - Object.keys(VoteValues) - .filter((key) => isNaN(Number(key))) - .forEach((key, i) => { - parsedResults[key] = Number(results[i]); - }); - return parsedResults; +export function vochainVoteResultsToProposal( + questions: IQuestion[] +): TokenVotingProposalResult { + let parsedResults: { [key: string]: bigint } = {}; + questions[0].choices.map((choice: IChoice) => { + if (VoteValues[choice.title.default.toUpperCase() as any] !== undefined) { + parsedResults[choice.title.default.toLowerCase()] = + choice.results !== undefined ? BigInt(choice.results) : BigInt(0); + } + }); + return parsedResults as TokenVotingProposalResult; } -function hasSupportThreshold( - yes: number, - no: number, +export function hasSupportThreshold( + yes: bigint, + no: bigint, supportThreshold: number ): boolean { - return (1 - supportThreshold) * yes > supportThreshold * no; + return ( + (BigInt(1) - BigInt(supportThreshold)) * yes > BigInt(supportThreshold) * no + ); } -function hasMinParticipation( - yes: number, - no: number, - abstain: number, - totalVotes: number, +export function hasMinParticipation( + yes: bigint, + no: bigint, + abstain: bigint, minParticipation: number ): boolean { - return yes + no + abstain > minParticipation * totalVotes; + // const calculatedTotal = Object.values(results) + // // .map((x) => results[x]) + // .reduce((a, b) => a + b); + // // if (calculatedTotal != totalVotes) throw new InvalidResults(); + + return yes + no + abstain > BigInt(minParticipation) * (yes + no + abstain); } -function isProposalApproved( - results: string[][], - totalVotes: number, +export function isProposalApproved( + results: TokenVotingProposalResult, supportThreshold: number, minParticipation: number ): boolean { - const parsedResults = vochainVoteResultsToProposal(results); - const calculatedTotal = Object.keys(VoteValues) - .filter((key) => isNaN(Number(key))) - .map((x) => parsedResults[x]) - .reduce((a, b) => a + b); - if (calculatedTotal != totalVotes) throw new InvalidResults(); - + if (!results) return false; return ( - hasSupportThreshold( - parsedResults[VoteValues.YES.toString()], - parsedResults[VoteValues.NO.toString()], - supportThreshold - ) && + hasSupportThreshold(results.yes, results.no, supportThreshold) && hasMinParticipation( - parsedResults[VoteValues.YES.toString()], - parsedResults[VoteValues.NO.toString()], - parsedResults[VoteValues.ABSTAIN.toString()], - totalVotes, + results.yes, + results.no, + results.abstain, minParticipation ) ); } export function vochainStatusToProposalStatus( + parsedResults: TokenVotingProposalResult, vochainProposal: PublishedElection, executed: boolean, supportThreshold: number, @@ -296,8 +300,7 @@ export function vochainStatusToProposalStatus( if (executed) return ProposalStatus.EXECUTED; else if (vochainProposal.finalResults) { return isProposalApproved( - vochainProposal.results, - vochainProposal.voteCount, + parsedResults, supportThreshold, minParticipation ) @@ -331,6 +334,7 @@ export function toNewProposal( metadata.title = vochainProposal.title.default; metadata.description = vochainProposal.description?.default || metadata.description; + const result = vochainVoteResultsToProposal(vochainProposal.questions); return { id: `0x${SCproposalID.toString()}`, // string; dao: { @@ -344,6 +348,7 @@ export function toNewProposal( creationDate: vochainProposal.creationTime, //Date; actions: SCProposal.actions, //DaoAction[]; status: vochainStatusToProposalStatus( + result, vochainProposal, SCProposal.executed, settings.supportThreshold, @@ -360,8 +365,14 @@ export function toNewProposal( allowFailureMap: SCProposal.allowFailureMap, //number; tally: SCProposal.tally, //number[][]; settings, - vochainMetadata: vochainProposal, - tallyVochain: vochainProposal.results.map((x) => x.map((y) => BigInt(y))), - tallyVochainFinal: vochainProposal.finalResults, + vochain: { + metadata: vochainProposal, + tally: { + final: vochainProposal.finalResults, + value: vochainProposal.results[0].map((y) => BigInt(y)), + parsed: result, + }, + }, + totalVotingWeight: Object.values(result).reduce((a, b) => a + b), } as GaslessVotingProposal; } diff --git a/packages/js-client/src/types.ts b/packages/js-client/src/types.ts index 7a34a815..9d0b52c7 100644 --- a/packages/js-client/src/types.ts +++ b/packages/js-client/src/types.ts @@ -1,5 +1,8 @@ import { IDAO } from '@aragon/osx-ethers'; -import { CreateProposalBaseParams } from '@aragon/sdk-client'; +import { + CreateProposalBaseParams, + TokenVotingProposalResult, +} from '@aragon/sdk-client'; import { ContextState, OverriddenState, @@ -170,9 +173,15 @@ export type GaslessVotingProposal = ProposalBase & { allowFailureMap: number; tally: number[][]; settings: GaslessPluginVotingSettings; - vochainMetadata: PublishedElection; - tallyVochain: number[][]; - tallyVochainFinal: boolean; + vochain: { + metadata: PublishedElection; + tally: { + final: boolean; + value: bigint[]; + parsed: TokenVotingProposalResult; + }; + }; + totalVotingWeight: bigint; }; export type CreateGasslessProposalParams = CreateProposalBaseParams &