@snapshot-labs/snapshot.js 0.12.52 → 0.12.53

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@snapshot-labs/snapshot.js",
3
- "version": "0.12.52",
3
+ "version": "0.12.53",
4
4
  "repository": "snapshot-labs/snapshot.js",
5
5
  "license": "MIT",
6
6
  "main": "dist/snapshot.cjs.js",
@@ -2,27 +2,27 @@
2
2
 
3
3
  exports[`Partial ranking 1`] = `
4
4
  [
5
- 4,
6
- 6,
7
- 2,
5
+ 1.6666666666666665,
6
+ 2.5,
7
+ 0.8333333333333333,
8
8
  0,
9
9
  ]
10
10
  `;
11
11
 
12
12
  exports[`getScores 1`] = `
13
13
  [
14
- 5,
15
- 5,
16
- 2,
14
+ 1.6666666666666667,
15
+ 1.6666666666666667,
16
+ 0.6666666666666666,
17
17
  0,
18
18
  ]
19
19
  `;
20
20
 
21
21
  exports[`getScores 2`] = `
22
22
  [
23
- 5,
24
- 5,
25
- 2,
23
+ 1.6666666666666667,
24
+ 1.6666666666666667,
25
+ 0.6666666666666666,
26
26
  0,
27
27
  ]
28
28
  `;
@@ -45,16 +45,32 @@ exports[`getScores 4`] = `
45
45
  ]
46
46
  `;
47
47
 
48
+ exports[`getScores with 0 votes 1`] = `
49
+ [
50
+ 0,
51
+ 0,
52
+ 0,
53
+ ]
54
+ `;
55
+
56
+ exports[`getScores with mixed voting powers 1`] = `
57
+ [
58
+ 668334.3333333333,
59
+ 334167.1666666666,
60
+ 0,
61
+ ]
62
+ `;
63
+
48
64
  exports[`getScoresByStrategy 1`] = `
49
65
  [
50
66
  [
51
- 5,
67
+ 1.6666666666666667,
52
68
  ],
53
69
  [
54
- 5,
70
+ 1.6666666666666667,
55
71
  ],
56
72
  [
57
- 2,
73
+ 0.6666666666666666,
58
74
  ],
59
75
  [
60
76
  0,
@@ -65,13 +81,13 @@ exports[`getScoresByStrategy 1`] = `
65
81
  exports[`getScoresByStrategy 2`] = `
66
82
  [
67
83
  [
68
- 5,
84
+ 1.6666666666666667,
69
85
  ],
70
86
  [
71
- 5,
87
+ 1.6666666666666667,
72
88
  ],
73
89
  [
74
- 2,
90
+ 0.6666666666666666,
75
91
  ],
76
92
  [
77
93
  0,
@@ -82,19 +98,19 @@ exports[`getScoresByStrategy 2`] = `
82
98
  exports[`getScoresByStrategy 3`] = `
83
99
  [
84
100
  [
85
- 5,
86
- 5,
87
- 5,
101
+ 1.6666666666666667,
102
+ 1.6666666666666667,
103
+ 1.6666666666666667,
88
104
  ],
89
105
  [
90
- 5,
91
- 5,
92
- 5,
106
+ 1.6666666666666667,
107
+ 1.6666666666666667,
108
+ 1.6666666666666667,
93
109
  ],
94
110
  [
95
- 2,
96
- 2,
97
- 2,
111
+ 0.6666666666666666,
112
+ 0.6666666666666666,
113
+ 0.6666666666666666,
98
114
  ],
99
115
  [
100
116
  0,
@@ -107,19 +123,19 @@ exports[`getScoresByStrategy 3`] = `
107
123
  exports[`getScoresByStrategy 4`] = `
108
124
  [
109
125
  [
110
- 5,
111
- 5,
112
- 5,
126
+ 1.6666666666666667,
127
+ 1.6666666666666667,
128
+ 1.6666666666666667,
113
129
  ],
114
130
  [
115
- 5,
116
- 5,
117
- 5,
131
+ 1.6666666666666667,
132
+ 1.6666666666666667,
133
+ 1.6666666666666667,
118
134
  ],
119
135
  [
120
- 2,
121
- 2,
122
- 2,
136
+ 0.6666666666666666,
137
+ 0.6666666666666666,
138
+ 0.6666666666666666,
123
139
  ],
124
140
  [
125
141
  0,
@@ -129,6 +145,23 @@ exports[`getScoresByStrategy 4`] = `
129
145
  ]
130
146
  `;
131
147
 
148
+ exports[`getScoresByStrategy normalizes correctly 1`] = `
149
+ [
150
+ [
151
+ 7.5,
152
+ 22.5,
153
+ ],
154
+ [
155
+ 7.5,
156
+ 22.5,
157
+ ],
158
+ [
159
+ 7.5,
160
+ 22.5,
161
+ ],
162
+ ]
163
+ `;
164
+
132
165
  exports[`getScoresTotal 1`] = `4`;
133
166
 
134
167
  exports[`getScoresTotal 2`] = `12`;
@@ -49,6 +49,44 @@ const votesWithInvalidChoices2 = () => {
49
49
  return [...invalidVotes, ...example2().votes];
50
50
  };
51
51
 
52
+ // Helper function to create example with decimal voting powers
53
+ const exampleWithDecimals = () => {
54
+ const proposal = {
55
+ choices: ['Alice', 'Bob', 'Carol']
56
+ };
57
+ const strategies = [{ name: 'ticket', network: '1', params: {} }];
58
+ const votes = [
59
+ { choice: [1, 2, 3], balance: 1.5, scores: [1.5] },
60
+ { choice: [2, 1, 3], balance: 2.75, scores: [2.75] },
61
+ { choice: [3, 2, 1], balance: 0.25, scores: [0.25] }
62
+ ];
63
+
64
+ return {
65
+ proposal,
66
+ strategies,
67
+ votes
68
+ };
69
+ };
70
+
71
+ // Helper function to create example with high voting powers
72
+ const exampleWithHighPowers = () => {
73
+ const proposal = {
74
+ choices: ['Alice', 'Bob', 'Carol']
75
+ };
76
+ const strategies = [{ name: 'ticket', network: '1', params: {} }];
77
+ const votes = [
78
+ { choice: [1, 2, 3], balance: 1000000, scores: [1000000] },
79
+ { choice: [2, 1, 3], balance: 2500000, scores: [2500000] },
80
+ { choice: [3, 2, 1], balance: 1500000, scores: [1500000] }
81
+ ];
82
+
83
+ return {
84
+ proposal,
85
+ strategies,
86
+ votes
87
+ };
88
+ };
89
+
52
90
  // Test cases for getScores method
53
91
  test.each([
54
92
  [example.proposal, example.votes, example.strategies],
@@ -81,6 +119,42 @@ test.each([
81
119
  expect(copeland.getScoresByStrategy()).toMatchSnapshot();
82
120
  });
83
121
 
122
+ // Add test for verifying strategy normalization
123
+ test('getScoresByStrategy normalizes correctly', () => {
124
+ const proposal = {
125
+ choices: ['Alice', 'Bob', 'Carol']
126
+ };
127
+ const strategies = [
128
+ { name: 'ticket', network: '1', params: {} },
129
+ { name: 'erc20-balance-of', network: '1', params: {} }
130
+ ];
131
+ const votes = [
132
+ { choice: [1, 2, 3], balance: 10, scores: [5, 15] },
133
+ { choice: [2, 3, 1], balance: 20, scores: [10, 30] },
134
+ { choice: [3, 1, 2], balance: 15, scores: [7.5, 22.5] }
135
+ ];
136
+
137
+ const copeland = new CopelandVoting(proposal, votes, strategies, [1]);
138
+
139
+ const scoresByStrategy = copeland.getScoresByStrategy();
140
+ expect(scoresByStrategy).toMatchSnapshot();
141
+
142
+ // Verify totals per strategy
143
+ const strategyTotals = [22.5, 67.5]; // Sum of all votes per strategy
144
+
145
+ // Sum the scores for each strategy
146
+ const strategyTotalResults = [0, 0];
147
+ for (let i = 0; i < strategies.length; i++) {
148
+ for (let j = 0; j < proposal.choices.length; j++) {
149
+ strategyTotalResults[i] += scoresByStrategy[j][i];
150
+ }
151
+ }
152
+
153
+ // Verify total voting power is preserved for each strategy
154
+ expect(strategyTotalResults[0]).toBeCloseTo(strategyTotals[0], 5);
155
+ expect(strategyTotalResults[1]).toBeCloseTo(strategyTotals[1], 5);
156
+ });
157
+
84
158
  // Test cases for getScoresTotal method
85
159
  test.each([
86
160
  [example.proposal, example.votes, example.strategies],
@@ -127,3 +201,30 @@ test('Partial ranking', () => {
127
201
  );
128
202
  expect(copeland.getScores()).toMatchSnapshot();
129
203
  });
204
+
205
+ test('getScores with mixed voting powers', () => {
206
+ const proposal = {
207
+ choices: ['Alice', 'Bob', 'Carol']
208
+ };
209
+ const votes = [
210
+ { choice: [1, 2, 3], balance: 1000000.75, scores: [1000000.75] },
211
+ { choice: [2, 3, 1], balance: 0.25, scores: [0.25] },
212
+ { choice: [3, 1, 2], balance: 2500.5, scores: [2500.5] }
213
+ ];
214
+ const copeland = new CopelandVoting(proposal, votes, example.strategies, [1]);
215
+ const scores = copeland.getScores();
216
+ expect(scores).toMatchSnapshot();
217
+ // Verify total voting power is preserved
218
+ expect(scores.reduce((a, b) => a + b)).toBeCloseTo(1002501.5, 5);
219
+ });
220
+
221
+ test('getScores with 0 votes', () => {
222
+ const proposal = {
223
+ choices: ['Alice', 'Bob', 'Carol']
224
+ };
225
+ const votes = [];
226
+ const copeland = new CopelandVoting(proposal, votes, example.strategies, [1]);
227
+ const scores = copeland.getScores();
228
+ expect(scores).toMatchSnapshot();
229
+ // Verify total voting power is preserved
230
+ });
@@ -57,37 +57,64 @@ export default class CopelandVoting {
57
57
  const pairwiseComparisons = Array.from({ length: choicesCount }, () =>
58
58
  Array(choicesCount).fill(0)
59
59
  );
60
+ const totalVotingPower = this.getScoresTotal();
60
61
 
61
62
  // Calculate pairwise comparisons
62
63
  for (const vote of validVotes) {
63
- for (let i = 0; i < vote.choice.length; i++) {
64
- for (let j = i + 1; j < vote.choice.length; j++) {
65
- const winner = vote.choice[i] - 1;
66
- const loser = vote.choice[j] - 1;
67
- pairwiseComparisons[winner][loser] += vote.balance;
68
- pairwiseComparisons[loser][winner] -= vote.balance;
64
+ for (
65
+ let currentRank = 0;
66
+ currentRank < vote.choice.length;
67
+ currentRank++
68
+ ) {
69
+ for (
70
+ let nextRank = currentRank + 1;
71
+ nextRank < vote.choice.length;
72
+ nextRank++
73
+ ) {
74
+ const preferredChoice = vote.choice[currentRank] - 1;
75
+ const lowerChoice = vote.choice[nextRank] - 1;
76
+ pairwiseComparisons[preferredChoice][lowerChoice] += vote.balance;
77
+ pairwiseComparisons[lowerChoice][preferredChoice] -= vote.balance;
69
78
  }
70
79
  }
71
80
  }
72
81
 
73
82
  // Calculate Copeland scores
74
83
  const scores = Array(choicesCount).fill(0);
75
- for (let i = 0; i < choicesCount; i++) {
76
- for (let j = 0; j < choicesCount; j++) {
77
- if (i !== j) {
78
- if (pairwiseComparisons[i][j] > 0) {
79
- scores[i]++;
80
- } else if (pairwiseComparisons[i][j] < 0) {
81
- scores[j]++;
84
+ let totalCopelandScore = 0;
85
+
86
+ for (let choiceIndex = 0; choiceIndex < choicesCount; choiceIndex++) {
87
+ for (
88
+ let opponentIndex = 0;
89
+ opponentIndex < choicesCount;
90
+ opponentIndex++
91
+ ) {
92
+ if (choiceIndex !== opponentIndex) {
93
+ const comparison = pairwiseComparisons[choiceIndex][opponentIndex];
94
+ if (comparison > 0) {
95
+ scores[choiceIndex]++;
96
+ } else if (comparison < 0) {
97
+ scores[opponentIndex]++;
82
98
  } else {
83
- scores[i] += 0.5;
84
- scores[j] += 0.5;
99
+ scores[choiceIndex] += 0.5;
100
+ scores[opponentIndex] += 0.5;
85
101
  }
86
102
  }
87
103
  }
88
104
  }
89
105
 
90
- return scores;
106
+ // Calculate total Copeland score for normalization
107
+ totalCopelandScore = scores.reduce((sum, score) => sum + score, 0);
108
+
109
+ // Normalize scores to distribute voting power
110
+ if (totalCopelandScore > 0) {
111
+ return scores.map(
112
+ (score) => (score / totalCopelandScore) * totalVotingPower
113
+ );
114
+ }
115
+
116
+ // If no clear winners, distribute power equally
117
+ return scores.map(() => totalVotingPower / choicesCount);
91
118
  }
92
119
 
93
120
  // Calculates the Copeland scores for each choice, broken down by strategy
@@ -99,15 +126,37 @@ export default class CopelandVoting {
99
126
  Array.from({ length: choicesCount }, () => Array(strategiesCount).fill(0))
100
127
  );
101
128
 
129
+ // Calculate total voting power per strategy
130
+ const strategyTotals = Array(strategiesCount).fill(0);
131
+ for (const vote of validVotes) {
132
+ for (let i = 0; i < strategiesCount; i++) {
133
+ strategyTotals[i] += vote.scores[i];
134
+ }
135
+ }
136
+
102
137
  // Calculate pairwise comparisons for each strategy
103
138
  for (const vote of validVotes) {
104
- for (let i = 0; i < vote.choice.length; i++) {
105
- for (let j = i + 1; j < vote.choice.length; j++) {
106
- const winner = vote.choice[i] - 1;
107
- const loser = vote.choice[j] - 1;
108
- for (let s = 0; s < strategiesCount; s++) {
109
- pairwiseComparisons[winner][loser][s] += vote.scores[s];
110
- pairwiseComparisons[loser][winner][s] -= vote.scores[s];
139
+ for (
140
+ let currentRank = 0;
141
+ currentRank < vote.choice.length;
142
+ currentRank++
143
+ ) {
144
+ for (
145
+ let nextRank = currentRank + 1;
146
+ nextRank < vote.choice.length;
147
+ nextRank++
148
+ ) {
149
+ const preferredChoice = vote.choice[currentRank] - 1;
150
+ const lowerChoice = vote.choice[nextRank] - 1;
151
+ for (
152
+ let strategyIndex = 0;
153
+ strategyIndex < strategiesCount;
154
+ strategyIndex++
155
+ ) {
156
+ pairwiseComparisons[preferredChoice][lowerChoice][strategyIndex] +=
157
+ vote.scores[strategyIndex];
158
+ pairwiseComparisons[lowerChoice][preferredChoice][strategyIndex] -=
159
+ vote.scores[strategyIndex];
111
160
  }
112
161
  }
113
162
  }
@@ -118,24 +167,66 @@ export default class CopelandVoting {
118
167
  Array(strategiesCount).fill(0)
119
168
  );
120
169
 
121
- for (let i = 0; i < choicesCount; i++) {
122
- for (let j = 0; j < choicesCount; j++) {
123
- if (i !== j) {
124
- for (let s = 0; s < strategiesCount; s++) {
125
- if (pairwiseComparisons[i][j][s] > 0) {
126
- scores[i][s]++;
127
- } else if (pairwiseComparisons[i][j][s] < 0) {
128
- scores[j][s]++;
170
+ for (let choiceIndex = 0; choiceIndex < choicesCount; choiceIndex++) {
171
+ for (
172
+ let opponentIndex = 0;
173
+ opponentIndex < choicesCount;
174
+ opponentIndex++
175
+ ) {
176
+ if (choiceIndex !== opponentIndex) {
177
+ for (
178
+ let strategyIndex = 0;
179
+ strategyIndex < strategiesCount;
180
+ strategyIndex++
181
+ ) {
182
+ const comparison =
183
+ pairwiseComparisons[choiceIndex][opponentIndex][strategyIndex];
184
+ if (comparison > 0) {
185
+ scores[choiceIndex][strategyIndex]++;
186
+ } else if (comparison < 0) {
187
+ scores[opponentIndex][strategyIndex]++;
129
188
  } else {
130
- scores[i][s] += 0.5;
131
- scores[j][s] += 0.5;
189
+ scores[choiceIndex][strategyIndex] += 0.5;
190
+ scores[opponentIndex][strategyIndex] += 0.5;
132
191
  }
133
192
  }
134
193
  }
135
194
  }
136
195
  }
137
196
 
138
- return scores;
197
+ // Normalize scores by strategy to distribute voting power
198
+ const normalizedScores = Array.from({ length: choicesCount }, () =>
199
+ Array(strategiesCount).fill(0)
200
+ );
201
+
202
+ for (
203
+ let strategyIndex = 0;
204
+ strategyIndex < strategiesCount;
205
+ strategyIndex++
206
+ ) {
207
+ // Calculate total Copeland score for this strategy
208
+ let totalCopelandScore = 0;
209
+ for (let choiceIndex = 0; choiceIndex < choicesCount; choiceIndex++) {
210
+ totalCopelandScore += scores[choiceIndex][strategyIndex];
211
+ }
212
+
213
+ // Normalize scores to distribute voting power for this strategy
214
+ if (totalCopelandScore > 0) {
215
+ for (let choiceIndex = 0; choiceIndex < choicesCount; choiceIndex++) {
216
+ normalizedScores[choiceIndex][strategyIndex] =
217
+ (scores[choiceIndex][strategyIndex] / totalCopelandScore) *
218
+ strategyTotals[strategyIndex];
219
+ }
220
+ } else if (strategyTotals[strategyIndex] > 0) {
221
+ // If no clear winners, distribute power equally for this strategy
222
+ for (let choiceIndex = 0; choiceIndex < choicesCount; choiceIndex++) {
223
+ normalizedScores[choiceIndex][strategyIndex] =
224
+ strategyTotals[strategyIndex] / choicesCount;
225
+ }
226
+ }
227
+ }
228
+
229
+ return normalizedScores;
139
230
  }
140
231
 
141
232
  // Calculates the total score (sum of all valid vote balances)