Skip to content
This repository was archived by the owner on Aug 22, 2025. It is now read-only.

Commit 2152687

Browse files
committed
fix: only process addresses from relevant protocols
1 parent cd892cb commit 2152687

9 files changed

Lines changed: 682 additions & 324 deletions

File tree

src/utils.ts

Lines changed: 1 addition & 65 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,7 @@ import snapshot from '@snapshot-labs/snapshot.js';
44
import { getDelegations } from './utils/delegation';
55
import { getVp, getDelegations as getCoreDelegations } from './utils/vp';
66
import { createHash } from 'crypto';
7-
import { Protocol, Score, Snapshot } from './types';
8-
import { VALID_PROTOCOLS } from './constants';
7+
import { Score, Snapshot } from './types';
98

109
export function sha256(str) {
1110
return createHash('sha256').update(str).digest('hex');
@@ -100,68 +99,6 @@ export function customFetch(
10099
]);
101100
}
102101

103-
/**
104-
* Validates that protocols are non-empty and contain only valid protocol names.
105-
*
106-
* @param protocols - Array of protocol names to validate
107-
*/
108-
function validateProtocols(protocols: Protocol[]): void {
109-
if (!protocols.length) {
110-
throw new Error('At least one protocol must be specified');
111-
}
112-
113-
const invalidProtocols = protocols.filter(
114-
(p) => !VALID_PROTOCOLS.includes(p)
115-
);
116-
if (invalidProtocols.length > 0) {
117-
throw new Error(`Invalid protocol(s): ${invalidProtocols.join(', ')}`);
118-
}
119-
}
120-
121-
/**
122-
* Formats addresses according to the specified blockchain protocols.
123-
*
124-
* This function takes a list of addresses and formats them according to the provided
125-
* protocols. It prioritizes EVM formatting when multiple protocols are specified and
126-
* an address is valid for both. If EVM formatting fails but Starknet is supported,
127-
* it falls back to Starknet formatting. Throws an error if any address cannot be
128-
* formatted according to the specified protocols.
129-
*
130-
* @param addresses - Array of blockchain addresses to format
131-
* @param protocols - Array of protocol names to validate against. Defaults to ['evm'].
132-
* Valid protocols are 'evm' and 'starknet'.
133-
*
134-
* @returns Array of formatted addresses in the same order as input
135-
*/
136-
export function getFormattedAddressesByProtocol(
137-
addresses: string[],
138-
protocols: Protocol[] = ['evm']
139-
): string[] {
140-
validateProtocols(protocols);
141-
142-
return addresses.map((address) => {
143-
if (protocols.includes('evm')) {
144-
try {
145-
return snapshot.utils.getFormattedAddress(address, 'evm');
146-
} catch (e) {
147-
// Continue to starknet if evm formatting fails and starknet is supported
148-
}
149-
}
150-
151-
if (protocols.includes('starknet')) {
152-
try {
153-
return snapshot.utils.getFormattedAddress(address, 'starknet');
154-
} catch (e) {
155-
// Address format not supported by any protocol
156-
}
157-
}
158-
159-
throw new Error(
160-
`Address "${address}" is not a valid ${protocols.join(' or ')} address`
161-
);
162-
});
163-
}
164-
165102
export const {
166103
multicall,
167104
Multicaller,
@@ -180,7 +117,6 @@ export default {
180117
sha256,
181118
getScoresDirect,
182119
customFetch,
183-
getFormattedAddressesByProtocol,
184120
multicall,
185121
Multicaller,
186122
subgraphRequest,

src/utils/vp.ts

Lines changed: 112 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,26 @@
11
import { formatBytes32String } from '@ethersproject/strings';
2-
import { getAddress } from '@ethersproject/address';
2+
import { getAddress, isAddress } from '@ethersproject/address';
33
import subgraphs from '@snapshot-labs/snapshot.js/src/delegationSubgraphs.json';
4+
import snapshot from '@snapshot-labs/snapshot.js';
45
import {
5-
getFormattedAddressesByProtocol,
66
getProvider,
77
getSnapshots,
88
Multicaller,
99
subgraphRequest
1010
} from '../utils';
1111
import _strategies from '../strategies';
12-
import { Score, Snapshot, VotingPower } from '../types';
13-
import { DEFAULT_SUPPORTED_PROTOCOLS } from '../constants';
12+
import { Score, Snapshot, VotingPower, Protocol } from '../types';
13+
import { DEFAULT_SUPPORTED_PROTOCOLS, VALID_PROTOCOLS } from '../constants';
1414

1515
const DELEGATION_CONTRACT = '0x469788fE6E9E9681C6ebF3bF78e7Fd26Fc015446';
1616
const EMPTY_ADDRESS = '0x0000000000000000000000000000000000000000';
1717
const EMPTY_SPACE = formatBytes32String('');
1818
const abi = ['function delegation(address, bytes32) view returns (address)'];
1919

20+
// Address format constants
21+
const EVM_ADDRESS_REGEX = /^0x[a-fA-F0-9]{40}$/;
22+
const STARKNET_ADDRESS_REGEX = /^0x[a-fA-F0-9]{64}$/;
23+
2024
interface Delegation {
2125
in: string[];
2226
out: string | null;
@@ -30,6 +34,9 @@ export async function getVp(
3034
space: string,
3135
delegation?: boolean
3236
): Promise<VotingPower> {
37+
validateStrategies(strategies);
38+
validateVoterAddress(address);
39+
3340
const networks = [...new Set(strategies.map((s) => s.network || network))];
3441
const snapshots = await getSnapshots(
3542
network,
@@ -38,8 +45,10 @@ export async function getVp(
3845
networks
3946
);
4047

48+
// TODO: Delegation support for Starknet
49+
const delegationSupported = delegation && isAddress(address);
4150
const delegations = {};
42-
if (delegation) {
51+
if (delegationSupported) {
4352
const ds = await Promise.all(
4453
networks.map((n) => getDelegations(address, n, snapshots[n], space))
4554
);
@@ -49,19 +58,19 @@ export async function getVp(
4958
const p: Score[] = strategies.map((strategy) => {
5059
const n = strategy.network || network;
5160
let addresses = [address];
61+
const supportedProtocols = _strategies[strategy.name].supportedProtocols;
5262

53-
if (delegation) {
63+
if (delegationSupported && supportedProtocols.includes('evm')) {
5464
addresses = delegations[n].in;
5565
if (!delegations[n].out) addresses.push(address);
5666
addresses = [...new Set(addresses)];
5767
if (addresses.length === 0) return {};
5868
}
5969

60-
addresses = getFormattedAddressesByProtocol(
61-
addresses,
62-
_strategies[strategy.name].supportedProtocols ??
63-
DEFAULT_SUPPORTED_PROTOCOLS
64-
);
70+
addresses = formatSupportedAddresses(addresses, supportedProtocols);
71+
72+
if (addresses.length === 0) return {};
73+
6574
return _strategies[strategy.name].strategy(
6675
space,
6776
n,
@@ -76,18 +85,16 @@ export async function getVp(
7685
const vpByStrategy = scores.map((score, i) => {
7786
const n = strategies[i].network || network;
7887
let addresses = [address];
88+
const supportedProtocols =
89+
_strategies[strategies[i].name].supportedProtocols;
7990

80-
if (delegation) {
91+
if (delegationSupported && supportedProtocols.includes('evm')) {
8192
addresses = delegations[n].in;
8293
if (!delegations[n].out) addresses.push(address);
8394
addresses = [...new Set(addresses)];
8495
}
8596

86-
addresses = getFormattedAddressesByProtocol(
87-
addresses,
88-
_strategies[strategies[i].name].supportedProtocols ??
89-
DEFAULT_SUPPORTED_PROTOCOLS
90-
);
97+
addresses = formatSupportedAddresses(addresses, supportedProtocols);
9198
return addresses.reduce((a, b) => a + (score[b] || 0), 0);
9299
});
93100
const vp = vpByStrategy.reduce((a, b) => a + b, 0);
@@ -228,3 +235,91 @@ export async function getDelegations(
228235
out: delegationOut
229236
};
230237
}
238+
239+
// Ensure that the address is either a valid EVM address or Starknet address
240+
function validateVoterAddress(address: string): void {
241+
try {
242+
const result = formatSupportedAddresses([address], VALID_PROTOCOLS);
243+
if (!result.length) {
244+
throw new Error('invalid address');
245+
}
246+
} catch {
247+
throw new Error('invalid address');
248+
}
249+
}
250+
251+
function validateStrategies(strategies: any[]): void {
252+
const invalidStrategies = strategies
253+
.filter((s) => !_strategies[s.name])
254+
.map((s) => s.name);
255+
256+
if (invalidStrategies.length > 0) {
257+
throw new Error(`invalid strategies: ${invalidStrategies.join(', ')}`);
258+
}
259+
}
260+
261+
/**
262+
* Validates that protocols are non-empty and contain only valid protocol names.
263+
*
264+
* @param protocols - Array of protocol names to validate
265+
*/
266+
function validateProtocols(protocols: Protocol[]): void {
267+
if (!protocols.length) {
268+
throw new Error('At least one protocol must be specified');
269+
}
270+
271+
const invalidProtocols = protocols.filter(
272+
(p) => !VALID_PROTOCOLS.includes(p)
273+
);
274+
if (invalidProtocols.length > 0) {
275+
throw new Error(`Invalid protocol(s): ${invalidProtocols.join(', ')}`);
276+
}
277+
}
278+
279+
/**
280+
* Formats addresses relevant to the given protocols according to the specified blockchain protocols.
281+
*
282+
* Will ignore addresses that are not evm or starknet like addresses
283+
*
284+
* @param addresses - Array of blockchain addresses to format
285+
* @param protocols - Array of protocol names to validate against. Defaults to ['evm'].
286+
* Valid protocols are 'evm' and 'starknet'.
287+
*
288+
* @returns Array of formatted addresses in the same order as input
289+
*/
290+
export function formatSupportedAddresses(
291+
addresses: string[],
292+
protocols: Protocol[] = DEFAULT_SUPPORTED_PROTOCOLS
293+
): string[] {
294+
validateProtocols(protocols);
295+
296+
const supportsEvm = protocols.includes('evm');
297+
const supportsStarknet = protocols.includes('starknet');
298+
299+
const getAddressType = (address: string): 'evm' | 'starknet' | null => {
300+
if (supportsEvm && EVM_ADDRESS_REGEX.test(address)) return 'evm';
301+
if (supportsStarknet && STARKNET_ADDRESS_REGEX.test(address))
302+
return 'starknet';
303+
return null;
304+
};
305+
306+
const result: string[] = [];
307+
for (const address of addresses) {
308+
const addressType = getAddressType(address);
309+
if (!addressType) continue; // Skip unsupported addresses
310+
311+
try {
312+
const formattedAddress = snapshot.utils.getFormattedAddress(
313+
address,
314+
addressType
315+
);
316+
result.push(formattedAddress);
317+
} catch (e) {
318+
throw new Error(
319+
`Address "${address}" is not a valid ${protocols.join(' or ')} address`
320+
);
321+
}
322+
}
323+
324+
return result;
325+
}

test/__snapshots__/vp.test.ts.snap

Lines changed: 0 additions & 27 deletions
This file was deleted.

0 commit comments

Comments
 (0)