From 84972ea0f0a0150e59baaf216d31218caae56694 Mon Sep 17 00:00:00 2001 From: line-6000 <154492442+line-6000@users.noreply.github.com> Date: Mon, 11 May 2026 13:40:07 +0800 Subject: [PATCH] fix: resolve chain tokens dynamically in block listeners - resolve EVM ERC20 tokens by chain_tokens contract_address instead of hardcoded contracts --- src/model/service/task_service.go | 73 ++++++++------------------ src/model/service/task_service_test.go | 67 +++++++++++++++++++++++ 2 files changed, 89 insertions(+), 51 deletions(-) diff --git a/src/model/service/task_service.go b/src/model/service/task_service.go index 9e24043..8b94b51 100644 --- a/src/model/service/task_service.go +++ b/src/model/service/task_service.go @@ -195,64 +195,31 @@ func TryProcessEvmERC20Transfer(chainNetwork string, contract common.Address, to } }() - var usdt, usdc common.Address - var polygonUsdcE common.Address - switch chainNetwork { - case mdb.NetworkEthereum: - usdt = common.HexToAddress("0xdAC17F958D2ee523a2206206994597C13D831ec7") - usdc = common.HexToAddress("0xA0b86991c6218b36c1d19d4a2e9eb0ce3606eb48") - case mdb.NetworkBsc: - usdt = common.HexToAddress("0x55d398326f99059fF775485246999027B3197955") - usdc = common.HexToAddress("0x8AC76a51cc950d9822D68b83fE1Ad97B32Cd580d") - case mdb.NetworkPolygon: - usdt = common.HexToAddress("0xc2132D05D31c914a87C6611C10748AEb04B58e8F") - usdc = common.HexToAddress("0x3c499c542cEF5E3811e1192ce70d8cC03d5c3359") - polygonUsdcE = common.HexToAddress("0x2791Bca1f2de4661ED88A30C99A7a9449Aa84174") - case mdb.NetworkPlasma: - // USDT0(官方),6 decimals;链上暂无与 ETH 同级的 Circle USDC 部署,仅匹配 USDT 订单 - usdt = common.HexToAddress("0xB8CE59FC3717ada4C02eaDF9682A9e934F625ebb") - default: - return - } - - var tokenSym string - switch { - case contract == usdt: - tokenSym = "USDT" - case contract == usdc || (polygonUsdcE != (common.Address{}) && contract == polygonUsdcE): - tokenSym = "USDC" - default: - net := evmChainLogLabel(chainNetwork) - log.Sugar.Warnf("[%s-WS] skip unsupported contract %s", net, contract.Hex()) - return - } - net := evmChainLogLabel(chainNetwork) + tokenConfig, err := data.GetEnabledChainTokenByContract(chainNetwork, contract.Hex()) + if err != nil { + log.Sugar.Warnf("[%s-WS] load chain token contract=%s: %v", net, contract.Hex(), err) + return + } + if tokenConfig == nil || tokenConfig.ID == 0 { + log.Sugar.Warnf("[%s-WS] skip unconfigured contract %s", net, contract.Hex()) + return + } + tokenSym := strings.ToUpper(strings.TrimSpace(tokenConfig.Symbol)) + if tokenSym == "" { + log.Sugar.Warnf("[%s-WS] skip contract %s with empty token symbol", net, contract.Hex()) + return + } walletAddr := strings.ToLower(toAddr.Hex()) if rawValue == nil || rawValue.Sign() <= 0 { log.Sugar.Infof("[%s-%s][%s] skip non-positive or nil amount", net, tokenSym, walletAddr) return } - - chainTokens, err := data.ListChainTokens(chainNetwork) - if err != nil { - log.Sugar.Warnf("[%s-%s] load chain tokens: %v", net, tokenSym, err) - return + decimals := tokenConfig.Decimals + if decimals < 0 { + decimals = 0 } - var tokenConfig *mdb.ChainToken - for _, t := range chainTokens { - if strings.EqualFold(t.Symbol, tokenSym) { - tokenConfig = &t - break - } - } - if tokenConfig == nil || !tokenConfig.Enabled { - log.Sugar.Warnf("[%s-%s] token not enabled or configured in chain_tokens", net, tokenSym) - return - } - - pow := decimal.New(1, int32(tokenConfig.Decimals)) - log.Sugar.Warnf("tokenConfig.Decimals %d pow %s", tokenConfig.Decimals, pow.String()) + pow := decimal.New(1, int32(decimals)) decimalQuant := decimal.NewFromBigInt(rawValue, 0) amount := math.MustParsePrecFloat64(decimalQuant.Div(pow).InexactFloat64(), 2) @@ -260,6 +227,10 @@ func TryProcessEvmERC20Transfer(chainNetwork string, contract common.Address, to log.Sugar.Warnf("[%s-%s][%s] skip non-positive amount %.2f", net, tokenSym, walletAddr, amount) return } + if tokenConfig.MinAmount > 0 && amount < tokenConfig.MinAmount { + log.Sugar.Debugf("[%s-%s][%s] skip below min amount hash=%s amount=%.2f min=%.2f", net, tokenSym, walletAddr, txHash, amount, tokenConfig.MinAmount) + return + } log.Sugar.Debugf("[%s-%s][%s] processing transfer hash=%s amount=%.2f", net, tokenSym, walletAddr, txHash, amount) diff --git a/src/model/service/task_service_test.go b/src/model/service/task_service_test.go index 6a2134a..5d0e80d 100644 --- a/src/model/service/task_service_test.go +++ b/src/model/service/task_service_test.go @@ -1,14 +1,17 @@ package service import ( + "math/big" "strings" "testing" "time" "github.com/GMWalletApp/epusdt/internal/testutil" "github.com/GMWalletApp/epusdt/model/dao" + "github.com/GMWalletApp/epusdt/model/data" "github.com/GMWalletApp/epusdt/model/mdb" "github.com/GMWalletApp/epusdt/notify" + "github.com/ethereum/go-ethereum/common" ) func TestSendPaymentNotificationUsesLatestOrderUpdatedAt(t *testing.T) { @@ -77,3 +80,67 @@ func TestSendPaymentNotificationUsesLatestOrderUpdatedAt(t *testing.T) { t.Fatal("timed out waiting for notification") } } + +func TestTryProcessEvmERC20TransferUsesChainTokenContract(t *testing.T) { + cleanup := testutil.SetupTestDatabases(t) + defer cleanup() + + const ( + tradeID = "T202605110001" + orderID = "ORD202605110001" + tokenSym = "FOO" + amount = 12.34 + ) + contract := common.HexToAddress("0x1111111111111111111111111111111111111111") + receiveAddress := common.HexToAddress("0x2222222222222222222222222222222222222222") + + if err := dao.Mdb.Create(&mdb.ChainToken{ + Network: mdb.NetworkEthereum, + Symbol: tokenSym, + ContractAddress: contract.Hex(), + Decimals: 8, + Enabled: true, + }).Error; err != nil { + t.Fatalf("seed chain token: %v", err) + } + + order := &mdb.Orders{ + TradeId: tradeID, + OrderId: orderID, + Amount: 100, + Currency: "CNY", + ActualAmount: amount, + Token: tokenSym, + Network: mdb.NetworkEthereum, + ReceiveAddress: strings.ToLower(receiveAddress.Hex()), + Status: mdb.StatusWaitPay, + } + if err := dao.Mdb.Create(order).Error; err != nil { + t.Fatalf("seed order: %v", err) + } + if err := data.LockTransaction(mdb.NetworkEthereum, order.ReceiveAddress, order.Token, order.TradeId, order.ActualAmount, time.Hour); err != nil { + t.Fatalf("lock transaction: %v", err) + } + + rawValue := big.NewInt(1_234_000_000) // 12.34 with 8 decimals + TryProcessEvmERC20Transfer(mdb.NetworkEthereum, contract, receiveAddress, rawValue, "0xfoo-hash", time.Now().UnixMilli()) + + paid, err := data.GetOrderInfoByTradeId(tradeID) + if err != nil { + t.Fatalf("load paid order: %v", err) + } + if paid.Status != mdb.StatusPaySuccess { + t.Fatalf("order status = %d, want %d", paid.Status, mdb.StatusPaySuccess) + } + if paid.BlockTransactionId != "0xfoo-hash" { + t.Fatalf("block transaction id = %q, want %q", paid.BlockTransactionId, "0xfoo-hash") + } + + lockTradeID, err := data.GetTradeIdByWalletAddressAndAmountAndToken(mdb.NetworkEthereum, receiveAddress.Hex(), tokenSym, amount) + if err != nil { + t.Fatalf("lookup released lock: %v", err) + } + if lockTradeID != "" { + t.Fatalf("runtime lock still exists for trade_id=%q", lockTradeID) + } +}