From 6d8901934f5f6c6a44c60ae27b9642c28c178575 Mon Sep 17 00:00:00 2001 From: Nayan Das Date: Thu, 22 Oct 2026 14:08:26 +0530 Subject: [PATCH] fix(statics): key ERC20 duplicate-address test by chain The test keyed duplicates by contract address alone and rejected any match on a non-ETH family, so a token deployed at the same address on two different chains (e.g. eth:aaplx and okbxlayer:aaplx) failed the suite even though CoinMap accepts it. It was also order-dependent and interpolated the accumulator object as [object Object]. Key by family:contractAddress:network.type to mirror CoinMap.addCoin so that cross-chain address reuse passes while same-chain duplicates still fail. Ticket: CECHO-2510 --- modules/statics/test/unit/coins.ts | 39 +++++++++++------------------- 1 file changed, 14 insertions(+), 25 deletions(-) diff --git a/modules/statics/test/unit/coins.ts b/modules/statics/test/unit/coins.ts index 5c1c067df90..969ff514bb1 100644 --- a/modules/statics/test/unit/coins.ts +++ b/modules/statics/test/unit/coins.ts @@ -58,11 +58,6 @@ import { } from '../../src/account'; import { allCoinsAndTokens } from '../../src/allCoinsAndTokens'; -interface DuplicateCoinObject { - name: string; - network: BaseNetwork; -} - const custodyFeatures: Record = { algo: { features: [ @@ -1152,32 +1147,26 @@ describe('WTGXX Trust-only custody', () => { }); describe('ERC20 Coins', () => { - it('should have no duplicate contract addresses', () => { + it('should have no duplicate contract addresses on the same chain', () => { + // Mirrors the invariant enforced by `CoinMap.addCoin`: a token claims a + // `family:contractAddress` key and two tokens may only share it when they live on networks of + // a different type (mainnet vs testnet). Sharing a contract address across different chains + // is legitimate (e.g. an xStock deployed at a deterministic address on both Ethereum and + // OKB X Layer), so it must not be flagged as a duplicate. + const seen = new Map(); coins .filter((coin) => coin instanceof Erc20Coin) - .reduce((acc: { [index: string]: DuplicateCoinObject }, token) => { + .forEach((token) => { const address = (token as Readonly).contractAddress.toString(); - - // If not ETH, should never have duplicates - if (acc[address] && token.network.family !== CoinFamily.ETH) { + const key = `${token.family}:${address}:${token.network.type}`; + const duplicate = seen.get(key); + if (duplicate) { throw new Error( - `ERC20 tokens '${acc[address].name}' and '${token.name}' have identical contract address '${address}'` + `ERC20 tokens '${duplicate}' and '${token.name}' have identical contract address '${address}' on the same chain (${token.family})` ); } - - // If ETH, must check if chainId is different for the tokens before concluding they are duplicates - if (acc[address] && token.network.family === CoinFamily.ETH) { - const network = token.network as EthereumNetwork; - const accEntry = acc[address].network as EthereumNetwork; - if (network.chainId === accEntry.chainId) { - throw new Error( - `ERC20 tokens '${acc[address]}' and '${token.name}' have identical contract address '${address}'` - ); - } - } - acc[address] = { name: token.name, network: token.network }; - return acc; - }, {}); + seen.set(key, token.name); + }); }); });