From 79c70a9a4fcd1ca4e9f1a73f625b84e2cfcde6e0 Mon Sep 17 00:00:00 2001 From: Ryan Bennett <126922418+rbennettcw@users.noreply.github.com> Date: Tue, 6 Aug 2024 11:26:06 -0700 Subject: [PATCH] Remove extra upvote weight + prevent stakeless upvotes in staked community (#8768) * remove extra upvote weight + prevent stakeless upvotes * apply upvote check to comment reaction * add UI toast on failed staked reaction * fix test --- libs/shared/src/commonProtocol/utils.ts | 3 +-- .../commonwealth/client/scripts/models/Reaction.ts | 2 +- .../commonwealth/client/scripts/models/Thread.ts | 2 +- .../scripts/state/api/comments/createReaction.ts | 12 ++++++++++-- .../scripts/state/api/threads/createReaction.ts | 14 +++++++++++--- .../create_comment_reaction.ts | 9 ++++++++- .../create_thread_reaction.ts | 9 ++++++++- .../integration/api/reactionVoteWeight.spec.ts | 12 ++++++------ .../server_comments_controller.spec.ts | 6 +----- .../server_threads_controller.spec.ts | 6 +----- 10 files changed, 48 insertions(+), 27 deletions(-) diff --git a/libs/shared/src/commonProtocol/utils.ts b/libs/shared/src/commonProtocol/utils.ts index 1680494b9cf..8a770d534e4 100644 --- a/libs/shared/src/commonProtocol/utils.ts +++ b/libs/shared/src/commonProtocol/utils.ts @@ -2,8 +2,7 @@ export const calculateVoteWeight = ( stakeBalance: string, voteWeight: number, ) => { - // all community members get 1 weight by default - return 1 + parseInt(stakeBalance, 10) * voteWeight; + return parseInt(stakeBalance, 10) * voteWeight; }; export enum Denominations { diff --git a/packages/commonwealth/client/scripts/models/Reaction.ts b/packages/commonwealth/client/scripts/models/Reaction.ts index c1fab0a40f9..22f469a621f 100644 --- a/packages/commonwealth/client/scripts/models/Reaction.ts +++ b/packages/commonwealth/client/scripts/models/Reaction.ts @@ -44,7 +44,7 @@ class Reaction { this.author_chain = author_chain; this.canvasSignedData = canvas_signed_data; this.canvasHash = canvas_hash; - this.calculatedVotingWeight = calculated_voting_weight || 1; + this.calculatedVotingWeight = calculated_voting_weight || 0; this.updatedAt = updated_at; this.profile = addressToUserProfile(Address); diff --git a/packages/commonwealth/client/scripts/models/Thread.ts b/packages/commonwealth/client/scripts/models/Thread.ts index e76bf86518f..ede01d428ab 100644 --- a/packages/commonwealth/client/scripts/models/Thread.ts +++ b/packages/commonwealth/client/scripts/models/Thread.ts @@ -137,7 +137,7 @@ function processAssociatedReactions( type: tempReactionType[i], address: tempAddressesReacted[i], updated_at: tempReactionTimestamps[i], - voting_weight: tempReactionWeights[i] || 1, + voting_weight: tempReactionWeights[i] || 0, reactedProfileName: emptyStringToNull(reactedProfileName?.[i]), reactedProfileAvatarUrl: emptyStringToNull( reactedProfileAvatarUrl?.[i], diff --git a/packages/commonwealth/client/scripts/state/api/comments/createReaction.ts b/packages/commonwealth/client/scripts/state/api/comments/createReaction.ts index 00b455db0ce..f2f786f71b9 100644 --- a/packages/commonwealth/client/scripts/state/api/comments/createReaction.ts +++ b/packages/commonwealth/client/scripts/state/api/comments/createReaction.ts @@ -1,6 +1,7 @@ import { toCanvasSignedDataApiArgs } from '@hicommonwealth/shared'; import { useMutation, useQueryClient } from '@tanstack/react-query'; -import axios from 'axios'; +import axios, { AxiosError } from 'axios'; +import { notifyError } from 'client/scripts/controllers/app/notifications'; import { signCommentReaction } from 'controllers/server/sessions'; import Reaction from 'models/Reaction'; import app from 'state'; @@ -87,7 +88,14 @@ const useCreateCommentReactionMutation = ({ return reaction; }, - onError: (error) => checkForSessionKeyRevalidationErrors(error), + onError: (error) => { + if (error instanceof AxiosError) { + if (error.response?.data?.error?.toLowerCase().includes('stake')) { + notifyError('Buy stake in community to upvote comments'); + } + } + return checkForSessionKeyRevalidationErrors(error); + }, }); }; diff --git a/packages/commonwealth/client/scripts/state/api/threads/createReaction.ts b/packages/commonwealth/client/scripts/state/api/threads/createReaction.ts index 534ac23f2db..afbe16f0b55 100644 --- a/packages/commonwealth/client/scripts/state/api/threads/createReaction.ts +++ b/packages/commonwealth/client/scripts/state/api/threads/createReaction.ts @@ -1,6 +1,7 @@ import { toCanvasSignedDataApiArgs } from '@hicommonwealth/shared'; import { useMutation } from '@tanstack/react-query'; -import axios from 'axios'; +import axios, { AxiosError } from 'axios'; +import { notifyError } from 'client/scripts/controllers/app/notifications'; import { signThreadReaction } from 'controllers/server/sessions'; import app from 'state'; import useUserOnboardingSliderMutationStore from 'state/ui/userTrainingCards'; @@ -68,7 +69,7 @@ const useCreateThreadReactionMutation = ({ address: response.data.result.Address.address, type: 'like', updated_at: response.data.result.updated_at, - voting_weight: response.data.result.calculated_voting_weight || 1, + voting_weight: response.data.result.calculated_voting_weight || 0, }; updateThreadInAllCaches( communityId, @@ -81,7 +82,14 @@ const useCreateThreadReactionMutation = ({ userId && markTrainingActionAsComplete(UserTrainingCardTypes.GiveUpvote, userId); }, - onError: (error) => checkForSessionKeyRevalidationErrors(error), + onError: (error) => { + if (error instanceof AxiosError) { + if (error.response?.data?.error?.toLowerCase().includes('stake')) { + notifyError('Buy stake in community to upvote threads'); + } + } + return checkForSessionKeyRevalidationErrors(error); + }, }); }; diff --git a/packages/commonwealth/server/controllers/server_comments_methods/create_comment_reaction.ts b/packages/commonwealth/server/controllers/server_comments_methods/create_comment_reaction.ts index a8ed1c4877c..c7840d13d6b 100644 --- a/packages/commonwealth/server/controllers/server_comments_methods/create_comment_reaction.ts +++ b/packages/commonwealth/server/controllers/server_comments_methods/create_comment_reaction.ts @@ -7,6 +7,7 @@ import { } from '@hicommonwealth/model'; import { PermissionEnum } from '@hicommonwealth/schemas'; import { NotificationCategories, commonProtocol } from '@hicommonwealth/shared'; +import { BigNumber } from 'ethers'; import { MixpanelCommunityInteractionEvent } from '../../../shared/analytics/types'; import { config } from '../../config'; import { validateTopicGroupsMembership } from '../../util/requirementsModule/validateTopicGroupsMembership'; @@ -23,6 +24,7 @@ const Errors = { BalanceCheckFailed: 'Could not verify user token balance', FailedCreateReaction: 'Failed to create reaction', CommunityNotFound: 'Community not found', + MustHaveStake: 'Must have stake to upvote', }; export type CreateCommentReactionOptions = { @@ -133,8 +135,13 @@ export async function __createCommentReaction( node.eth_chain_id, [address.address], ); + const stakeBalance = stakeBalances[address.address]; + if (BigNumber.from(stakeBalance).lte(0)) { + // stake is enabled but user has no stake + throw new AppError(Errors.MustHaveStake); + } calculatedVotingWeight = commonProtocol.calculateVoteWeight( - stakeBalances[address.address], + stakeBalance, voteWeight, ); } diff --git a/packages/commonwealth/server/controllers/server_threads_methods/create_thread_reaction.ts b/packages/commonwealth/server/controllers/server_threads_methods/create_thread_reaction.ts index a332c4d8bb9..6a3e39eb581 100644 --- a/packages/commonwealth/server/controllers/server_threads_methods/create_thread_reaction.ts +++ b/packages/commonwealth/server/controllers/server_threads_methods/create_thread_reaction.ts @@ -7,6 +7,7 @@ import { } from '@hicommonwealth/model'; import { PermissionEnum } from '@hicommonwealth/schemas'; import { NotificationCategories, commonProtocol } from '@hicommonwealth/shared'; +import { BigNumber } from 'ethers'; import { MixpanelCommunityInteractionEvent } from '../../../shared/analytics/types'; import { config } from '../../config'; import { validateTopicGroupsMembership } from '../../util/requirementsModule/validateTopicGroupsMembership'; @@ -23,6 +24,7 @@ export const Errors = { ThreadArchived: 'Thread is archived', FailedCreateReaction: 'Failed to create reaction', CommunityNotFound: 'Community not found', + MustHaveStake: 'Must have stake to upvote', }; export type CreateThreadReactionOptions = { @@ -125,8 +127,13 @@ export async function __createThreadReaction( node.eth_chain_id, [address.address], ); + const stakeBalance = stakeBalances[address.address]; + if (BigNumber.from(stakeBalance).lte(0)) { + // stake is enabled but user has no stake + throw new AppError(Errors.MustHaveStake); + } calculatedVotingWeight = commonProtocol.calculateVoteWeight( - stakeBalances[address.address], + stakeBalance, voteWeight, ); } diff --git a/packages/commonwealth/test/integration/api/reactionVoteWeight.spec.ts b/packages/commonwealth/test/integration/api/reactionVoteWeight.spec.ts index 3e9420b94fe..f1f1146e9c2 100644 --- a/packages/commonwealth/test/integration/api/reactionVoteWeight.spec.ts +++ b/packages/commonwealth/test/integration/api/reactionVoteWeight.spec.ts @@ -122,7 +122,7 @@ describe('Reaction vote weight', () => { reaction: 'like', threadId: thread.id, }); - const expectedWeight = 1 + 50 * 200; + const expectedWeight = 50 * 200; expect(reaction.calculated_voting_weight).to.eq(expectedWeight); const t = await server.models.Thread.findByPk(thread.id); // @ts-expect-error StrictNullChecks @@ -142,7 +142,7 @@ describe('Reaction vote weight', () => { reaction: 'like', commentId: comment.id, }); - const expectedWeight = 1 + 50 * 200; + const expectedWeight = 50 * 200; expect(reaction.calculated_voting_weight).to.eq(expectedWeight); const c = await server.models.Comment.findByPk(comment.id); // @ts-expect-error StrictNullChecks @@ -152,7 +152,7 @@ describe('Reaction vote weight', () => { test('should set thread reaction vote weight to min 1', async () => { Sinon.stub(commonProtocol.contractHelpers, 'getNamespaceBalance').resolves({ // @ts-expect-error StrictNullChecks - [address.address]: '0', + [address.address]: '17', }); const thread = await createThread(); const [reaction] = await threadsController.createThreadReaction({ @@ -161,14 +161,14 @@ describe('Reaction vote weight', () => { reaction: 'like', threadId: thread.id, }); - const expectedWeight = 1; + const expectedWeight = 17 * 200; expect(reaction.calculated_voting_weight).to.eq(expectedWeight); }); test('should set comment reaction vote weight to min 1', async () => { Sinon.stub(commonProtocol.contractHelpers, 'getNamespaceBalance').resolves({ // @ts-expect-error StrictNullChecks - [address.address]: '0', + [address.address]: '7', }); const thread = await createThread(); const comment = await createComment(thread.id); @@ -178,7 +178,7 @@ describe('Reaction vote weight', () => { reaction: 'like', commentId: comment.id, }); - const expectedWeight = 1; + const expectedWeight = 7 * 200; expect(reaction.calculated_voting_weight).to.eq(expectedWeight); }); }); diff --git a/packages/commonwealth/test/unit/server_controllers/server_comments_controller.spec.ts b/packages/commonwealth/test/unit/server_controllers/server_comments_controller.spec.ts index d8babc66940..668b880a31a 100644 --- a/packages/commonwealth/test/unit/server_controllers/server_comments_controller.spec.ts +++ b/packages/commonwealth/test/unit/server_controllers/server_comments_controller.spec.ts @@ -70,11 +70,7 @@ describe('ServerCommentsController', () => { }), }, CommunityStake: { - findOne: sandbox.stub().resolves({ - id: 5, - stake_id: 1, - vote_weight: 1, - }), + findOne: sandbox.stub().resolves(null), }, Community: { findByPk: async () => ({ diff --git a/packages/commonwealth/test/unit/server_controllers/server_threads_controller.spec.ts b/packages/commonwealth/test/unit/server_controllers/server_threads_controller.spec.ts index d57f50f6063..c8be0fb96b5 100644 --- a/packages/commonwealth/test/unit/server_controllers/server_threads_controller.spec.ts +++ b/packages/commonwealth/test/unit/server_controllers/server_threads_controller.spec.ts @@ -70,11 +70,7 @@ describe('ServerThreadsController', () => { }), }, CommunityStake: { - findOne: sandbox.stub().resolves({ - id: 5, - stake_id: 1, - vote_weight: 1, - }), + findOne: sandbox.stub().resolves(null), }, Community: {