• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

hicommonwealth / commonwealth / 12304263166

12 Dec 2024 08:28PM UTC coverage: 46.87%. First build
12304263166

Pull #10167

github

web-flow
Merge 8892fe0b8 into 807071c54
Pull Request #10167: Farcaster weighted voting

1192 of 2847 branches covered (41.87%)

Branch coverage included in aggregate %.

4 of 59 new or added lines in 5 files covered. (6.78%)

2447 of 4917 relevant lines covered (49.77%)

30.9 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

35.82
/libs/model/src/services/stakeHelper.ts
1
import { InvalidState } from '@hicommonwealth/core';
2
import { commonProtocol } from '@hicommonwealth/evm-protocols';
3
import { TopicWeightedVoting } from '@hicommonwealth/schemas';
4
import { BalanceSourceType, ZERO_ADDRESS } from '@hicommonwealth/shared';
5
import { GetBalancesOptions, tokenBalanceCache } from '.';
6
import { config } from '../config';
7
import { models } from '../database';
8
import { mustExist } from '../middleware/guards';
9
import { contractHelpers } from '../services/commonProtocol';
10
import { getTokenAttributes } from './commonProtocol/contractHelpers';
11

12
/**
13
 * Calculates voting weight of address based on the topic
14
 * @param topic_id topic id
15
 * @param address user's address
16
 * @returns voting weight or null if no stake if found
17
 */
18
export async function getVotingWeight(
19
  topic_id: number,
20
  address: string,
21
): Promise<bigint | null> {
22
  if (config.STAKE.REACTION_WEIGHT_OVERRIDE)
11!
NEW
23
    return BigInt(config.STAKE.REACTION_WEIGHT_OVERRIDE);
×
24

25
  const topic = await models.Topic.findByPk(topic_id, {
11✔
26
    include: [
27
      {
28
        model: models.Community,
29
        as: 'community',
30
        required: true,
31
        include: [
32
          {
33
            model: models.ChainNode.scope('withPrivateData'),
34
            required: false,
35
          },
36
          {
37
            model: models.CommunityStake,
38
            required: false,
39
          },
40
        ],
41
      },
42
      {
43
        model: models.ChainNode.scope('withPrivateData'),
44
        required: false,
45
      },
46
    ],
47
  });
48
  mustExist('Topic', topic);
11✔
49

50
  const { community } = topic;
11✔
51
  mustExist('Community', community);
11✔
52

53
  const namespaceChainNode = community.ChainNode;
11✔
54

55
  if (topic.weighted_voting === TopicWeightedVoting.Stake) {
11✔
56
    mustExist('Chain Node Eth Chain Id', namespaceChainNode?.eth_chain_id);
9✔
57
    mustExist('Community Namespace Address', community.namespace_address);
9✔
58

59
    const stake = topic.community?.CommunityStakes?.at(0);
9✔
60
    mustExist('Community Stake', stake);
9✔
61

62
    const stakeBalances = await contractHelpers.getNamespaceBalance(
9✔
63
      community.namespace_address,
64
      stake.stake_id,
65
      namespaceChainNode.eth_chain_id,
66
      [address],
67
    );
68
    const stakeBalance = stakeBalances[address];
9✔
69
    if (BigInt(stakeBalance) === BigInt(0)) {
9✔
70
      throw new InvalidState('Must have stake to upvote');
2✔
71
    }
72

73
    return commonProtocol.calculateVoteWeight(stakeBalance, stake.vote_weight);
7✔
74
  } else if (topic.weighted_voting === TopicWeightedVoting.ERC20) {
2!
75
    // if topic chain node is missing, fallback on community chain node
76
    const chainNode = topic.ChainNode || community.ChainNode!;
×
77
    const { eth_chain_id, private_url, url } = chainNode;
×
78
    mustExist('Chain Node Eth Chain Id', eth_chain_id);
×
79
    const chainNodeUrl = private_url! || url!;
×
80
    mustExist('Chain Node URL', chainNodeUrl);
×
81
    mustExist('Topic Token Address', topic.token_address);
×
82

NEW
83
    const numFullTokens = await getWeightedNumTokens(
×
84
      address,
85
      topic.token_address,
86
      eth_chain_id,
87
      chainNodeUrl,
88
      topic.vote_weight_multiplier!,
89
    );
NEW
90
    if (numFullTokens === BigInt(0)) {
×
91
      // if the weighted value is not at least a full token, reject the action
92
      throw new InvalidState('Insufficient token balance');
×
93
    }
94
    return numFullTokens;
×
95
  }
96

97
  // no weighted voting
98
  return null;
2✔
99
}
100

101
export async function getWeightedNumTokens(
102
  address: string,
103
  tokenAddress: string,
104
  ethChainId: number,
105
  chainNodeUrl: string,
106
  voteWeightMultiplier: number,
107
): Promise<bigint> {
108
  const balanceOptions: GetBalancesOptions =
NEW
109
    tokenAddress == ZERO_ADDRESS
×
110
      ? {
111
          balanceSourceType: BalanceSourceType.ETHNative,
112
          addresses: [address],
113
          sourceOptions: {
114
            evmChainId: ethChainId,
115
          },
116
        }
117
      : {
118
          balanceSourceType: BalanceSourceType.ERC20,
119
          addresses: [address],
120
          sourceOptions: {
121
            evmChainId: ethChainId,
122
            contractAddress: tokenAddress,
123
          },
124
        };
125

NEW
126
  balanceOptions.cacheRefresh = true;
×
127

NEW
128
  const balances = await tokenBalanceCache.getBalances(balanceOptions);
×
129

NEW
130
  const tokenBalance = balances[address];
×
131

NEW
132
  if (BigInt(tokenBalance || 0) <= BigInt(0)) {
×
NEW
133
    throw new InvalidState('Insufficient token balance');
×
134
  }
NEW
135
  const result = commonProtocol.calculateVoteWeight(
×
136
    tokenBalance,
137
    voteWeightMultiplier,
138
  );
NEW
139
  const { decimals } = await getTokenAttributes(
×
140
    tokenAddress,
141
    chainNodeUrl,
142
    false,
143
  );
144
  // only count full ERC20 tokens
NEW
145
  const numFullTokens = result ? result / BigInt(10 ** decimals) : null;
×
NEW
146
  if (!numFullTokens || numFullTokens === BigInt(0)) {
×
NEW
147
    return BigInt(0);
×
148
  }
NEW
149
  return numFullTokens;
×
150
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc