Skip to content

Commit c55d134

Browse files
committed
test: naming convention for eth tokens
Ticket: [COIN-5165]
1 parent 95e0985 commit c55d134

File tree

1 file changed

+364
-0
lines changed

1 file changed

+364
-0
lines changed
Lines changed: 364 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,364 @@
1+
import { erc20Coins } from '../../src/coins/erc20Coins';
2+
import { NetworkType } from '../../src/networks';
3+
4+
describe('Token Naming Convention Tests', function () {
5+
const allTokens = erc20Coins; //TODO: Include other token sources
6+
7+
// Helper function to filter tokens by network type
8+
function getTokensByNetworkType(networkType: NetworkType) {
9+
return allTokens.filter((token) => token.network.type === networkType);
10+
}
11+
12+
// Get mainnet and testnet tokens
13+
const mainnetTokens = getTokensByNetworkType(NetworkType.MAINNET);
14+
const testnetTokens = getTokensByNetworkType(NetworkType.TESTNET);
15+
16+
it('should ensure testnet tokens are properly prefixed and mainnet tokens are not', function () {
17+
// List of testnet tokens that are exceptions to the 't' prefix rule
18+
const testnetPrefixExceptions: string[] = [
19+
'gteth',
20+
'hteth',
21+
'gusdt',
22+
'ghdo',
23+
'gterc2dp',
24+
'gterc6dp',
25+
'ghcn',
26+
'gterc18dp',
27+
'gtaave18dp',
28+
'gtbat18dp',
29+
'gtcomp18dp',
30+
'gtgrt18dp',
31+
'gtlink18dp',
32+
'gtmkr18dp',
33+
'gtsnx18dp',
34+
'gtuni18dp',
35+
'gtusdt6dp',
36+
'gtyfi18dp',
37+
'gtwbtc18dp',
38+
'hteth:stgusd1',
39+
'hteth:tsteth',
40+
'hteth:gousd',
41+
'hteth:usd1',
42+
'hterc18dp',
43+
'hteth:bgerchv2',
44+
'hteth:aut',
45+
'hterc6dp',
46+
'hterc2dp',
47+
'fixed',
48+
'schz',
49+
'bgerch',
50+
];
51+
52+
testnetTokens.forEach((token) => {
53+
const tokenName = token.name;
54+
55+
// Skip known exceptions
56+
if (testnetPrefixExceptions.includes(tokenName)) {
57+
return;
58+
}
59+
60+
// All testnet tokens should start with 't'
61+
tokenName.should.startWith('t', `Testnet token "${tokenName}" should start with 't'`);
62+
});
63+
64+
mainnetTokens.forEach((token) => {
65+
const tokenName = token.name;
66+
const parts = tokenName.split(':');
67+
const prefix = parts[0];
68+
69+
// If token name has a colon, check the prefix; otherwise, check the whole name
70+
const nameToCheck = parts.length > 1 ? prefix : tokenName;
71+
72+
// List of mainnet tokens that legitimately start with 't'
73+
const allowedTPrefixMainnet: string[] = [
74+
'threshold',
75+
'taud',
76+
'tbill',
77+
'tbtc1',
78+
'tbtc2',
79+
'tcad',
80+
'tco',
81+
'tel',
82+
'ten',
83+
'tenx',
84+
'tgbp',
85+
'thkd',
86+
'thunder',
87+
'tiox',
88+
'tknt',
89+
'tko',
90+
'tkx',
91+
'tlab',
92+
'tlm',
93+
'tlos',
94+
'tnt',
95+
'tok',
96+
'trac',
97+
'traxx',
98+
'trb',
99+
'tribe',
100+
'tribl',
101+
'trl',
102+
'troy',
103+
'trst',
104+
'tru',
105+
'truf',
106+
'trufv2',
107+
'tryb',
108+
'tryx',
109+
'tst',
110+
'tusd',
111+
'txl',
112+
'tomobear',
113+
'tomobull',
114+
'trxbear',
115+
'trxbull',
116+
'trxhedge',
117+
'telegramdao',
118+
'term',
119+
'tio',
120+
'tokamak',
121+
'toke',
122+
'token',
123+
'tomi',
124+
'tomobear2',
125+
'trumplose',
126+
'trumpwin',
127+
'trx-erc20',
128+
'trxdoom',
129+
'trxmoon',
130+
'trybbear',
131+
'trybbull',
132+
'tsuka',
133+
'toncoin',
134+
];
135+
136+
if (allowedTPrefixMainnet.includes(nameToCheck)) {
137+
return;
138+
}
139+
140+
// Mainnet tokens should not start with 't'
141+
nameToCheck.startsWith('t').should.be.false(`Mainnet token "${tokenName}" should not start with 't'`);
142+
});
143+
});
144+
145+
it('should have matching network prefixes between mainnet and testnet token pairs', function () {
146+
// Only test tokens that follow the network:token pattern
147+
const tokensWithColon = allTokens.filter((token) => token.name.includes(':'));
148+
149+
// Group tokens by their base name (removing 't' prefix for testnet)
150+
const tokensByBase = new Map<
151+
string,
152+
{ token: unknown; networkPrefix: string; tokenId: string; isTestnet: boolean }[]
153+
>();
154+
155+
tokensWithColon.forEach((token) => {
156+
const tokenName = token.name;
157+
const parts = tokenName.split(':');
158+
const networkPrefix = parts[0];
159+
const tokenId = parts[1];
160+
161+
// For testnet tokens, remove the 't' prefix to get the base network name
162+
let baseNetworkName: string;
163+
if (token.network.type === NetworkType.TESTNET) {
164+
baseNetworkName = networkPrefix.startsWith('t') ? networkPrefix.substring(1) : networkPrefix; // Remove 't' prefix
165+
} else {
166+
baseNetworkName = networkPrefix;
167+
}
168+
169+
// Create a key with the base network name and token identifier
170+
const key = `${baseNetworkName}:${tokenId}`;
171+
172+
if (!tokensByBase.has(key)) {
173+
tokensByBase.set(key, []);
174+
}
175+
176+
tokensByBase.get(key)?.push({
177+
token,
178+
networkPrefix,
179+
tokenId,
180+
isTestnet: token.network.type === NetworkType.TESTNET,
181+
});
182+
});
183+
184+
// Check pairs to ensure proper testnet/mainnet naming convention
185+
tokensByBase.forEach((tokenVersions, _key) => {
186+
// If there are multiple versions of the same token (testnet and mainnet)
187+
if (tokenVersions.length > 1) {
188+
const testnetVersions = tokenVersions.filter(
189+
(v): v is (typeof tokenVersions)[number] & { isTestnet: true } => v.isTestnet
190+
);
191+
const mainnetVersions = tokenVersions.filter(
192+
(v): v is (typeof tokenVersions)[number] & { isTestnet: false } => !v.isTestnet
193+
);
194+
195+
// Only check if we have both testnet and mainnet versions
196+
if (testnetVersions.length > 0 && mainnetVersions.length > 0) {
197+
testnetVersions.forEach((testnetVersion: any) => {
198+
mainnetVersions.forEach((mainnetVersion: any) => {
199+
// Special case for ofcerc20 tokens which don't follow the pattern
200+
if (testnetVersion.networkPrefix === 'ofct' && mainnetVersion.networkPrefix === 'ofc') {
201+
return; // Skip the check for these special cases
202+
}
203+
204+
// Skip tokens with known prefix mismatches
205+
const knownMismatches: string[] = [];
206+
207+
if (knownMismatches.some((m) => mainnetVersion.token.name.includes(m))) {
208+
return; // Skip known mismatches
209+
}
210+
211+
// Testnet version should be 't' + mainnet network prefix
212+
testnetVersion.networkPrefix.should.equal(
213+
`t${mainnetVersion.networkPrefix}`,
214+
`Testnet token ${testnetVersion.token.name} should have network prefix 't${mainnetVersion.networkPrefix}'`
215+
);
216+
});
217+
});
218+
}
219+
}
220+
});
221+
});
222+
223+
it('should maintain matching decimal places between mainnet and testnet pairs when possible', function () {
224+
// Only test tokens that follow the network:token pattern
225+
const tokensWithColon = allTokens.filter((token) => token.name.includes(':'));
226+
227+
// Known exceptions where mainnet and testnet tokens intentionally have different decimal places
228+
const knownDecimalExceptions: { mainnet: string; testnet: string }[] = [
229+
{ mainnet: 'eth:dot', testnet: 'teth:dot' }, // 10 vs 12 decimal places
230+
{ mainnet: 'eth:usdc', testnet: 'teth:usdc' }, // 6 vs 18 decimal places
231+
{ mainnet: 'eth:usdt', testnet: 'teth:usdt' }, // 6 vs 18 decimal places
232+
{ mainnet: 'eth:link', testnet: 'teth:link' }, // 18 vs 6 decimal places
233+
];
234+
235+
// Group tokens by their base token identifier
236+
const tokenPairs = new Map<string, { token: any; networkPrefix: string; isTestnet: boolean }[]>();
237+
238+
tokensWithColon.forEach((token) => {
239+
const tokenName = token.name;
240+
const parts = tokenName.split(':');
241+
const networkPrefix = parts[0];
242+
const tokenId = parts[1];
243+
244+
// Create a key based on just the token identifier
245+
const key = tokenId;
246+
247+
if (!tokenPairs.has(key)) {
248+
tokenPairs.set(key, []);
249+
}
250+
tokenPairs.get(key)?.push({
251+
token,
252+
networkPrefix,
253+
isTestnet: token.network.type === NetworkType.TESTNET,
254+
});
255+
});
256+
257+
// Check that decimal places match between testnet and mainnet tokens
258+
tokenPairs.forEach((tokens, _tokenId) => {
259+
// If there are both mainnet and testnet versions of this token
260+
if (tokens.length > 1) {
261+
const testnetTokens = tokens.filter((t: any) => t.isTestnet);
262+
const mainnetTokens = tokens.filter((t: any) => !t.isTestnet);
263+
264+
if (testnetTokens.length > 0 && mainnetTokens.length > 0) {
265+
testnetTokens.forEach((testnetToken: any) => {
266+
mainnetTokens.forEach((mainnetToken: any) => {
267+
// Skip checking decimal places for known exceptions
268+
const isException = knownDecimalExceptions.some(
269+
(exception) =>
270+
mainnetToken.token.name === exception.mainnet && testnetToken.token.name === exception.testnet
271+
);
272+
273+
if (isException) {
274+
return; // Skip this check for known exceptions
275+
}
276+
277+
testnetToken.token.decimalPlaces.should.equal(
278+
mainnetToken.token.decimalPlaces,
279+
`Token pair ${mainnetToken.token.name}/${testnetToken.token.name} should have matching decimal places`
280+
);
281+
});
282+
});
283+
}
284+
}
285+
});
286+
});
287+
288+
it('should ensure all token names are lowercase', function () {
289+
allTokens.forEach((token) => {
290+
const tokenName = token.name;
291+
tokenName.should.equal(tokenName.toLowerCase(), `Token "${tokenName}" should be lowercase`);
292+
});
293+
});
294+
295+
it('should have consistent naming for non-colon tokens', function () {
296+
// Get base coins (without colons)
297+
const baseCoins = allTokens.filter((token) => !token.name.includes(':'));
298+
299+
// Group by basename (removing 't' prefix for testnet)
300+
const tokensByBase = new Map<string, { token: any; name: string; isTestnet: boolean }[]>();
301+
302+
baseCoins.forEach((token) => {
303+
const tokenName = token.name;
304+
let baseName: string;
305+
306+
// For testnet tokens, remove the 't' prefix to get the base name
307+
if (token.network.type === NetworkType.TESTNET && tokenName.startsWith('t')) {
308+
baseName = tokenName.substring(1); // Remove 't' prefix
309+
} else {
310+
baseName = tokenName;
311+
}
312+
313+
// Skip special cases that legitimately start with 't'
314+
const legitimateTPrefixTokens: string[] = [
315+
'threshold',
316+
'taud',
317+
'tbill',
318+
'tbtc1',
319+
'tbtc2',
320+
'tcad',
321+
'tel',
322+
'tenx',
323+
'tgbp',
324+
'token',
325+
'tusd',
326+
];
327+
if (legitimateTPrefixTokens.includes(baseName)) {
328+
return;
329+
}
330+
331+
if (!tokensByBase.has(baseName)) {
332+
tokensByBase.set(baseName, []);
333+
}
334+
335+
tokensByBase.get(baseName)?.push({
336+
token,
337+
name: tokenName,
338+
isTestnet: token.network.type === NetworkType.TESTNET,
339+
});
340+
});
341+
342+
// Check pairs
343+
tokensByBase.forEach((versions, _baseName) => {
344+
// If we have multiple versions of the same base coin
345+
if (versions.length > 1) {
346+
const testnetVersions = versions.filter((v: any) => v.isTestnet);
347+
const mainnetVersions = versions.filter((v: any) => !v.isTestnet);
348+
349+
// Check if we have both testnet and mainnet versions
350+
if (testnetVersions.length > 0 && mainnetVersions.length > 0) {
351+
testnetVersions.forEach((testnetVersion: any) => {
352+
mainnetVersions.forEach((mainnetVersion: any) => {
353+
// Testnet name should be 't' + mainnet name
354+
testnetVersion.name.should.equal(
355+
`t${mainnetVersion.name}`,
356+
`Testnet token ${testnetVersion.token.name} should be named 't${mainnetVersion.name}'`
357+
);
358+
});
359+
});
360+
}
361+
}
362+
});
363+
});
364+
});

0 commit comments

Comments
 (0)