Skip to content

Commit

Permalink
Refactor society proposal vote, #1034
Browse files Browse the repository at this point in the history
  • Loading branch information
hyifeng committed Sep 3, 2024
1 parent f0fe87a commit 4a044ab
Show file tree
Hide file tree
Showing 15 changed files with 288 additions and 118 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -137,7 +137,12 @@ function checkNetworkConfig(data) {
const spaceService = spaceServices[space];
if (
!isEqual(networksConfig, {
...pick(spaceService, ["symbol", "decimals", "networks"]),
...pick(spaceService, [
"symbol",
"decimals",
"networks",
"accessibility",
]),
strategies: spaceService.weightStrategy,
...pick(spaceService, ["quorum", "version"]),
})
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
const { HttpError } = require("../../exc");
const { getProposalTemplateCollection } = require("../../mongo");
const { isAdmin } = require("../../utils/admin");
const { checkProposalContent } = require("./proposal.controller");
const { checkProposalContent } = require("./checkProposalContent");

async function saveProposalSettings(ctx) {
const { data, address } = ctx.request.body;
Expand Down
80 changes: 72 additions & 8 deletions backend/packages/backend/src/features/proposals/vote.js
Original file line number Diff line number Diff line change
@@ -1,14 +1,11 @@
const { ChoiceType } = require("../../constants");
const { Accessibility } = require("../../consts/space");
const { HttpError } = require("../../exc");
const { getProposalCollection } = require("../../mongo");
const proposalService = require("../../services/proposal.service");
const { spaces: spaceServices } = require("../../spaces");

async function vote(ctx) {
const { data, address, signature } = ctx.request.body;
const { proposalCid, choices, remark, realVoter, voterNetwork } = data;

if (!proposalCid) {
throw new HttpError(400, { proposalCid: ["Proposal CID is missing"] });
}

function checkVoteChoices(choices, proposal) {
if (!choices) {
throw new HttpError(400, { choices: ["Choices is missing"] });
}
Expand All @@ -23,10 +20,77 @@ async function vote(ctx) {
});
}

if (proposal.choiceType === ChoiceType.Single && choices.length !== 1) {
throw new HttpError(400, "Can vote single choice only");
}

for (const choice of choices) {
if (!proposal.choices?.includes(choice)) {
throw new HttpError(400, `Invalid choice: ${choice}`);
}
}
}

function checkProposal(proposal) {
const now = new Date();

if (proposal.startDate > now.getTime()) {
throw new HttpError(400, "The voting is not started yet");
}

if (proposal.endDate < now.getTime()) {
throw new HttpError(400, "The voting had already ended");
}
}

function checkVoterNetwork(voterNetwork, proposal) {
if (!voterNetwork) {
throw new HttpError(400, { voterNetwork: ["Voter network is missing"] });
}

const space = proposal.space;
const spaceService = spaceServices[space];
if (!spaceService) {
throw new HttpError(500, "Unknown space");
}

const snapshotNetworks = Object.keys(proposal.snapshotHeights);
if (!snapshotNetworks.includes(voterNetwork)) {
throw new HttpError(400, "Voter network is not supported by this proposal");
}
}

async function vote(ctx) {
const { data, address, signature } = ctx.request.body;
const { proposalCid, choices, remark, realVoter, voterNetwork } = data;

if (!proposalCid) {
throw new HttpError(400, { proposalCid: ["Proposal CID is missing"] });
}
const proposalCol = await getProposalCollection();
const proposal = await proposalCol.findOne({ cid: proposalCid });
if (!proposal) {
throw new HttpError(400, "Proposal not found.");
}

checkProposal(proposal);
checkVoteChoices(choices, proposal);
checkVoterNetwork(voterNetwork, proposal);

if (proposal.networksConfig.accessibility === Accessibility.SOCIETY) {
ctx.body = await proposalService.voteSocietyProposal(
proposalCid,
choices,
remark,
realVoter,
data,
address,
voterNetwork,
signature,
);
return;
}

ctx.body = await proposalService.vote(
proposalCid,
choices,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
const { NODE_API_ENDPOINT } = require("../../env");
const { fetchApi } = require("../../utils/fech.api");

async function getSocietyMember(network, address, height) {
const url = `${NODE_API_ENDPOINT}/${network}/society/members/${address}/height/${height}`;
return await fetchApi(url);
}

module.exports = {
getSocietyMember,
};
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,8 @@ const { nextPostUid } = require("../status.service");
const { getProposalCollection } = require("../../mongo");
const { HttpError } = require("../../exc");
const { ContentType } = require("../../constants");
const { getLatestHeight } = require("../chain.service");
const { spaces: spaceServices } = require("../../spaces");
const { checkDelegation } = require("../../services/node.service");
const { pinData, createSpaceNotifications } = require("./common");
const { isAdmin } = require("../../utils/admin");

async function createSocietyProposal({
space,
Expand All @@ -29,18 +26,8 @@ async function createSocietyProposal({
signature,
}) {
const spaceService = spaceServices[space];
if (spaceService.onlyAdminCanCreateProposals && !isAdmin(space, address)) {
throw new HttpError(401, `Only the space admins can create proposals`);
}

const weightStrategy = spaceService.weightStrategy;

const lastHeight = await getLatestHeight(proposerNetwork);

if (realProposer && realProposer !== address) {
await checkDelegation(proposerNetwork, address, realProposer, lastHeight);
}

const proposer = realProposer || address;

const { cid, pinHash } = await pinData({ data, address, signature });
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ const { getProposalById } = require("./getProposalById");
const { postComment } = require("./postComment");
const { getComments } = require("./getComments");
const { vote } = require("./vote");
const { voteSocietyProposal } = require("./voteSocietyProposal");
const { getVotes } = require("./getVotes");
const { getAddressVote } = require("./getAddressVote");
const { getStats } = require("./getStats");
Expand All @@ -19,6 +20,7 @@ module.exports = {
postComment,
getComments,
vote,
voteSocietyProposal,
getVotes,
getAddressVote,
getStats,
Expand Down
95 changes: 25 additions & 70 deletions backend/packages/backend/src/services/proposal.service/vote.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,17 +7,14 @@ const {
getSpaceCollection,
} = require("../../mongo");
const { HttpError } = require("../../exc");
const { spaces: spaceServices } = require("../../spaces");
const { checkDelegation } = require("../../services/node.service");
const { toDecimal128 } = require("../../utils");
const { getBalanceFromNetwork } = require("../../services/node.service");
const { ChoiceType } = require("../../constants");
const { pinData } = require("./common");
const { getBeenDelegated } = require("../node.service/getBeenDelegated");
const { adaptBalance } = require("../../utils/balance");
const { getDemocracyDelegated } = require("../node.service/getDelegated");
const { findDelegationStrategies } = require("../../utils/delegation");
const { getSocietyMembers } = require("../node.service/getSocietyMembers");

async function getDelegatorBalances({
proposal,
Expand Down Expand Up @@ -167,21 +164,33 @@ async function checkVoterDelegation({
}
}

async function checkWhoCanVote({ proposal, voterNetwork, address, realVoter }) {
const snapshotHeight = proposal.snapshotHeights?.[voterNetwork];
const voter = realVoter || address;
async function checkVoteThreshold({
networkBalanceDetails,
proposal,
voterNetwork,
}) {
let networksConfig = null;

if (proposal.networksConfig?.version === "4") {
networksConfig = proposal.networksConfig;
} else {
const spaceCol = await getSpaceCollection();
const spaceConfig = await spaceCol.findOne({ id: proposal.space });
networksConfig = spaceConfig;
}

const networkCfg = proposal.networksConfig.networks?.find(
(networkCfg) => networkCfg.network === voterNetwork,
const networkConfig = networksConfig?.networks?.find(
(item) => item.network === voterNetwork,
);
if (networkCfg && networkCfg.whoCanVote === "societyMember") {
const societyMembers = await getSocietyMembers(
voterNetwork,
snapshotHeight,
if (networkConfig?.assets) {
const passThreshold = networkConfig?.assets?.some((item) =>
networkBalanceDetails?.some((balance) =>
new BigNumber(item.votingThreshold || 0).lte(balance.balance),
),
);
const item = societyMembers.find((item) => item.address === voter);
if (!item) {
throw new HttpError(400, "Cannot vote");

if (!passThreshold) {
throw new HttpError(400, "You don't have enough balance to vote");
}
}
}
Expand All @@ -202,37 +211,8 @@ async function vote(
throw new HttpError(400, "Proposal not found.");
}

if (proposal.choiceType === ChoiceType.Single && choices.length !== 1) {
throw new HttpError(400, "Can vote single choice only");
}

for (const choice of choices) {
if (!proposal.choices?.includes(choice)) {
throw new HttpError(400, `Invalid choice: ${choice}`);
}
}

const now = new Date();

if (proposal.startDate > now.getTime()) {
throw new HttpError(400, "The voting is not started yet");
}

if (proposal.endDate < now.getTime()) {
throw new HttpError(400, "The voting had already ended");
}

const space = proposal.space;
const spaceService = spaceServices[space];
if (!spaceService) {
throw new HttpError(500, "Unknown space");
}

const snapshotNetworks = Object.keys(proposal.snapshotHeights);
if (!snapshotNetworks.includes(voterNetwork)) {
throw new HttpError(400, "Voter network is not supported by this proposal");
}

await checkVoterDelegation({
proposal,
voterNetwork,
Expand All @@ -243,8 +223,6 @@ async function vote(
const snapshotHeight = proposal.snapshotHeights?.[voterNetwork];
const voter = realVoter || address;

await checkWhoCanVote({ proposal, voterNetwork, address, realVoter });

const networkBalance = await getBalanceFromNetwork({
networksConfig: proposal.networksConfig,
networkName: voterNetwork,
Expand All @@ -255,30 +233,7 @@ async function vote(
const balanceOf = networkBalance?.balanceOf;
const networkBalanceDetails = networkBalance?.details;

let networksConfig = null;

if (proposal.networksConfig?.version === "4") {
networksConfig = proposal.networksConfig;
} else {
const spaceCol = await getSpaceCollection();
const spaceConfig = await spaceCol.findOne({ id: proposal.space });
networksConfig = spaceConfig;
}

const networkConfig = networksConfig?.networks?.find(
(item) => item.network === voterNetwork,
);
if (networkConfig?.assets) {
const passThreshold = networkConfig?.assets?.some((item) =>
networkBalanceDetails?.some((balance) =>
new BigNumber(item.votingThreshold || 0).lte(balance.balance),
),
);

if (!passThreshold) {
throw new HttpError(400, "You don't have enough balance to vote");
}
}
await checkVoteThreshold({ networkBalanceDetails, proposal, voterNetwork });

const delegators = await getDelegatorBalances({
proposal,
Expand Down
Loading

0 comments on commit 4a044ab

Please sign in to comment.