Skip to content

Commit

Permalink
Merge pull request #10870 from hicommonwealth/ryan/fractional-upvotes1
Browse files Browse the repository at this point in the history
Allow fractional vote weights
  • Loading branch information
masvelio authored Feb 12, 2025
2 parents c74628d + 313001a commit b39c4f3
Show file tree
Hide file tree
Showing 23 changed files with 385 additions and 92 deletions.
2 changes: 1 addition & 1 deletion libs/evm-protocols/src/common-protocol/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ export type EvmClientType = Web3Type;
export const calculateVoteWeight = (
balance: string, // should be in wei
voteWeight: number = 0,
precision: number = 10 ** 18, // precision factor for multiplying
precision: number = 10 ** 16, // precision factor for multiplying
): bigint | null => {
if (!balance || voteWeight <= 0) return null;
// solution to multiply BigInt with fractional vote weight
Expand Down
9 changes: 0 additions & 9 deletions libs/model/src/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,6 @@ const {
PROVIDER_URL,
ETH_RPC,
COSMOS_REGISTRY_API,
REACTION_WEIGHT_OVERRIDE,
ALCHEMY_PRIVATE_APP_KEY,
ALCHEMY_PUBLIC_APP_KEY,
MEMBERSHIP_REFRESH_BATCH_SIZE,
Expand Down Expand Up @@ -89,11 +88,6 @@ export const config = configure(
? BLACKLISTED_EVENTS.split(',')
: [],
},
STAKE: {
REACTION_WEIGHT_OVERRIDE: REACTION_WEIGHT_OVERRIDE
? parseInt(REACTION_WEIGHT_OVERRIDE, 10)
: null,
},
CONTESTS: {
MIN_USER_ETH: 0,
MAX_USER_POSTS_PER_CONTEST: MAX_USER_POSTS_PER_CONTEST
Expand Down Expand Up @@ -216,9 +210,6 @@ export const config = configure(
OUTBOX: z.object({
BLACKLISTED_EVENTS: z.array(z.string()),
}),
STAKE: z.object({
REACTION_WEIGHT_OVERRIDE: z.number().int().nullish(),
}),
CONTESTS: z.object({
MIN_USER_ETH: z.number(),
MAX_USER_POSTS_PER_CONTEST: z.number().int(),
Expand Down
3 changes: 1 addition & 2 deletions libs/model/src/contest/Contests.projection.ts
Original file line number Diff line number Diff line change
Expand Up @@ -333,15 +333,14 @@ export function Contests(): Projection<typeof inputs> {
BigInt(payload.voting_power || 0) > BigInt(0) &&
add_action?.ContestManager?.vote_weight_multiplier
) {
const { eth_chain_id, url, private_url } =
const { eth_chain_id } =
add_action!.ContestManager!.Community!.ChainNode!;
const { funding_token_address, vote_weight_multiplier } =
add_action!.ContestManager!;
const numTokens = await getWeightedNumTokens(
payload.voter_address,
funding_token_address!,
eth_chain_id!,
getChainNodeUrl({ url, private_url }),
vote_weight_multiplier!,
);
calculated_voting_weight = numTokens.toString();
Expand Down
35 changes: 7 additions & 28 deletions libs/model/src/services/stakeHelper.ts
Original file line number Diff line number Diff line change
@@ -1,18 +1,14 @@
import { InvalidState } from '@hicommonwealth/core';
import {
commonProtocol,
getTokenAttributes,
} from '@hicommonwealth/evm-protocols';
import { commonProtocol } from '@hicommonwealth/evm-protocols';
import { TopicWeightedVoting } from '@hicommonwealth/schemas';
import { BalanceSourceType, ZERO_ADDRESS } from '@hicommonwealth/shared';
import { GetBalancesOptions, tokenBalanceCache } from '.';
import { config } from '../config';
import { models } from '../database';
import { mustExist } from '../middleware/guards';
import { contractHelpers } from '../services/commonProtocol';

/**
* Calculates voting weight of address based on the topic
* Calculates voting weight of address based on the topic in wei
* @param topic_id topic id
* @param address user's address
* @returns voting weight or null if no stake if found
Expand All @@ -21,9 +17,6 @@ export async function getVotingWeight(
topic_id: number,
address: string,
): Promise<bigint | null> {
if (config.STAKE.REACTION_WEIGHT_OVERRIDE)
return BigInt(config.STAKE.REACTION_WEIGHT_OVERRIDE);

const topic = await models.Topic.findByPk(topic_id, {
include: [
{
Expand Down Expand Up @@ -76,24 +69,21 @@ export async function getVotingWeight(
} else if (topic.weighted_voting === TopicWeightedVoting.ERC20) {
// if topic chain node is missing, fallback on community chain node
const chainNode = topic.ChainNode || community.ChainNode!;
const { eth_chain_id, private_url, url } = chainNode;
const { eth_chain_id } = chainNode;
mustExist('Chain Node Eth Chain Id', eth_chain_id);
const chainNodeUrl = private_url! || url!;
mustExist('Chain Node URL', chainNodeUrl);
mustExist('Topic Token Address', topic.token_address);

const numFullTokens = await getWeightedNumTokens(
const numTokens = await getWeightedNumTokens(
address,
topic.token_address,
eth_chain_id,
chainNodeUrl,
topic.vote_weight_multiplier!,
);
if (numFullTokens === BigInt(0)) {
if (numTokens === BigInt(0)) {
// if the weighted value is not at least a full token, reject the action
throw new InvalidState('Insufficient token balance');
}
return numFullTokens;
return numTokens;
}

// no weighted voting
Expand All @@ -104,7 +94,6 @@ export async function getWeightedNumTokens(
address: string,
tokenAddress: string,
ethChainId: number,
chainNodeUrl: string,
voteWeightMultiplier: number,
): Promise<bigint> {
const balanceOptions: GetBalancesOptions =
Expand Down Expand Up @@ -138,15 +127,5 @@ export async function getWeightedNumTokens(
tokenBalance,
voteWeightMultiplier,
);
const { decimals } = await getTokenAttributes(
tokenAddress,
chainNodeUrl,
false,
);
// only count full ERC20 tokens
const numFullTokens = result ? result / BigInt(10 ** decimals) : null;
if (!numFullTokens || numFullTokens === BigInt(0)) {
return BigInt(0);
}
return numFullTokens;
return result || BigInt(0);
}
3 changes: 2 additions & 1 deletion libs/model/src/thread/GetThreads.query.ts
Original file line number Diff line number Diff line change
Expand Up @@ -109,7 +109,8 @@ export function GetThreads(): Query<typeof schemas.GetThreads> {
'name', T.name,
'description', T.description,
'community_id', T.community_id,
'telegram', T.telegram
'telegram', T.telegram,
'weighted_voting', T.weighted_voting
) as topic,
json_build_object(
'id', A.id,
Expand Down
12 changes: 12 additions & 0 deletions libs/shared/src/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -474,3 +474,15 @@ export const buildContestPrizes = (
})
: [];
};

export const formatWeiToDecimal = (wei: string): string => {
return (parseFloat(wei) / 1e18).toString();
};

export const formatDecimalToWei = (
decimal: string,
defaultValue: number = 0,
): string => {
const value = parseFloat(decimal) * 10 ** 18;
return (value || defaultValue).toString();
};
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { AxiosError } from 'axios';
import { notifyError } from 'client/scripts/controllers/app/notifications';
import { trpc } from 'client/scripts/utils/trpcClient';
import { signThreadReaction } from 'controllers/server/sessions';
import { BigNumber } from 'ethers';
import app from 'state';
import useUserOnboardingSliderMutationStore from 'state/ui/userTrainingCards';
import { UserTrainingCardTypes } from 'views/components/UserTrainingSlider/types';
Expand Down Expand Up @@ -78,12 +79,15 @@ const useCreateThreadReactionMutation = ({
{ associatedReactions: [reaction] },
'combineAndRemoveDups',
);

const addition = BigNumber.from(currentReactionWeightsSum)
.add(BigNumber.from(newReaction.calculated_voting_weight || '0'))
.toString();

updateThreadInAllCaches(communityId, threadId, {
reactionCount: currentReactionCount + 1,
reactionWeightsSum: `${
parseInt(currentReactionWeightsSum) +
parseInt(newReaction.calculated_voting_weight || `0`)
}`,
// I think it is broken here
reactionWeightsSum: addition,
});

const userId = user.addresses?.[0]?.profile?.userId;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { toCanvasSignedDataApiArgs } from '@hicommonwealth/shared';
import { trpc } from 'client/scripts/utils/trpcClient';
import { signDeleteThreadReaction } from 'controllers/server/sessions';
import { BigNumber } from 'ethers';
import app from 'state';
import { useAuthModalStore } from '../../ui/modals';
import { userStore } from '../../ui/user';
Expand Down Expand Up @@ -68,12 +69,13 @@ const useDeleteThreadReactionMutation = ({
'removeFromExisting',
);

const subtraction = BigNumber.from(currentReactionWeightsSum)
.sub(BigNumber.from(deletedReaction?.calculated_voting_weight || '0'))
.toString();

updateThreadInAllCaches(communityId, threadId, {
reactionCount: currentReactionCount - 1,
reactionWeightsSum: `${
parseInt(currentReactionWeightsSum) -
parseInt(deletedReaction?.calculated_voting_weight || `0`)
}`,
reactionWeightsSum: subtraction,
});
}
},
Expand Down
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
import { TopicWeightedVoting } from '@hicommonwealth/schemas';
import { buildCreateCommentReactionInput } from 'client/scripts/state/api/comments/createReaction';
import { buildDeleteCommentReactionInput } from 'client/scripts/state/api/comments/deleteReaction';
import { useAuthModalStore } from 'client/scripts/state/ui/modals';
import { notifyError } from 'controllers/app/notifications';
import { SessionKeyError } from 'controllers/server/sessions';
import { BigNumber } from 'ethers';
import React, { useState } from 'react';
import { prettyVoteWeight } from 'shared/adapters/currency';
import app from 'state';
import useUserStore from 'state/ui/user';
import CWUpvoteSmall from 'views/components/component_kit/new_designs/CWUpvoteSmall';
Expand All @@ -21,13 +23,15 @@ type CommentReactionButtonProps = {
disabled: boolean;
tooltipText?: string;
onReaction?: () => void;
weightType?: TopicWeightedVoting | null;
};

export const CommentReactionButton = ({
comment,
disabled,
tooltipText = '',
onReaction,
weightType,
}: CommentReactionButtonProps) => {
const [isAuthModalOpen, setIsAuthModalOpen] = useState<boolean>(false);
const user = useUserStore();
Expand Down Expand Up @@ -109,14 +113,21 @@ export const CommentReactionButton = ({
}
};

const formattedVoteCount = prettyVoteWeight(
reactionWeightsSum.toString(),
weightType,
1,
6,
);

return (
<>
<AuthModal
onClose={() => setIsAuthModalOpen(false)}
isOpen={isAuthModalOpen}
/>
<CWUpvoteSmall
voteCount={reactionWeightsSum.toString()}
voteCount={formattedVoteCount}
disabled={!user.activeAccount || disabled}
selected={hasReacted}
onClick={handleVoteClick}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { TopicWeightedVoting } from '@hicommonwealth/schemas';
import { DEFAULT_NAME } from '@hicommonwealth/shared';
import React, { Dispatch, SetStateAction } from 'react';
import app from 'state';
Expand All @@ -8,12 +9,14 @@ type ViewCommentUpvotesDrawerProps = {
comment?: CommentViewParams;
isOpen: boolean;
setIsOpen: Dispatch<SetStateAction<boolean>>;
weightType?: TopicWeightedVoting | null;
};

export const ViewCommentUpvotesDrawer = ({
comment,
isOpen,
setIsOpen,
weightType,
}: ViewCommentUpvotesDrawerProps) => {
return (
<ViewUpvotesDrawer
Expand All @@ -35,6 +38,7 @@ export const ViewCommentUpvotesDrawer = ({
}
// @ts-expect-error <StrictNullChecks/>
publishDate={(comment?.created_at as string) || ''} // TODO: fix type
topicWeight={weightType}
/>
);
};
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ export const ViewThreadUpvotesDrawer = ({
? app.chain.accounts.get(thread?.author)
: null
}
topicWeight={thread?.topic?.weighted_voting}
publishDate={thread.createdAt}
/>
);
Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
import { TopicWeightedVoting } from '@hicommonwealth/schemas';
import { APIOrderDirection } from 'helpers/constants';
import Account from 'models/Account';
import AddressInfo from 'models/AddressInfo';
import MinimumProfile from 'models/MinimumProfile';
import React, { Dispatch, SetStateAction } from 'react';
import { prettyVoteWeight } from 'shared/adapters/currency';
import app from 'state';
import { User } from 'views/components/user/user';
import { AuthorAndPublishInfo } from '../../../pages/discussions/ThreadCard/AuthorAndPublishInfo';
Expand All @@ -24,6 +26,7 @@ type ViewUpvotesDrawerProps = {
publishDate: moment.Moment;
isOpen: boolean;
setIsOpen: Dispatch<SetStateAction<boolean>>;
topicWeight?: TopicWeightedVoting | null | undefined;
};

type Upvoter = {
Expand Down Expand Up @@ -63,9 +66,17 @@ export const ViewUpvotesDrawer = ({
publishDate,
isOpen,
setIsOpen,
topicWeight,
}: ViewUpvotesDrawerProps) => {
const tableState = useCWTableState({
columns,
columns: columns.map((c) =>
c.key === 'voteWeight'
? {
...c,
weightedVoting: topicWeight,
}
: c,
),
initialSortColumn: 'timestamp',
initialSortDirection: APIOrderDirection.Desc,
});
Expand Down Expand Up @@ -93,7 +104,10 @@ export const ViewUpvotesDrawer = ({
};

const getVoteWeightTotal = (voters: Upvoter[]) => {
return voters.reduce((memo, current) => memo + current.voting_weight, 0);
return voters.reduce(
(memo, current) => memo + Number(current.voting_weight),
0,
);
};

const getAuthorCommunityId = (contentAuthor: Profile) => {
Expand Down Expand Up @@ -175,7 +189,14 @@ export const ViewUpvotesDrawer = ({
<CWText type="caption" fontWeight="uppercase">
Total
</CWText>
<CWText type="b2">{getVoteWeightTotal(reactorData)}</CWText>
<CWText type="b2">
{prettyVoteWeight(
getVoteWeightTotal(reactorData).toString(),
topicWeight,
1,
6,
)}
</CWText>
</div>
</div>
</>
Expand Down
Loading

0 comments on commit b39c4f3

Please sign in to comment.