diff --git a/app/upgrade_test.go b/app/upgrade_test.go index 82e7757c..59b54728 100644 --- a/app/upgrade_test.go +++ b/app/upgrade_test.go @@ -1,7 +1,11 @@ package app_test import ( + "crypto/sha256" + "encoding/json" + "fmt" "os" + "path" "path/filepath" "testing" @@ -10,22 +14,31 @@ import ( storetypes "cosmossdk.io/store/types" "cosmossdk.io/x/upgrade" upgradetypes "cosmossdk.io/x/upgrade/types" + tmjson "github.com/cometbft/cometbft/libs/json" tmproto "github.com/cometbft/cometbft/proto/tendermint/types" tmtime "github.com/cometbft/cometbft/types/time" dbm "github.com/cosmos/cosmos-db" cryptotypes "github.com/cosmos/cosmos-sdk/crypto/types" sdk "github.com/cosmos/cosmos-sdk/types" + authtypes "github.com/cosmos/cosmos-sdk/x/auth/types" + banktypes "github.com/cosmos/cosmos-sdk/x/bank/types" distributiontypes "github.com/cosmos/cosmos-sdk/x/distribution/types" govtypes "github.com/cosmos/cosmos-sdk/x/gov/types" stakingtypes "github.com/cosmos/cosmos-sdk/x/staking/types" + ibctransfertypes "github.com/cosmos/ibc-go/v8/modules/apps/transfer/types" + "github.com/ethereum/go-ethereum/common" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "github.com/functionx/fx-core/v8/app" nextversion "github.com/functionx/fx-core/v8/app/upgrades/v8" + "github.com/functionx/fx-core/v8/contract" "github.com/functionx/fx-core/v8/testutil/helpers" fxtypes "github.com/functionx/fx-core/v8/types" + bsctypes "github.com/functionx/fx-core/v8/x/bsc/types" "github.com/functionx/fx-core/v8/x/crosschain/types" + erc20types "github.com/functionx/fx-core/v8/x/erc20/types" + ethtypes "github.com/functionx/fx-core/v8/x/eth/types" fxgovv8 "github.com/functionx/fx-core/v8/x/gov/migrations/v8" fxgovtypes "github.com/functionx/fx-core/v8/x/gov/types" fxstakingv8 "github.com/functionx/fx-core/v8/x/staking/migrations/v8" @@ -108,6 +121,8 @@ func checkAppUpgrade(t *testing.T, ctx sdk.Context, myApp *app.App) { checkErc20Keys(t, ctx, myApp) checkOutgoingBatch(t, ctx, myApp) + + checkPundixChainMigrate(t, ctx, myApp) } func checkErc20Keys(t *testing.T, ctx sdk.Context, myApp *app.App) { @@ -158,3 +173,67 @@ func checkOutgoingBatch(t *testing.T, ctx sdk.Context, myApp *app.App) { }) } } + +func checkPundixChainMigrate(t *testing.T, ctx sdk.Context, myApp *app.App) { + t.Helper() + + pundixGenesisPath := path.Join(fxtypes.GetDefaultNodeHome(), "config/pundix_genesis.json") + appState, err := nextversion.ReadGenesisState(pundixGenesisPath) + require.NoError(t, err) + + bankGenesis, ok := appState[banktypes.ModuleName] + require.True(t, ok) + require.NotEmpty(t, bankGenesis) + checkPundixBank(t, ctx, myApp, bankGenesis) +} + +func checkPundixBank(t *testing.T, ctx sdk.Context, myApp *app.App, raw json.RawMessage) { + t.Helper() + + var bankGenesis banktypes.GenesisState + require.NoError(t, tmjson.Unmarshal(raw, &bankGenesis)) + erc20TokenKeeper := contract.NewERC20TokenKeeper(myApp.EvmKeeper) + + // pundix token + pundixToken, err := myApp.Erc20Keeper.GetERC20Token(ctx, "pundix") + require.NoError(t, err) + pundixBridgeToken, err := myApp.Erc20Keeper.GetBridgeToken(ctx, ethtypes.ModuleName, pundixToken.GetDenom()) + require.NoError(t, err) + pundixDenomHash := sha256.Sum256([]byte(fmt.Sprintf("%s/channel-0/%s", ibctransfertypes.ModuleName, pundixBridgeToken.BridgeDenom()))) + pundixIBCDenom := fmt.Sprintf("%s/%X", ibctransfertypes.DenomPrefix, pundixDenomHash[:]) + + // purse token + purseToken, err := myApp.Erc20Keeper.GetERC20Token(ctx, "purse") + require.NoError(t, err) + purseBscBridgeToken, err := myApp.Erc20Keeper.GetBridgeToken(ctx, bsctypes.ModuleName, purseToken.GetDenom()) + require.NoError(t, err) + + totalSupply, err := erc20TokenKeeper.TotalSupply(ctx, purseToken.GetERC20Contract()) + require.NoError(t, err) + require.Equal(t, totalSupply.String(), bankGenesis.Supply.AmountOf(purseBscBridgeToken.BridgeDenom()).String()) + + erc20Addr := common.BytesToAddress(authtypes.NewModuleAddress(erc20types.ModuleName)) + balOf, err := erc20TokenKeeper.BalanceOf(ctx, purseToken.GetERC20Contract(), erc20Addr) + require.NoError(t, err) + supply := myApp.BankKeeper.GetSupply(ctx, purseToken.GetDenom()) + require.Equal(t, balOf.String(), supply.Amount.String()) + + pxEscrowAddr, err := nextversion.GetPxChannelEscrowAddr() + require.NoError(t, err) + + for _, bal := range bankGenesis.Balances { + if bal.Address == pxEscrowAddr { + continue + } + bech32Addr, err := sdk.GetFromBech32(bal.Address, "px") + require.NoError(t, err) + account := myApp.AccountKeeper.GetAccount(ctx, bech32Addr) + if _, ok := account.(sdk.ModuleAccountI); ok { + continue + } + + allBal := myApp.BankKeeper.GetAllBalances(ctx, bech32Addr) + require.True(t, allBal.AmountOf(pundixToken.GetDenom()).GTE(bal.Coins.AmountOf(pundixIBCDenom))) + require.True(t, allBal.AmountOf(purseToken.GetDenom()).GTE(bal.Coins.AmountOf(purseBscBridgeToken.BridgeDenom()))) + } +} diff --git a/app/upgrades/v8/pundix.go b/app/upgrades/v8/pundix.go new file mode 100644 index 00000000..c3e0bb88 --- /dev/null +++ b/app/upgrades/v8/pundix.go @@ -0,0 +1,336 @@ +package v8 + +import ( + "crypto/sha256" + "encoding/json" + "fmt" + "os" + "path" + "strings" + + "cosmossdk.io/collections" + sdkmath "cosmossdk.io/math" + "cosmossdk.io/store/prefix" + storetypes "cosmossdk.io/store/types" + tmjson "github.com/cometbft/cometbft/libs/json" + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/cosmos/cosmos-sdk/types/bech32" + sdkerrors "github.com/cosmos/cosmos-sdk/types/errors" + authkeeper "github.com/cosmos/cosmos-sdk/x/auth/keeper" + "github.com/cosmos/cosmos-sdk/x/auth/types" + bankkeeper "github.com/cosmos/cosmos-sdk/x/bank/keeper" + banktypes "github.com/cosmos/cosmos-sdk/x/bank/types" + ibctransferkeeper "github.com/cosmos/ibc-go/v8/modules/apps/transfer/keeper" + ibctransfertypes "github.com/cosmos/ibc-go/v8/modules/apps/transfer/types" + "github.com/ethereum/go-ethereum/common" + evmtypes "github.com/evmos/ethermint/x/evm/types" + + "github.com/functionx/fx-core/v8/app/keepers" + "github.com/functionx/fx-core/v8/contract" + fxtypes "github.com/functionx/fx-core/v8/types" + bsctypes "github.com/functionx/fx-core/v8/x/bsc/types" + crosschaintypes "github.com/functionx/fx-core/v8/x/crosschain/types" + erc20keeper "github.com/functionx/fx-core/v8/x/erc20/keeper" + erc20types "github.com/functionx/fx-core/v8/x/erc20/types" + ethtypes "github.com/functionx/fx-core/v8/x/eth/types" +) + +type Pundix struct { + accountKeeper authkeeper.AccountKeeper + erc20Keeper erc20keeper.Keeper + bankKeeper bankkeeper.Keeper + ibcTransferKeeper ibctransferkeeper.Keeper + erc20TokenKeeper contract.ERC20TokenKeeper + storeKey storetypes.StoreKey +} + +func NewPundix(app *keepers.AppKeepers) *Pundix { + return &Pundix{ + accountKeeper: app.AccountKeeper, + erc20Keeper: app.Erc20Keeper, + bankKeeper: app.BankKeeper, + ibcTransferKeeper: app.IBCTransferKeeper, + erc20TokenKeeper: contract.NewERC20TokenKeeper(app.EvmKeeper), + storeKey: app.GetKey(ibctransfertypes.StoreKey), + } +} + +func (m *Pundix) Migrate(ctx sdk.Context) error { + pundixGenesisPath := path.Join(fxtypes.GetDefaultNodeHome(), "config/pundix_genesis.json") + appState, err := ReadGenesisState(pundixGenesisPath) + if err != nil { + return err + } + // todo migrate other data + bankGenesis, ok := appState[banktypes.ModuleName] + if !ok || len(bankGenesis) == 0 { + return sdkerrors.ErrNotFound.Wrap("bank genesis") + } + return m.migrateBank(ctx, bankGenesis) +} + +func (m *Pundix) migrateBank(ctx sdk.Context, bankRaw json.RawMessage) error { + var bankGenesis banktypes.GenesisState + if err := tmjson.Unmarshal(bankRaw, &bankGenesis); err != nil { + return err + } + if err := m.migratePUNDIX(ctx, bankGenesis.Balances, bankGenesis.Supply); err != nil { + return err + } + return m.migratePURSE(ctx, bankGenesis.Balances, bankGenesis.Supply) +} + +func (m *Pundix) migratePUNDIX(ctx sdk.Context, balances []banktypes.Balance, supply sdk.Coins) error { + baseDenom := "pundix" + bridgeToken, err := m.erc20Keeper.GetBridgeToken(ctx, ethtypes.ModuleName, baseDenom) + if err != nil { + return err + } + pundixGenesisAmount := sdkmath.NewInt(3400).Mul(sdkmath.NewInt(1e18)) + pundixDenomHash := sha256.Sum256([]byte(fmt.Sprintf("%s/channel-0/%s", ibctransfertypes.ModuleName, bridgeToken.BridgeDenom()))) + pundixIBCDenom := fmt.Sprintf("%s/%X", ibctransfertypes.DenomPrefix, pundixDenomHash[:]) + escrowAddr := ibctransfertypes.GetEscrowAddress(ibctransfertypes.ModuleName, "channel-0") + + pundixSupply := supply.AmountOf(pundixIBCDenom) + ibcTotalEscrow := m.ibcTransferKeeper.GetTotalEscrowForDenom(ctx, bridgeToken.BridgeDenom()) + ibcChannelBalance := m.bankKeeper.GetBalance(ctx, escrowAddr, bridgeToken.BridgeDenom()) + + if ibcChannelBalance.Amount.Add(pundixGenesisAmount).LT(pundixSupply) || + ibcTotalEscrow.Amount.LT(ibcChannelBalance.Amount) { + return sdkerrors.ErrInvalidCoins.Wrap("pundix ibc amount not match") + } + + // remove ibc channel pundix amount + if err = m.bankKeeper.SendCoinsFromAccountToModule(ctx, escrowAddr, crosschaintypes.ModuleName, sdk.NewCoins(ibcChannelBalance)); err != nil { + return err + } + totalEscrow := m.ibcTransferKeeper.GetTotalEscrowForDenom(ctx, bridgeToken.BridgeDenom()) + totalEscrow = totalEscrow.Sub(ibcChannelBalance) + m.ibcTransferKeeper.SetTotalEscrowForDenom(ctx, totalEscrow) + + // pundix chain will burn pundix token if slash validator + uncountedAmount := pundixSupply.Sub(ibcChannelBalance.Amount) + uncountedCoin := sdk.NewCoins(sdk.NewCoin(ibcChannelBalance.Denom, uncountedAmount.Abs())) + if uncountedAmount.IsPositive() { + if err = m.bankKeeper.MintCoins(ctx, crosschaintypes.ModuleName, uncountedCoin); err != nil { + return err + } + } else if uncountedAmount.IsNegative() { + if err = m.bankKeeper.BurnCoins(ctx, crosschaintypes.ModuleName, uncountedCoin); err != nil { + return err + } + } + + for _, bal := range balances { + bech32Addr, err := sdk.GetFromBech32(bal.Address, "px") + if err != nil { + return err + } + account := m.accountKeeper.GetAccount(ctx, bech32Addr) + if ma, ok := account.(sdk.ModuleAccountI); ok { + // todo migrate staking and reward + ctx.Logger().Info("migrate PUNDIX skip module account", "module", ma.GetName(), "address", bal.Address, "balance", bal.Coins.String()) + continue + } + baesPundixCoin := sdk.NewCoins(sdk.NewCoin(baseDenom, bal.Coins.AmountOf(pundixIBCDenom))) + if err = m.bankKeeper.MintCoins(ctx, crosschaintypes.ModuleName, baesPundixCoin); err != nil { + return err + } + if err = m.bankKeeper.SendCoinsFromModuleToAccount(ctx, crosschaintypes.ModuleName, bech32Addr, baesPundixCoin); err != nil { + return err + } + } + return nil +} + +func (m *Pundix) migratePURSE(ctx sdk.Context, balances []banktypes.Balance, supply sdk.Coins) error { + baseDenom := "purse" + + erc20Token, err := m.erc20Keeper.GetERC20Token(ctx, baseDenom) + if err != nil { + return err + } + ibcToken, err := m.erc20Keeper.GetIBCToken(ctx, "channel-0", baseDenom) + if err != nil { + return err + } + bscBridgeToken, err := m.erc20Keeper.GetBridgeToken(ctx, bsctypes.ModuleName, baseDenom) + if err != nil { + return err + } + + ibcPurseSupply := m.bankKeeper.GetSupply(ctx, ibcToken.IbcDenom) + // bsc bridge denom is origin denom of pundix chain + pundixPurseSupply := supply.AmountOf(bscBridgeToken.BridgeDenom()) + pxEscrowAddr, pxEscrowPurseAmount, err := pxEscrowAddrAndAmount(balances, bscBridgeToken.BridgeDenom()) + if err != nil { + return err + } + + if err = m.lockFxcorePurse(ctx, erc20Token, bscBridgeToken, ibcToken, ibcPurseSupply.Amount, pxEscrowPurseAmount); err != nil { + return err + } + if err = m.migratePundixPurse(ctx, erc20Token, bscBridgeToken, balances, pundixPurseSupply.Sub(pxEscrowPurseAmount), pxEscrowAddr); err != nil { + return err + } + if err = m.updatePurseErc20Owner(ctx, erc20Token); err != nil { + return err + } + return m.removeDeprecatedPurse(ctx, baseDenom, "channel-0", ibcToken) +} + +func pxEscrowAddrAndAmount(balances []banktypes.Balance, denom string) (string, sdkmath.Int, error) { + pxEscrowAddr, err := GetPxChannelEscrowAddr() + if err != nil { + return "", sdkmath.Int{}, err + } + // bsc bridge denom is origin denom of pundix chain + for _, bal := range balances { + if bal.Address == pxEscrowAddr { + return pxEscrowAddr, bal.Coins.AmountOf(denom), nil + } + } + return pxEscrowAddr, sdkmath.ZeroInt(), nil +} + +func GetPxChannelEscrowAddr() (string, error) { + return bech32.ConvertAndEncode("px", ibctransfertypes.GetEscrowAddress(ibctransfertypes.ModuleName, "channel-0")) +} + +func (m *Pundix) lockFxcorePurse( + ctx sdk.Context, + erc20Token erc20types.ERC20Token, + bscBridgeToken erc20types.BridgeToken, + ibcToken erc20types.IBCToken, + ibcDenomPurseSupply, pxEscrowPurseAmount sdkmath.Int, +) error { + erc20ModuleAddress := common.BytesToAddress(types.NewModuleAddress(erc20types.ModuleName).Bytes()) + erc20PurseTotalSupply, err := m.erc20TokenKeeper.TotalSupply(ctx, erc20Token.GetERC20Contract()) + if err != nil { + return err + } + // cosmosPurseBal contain burned bsc bridge token + cosmosPurseBal := pxEscrowPurseAmount.Sub(sdkmath.NewIntFromBigInt(erc20PurseTotalSupply)) + if _, err = m.erc20TokenKeeper.Mint(ctx, erc20Token.GetERC20Contract(), erc20ModuleAddress, erc20ModuleAddress, cosmosPurseBal.BigInt()); err != nil { + return err + } + // make up the balance of base purse + if err = m.bankKeeper.MintCoins(ctx, erc20types.ModuleName, sdk.NewCoins(sdk.NewCoin(erc20Token.GetDenom(), cosmosPurseBal))); err != nil { + return err + } + + // convert ibc purse to base purse, exclude erc20 module + m.bankKeeper.IterateAllBalances(ctx, func(addr sdk.AccAddress, coin sdk.Coin) (stop bool) { + if coin.GetDenom() != ibcToken.GetIbcDenom() { + return false + } + account := m.accountKeeper.GetAccount(ctx, addr) + if ma, ok := account.(sdk.ModuleAccountI); ok && ma.GetName() == erc20types.ModuleName { + return false + } + + if err = m.bankKeeper.SendCoinsFromAccountToModule(ctx, addr, erc20types.ModuleName, sdk.NewCoins(coin)); err != nil { + return true + } + basePurse := sdk.NewCoins(sdk.NewCoin(erc20Token.GetDenom(), coin.Amount)) + if err = m.bankKeeper.SendCoinsFromModuleToAccount(ctx, erc20types.ModuleName, addr, basePurse); err != nil { + return true + } + return false + }) + if err != nil { + return err + } + + // mint bsc bridge token to module + bscBridgeAmount := pxEscrowPurseAmount.Sub(ibcDenomPurseSupply) + if err = m.bankKeeper.MintCoins(ctx, bsctypes.ModuleName, sdk.NewCoins(sdk.NewCoin(bscBridgeToken.BridgeDenom(), bscBridgeAmount))); err != nil { + return err + } + + // burn all ibc purse + return m.bankKeeper.BurnCoins(ctx, erc20types.ModuleName, sdk.NewCoins(sdk.NewCoin(ibcToken.GetIbcDenom(), ibcDenomPurseSupply))) +} + +func (m *Pundix) migratePundixPurse( + ctx sdk.Context, + erc20Token erc20types.ERC20Token, + bridgeToken erc20types.BridgeToken, + purseBals []banktypes.Balance, + supplyExcludeEscrow sdkmath.Int, + pxEscrowAddr string, +) error { + erc20ModuleAddress := common.BytesToAddress(types.NewModuleAddress(erc20types.ModuleName).Bytes()) + if _, err := m.erc20TokenKeeper.Mint(ctx, erc20Token.GetERC20Contract(), erc20ModuleAddress, erc20ModuleAddress, supplyExcludeEscrow.BigInt()); err != nil { + return err + } + if err := m.bankKeeper.MintCoins(ctx, erc20types.ModuleName, sdk.NewCoins(sdk.NewCoin(erc20Token.Denom, supplyExcludeEscrow))); err != nil { + return err + } + for _, bal := range purseBals { + if bal.Address == pxEscrowAddr { + continue + } + bech32Addr, err := sdk.GetFromBech32(bal.Address, "px") + if err != nil { + return err + } + account := m.accountKeeper.GetAccount(ctx, bech32Addr) + if ma, ok := account.(sdk.ModuleAccountI); ok { + // todo migrate staking and reward + ctx.Logger().Info("migrate PURSE skip module account", "module", ma.GetName(), "address", bal.Address, "balance", bal.Coins.String()) + continue + } + purseBalance := sdk.NewCoin(erc20Token.Denom, bal.Coins.AmountOf(bridgeToken.BridgeDenom())) + if err = m.bankKeeper.SendCoinsFromModuleToAccount(ctx, erc20types.ModuleName, bech32Addr, sdk.NewCoins(purseBalance)); err != nil { + return err + } + } + return nil +} + +func (m *Pundix) updatePurseErc20Owner(ctx sdk.Context, erc20Token erc20types.ERC20Token) error { + erc20ModuleHexAddress := common.BytesToAddress(types.NewModuleAddress(erc20types.ModuleName).Bytes()) + newOwner := common.BytesToAddress(types.NewModuleAddress(evmtypes.ModuleName)) + if _, err := m.erc20TokenKeeper.TransferOwnership(ctx, erc20Token.GetERC20Contract(), erc20ModuleHexAddress, newOwner); err != nil { + return err + } + erc20Token.ContractOwner = erc20types.OWNER_EXTERNAL + return m.erc20Keeper.ERC20Token.Set(ctx, erc20Token.Denom, erc20Token) +} + +func (m *Pundix) removeDeprecatedPurse(ctx sdk.Context, baseDenom, channelId string, ibcToken erc20types.IBCToken) error { + // remove ibc token + if err := m.erc20Keeper.DenomIndex.Remove(ctx, ibcToken.IbcDenom); err != nil { + return err + } + key := collections.Join(baseDenom, channelId) + if err := m.erc20Keeper.IBCToken.Remove(ctx, key); err != nil { + return err + } + + // remove denom trace + hexHash := strings.TrimPrefix(ibcToken.IbcDenom, ibctransfertypes.DenomPrefix+"/") + hash, err := ibctransfertypes.ParseHexHash(hexHash) + if err != nil { + return err + } + kvStore := prefix.NewStore(ctx.KVStore(m.storeKey), ibctransfertypes.DenomTraceKey) + kvStore.Delete(hash) + return nil +} + +func ReadGenesisState(genesisPath string) (map[string]json.RawMessage, error) { + genesisFile, err := os.ReadFile(genesisPath) + if err != nil { + return nil, err + } + genesisState := make(map[string]json.RawMessage) + if err = tmjson.Unmarshal(genesisFile, &genesisState); err != nil { + return nil, err + } + appState := make(map[string]json.RawMessage) + appStateBz := genesisState["app_state"] + err = tmjson.Unmarshal(appStateBz, &appState) + return appState, err +} diff --git a/app/upgrades/v8/upgrade.go b/app/upgrades/v8/upgrade.go index e0580499..5e582b7c 100644 --- a/app/upgrades/v8/upgrade.go +++ b/app/upgrades/v8/upgrade.go @@ -55,6 +55,10 @@ func CreateUpgradeHandler(mm *module.Manager, configurator module.Configurator, return fromVM, err } + if err = NewPundix(app).Migrate(cacheCtx); err != nil { + return fromVM, err + } + if err = migrateBridgeBalance(cacheCtx, app.BankKeeper, app.AccountKeeper); err != nil { return fromVM, err } @@ -136,7 +140,6 @@ func migrateBridgeBalance(ctx sdk.Context, bankKeeper bankkeeper.Keeper, account } } } - // todo migrate bridge token and ibc token balance to crosschain module return nil } diff --git a/x/erc20/migrations/v8/migrate.go b/x/erc20/migrations/v8/migrate.go index bf06a414..077a1ad1 100644 --- a/x/erc20/migrations/v8/migrate.go +++ b/x/erc20/migrations/v8/migrate.go @@ -9,6 +9,8 @@ import ( fxtypes "github.com/functionx/fx-core/v8/types" arbitrumtypes "github.com/functionx/fx-core/v8/x/arbitrum/types" + bsctypes "github.com/functionx/fx-core/v8/x/bsc/types" + crosschaintypes "github.com/functionx/fx-core/v8/x/crosschain/types" ethtypes "github.com/functionx/fx-core/v8/x/eth/types" optimismtypes "github.com/functionx/fx-core/v8/x/optimism/types" ) @@ -39,6 +41,13 @@ func (m Migrator) MigrateToken(ctx sdk.Context) error { if err := m.addToken(ctx, newBaseDenom, md.Base); err != nil { return err } + + // add purse bsc module bridge token + if strings.HasPrefix(md.Base, ibctransfertypes.DenomPrefix+"/") { + if err := m.addBscBridgePurse(ctx, newBaseDenom, md.Base); err != nil { + return err + } + } } return nil } @@ -88,10 +97,28 @@ func (m Migrator) addBridgeToken(ctx sdk.Context, base, alias string) error { return err } ctx.Logger().Info("add bridge token", "base-denom", base, "alias", alias, "module", ck.ModuleName(), "contract", legacyBridgeToken.Token) - if err := m.keeper.AddBridgeToken(ctx, base, ck.ModuleName(), legacyBridgeToken.Token, erc20Token.IsNativeERC20()); err != nil { + if err = m.keeper.AddBridgeToken(ctx, base, ck.ModuleName(), legacyBridgeToken.Token, erc20Token.IsNativeERC20()); err != nil { return err } break } return nil } + +func (m Migrator) addBscBridgePurse(ctx sdk.Context, newBaseDenom, base string) error { + for _, ck := range m.crosschainKeepers { + if ck.ModuleName() != bsctypes.ModuleName { + continue + } + legacyBridgeToken, found := ck.LegacyGetDenomBridgeToken(ctx, base) + if !found { + return sdkerrors.ErrKeyNotFound.Wrapf("module %s bridge token: %s", ck.ModuleName(), base) + } + bridgeDenom := crosschaintypes.NewBridgeDenom(ck.ModuleName(), legacyBridgeToken.Token) + ctx.Logger().Info("add bridge token", "base-denom", newBaseDenom, "alias", bridgeDenom, "module", ck.ModuleName(), "contract", legacyBridgeToken.Token) + if err := m.keeper.AddBridgeToken(ctx, newBaseDenom, ck.ModuleName(), legacyBridgeToken.Token, false); err != nil { + return err + } + } + return nil +}