From 92c7e4e42059f8650b11416ab5a5f6df250f318f Mon Sep 17 00:00:00 2001 From: chaojun Date: Tue, 3 Sep 2024 09:39:58 +0800 Subject: [PATCH] Check social member, #1034 --- .../src/scripts/spaces/kusamaSociety.js | 1 + .../node.service/getSocietyMembers.js | 11 +++ .../src/services/proposal.service/vote.js | 97 +++++++++++++------ .../src/features/society/getSocietyMembers.js | 40 ++++++++ .../node-api/src/features/society/routes.js | 7 ++ backend/packages/node-api/src/routes.js | 7 ++ 6 files changed, 135 insertions(+), 28 deletions(-) create mode 100644 backend/packages/backend/src/services/node.service/getSocietyMembers.js create mode 100644 backend/packages/node-api/src/features/society/getSocietyMembers.js create mode 100644 backend/packages/node-api/src/features/society/routes.js diff --git a/backend/packages/backend/src/scripts/spaces/kusamaSociety.js b/backend/packages/backend/src/scripts/spaces/kusamaSociety.js index ece85158..37c826ae 100644 --- a/backend/packages/backend/src/scripts/spaces/kusamaSociety.js +++ b/backend/packages/backend/src/scripts/spaces/kusamaSociety.js @@ -9,6 +9,7 @@ const config = { { network: networks.kusama, ss58Format: 2, + whoCanVote: "societyMember", assets: [ { symbol: "KSM", diff --git a/backend/packages/backend/src/services/node.service/getSocietyMembers.js b/backend/packages/backend/src/services/node.service/getSocietyMembers.js new file mode 100644 index 00000000..0961ec2a --- /dev/null +++ b/backend/packages/backend/src/services/node.service/getSocietyMembers.js @@ -0,0 +1,11 @@ +const { NODE_API_ENDPOINT } = require("../../env"); +const { fetchApi } = require("../../utils/fech.api"); + +async function getSocietyMembers(network, height) { + const url = `${NODE_API_ENDPOINT}/${network}/society/members/height/${height}`; + return await fetchApi(url); +} + +module.exports = { + getSocietyMembers, +}; diff --git a/backend/packages/backend/src/services/proposal.service/vote.js b/backend/packages/backend/src/services/proposal.service/vote.js index 09c42d64..cf945c2c 100644 --- a/backend/packages/backend/src/services/proposal.service/vote.js +++ b/backend/packages/backend/src/services/proposal.service/vote.js @@ -17,6 +17,7 @@ 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, @@ -134,6 +135,57 @@ async function addDelegatedVotes( } } +async function checkVoterDelegation({ + proposal, + voterNetwork, + address, + realVoter, +}) { + const snapshotHeight = proposal.snapshotHeights?.[voterNetwork]; + const voter = realVoter || address; + + if (realVoter && realVoter !== address) { + await checkDelegation(voterNetwork, address, realVoter, snapshotHeight); + } + + const delegationStrategies = findDelegationStrategies( + proposal.networksConfig, + voterNetwork, + ); + if (delegationStrategies.includes("democracy")) { + const delegation = await getDemocracyDelegated( + voterNetwork, + snapshotHeight, + voter, + ); + if (!isEmpty(delegation)) { + throw new HttpError( + 400, + "You can't vote because you have delegated your votes", + ); + } + } +} + +async function checkWhoCanVote({ proposal, voterNetwork, address, realVoter }) { + const snapshotHeight = proposal.snapshotHeights?.[voterNetwork]; + const voter = realVoter || address; + + const networkCfg = proposal.networksConfig.networks?.find( + (networkCfg) => networkCfg.network === voterNetwork, + ); + if (networkCfg && networkCfg.whoCanVote === "societyMember") { + const societyMembers = await getSocietyMembers( + voterNetwork, + snapshotHeight, + ); + const item = societyMembers.find((item) => item.address === voter); + if (!item) { + throw new HttpError(400, "Cannot vote"); + } + } +} + async function vote( proposalCid, choices, @@ -181,30 +233,17 @@ async function vote( throw new HttpError(400, "Voter network is not supported by this proposal"); } - const snapshotHeight = proposal.snapshotHeights?.[voterNetwork]; - if (realVoter && realVoter !== address) { - await checkDelegation(voterNetwork, address, realVoter, snapshotHeight); - } + await checkVoterDelegation({ + proposal, + voterNetwork, + address, + realVoter, + }); + const snapshotHeight = proposal.snapshotHeights?.[voterNetwork]; const voter = realVoter || address; - const delegationStrategies = findDelegationStrategies( - proposal.networksConfig, - voterNetwork, - ); - if (delegationStrategies.includes("democracy")) { - const delegation = await getDemocracyDelegated( - voterNetwork, - snapshotHeight, - voter, - ); - if (!isEmpty(delegation)) { - throw new HttpError( - 400, - "You can't vote because you have delegated your votes", - ); - } - } + await checkWhoCanVote({ proposal, voterNetwork, address, realVoter }); const networkBalance = await getBalanceFromNetwork({ networksConfig: proposal.networksConfig, @@ -229,14 +268,16 @@ async function vote( const networkConfig = networksConfig?.networks?.find( (item) => item.network === voterNetwork, ); - const passThreshold = networkConfig?.assets?.some((item) => - networkBalanceDetails?.some((balance) => - new BigNumber(item.votingThreshold || 0).lte(balance.balance), - ), - ); + 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"); + if (!passThreshold) { + throw new HttpError(400, "You don't have enough balance to vote"); + } } const delegators = await getDelegatorBalances({ diff --git a/backend/packages/node-api/src/features/society/getSocietyMembers.js b/backend/packages/node-api/src/features/society/getSocietyMembers.js new file mode 100644 index 00000000..fc91f204 --- /dev/null +++ b/backend/packages/node-api/src/features/society/getSocietyMembers.js @@ -0,0 +1,40 @@ +const { getApis, getBlockApi } = require("@osn/polkadot-api-container"); +const { chains } = require("../../constants"); + +async function getSocietyMembersFromOneApi(api, blockHashOrHeight) { + let blockApi = await getBlockApi(api, blockHashOrHeight); + const societyMembers = await blockApi.query.society.members.entries(); + + return societyMembers.map(([key, value]) => { + const { + args: [id], + } = key; + const address = id.toJSON(); + const data = value.toJSON(); + return { address, ...data }; + }); +} + +async function getSocietyMembersFromApis(apis, blockHashOrHeight) { + const promises = []; + for (const api of apis) { + promises.push(getSocietyMembersFromOneApi(api, blockHashOrHeight)); + } + + return await Promise.any(promises); +} + +async function getSocietyMembers(ctx) { + const { chain } = ctx.params; + const { block: blockHashOrHeight } = ctx.query; + if (![chains.kusama].includes(chain)) { + ctx.throw(400, `Not support chain ${chain}`); + } + + const apis = getApis(chain); + ctx.body = await getSocietyMembersFromApis(apis, blockHashOrHeight); +} + +module.exports = { + getSocietyMembers, +}; diff --git a/backend/packages/node-api/src/features/society/routes.js b/backend/packages/node-api/src/features/society/routes.js new file mode 100644 index 00000000..49209b0e --- /dev/null +++ b/backend/packages/node-api/src/features/society/routes.js @@ -0,0 +1,7 @@ +const Router = require("koa-router"); +const { getSocietyMembers } = require("./getSocietyMembers"); + +const router = new Router(); +router.get("/society/members/height/:blockHashOrHeight?", getSocietyMembers); + +module.exports = router; diff --git a/backend/packages/node-api/src/routes.js b/backend/packages/node-api/src/routes.js index ba9a4750..9dde57b7 100644 --- a/backend/packages/node-api/src/routes.js +++ b/backend/packages/node-api/src/routes.js @@ -14,6 +14,7 @@ const tokenMetaRoutes = require("./features/tokenMeta/routes"); const evmRoutes = require("./features/evm/routes"); const issuanceRoutes = require("./features/issuance/routes"); const stafiRoutes = require("./features/stafi/routes"); +const societyRoutes = require("./features/society/routes"); const { evmChains } = require("./constants"); const { chains } = require("./constants"); @@ -38,6 +39,12 @@ module.exports = (app) => { stafiRoutes.allowedMethods({ throw: true }), ); + router.use( + "/:chain(kusama)", + societyRoutes.routes(), + societyRoutes.allowedMethods({ throw: true }), + ); + router.use( "/:chain", tokenMetaRoutes.routes(),