From 9105560a362ba22e44b4d1fe343c7533a0c0fd49 Mon Sep 17 00:00:00 2001 From: Satoshi Qazi Muhammed Date: Thu, 9 Jul 2026 19:53:32 -0700 Subject: [PATCH] test: verify Ethereum USDT sync too, closing the third originally-reported gap Adds a real EVM address for the Founder account - derived via standard BIP44 (m/44'/60'/0'/0/0) from the same already-verified mnemonic used for the substrate address, since no dedicated founder EVM wallet record exists anywhere. Ethereum's USDT-ERC20 assetId isn't a fixed integer like Substrate assets (EvmAssetsSyncService hashes the contract address at sync time), so it's resolved dynamically via ChainAssetDao instead of hardcoded in the JSON fixture. This closes the loop on all 3 originally-reported symptoms from this investigation: Tron disabled (fixed, unaffected by any revert), Pezkuwi tokens missing (fixed - ordering bug), USDT on Polkadot AH/Ethereum missing (Polkadot AH already covered, Ethereum added here). Ethereum ERC20 sync uses an entirely separate mechanism (EthereumRequestsAggregator, not Substrate's StorageSubscriptionMultiplexer) so it was likely never actually broken by anything this session touched - this verifies that directly instead of assuming it from code reading. --- .../PezkuwiFullArchitectureBalancesTest.kt | 51 ++++++++++++++++--- 1 file changed, 45 insertions(+), 6 deletions(-) diff --git a/app/src/androidTest/java/io/novafoundation/nova/balances/PezkuwiFullArchitectureBalancesTest.kt b/app/src/androidTest/java/io/novafoundation/nova/balances/PezkuwiFullArchitectureBalancesTest.kt index d0d6fe34..9ffcb697 100644 --- a/app/src/androidTest/java/io/novafoundation/nova/balances/PezkuwiFullArchitectureBalancesTest.kt +++ b/app/src/androidTest/java/io/novafoundation/nova/balances/PezkuwiFullArchitectureBalancesTest.kt @@ -7,11 +7,13 @@ import io.novafoundation.nova.common.di.FeatureUtils import io.novafoundation.nova.common.utils.fromJson import io.novafoundation.nova.core.model.CryptoType import io.novafoundation.nova.core_db.dao.AssetDao +import io.novafoundation.nova.core_db.dao.ChainAssetDao import io.novafoundation.nova.core_db.dao.MetaAccountDao import io.novafoundation.nova.core_db.di.DbApi import io.novafoundation.nova.core_db.model.chain.account.MetaAccountLocal import io.novafoundation.nova.feature_assets.di.AssetsFeatureApi import io.novafoundation.nova.runtime.BuildConfig.TEST_ASSETS_URL +import io.novasama.substrate_sdk_android.extensions.fromHex import io.novasama.substrate_sdk_android.ss58.SS58Encoder.toAccountId import kotlinx.coroutines.delay import kotlinx.coroutines.launch @@ -48,11 +50,21 @@ private data class AssetsFixtureFile(val account: String, val assets: List() private val dbApi = FeatureUtils.getFeature(context, DbApi::class.java) private val metaAccountDao = dbApi.metaAccountDao() private val assetDao: AssetDao = dbApi.provideAssetDao() + private val chainAssetDao: ChainAssetDao = dbApi.chainAssetDao() private val assetsFeatureApi = FeatureUtils.getFeature(context, AssetsFeatureApi::class.java) @@ -63,9 +75,35 @@ class PezkuwiFullArchitectureBalancesTest { val updateSystemJob = launch { assetsFeatureApi.updateSystem.start().collect {} } try { - val metaId = insertAndSelectWatchAccount(metaAccountDao, fixture.account) + val metaId = insertAndSelectWatchAccount(metaAccountDao, fixture.account, founderEthereumAddress) + + // Ethereum's USDT isn't in the JSON fixture because its assetId isn't a fixed, known integer like + // Substrate assets - EvmAssetsSyncService computes it as a hash of the ERC20 contract address at + // sync time (see chainAssetIdOfErc20Token()), so it has to be resolved dynamically here instead of + // hardcoded. This closes out the third of the three originally-reported symptoms (Tron disabled, + // Pezkuwi tokens missing, USDT on Polkadot AH/Ethereum missing) - the first two are already covered + // by the fixture-driven assets above. + val ethereumUsdtAssetId = withTimeoutOrNull(30.seconds) { + var assetId: Int? = null + while (assetId == null) { + assetId = chainAssetDao.getEnabledAssets() + .firstOrNull { it.chainId == ethereumChainId && it.symbol == "USDT" } + ?.id + if (assetId == null) delay(2.seconds) + } + assetId + } + assertTrue( + "USDT was never registered as an enabled asset on Ethereum (chainId=$ethereumChainId) within 30s - " + + "EvmAssetsSyncService may have failed to sync from EVM_ASSETS_URL.", + ethereumUsdtAssetId != null + ) + + val stillMissing = ( + fixture.assets + + AssetFixture(ethereumChainId, "Ethereum", ethereumUsdtAssetId!!, "USDT") + ).toMutableList() - val stillMissing = fixture.assets.toMutableList() withTimeoutOrNull(120.seconds) { while (stillMissing.isNotEmpty()) { val found = stillMissing.filter { asset -> @@ -78,9 +116,9 @@ class PezkuwiFullArchitectureBalancesTest { assertTrue( "No `assets` row was ever written for: ${stillMissing.joinToString { "${it.symbol} on ${it.chainName}" }} " + - "(metaId=$metaId) within 120s, out of ${fixture.assets.size} total. The real BalancesUpdateSystem " + + "(metaId=$metaId) within 120s, out of ${fixture.assets.size + 1} total. The real BalancesUpdateSystem " + "pipeline never completed a sync for these - check the standard FullSyncPaymentUpdater/" + - "NativeAssetBalance/StatemineAssetBalance error logs in logcat.", + "NativeAssetBalance/StatemineAssetBalance/EvmErc20AssetBalance error logs in logcat.", stillMissing.isEmpty() ) } finally { @@ -88,15 +126,16 @@ class PezkuwiFullArchitectureBalancesTest { } } - private suspend fun insertAndSelectWatchAccount(dao: MetaAccountDao, substrateAddress: String): Long { + private suspend fun insertAndSelectWatchAccount(dao: MetaAccountDao, substrateAddress: String, ethereumAddress: String): Long { val accountId = substrateAddress.toAccountId() + val evmAddress = ethereumAddress.removePrefix("0x").fromHex() val metaAccount = MetaAccountLocal( substratePublicKey = accountId, substrateCryptoType = CryptoType.SR25519, substrateAccountId = accountId, ethereumPublicKey = null, - ethereumAddress = null, + ethereumAddress = evmAddress, name = "PezkuwiFullArchitectureBalancesTest", parentMetaId = null, isSelected = false,