diff --git a/proto/dymensionxyz/dymension/dymns/dym_name.proto b/proto/dymensionxyz/dymension/dymns/dym_name.proto index ccc0ca8c2..4d04c6943 100644 --- a/proto/dymensionxyz/dymension/dymns/dym_name.proto +++ b/proto/dymensionxyz/dymension/dymns/dym_name.proto @@ -50,7 +50,10 @@ message DymNameConfig { DymNameConfigType type = 1; // chain_id is the chain-id of the Dym-Name configuration (equals to top-level-domain). - // If empty, the configuration is for host chain (Dymension Hub). + // There are 3 format types: + // - If empty, the configuration is for host chain (Dymension Hub). + // - If not empty and numeric only, then it is EIP-155 chain-id of RollApp. + // - Otherwise, it's external chains. string chain_id = 2; // path of the Dym-Name configuration (equals to Host in DNS). diff --git a/x/dymns/keeper/alias.go b/x/dymns/keeper/alias.go index 44fd57c32..228078848 100644 --- a/x/dymns/keeper/alias.go +++ b/x/dymns/keeper/alias.go @@ -1,7 +1,11 @@ package keeper import ( + "fmt" "slices" + "strconv" + + rollapptypes "github.com/dymensionxyz/dymension/v3/x/rollapp/types" errorsmod "cosmossdk.io/errors" sdk "github.com/cosmos/cosmos-sdk/types" @@ -17,10 +21,17 @@ func (k Keeper) GetRollAppIdByAlias(ctx sdk.Context, alias string) (rollAppId st }() store := ctx.KVStore(k.storeKey) - key := dymnstypes.AliasToRollAppIdRvlKey(alias) + key := dymnstypes.AliasToRollAppEip155IdRvlKey(alias) bz := store.Get(key) if bz != nil { - rollAppId = string(bz) + rollAppEip155Id := string(bz) + eip155, _ := strconv.ParseUint(rollAppEip155Id, 10, 64) + var foundRollApp bool + rollAppId, foundRollApp = k.rollappKeeper.GetRollAppIdByEIP155(ctx, eip155) + if !foundRollApp { + // this should not happen as validated before + panic(fmt.Sprintf("rollapp not found by EIP155 of alias '%s': %s", alias, rollAppEip155Id)) + } } return @@ -59,23 +70,25 @@ func (k Keeper) SetAliasForRollAppId(ctx sdk.Context, rollAppId, alias string) e } store := ctx.KVStore(k.storeKey) - keyR2A := dymnstypes.RollAppIdToAliasesKey(rollAppId) - keyA2R := dymnstypes.AliasToRollAppIdRvlKey(alias) + keyRE155toA := dymnstypes.RollAppIdToAliasesKey(rollAppId) + keyAtoRE155 := dymnstypes.AliasToRollAppEip155IdRvlKey(alias) // ensure the alias is not being used by another RollApp - if bz := store.Get(keyA2R); bz != nil { - return errorsmod.Wrapf(gerrc.ErrAlreadyExists, "alias currently being in used by: %s", string(bz)) + if bz := store.Get(keyAtoRE155); bz != nil { + eip155, _ := strconv.ParseUint(string(bz), 10, 64) + usedByRollAppId, _ := k.rollappKeeper.GetRollAppIdByEIP155(ctx, eip155) + return errorsmod.Wrapf(gerrc.ErrAlreadyExists, "alias currently being in used by: %s", usedByRollAppId) } // one RollApp can have multiple aliases, append to the existing list var multipleAliases dymnstypes.MultipleAliases - if bz := store.Get(keyR2A); bz != nil { + if bz := store.Get(keyRE155toA); bz != nil { k.cdc.MustUnmarshal(bz, &multipleAliases) } multipleAliases.Aliases = append(multipleAliases.Aliases, alias) - store.Set(keyR2A, k.cdc.MustMarshal(&multipleAliases)) - store.Set(keyA2R, []byte(rollAppId)) + store.Set(keyRE155toA, k.cdc.MustMarshal(&multipleAliases)) + store.Set(keyAtoRE155, []byte(dymnsutils.MustGetEIP155ChainIdFromRollAppId(rollAppId))) return nil } @@ -136,20 +149,24 @@ func (k Keeper) RemoveAliasFromRollAppId(ctx sdk.Context, rollAppId, alias strin } store := ctx.KVStore(k.storeKey) - keyR2A := dymnstypes.RollAppIdToAliasesKey(rollAppId) - keyA2R := dymnstypes.AliasToRollAppIdRvlKey(alias) + keyRE155toA := dymnstypes.RollAppIdToAliasesKey(rollAppId) + keyAtoRE155 := dymnstypes.AliasToRollAppEip155IdRvlKey(alias) // ensure the alias is being used by the RollApp - bzRollAppId := store.Get(keyA2R) - if bzRollAppId == nil { + bzRollAppEip155Id := store.Get(keyAtoRE155) + if bzRollAppEip155Id == nil { return errorsmod.Wrapf(gerrc.ErrNotFound, "alias not found: %s", alias) - } else if string(bzRollAppId) != rollAppId { - return errorsmod.Wrapf(gerrc.ErrPermissionDenied, "alias currently being in used by: %s", string(bzRollAppId)) + } + + if string(bzRollAppEip155Id) != dymnsutils.MustGetEIP155ChainIdFromRollAppId(rollAppId) { + eip155, _ := strconv.ParseUint(string(bzRollAppEip155Id), 10, 64) + usedByRollAppId, _ := k.rollappKeeper.GetRollAppIdByEIP155(ctx, eip155) + return errorsmod.Wrapf(gerrc.ErrPermissionDenied, "alias currently being in used by: %s", usedByRollAppId) } // load the existing aliases of the RollApp var multipleAliases dymnstypes.MultipleAliases - if bz := store.Get(keyR2A); bz != nil { + if bz := store.Get(keyRE155toA); bz != nil { k.cdc.MustUnmarshal(bz, &multipleAliases) } @@ -166,13 +183,13 @@ func (k Keeper) RemoveAliasFromRollAppId(ctx sdk.Context, rollAppId, alias strin // if no alias left, remove the key, otherwise update new list if len(multipleAliases.Aliases) == 0 { - store.Delete(keyR2A) + store.Delete(keyRE155toA) } else { - store.Set(keyR2A, k.cdc.MustMarshal(&multipleAliases)) + store.Set(keyRE155toA, k.cdc.MustMarshal(&multipleAliases)) } // remove the alias to RollAppId mapping - store.Delete(keyA2R) + store.Delete(keyAtoRE155) return nil } @@ -235,6 +252,10 @@ func (k Keeper) IsAliasPresentsInParamsAsAliasOrChainId(ctx sdk.Context, alias s // SetDefaultAliasForRollApp move the alias into the first place, so it can be used as default alias in resolution. func (k Keeper) SetDefaultAliasForRollApp(ctx sdk.Context, rollAppId, alias string) error { + if _, err := rollapptypes.NewChainID(rollAppId); err != nil { + return errorsmod.Wrapf(gerrc.ErrInvalidArgument, "invalid RollApp chain-id: %s", rollAppId) + } + // load the existing aliases of the RollApp from store existingAliases := k.GetAliasesOfRollAppId(ctx, rollAppId) @@ -260,8 +281,8 @@ func (k Keeper) SetDefaultAliasForRollApp(ctx sdk.Context, rollAppId, alias stri // update the new list into store store := ctx.KVStore(k.storeKey) - keyR2A := dymnstypes.RollAppIdToAliasesKey(rollAppId) - store.Set(keyR2A, k.cdc.MustMarshal(&dymnstypes.MultipleAliases{Aliases: existingAliases})) + keyRE155toA := dymnstypes.RollAppIdToAliasesKey(rollAppId) + store.Set(keyRE155toA, k.cdc.MustMarshal(&dymnstypes.MultipleAliases{Aliases: existingAliases})) return nil } @@ -270,16 +291,35 @@ func (k Keeper) SetDefaultAliasForRollApp(ctx sdk.Context, rollAppId, alias stri func (k Keeper) GetAllRollAppsWithAliases(ctx sdk.Context) (list []dymnstypes.AliasesOfChainId) { store := ctx.KVStore(k.storeKey) - iterator := sdk.KVStorePrefixIterator(store, dymnstypes.KeyPrefixRollAppIdToAliases) + iterator := sdk.KVStorePrefixIterator(store, dymnstypes.KeyPrefixRollAppEip155IdToAliases) defer func() { _ = iterator.Close() }() + eip155ToRollAppIdCache := make(map[string]string) + // Describe usage of Go Map: used for caching purpose, no iteration. + for ; iterator.Valid(); iterator.Next() { var multipleAliases dymnstypes.MultipleAliases k.cdc.MustUnmarshal(iterator.Value(), &multipleAliases) + + var rollAppId string + eip155Id := string(iterator.Key()[len(dymnstypes.KeyPrefixRollAppEip155IdToAliases):]) + if cachedRollAppId, found := eip155ToRollAppIdCache[eip155Id]; found { + rollAppId = cachedRollAppId + } else { + eip155, _ := strconv.ParseUint(eip155Id, 10, 64) + var foundRollApp bool + rollAppId, foundRollApp = k.rollappKeeper.GetRollAppIdByEIP155(ctx, eip155) + if !foundRollApp { + // this should not happen as validated before + panic(fmt.Sprintf("rollapp not found by EIP155: %s", eip155Id)) + } + eip155ToRollAppIdCache[eip155Id] = rollAppId // cache the result + } + list = append(list, dymnstypes.AliasesOfChainId{ - ChainId: string(iterator.Key()[len(dymnstypes.KeyPrefixRollAppIdToAliases):]), + ChainId: rollAppId, Aliases: multipleAliases.Aliases, }) } diff --git a/x/dymns/keeper/alias_test.go b/x/dymns/keeper/alias_test.go index 1337ed22a..1ffe6f367 100644 --- a/x/dymns/keeper/alias_test.go +++ b/x/dymns/keeper/alias_test.go @@ -988,7 +988,7 @@ func (s *KeeperTestSuite) TestKeeper_SetDefaultAliasForRollApp() { existingAliases: nil, moveAlias: "default", wantErr: true, - wantErrContains: "alias is not linked to the RollApp", + wantErrContains: "invalid RollApp chain-id", wantAliases: nil, }, { @@ -1045,6 +1045,9 @@ func (s *KeeperTestSuite) TestKeeper_SetDefaultAliasForRollApp() { if tt.rollAppId == "" || s.T().Failed() { return } + if _, err := rollapptypes.NewChainID(tt.rollAppId); err != nil { + return + } aliasesAfter := s.dymNsKeeper.GetAliasesOfRollAppId(s.ctx, tt.rollAppId) if len(tt.wantAliases) == 0 { s.Empty(aliasesAfter) diff --git a/x/dymns/keeper/dym_name.go b/x/dymns/keeper/dym_name.go index 4a211facb..d27e820b3 100644 --- a/x/dymns/keeper/dym_name.go +++ b/x/dymns/keeper/dym_name.go @@ -1,6 +1,8 @@ package keeper import ( + "fmt" + "strconv" "strings" errorsmod "cosmossdk.io/errors" @@ -297,6 +299,9 @@ func (k Keeper) ResolveByDymNameAddress(ctx sdk.Context, dymNameAddress string) if lookupChainIdConfig == ctx.ChainID() { // Dym-Name configuration in store does not persist the chain-id if it is the host chain lookupChainIdConfig = "" + } else if k.IsRollAppId(ctx, lookupChainIdConfig) { + // Dym-Name configuration in store only persist EIP-155 chain id for RollApp + lookupChainIdConfig = dymnsutils.MustGetEIP155ChainIdFromRollAppId(lookupChainIdConfig) } // do filter @@ -721,6 +726,7 @@ func (k Keeper) ReverseResolveDymNameAddress(ctx sdk.Context, inputAddress, work ctx, inputAddress, workingChainId, + workingChainIdIsRollApp, ) if err != nil { return nil, err @@ -765,11 +771,7 @@ func (k Keeper) ReverseResolveDymNameAddress(ctx sdk.Context, inputAddress, work // reverseResolveDymNameAddressUsingConfiguredAddress resolves the input address into a Dym-Name-Address // which points to it. -func (k Keeper) reverseResolveDymNameAddressUsingConfiguredAddress( - ctx sdk.Context, - inputAddress, - workingChainId string, -) (outputDymNameAddresses dymnstypes.ReverseResolvedDymNameAddresses, err error) { +func (k Keeper) reverseResolveDymNameAddressUsingConfiguredAddress(ctx sdk.Context, inputAddress, workingChainId string, workingChainIdIsRollApp bool) (outputDymNameAddresses dymnstypes.ReverseResolvedDymNameAddresses, err error) { // find all the Dym-Names those contain the input address in configuration, // iterate through the configuration records to find out the Dym-Name-Address dymNames, err1 := k.GetDymNamesContainsConfiguredAddress(ctx, inputAddress) @@ -777,11 +779,19 @@ func (k Keeper) reverseResolveDymNameAddressUsingConfiguredAddress( return nil, err1 } + var eip155WorkingChainId string // only available if is RollApp + if workingChainIdIsRollApp { + eip155WorkingChainId = dymnsutils.MustGetEIP155ChainIdFromRollAppId(workingChainId) + } + for _, dymName := range dymNames { configuredAddresses, _ := dymName.GetAddressesForReverseMapping() configs := configuredAddresses[inputAddress] outputDymNameAddresses = outputDymNameAddresses.AppendConfigs(ctx, dymName, configs, func(address dymnstypes.ReverseResolvedDymNameAddress) bool { + if workingChainIdIsRollApp { + return address.ChainIdOrAlias == eip155WorkingChainId + } return address.ChainIdOrAlias == workingChainId }, ) @@ -829,11 +839,19 @@ func (k Keeper) reverseResolveDymNameAddressUsingHexAddress( return nil, err1 } + var eip155WorkingChainId string // only available if is RollApp + if workingChainIdIsRollApp { + eip155WorkingChainId = dymnsutils.MustGetEIP155ChainIdFromRollAppId(workingChainId) + } + for _, dymName := range dymNames { configuredAddresses, _ := dymName.GetAddressesForReverseMapping() configs := configuredAddresses[lookupKey] outputDymNameAddresses = outputDymNameAddresses.AppendConfigs(ctx, dymName, configs, func(address dymnstypes.ReverseResolvedDymNameAddress) bool { + if workingChainIdIsRollApp { + return address.ChainIdOrAlias == eip155WorkingChainId + } return address.ChainIdOrAlias == workingChainId }, ) @@ -909,6 +927,8 @@ func (k Keeper) ReplaceChainIdWithAliasIfPossible(ctx sdk.Context, reverseResolv resolvedCache := make(map[string]string) // Describe usage of Go Map: used for caching purpose, no iteration. + eip155ToRollAppIdCache := make(map[string]string) + // Describe usage of Go Map: used for caching purpose, no iteration. for i, reverseResolvedRecord := range reverseResolvedRecords { chainIdOrAlias := reverseResolvedRecord.ChainIdOrAlias @@ -919,6 +939,21 @@ func (k Keeper) ReplaceChainIdWithAliasIfPossible(ctx sdk.Context, reverseResolv // then at this place, we put it back chainIdOrAlias = ctx.ChainID() reverseResolvedRecords[i].ChainIdOrAlias = chainIdOrAlias + } else if dymnsutils.IsValidEIP155ChainId(chainIdOrAlias) { + fullRollAppId, found := eip155ToRollAppIdCache[chainIdOrAlias] + if !found { + rollAppEip155ChainId, _ := strconv.ParseUint(chainIdOrAlias, 10, 64) + var foundRA bool + fullRollAppId, foundRA = k.rollappKeeper.GetRollAppIdByEIP155(ctx, rollAppEip155ChainId) + if !foundRA { + // this should not happen because we have checked the existence of RollApp before + panic(fmt.Sprintf("rollapp not found by EIP155: %s", chainIdOrAlias)) + } + eip155ToRollAppIdCache[chainIdOrAlias] = fullRollAppId // cache result + } + + chainIdOrAlias = fullRollAppId + reverseResolvedRecords[i].ChainIdOrAlias = chainIdOrAlias } // check cache first diff --git a/x/dymns/keeper/dym_name_test.go b/x/dymns/keeper/dym_name_test.go index ee602b910..329f2e5b3 100644 --- a/x/dymns/keeper/dym_name_test.go +++ b/x/dymns/keeper/dym_name_test.go @@ -606,6 +606,31 @@ func (s *KeeperTestSuite) TestKeeper_ResolveByDymNameAddress() { dymNameAddress: "c.b.a@dymension_1100-1", wantOutputAddress: addr3a, }, + { + name: "success, RollApp chain-ID", + dymName: &dymnstypes.DymName{ + Name: "a", + Owner: addr1a, + Controller: addr2a, + ExpireAt: s.now.Unix() + 100, + Configs: []dymnstypes.DymNameConfig{{ + Type: dymnstypes.DymNameConfigType_DCT_NAME, + ChainId: "1122", + Value: addr2Acc.bech32C("nim"), + }, { + Type: dymnstypes.DymNameConfigType_DCT_NAME, + ChainId: "1122", + Path: "b", + Value: addr2Acc.bech32C("nim"), + }}, + }, + preSetup: func(s *KeeperTestSuite) { + s.persistRollApp(*newRollApp("nim_1122-1")) + }, + dymNameAddress: "a@nim_1122-1", + wantOutputAddress: addr2Acc.bech32C("nim"), + postTest: func(s *KeeperTestSuite) {}, + }, { name: "success, no sub name, alias", dymName: &dymnstypes.DymName{ @@ -850,11 +875,11 @@ func (s *KeeperTestSuite) TestKeeper_ResolveByDymNameAddress() { ExpireAt: s.now.Unix() + 100, Configs: []dymnstypes.DymNameConfig{{ Type: dymnstypes.DymNameConfigType_DCT_NAME, - ChainId: "nim_1122-1", + ChainId: "1122", Value: addr2Acc.bech32C("nim"), }, { Type: dymnstypes.DymNameConfigType_DCT_NAME, - ChainId: "nim_1122-1", + ChainId: "1122", Path: "b", Value: addr2Acc.bech32C("nim"), }}, @@ -2419,7 +2444,7 @@ func (s *KeeperTestSuite) TestKeeper_ReverseResolveDymNameAddress() { dymNames: newDN("a", ownerAcc.bech32()). exp(s.now, +1). cfgN("", "b", ownerAcc.bech32()). - cfgN(rollAppId1, "ica", icaAcc.bech32()). + cfgNForRollApp(rollAppId1, "ica", icaAcc.bech32()). buildSlice(), additionalSetup: nil, inputAddress: icaAcc.bech32(), @@ -2458,7 +2483,7 @@ func (s *KeeperTestSuite) TestKeeper_ReverseResolveDymNameAddress() { dymNames: newDN("a", ownerAcc.bech32()). exp(s.now, +1). cfgN("", "b", ownerAcc.bech32()). - cfgN(rollAppId1, "ica", icaAcc.bech32()). + cfgNForRollApp(rollAppId1, "ica", icaAcc.bech32()). buildSlice(), additionalSetup: nil, inputAddress: swapCase(icaAcc.bech32()), @@ -2661,7 +2686,7 @@ func (s *KeeperTestSuite) TestKeeper_ReverseResolveDymNameAddress() { exp(s.now, +1). cfgN("", "b", ownerAcc.bech32()). cfgN("blumbus_111-1", "bb", ownerAcc.bech32()). - cfgN(rollAppId1, "ra", anotherAcc.bech32C(rollApp1Bech32)). + cfgNForRollApp(rollAppId1, "ra", anotherAcc.bech32C(rollApp1Bech32)). buildSlice(), inputAddress: anotherAcc.hexStr(), workingChainId: rollAppId1, @@ -2702,7 +2727,7 @@ func (s *KeeperTestSuite) TestKeeper_ReverseResolveDymNameAddress() { exp(s.now, +1). cfgN("", "b", ownerAcc.bech32()). cfgN("blumbus_111-1", "bb", ownerAcc.bech32()). - cfgN(rollAppId1, "ra", ownerAcc.bech32C(rollApp1Bech32)). + cfgNForRollApp(rollAppId1, "ra", ownerAcc.bech32C(rollApp1Bech32)). buildSlice(), inputAddress: ownerAcc.hexStr(), workingChainId: rollAppId1, @@ -2719,6 +2744,42 @@ func (s *KeeperTestSuite) TestKeeper_ReverseResolveDymNameAddress() { }, }, }, + { + name: "pass - lookup by hex on RollApp with bech32 prefix mapped, find out the matching configuration, even tho Chain-ID of RollApp in config is EIP-155 part only", + dymNames: newDN("a", ownerAcc.bech32()). + exp(s.now, +1). + cfgN("", "b", ownerAcc.bech32()). + cfgN("blumbus_111-1", "bb", ownerAcc.bech32()). + cfgNForRollApp(rollAppId1, "ra", ownerAcc.bech32C(rollApp1Bech32)). + buildSlice(), + inputAddress: ownerAcc.hexStr(), + workingChainId: rollAppId1, + additionalSetup: func(s *KeeperTestSuite) { + dymName := s.dymNsKeeper.GetDymName(s.ctx, "a") + s.Require().Len(dymName.Configs, 3) + + var found bool + for _, cfg := range dymName.Configs { + if cfg.ChainId == "1" /*EIP-155*/ && cfg.Path == "ra" { + found = true + break + } + } + s.Require().True(found, "expected to find the configuration with Chain-ID is EIP-155 part") + }, + wantErr: false, + want: dymnstypes.ReverseResolvedDymNameAddresses{ + { + // when bech32 found from mapped by chain-id, + // we convert the hex address into bech32 + // and perform lookup, so we should find out + // the existing configuration + SubName: "ra", + Name: "a", + ChainIdOrAlias: rollAppId1, + }, + }, + }, { name: "pass - skip lookup by hex after first try (direct match) if working-chain-id is Neither host-chain nor RollApp, by bech32", dymNames: newDN("a", ownerAcc.bech32()). @@ -3103,8 +3164,8 @@ func (s *KeeperTestSuite) TestKeeper_ReverseResolveDymNameAddress() { exp(s.now, +1). cfgN("", "b", ownerAcc.bech32()). cfgN("blumbus_111-1", "bb", ownerAcc.bech32()). - cfgN(rollAppId2, "", ownerAcc.bech32C(rollApp2Bech32)). - cfgN(rollAppId2, "b", ownerAcc.bech32C(rollApp2Bech32)). + cfgNForRollApp(rollAppId2, "", ownerAcc.bech32C(rollApp2Bech32)). + cfgNForRollApp(rollAppId2, "b", ownerAcc.bech32C(rollApp2Bech32)). buildSlice(), additionalSetup: func(s *KeeperTestSuite) { }, @@ -3130,7 +3191,7 @@ func (s *KeeperTestSuite) TestKeeper_ReverseResolveDymNameAddress() { exp(s.now, +1). cfgN("", "b", ownerAcc.bech32()). cfgN("blumbus_111-1", "bb", ownerAcc.bech32()). - cfgN(rollAppId2, "", ownerAcc.bech32C(rollApp2Bech32)). + cfgNForRollApp(rollAppId2, "", ownerAcc.bech32C(rollApp2Bech32)). buildSlice(), additionalSetup: func(s *KeeperTestSuite) { moduleParams := s.dymNsKeeper.GetParams(s.ctx) @@ -3164,10 +3225,42 @@ func (s *KeeperTestSuite) TestKeeper_ReverseResolveDymNameAddress() { exp(s.now, +1). cfgN("", "b", ownerAcc.bech32()). cfgN("blumbus_111-1", "bb", ownerAcc.bech32()). - cfgN(rollAppId2, "", ownerAcc.bech32C(rollApp2Bech32)). - cfgN(rollAppId2, "b", ownerAcc.bech32C(rollApp2Bech32)). + cfgNForRollApp(rollAppId2, "", ownerAcc.bech32C(rollApp2Bech32)). + cfgNForRollApp(rollAppId2, "b", ownerAcc.bech32C(rollApp2Bech32)). + buildSlice(), + additionalSetup: func(s *KeeperTestSuite) { + }, + inputAddress: ownerAcc.hexStr(), + workingChainId: rollAppId2, + wantErr: false, + want: dymnstypes.ReverseResolvedDymNameAddresses{ + { + SubName: "", + Name: "a", + ChainIdOrAlias: rollApp2Alias, // alias is used instead of chain-id + }, + { + SubName: "b", + Name: "a", + ChainIdOrAlias: rollApp2Alias, + }, + }, + }, + { + name: "pass - RollApp ID detected when config is EIP-155, alias is used if available", + dymNames: newDN("a", ownerAcc.bech32()). + exp(s.now, +1). + cfgNForRollApp(rollAppId2, "", ownerAcc.bech32C(rollApp2Bech32)). + cfgNForRollApp(rollAppId2, "b", ownerAcc.bech32C(rollApp2Bech32)). buildSlice(), additionalSetup: func(s *KeeperTestSuite) { + dymName := s.dymNsKeeper.GetDymName(s.ctx, "a") + s.Require().Len(dymName.Configs, 2) + + const eip155Id = "2" + for _, cfg := range dymName.Configs { + s.Require().Equal(eip155Id, cfg.ChainId) + } }, inputAddress: ownerAcc.hexStr(), workingChainId: rollAppId2, @@ -3384,6 +3477,42 @@ func (s *KeeperTestSuite) TestKeeper_ReplaceChainIdWithAliasIfPossible() { ) }) + s.Run("mapping correct EIP-155 part to RollApp ID and alias", func() { + input := []dymnstypes.ReverseResolvedDymNameAddress{ + { + SubName: "a", + Name: "b", + ChainIdOrAlias: "1", + }, + { + Name: "a", + ChainIdOrAlias: "2", + }, + { + Name: "b", + ChainIdOrAlias: "3", + }, + } + s.Require().Equal( + []dymnstypes.ReverseResolvedDymNameAddress{ + { + SubName: "a", + Name: "b", + ChainIdOrAlias: "ra1", + }, + { + Name: "a", + ChainIdOrAlias: "rollapp_2-2", + }, + { + Name: "b", + ChainIdOrAlias: "rollapp_3-3", + }, + }, + s.dymNsKeeper.ReplaceChainIdWithAliasIfPossible(s.ctx, input), + ) + }) + s.Run("mapping correct alias for RollApp by ID, when RollApp has multiple alias", func() { s.Require().NoError(s.dymNsKeeper.SetAliasForRollAppId(s.ctx, "rollapp_1-1", "ral12")) s.Require().NoError(s.dymNsKeeper.SetAliasForRollAppId(s.ctx, "rollapp_1-1", "ral13")) diff --git a/x/dymns/keeper/hooks.go b/x/dymns/keeper/hooks.go index cfb1db8e7..ad111f62a 100644 --- a/x/dymns/keeper/hooks.go +++ b/x/dymns/keeper/hooks.go @@ -1,16 +1,12 @@ package keeper import ( - "errors" - dymnsutils "github.com/dymensionxyz/dymension/v3/x/dymns/utils" errorsmod "cosmossdk.io/errors" "github.com/dymensionxyz/gerr-cosmos/gerrc" - "github.com/osmosis-labs/osmosis/v15/osmoutils" - sdk "github.com/cosmos/cosmos-sdk/types" rollapptypes "github.com/dymensionxyz/dymension/v3/x/rollapp/types" ) @@ -80,77 +76,3 @@ func (h rollappHooks) FraudSubmitted(_ sdk.Context, _ string, _ uint64, _ string func (h rollappHooks) AfterTransfersEnabled(_ sdk.Context, _, _ string) error { return nil } - -// FutureRollappHooks is temporary added to handle future hooks that not available yet. -type FutureRollappHooks interface { - // OnRollAppIdChanged is called when a RollApp's ID is changed, typically due to fraud submission. - // It migrates all aliases and Dym-Names associated with the previous RollApp ID to the new one. - // This function executes step by step in a branched context to prevent side effects, and any errors - // during execution will result in the state changes being discarded. - // - // Parameters: - // - ctx: The SDK context - // - previousRollAppId: The original ID of the RollApp - // - newRollAppId: The new ID assigned to the RollApp - OnRollAppIdChanged(ctx sdk.Context, previousRollAppId, newRollAppId string) - // Just a pseudo method signature, the actual method signature might be different. - - // TODO DymNS: connect to the actual implementation when the hooks are available. - // The implementation of OnRollAppIdChanged assume that both of the RollApp records are exists in the x/rollapp store. -} - -var _ FutureRollappHooks = rollappHooks{} - -func (k Keeper) GetFutureRollAppHooks() FutureRollappHooks { - return rollappHooks{ - Keeper: k, - } -} - -// OnRollAppIdChanged implements FutureRollappHooks. -func (h rollappHooks) OnRollAppIdChanged(ctx sdk.Context, previousRollAppId, newRollAppId string) { - logger := h.Logger(ctx).With( - "old-rollapp-id", previousRollAppId, "new-rollapp-id", newRollAppId, - ) - - logger.Info("begin DymNS hook on RollApp ID changed.") - - // Due to the critical nature reason of the hook, - // each step will be done in branched context and drop if error, to prevent any side effects. - - if err := osmoutils.ApplyFuncIfNoError(ctx, func(ctx sdk.Context) error { - aliasesLinkedToPreviousRollApp := h.GetAliasesOfRollAppId(ctx, previousRollAppId) - if len(aliasesLinkedToPreviousRollApp) == 0 { - return nil - } - - for _, alias := range aliasesLinkedToPreviousRollApp { - if err := h.MoveAliasToRollAppId(ctx, previousRollAppId, alias, newRollAppId); err != nil { - return errorsmod.Wrapf(errors.Join(gerrc.ErrInternal, err), "failed to migrate alias: %s", alias) - } - } - - // now priority the first alias from previous RollApp, because users are already familiar with it. - return h.SetDefaultAliasForRollApp(ctx, newRollAppId, aliasesLinkedToPreviousRollApp[0]) - }); err != nil { - logger.Error("aborted alias migration.", "error", err) - return - } - - if err := osmoutils.ApplyFuncIfNoError(ctx, func(ctx sdk.Context) error { - previousChainIdsToNewChainId := map[string]string{ - previousRollAppId: newRollAppId, - } - - if err := h.migrateChainIdsInDymNames(ctx, previousChainIdsToNewChainId); err != nil { - return errorsmod.Wrapf(errors.Join(gerrc.ErrInternal, err), "failed to migrate chain-ids in Dym-Names") - } - - return nil - }); err != nil { - logger.Error("aborted chain-id migration in Dym-Names configurations.", "error", err) - return - } - - logger.Info("finished DymNS hook on RollApp ID changed.") -} diff --git a/x/dymns/keeper/hooks_test.go b/x/dymns/keeper/hooks_test.go index 5e134d07e..addf6b0f6 100644 --- a/x/dymns/keeper/hooks_test.go +++ b/x/dymns/keeper/hooks_test.go @@ -443,12 +443,12 @@ func (s *KeeperTestSuite) Test_rollappHooks_RollappCreated() { Configs: []dymnstypes.DymNameConfig{ { Type: dymnstypes.DymNameConfigType_DCT_NAME, - ChainId: "rollapp_1-1", + ChainId: "1", Value: dymNameOwnerAcc.bech32(), }, { Type: dymnstypes.DymNameConfigType_DCT_NAME, - ChainId: "rollapp_1-1", + ChainId: "1", Path: "sub", Value: anotherAcc.bech32(), }, @@ -562,385 +562,3 @@ func (s *KeeperTestSuite) Test_rollappHooks_RollappCreated() { s.Equal(originalTxGas, s.ctx.GasMeter().GasConsumed(), "should not consume gas") }) } - -func (s *KeeperTestSuite) Test_rollappHooks_OnRollAppIdChanged() { - const previousRollAppId = "rollapp_1-1" - const newRollAppId = "rollapp_1-2" - - const name1 = "name1" - const name2 = "name2" - - user1Acc := testAddr(1) - user2Acc := testAddr(2) - user3Acc := testAddr(3) - user4Acc := testAddr(4) - user5Acc := testAddr(5) - - genRollApp := func(rollAppId string, aliases ...string) *rollapp { - ra := newRollApp(rollAppId) - for _, alias := range aliases { - _ = ra.WithAlias(alias) - } - return ra - } - - tests := []struct { - name string - setupFunc func(s *KeeperTestSuite) - testFunc func(s *KeeperTestSuite) - }{ - { - name: "pass - normal migration, with alias, with Dym-Name", - setupFunc: func(s *KeeperTestSuite) { - s.persistRollApp(*genRollApp(previousRollAppId, "alias")) - s.persistRollApp(*genRollApp(newRollAppId)) - - s.requireRollApp(previousRollAppId).HasAlias("alias") - s.requireRollApp(newRollAppId).HasNoAlias() - - s.setDymNameWithFunctionsAfter( - newDN(name1, user1Acc.bech32()). - cfgN(previousRollAppId, "", user2Acc.bech32()). - build(), - ) - - outputAddr, err := s.dymNsKeeper.ResolveByDymNameAddress(s.ctx, name1+"@"+previousRollAppId) - s.NoError(err) - s.Equal(user2Acc.bech32(), outputAddr) - - outputDymNameAddrs, err := s.dymNsKeeper.ReverseResolveDymNameAddress(s.ctx, user2Acc.bech32(), previousRollAppId) - s.NoError(err) - s.NotEmpty(outputDymNameAddrs) - - _, err = s.dymNsKeeper.ResolveByDymNameAddress(s.ctx, name1+"@"+newRollAppId) - s.ErrorContains(err, "not found") - - outputDymNameAddrs, err = s.dymNsKeeper.ReverseResolveDymNameAddress(s.ctx, user2Acc.bech32(), newRollAppId) - s.NoError(err) - s.Empty(outputDymNameAddrs) - }, - testFunc: func(s *KeeperTestSuite) { - s.requireRollApp(previousRollAppId).HasNoAlias() - s.requireRollApp(newRollAppId).HasAlias("alias") - - // - - _, err := s.dymNsKeeper.ResolveByDymNameAddress(s.ctx, name1+"@"+previousRollAppId) - s.ErrorContains(err, "not found") - - outputDymNameAddrs, err := s.dymNsKeeper.ReverseResolveDymNameAddress(s.ctx, user2Acc.bech32(), previousRollAppId) - s.NoError(err) - s.Empty(outputDymNameAddrs) - - // - - outputAddr, err := s.dymNsKeeper.ResolveByDymNameAddress(s.ctx, name1+"@"+newRollAppId) - s.NoError(err) - s.Equal(user2Acc.bech32(), outputAddr) - - outputDymNameAddrs, err = s.dymNsKeeper.ReverseResolveDymNameAddress(s.ctx, user2Acc.bech32(), newRollAppId) - s.NoError(err) - s.NotEmpty(outputDymNameAddrs) - }, - }, - { - name: "pass - normal migration, with multiple aliases, with multiple Dym-Names", - setupFunc: func(s *KeeperTestSuite) { - s.persistRollApp(*genRollApp(previousRollAppId, "one", "two", "three")) - s.persistRollApp(*genRollApp(newRollAppId)) - - s.requireRollApp(previousRollAppId).HasAlias("one", "two", "three") - s.requireRollApp(newRollAppId).HasNoAlias() - - s.setDymNameWithFunctionsAfter( - newDN(name1, user1Acc.bech32()). - cfgN(previousRollAppId, "", user2Acc.bech32()). - build(), - ) - - s.setDymNameWithFunctionsAfter( - newDN(name2, user1Acc.bech32()). - cfgN(previousRollAppId, "", user2Acc.bech32()). - cfgN(previousRollAppId, "sub2", user2Acc.bech32()). - cfgN(previousRollAppId, "sub3", user3Acc.bech32()). - cfgN("", "", user4Acc.bech32()). - cfgN("", "sub5", user5Acc.bech32()). - build(), - ) - - // - - outputAddr, err := s.dymNsKeeper.ResolveByDymNameAddress(s.ctx, name1+"@"+previousRollAppId) - s.NoError(err) - s.Equal(user2Acc.bech32(), outputAddr) - - outputAddr, err = s.dymNsKeeper.ResolveByDymNameAddress(s.ctx, name2+"@"+previousRollAppId) - s.NoError(err) - s.Equal(user2Acc.bech32(), outputAddr) - - outputDymNameAddrs, err := s.dymNsKeeper.ReverseResolveDymNameAddress(s.ctx, user2Acc.bech32(), previousRollAppId) - s.NoError(err) - s.Len(outputDymNameAddrs, 3) // 1 from name1, 2 from name2 - - // - - outputAddr, err = s.dymNsKeeper.ResolveByDymNameAddress(s.ctx, "sub2."+name2+"@"+previousRollAppId) - s.NoError(err) - s.Equal(user2Acc.bech32(), outputAddr) - - // - - outputAddr, err = s.dymNsKeeper.ResolveByDymNameAddress(s.ctx, "sub3."+name2+"@"+previousRollAppId) - s.NoError(err) - s.Equal(user3Acc.bech32(), outputAddr) - - outputDymNameAddrs, err = s.dymNsKeeper.ReverseResolveDymNameAddress(s.ctx, user3Acc.bech32(), previousRollAppId) - s.NoError(err) - s.Len(outputDymNameAddrs, 1) - - // - - outputAddr, err = s.dymNsKeeper.ResolveByDymNameAddress(s.ctx, name2+"@"+s.chainId) - s.NoError(err) - s.Equal(user4Acc.bech32(), outputAddr) - - outputDymNameAddrs, err = s.dymNsKeeper.ReverseResolveDymNameAddress(s.ctx, user4Acc.bech32(), s.chainId) - s.NoError(err) - s.Len(outputDymNameAddrs, 1) - - outputDymNameAddrs, err = s.dymNsKeeper.ReverseResolveDymNameAddress(s.ctx, user4Acc.bech32(), previousRollAppId) - s.NoError(err) - if s.Len(outputDymNameAddrs, 1) { - s.Equal(name2+"@one", outputDymNameAddrs[0].String()) // result of fallback lookup - } - - // - - outputAddr, err = s.dymNsKeeper.ResolveByDymNameAddress(s.ctx, "sub5."+name2+"@"+s.chainId) - s.NoError(err) - s.Equal(user5Acc.bech32(), outputAddr) - - outputDymNameAddrs, err = s.dymNsKeeper.ReverseResolveDymNameAddress(s.ctx, user5Acc.bech32(), s.chainId) - s.NoError(err) - s.Len(outputDymNameAddrs, 1) - - outputDymNameAddrs, err = s.dymNsKeeper.ReverseResolveDymNameAddress(s.ctx, user5Acc.bech32(), previousRollAppId) - s.NoError(err) - s.Empty(outputDymNameAddrs) // no fallback lookup because it's a sub-name - }, - testFunc: func(s *KeeperTestSuite) { - s.requireRollApp(previousRollAppId).HasNoAlias() - s.requireRollApp(newRollAppId).HasAlias("one", "two", "three") - - // - - outputAddr, err := s.dymNsKeeper.ResolveByDymNameAddress(s.ctx, name1+"@"+newRollAppId) - s.NoError(err) - s.Equal(user2Acc.bech32(), outputAddr) - - outputAddr, err = s.dymNsKeeper.ResolveByDymNameAddress(s.ctx, name2+"@"+newRollAppId) - s.NoError(err) - s.Equal(user2Acc.bech32(), outputAddr) - - outputDymNameAddrs, err := s.dymNsKeeper.ReverseResolveDymNameAddress(s.ctx, user2Acc.bech32(), newRollAppId) - s.NoError(err) - s.Len(outputDymNameAddrs, 3) // 1 from name1, 2 from name2 - - // - - outputAddr, err = s.dymNsKeeper.ResolveByDymNameAddress(s.ctx, "sub2."+name2+"@"+newRollAppId) - s.NoError(err) - s.Equal(user2Acc.bech32(), outputAddr) - - // - - outputAddr, err = s.dymNsKeeper.ResolveByDymNameAddress(s.ctx, "sub3."+name2+"@"+newRollAppId) - s.NoError(err) - s.Equal(user3Acc.bech32(), outputAddr) - - outputDymNameAddrs, err = s.dymNsKeeper.ReverseResolveDymNameAddress(s.ctx, user3Acc.bech32(), newRollAppId) - s.NoError(err) - s.Len(outputDymNameAddrs, 1) - - // - - outputAddr, err = s.dymNsKeeper.ResolveByDymNameAddress(s.ctx, name2+"@"+s.chainId) - s.NoError(err) - s.Equal(user4Acc.bech32(), outputAddr) - - outputDymNameAddrs, err = s.dymNsKeeper.ReverseResolveDymNameAddress(s.ctx, user4Acc.bech32(), s.chainId) - s.NoError(err) - s.Len(outputDymNameAddrs, 1) - - outputDymNameAddrs, err = s.dymNsKeeper.ReverseResolveDymNameAddress(s.ctx, user4Acc.bech32(), newRollAppId) - s.NoError(err) - if s.Len(outputDymNameAddrs, 1) { - s.Equal(name2+"@one", outputDymNameAddrs[0].String()) // result of fallback lookup - } - - // - - outputAddr, err = s.dymNsKeeper.ResolveByDymNameAddress(s.ctx, "sub5."+name2+"@"+s.chainId) - s.NoError(err) - s.Equal(user5Acc.bech32(), outputAddr) - - outputDymNameAddrs, err = s.dymNsKeeper.ReverseResolveDymNameAddress(s.ctx, user5Acc.bech32(), s.chainId) - s.NoError(err) - s.Len(outputDymNameAddrs, 1) - - outputDymNameAddrs, err = s.dymNsKeeper.ReverseResolveDymNameAddress(s.ctx, user5Acc.bech32(), newRollAppId) - s.NoError(err) - s.Empty(outputDymNameAddrs) // no fallback lookup because it's a sub-name - }, - }, - { - name: "fail - when migrate alias failed, should not change anything", - setupFunc: func(s *KeeperTestSuite) { - s.persistRollApp(*genRollApp(previousRollAppId, "alias")) - s.persistRollApp(*genRollApp(newRollAppId)) - - s.requireRollApp(previousRollAppId).HasAlias("alias") - s.requireRollApp(newRollAppId).HasNoAlias() - - s.setDymNameWithFunctionsAfter( - newDN(name1, user1Acc.bech32()). - cfgN(previousRollAppId, "", user2Acc.bech32()). - build(), - ) - - outputAddr, err := s.dymNsKeeper.ResolveByDymNameAddress(s.ctx, name1+"@"+previousRollAppId) - s.NoError(err) - s.Equal(user2Acc.bech32(), outputAddr) - - outputDymNameAddrs, err := s.dymNsKeeper.ReverseResolveDymNameAddress(s.ctx, user2Acc.bech32(), previousRollAppId) - s.NoError(err) - s.NotEmpty(outputDymNameAddrs) - - _, err = s.dymNsKeeper.ResolveByDymNameAddress(s.ctx, name1+"@"+newRollAppId) - s.ErrorContains(err, "not found") - - outputDymNameAddrs, err = s.dymNsKeeper.ReverseResolveDymNameAddress(s.ctx, user2Acc.bech32(), newRollAppId) - s.NoError(err) - s.Empty(outputDymNameAddrs) - - // delete the rollapp to make the migration fail - s.rollAppKeeper.RemoveRollapp(s.ctx, previousRollAppId) - - s.Require().False(s.dymNsKeeper.IsRollAppId(s.ctx, previousRollAppId)) - }, - testFunc: func(s *KeeperTestSuite) { - // unchanged - - s.requireRollApp(newRollAppId).HasNoAlias() - - outputAddr, err := s.dymNsKeeper.ResolveByDymNameAddress(s.ctx, name1+"@"+previousRollAppId) - s.NoError(err) - s.Equal(user2Acc.bech32(), outputAddr) - - outputDymNameAddrs, err := s.dymNsKeeper.ReverseResolveDymNameAddress(s.ctx, user2Acc.bech32(), previousRollAppId) - s.NoError(err) - s.NotEmpty(outputDymNameAddrs) - - _, err = s.dymNsKeeper.ResolveByDymNameAddress(s.ctx, name1+"@"+newRollAppId) - s.ErrorContains(err, "not found") - - outputDymNameAddrs, err = s.dymNsKeeper.ReverseResolveDymNameAddress(s.ctx, user2Acc.bech32(), newRollAppId) - s.NoError(err) - s.Empty(outputDymNameAddrs) - }, - }, - { - name: "pass - when the new RollApp has existing aliases, merge them", - setupFunc: func(s *KeeperTestSuite) { - s.persistRollApp(*genRollApp(previousRollAppId, "one", "two", "three")) - s.persistRollApp(*genRollApp(newRollAppId, "four", "five", "six")) - - s.setDymNameWithFunctionsAfter( - newDN(name1, user1Acc.bech32()). - cfgN(previousRollAppId, "", user2Acc.bech32()). - build(), - ) - - // - - s.requireRollApp(previousRollAppId).HasAlias("one", "two", "three") - s.requireRollApp(newRollAppId).HasAlias("four", "five", "six") - - outputAddr, err := s.dymNsKeeper.ResolveByDymNameAddress(s.ctx, name1+"@"+previousRollAppId) - s.NoError(err) - s.Equal(user2Acc.bech32(), outputAddr) - }, - testFunc: func(s *KeeperTestSuite) { - s.requireRollApp(previousRollAppId).HasNoAlias() - s.requireRollApp(newRollAppId).HasAlias("one", "two", "three", "four", "five", "six") - - // others - - outputAddr, err := s.dymNsKeeper.ResolveByDymNameAddress(s.ctx, name1+"@"+newRollAppId) - s.NoError(err) - s.Equal(user2Acc.bech32(), outputAddr) - }, - }, - { - name: "pass - when previous RollApp has no alias", - setupFunc: func(s *KeeperTestSuite) { - s.persistRollApp(*genRollApp(previousRollAppId)) - s.persistRollApp(*genRollApp(newRollAppId, "new")) - - s.setDymNameWithFunctionsAfter( - newDN(name1, user1Acc.bech32()). - cfgN(previousRollAppId, "", user2Acc.bech32()). - build(), - ) - - // - - s.requireRollApp(previousRollAppId).HasNoAlias() - s.requireRollApp(newRollAppId).HasAlias("new") - - outputAddr, err := s.dymNsKeeper.ResolveByDymNameAddress(s.ctx, name1+"@"+previousRollAppId) - s.NoError(err) - s.Equal(user2Acc.bech32(), outputAddr) - }, - testFunc: func(s *KeeperTestSuite) { - s.requireRollApp(previousRollAppId).HasNoAlias() - s.requireRollApp(newRollAppId).HasAlias("new") - - // others - - outputAddr, err := s.dymNsKeeper.ResolveByDymNameAddress(s.ctx, name1+"@"+newRollAppId) - s.NoError(err) - s.Equal(user2Acc.bech32(), outputAddr) - }, - }, - { - name: "pass - when the new RollApp has existing aliases, priority previous default alias", - setupFunc: func(s *KeeperTestSuite) { - s.persistRollApp(*genRollApp(previousRollAppId, "old", "journey")) - s.persistRollApp(*genRollApp(newRollAppId, "new")) - - s.requireRollApp(previousRollAppId).HasAlias("old", "journey") - s.requireRollApp(newRollAppId).HasAlias("new") - }, - testFunc: func(s *KeeperTestSuite) { - s.requireRollApp(previousRollAppId).HasNoAlias() - s.requireRollApp(newRollAppId).HasAliasesWithOrder("old", "new", "journey") - }, - }, - } - for _, tt := range tests { - s.Run(tt.name, func() { - s.RefreshContext() - - if tt.setupFunc != nil { - tt.setupFunc(s) - } - - s.dymNsKeeper.GetFutureRollAppHooks().OnRollAppIdChanged(s.ctx, previousRollAppId, newRollAppId) - - if tt.testFunc != nil { - tt.testFunc(s) - } - }) - } -} diff --git a/x/dymns/keeper/keeper_suite_test.go b/x/dymns/keeper/keeper_suite_test.go index 384a08c31..e592c44e0 100644 --- a/x/dymns/keeper/keeper_suite_test.go +++ b/x/dymns/keeper/keeper_suite_test.go @@ -6,6 +6,8 @@ import ( "testing" "time" + dymnsutils "github.com/dymensionxyz/dymension/v3/x/dymns/utils" + "github.com/dymensionxyz/sdk-utils/utils/uptr" tmdb "github.com/cometbft/cometbft-db" @@ -433,6 +435,16 @@ func (m *dymNameBuilder) cfgN(chainId, subName, resolveTo string) *dymNameBuilde return m } +func (m *dymNameBuilder) cfgNForRollApp(chainId, subName, resolveTo string) *dymNameBuilder { + m.configs = append(m.configs, dymnstypes.DymNameConfig{ + Type: dymnstypes.DymNameConfigType_DCT_NAME, + ChainId: dymnsutils.MustGetEIP155ChainIdFromRollAppId(chainId), + Path: subName, + Value: resolveTo, + }) + return m +} + func (m *dymNameBuilder) build() dymnstypes.DymName { return dymnstypes.DymName{ Name: m.name, diff --git a/x/dymns/keeper/migration_playground.go b/x/dymns/keeper/migration_playground.go new file mode 100644 index 000000000..ecd6302f8 --- /dev/null +++ b/x/dymns/keeper/migration_playground.go @@ -0,0 +1,180 @@ +package keeper + +import ( + "fmt" + "slices" + + sdk "github.com/cosmos/cosmos-sdk/types" + dymnstypes "github.com/dymensionxyz/dymension/v3/x/dymns/types" + dymnsutils "github.com/dymensionxyz/dymension/v3/x/dymns/utils" + rollapptypes "github.com/dymensionxyz/dymension/v3/x/rollapp/types" +) + +// TODO DymNS: delete this, only apply for playground + +func (k Keeper) BeginBlockMigrationForPlayground(ctx sdk.Context) { + var migrate bool + if ctx.ChainID() == "dymension_2018-1" { // Playground + migrate = ctx.BlockHeight()%100 == 0 + } else if ctx.ChainID() == "dymension_100-1" { // localnet + migrate = true + } + + if migrate { + k.MigrateStoreForPlayground(ctx) + } +} + +func (k Keeper) MigrateStoreForPlayground(ctx sdk.Context) (anyMigrated bool) { + m1 := k.migrateDymNameForPlayground(ctx) + m2 := k.migrateLinkingRollAppToAliasForPlayground(ctx) + + anyMigrated = m1 || m2 + if anyMigrated { + ctx.EventManager().EmitEvent( + sdk.NewEvent( + "dymns_migrated_for_playground", + sdk.NewAttribute("migrated dym-name", fmt.Sprintf("%t", m1)), + sdk.NewAttribute("migrated roll-app <=> alias linking", fmt.Sprintf("%t", m2)), + ), + ) + } + return +} + +func (k Keeper) migrateDymNameForPlayground(ctx sdk.Context) (anyMigrated bool) { + dymNames := k.GetAllDymNames(ctx) + for _, dymName := range dymNames { + var anyUpdated bool + for i, config := range dymName.Configs { + if config.ChainId == "" { + continue + } + + if dymnsutils.IsValidEIP155ChainId(config.ChainId) { + // migrated + continue + } + + rollAppId, err := rollapptypes.NewChainID(config.ChainId) + if err != nil { + // not RollApp + continue + } + + if !k.IsRollAppId(ctx, config.ChainId) { + // not target + continue + } + + config.ChainId = fmt.Sprintf("%d", rollAppId.GetEIP155ID()) + dymName.Configs[i] = config + + anyUpdated = true + } + + if anyUpdated { + err := k.SetDymName(ctx, dymName) + if err != nil { + panic(err) + } + anyMigrated = true + + // no need to call hooks + } + } + + return +} + +func (k Keeper) migrateLinkingRollAppToAliasForPlayground(ctx sdk.Context) (anyMigrated bool) { + store := ctx.KVStore(k.storeKey) + + type newLinking struct { + rollAppId string + multipleAliases dymnstypes.MultipleAliases + } + + newLinkingRecords, keysToDelete := func() ([]newLinking, [][]byte) { + // do not update on the fly + newLinkingRecords := make([]newLinking, 0) + keysToDelete := make([][]byte, 0) + + iterator := sdk.KVStorePrefixIterator(store, dymnstypes.KeyPrefixRollAppEip155IdToAliases) + defer func() { + _ = iterator.Close() + }() + + for ; iterator.Valid(); iterator.Next() { + key := iterator.Key() + rollAppId := string(key[len(dymnstypes.KeyPrefixRollAppEip155IdToAliases):]) + if dymnsutils.IsValidEIP155ChainId(rollAppId) { + // migrated + continue + } + + keysToDelete = append(keysToDelete, key) + + var multipleAliases dymnstypes.MultipleAliases + k.cdc.MustUnmarshal(iterator.Value(), &multipleAliases) + + if len(multipleAliases.Aliases) == 0 { + // no need to migrate + continue + } + + newLinkingRecords = append(newLinkingRecords, newLinking{ + rollAppId: rollAppId, + multipleAliases: multipleAliases, + }) + } + + return newLinkingRecords, keysToDelete + }() + + anyMigrated = len(newLinkingRecords) > 0 || len(keysToDelete) > 0 + + for _, key := range keysToDelete { + store.Delete(key) + } + + for _, linking := range newLinkingRecords { + key := dymnstypes.RollAppIdToAliasesKey(linking.rollAppId) + + bzExistingData := store.Get(key) + if len(bzExistingData) > 0 { // merge existing + var existingMultipleAliases dymnstypes.MultipleAliases + k.cdc.MustUnmarshal(bzExistingData, &existingMultipleAliases) + + if len(existingMultipleAliases.Aliases) > 0 { + unique := make(map[string]struct{}) + for _, alias := range existingMultipleAliases.Aliases { + unique[alias] = struct{}{} + } + for _, alias := range linking.multipleAliases.Aliases { + unique[alias] = struct{}{} + } + + uniqueAliases := make([]string, 0, len(unique)) + for alias := range unique { + uniqueAliases = append(uniqueAliases, alias) + } + + // important: sort to guarantee of consensus state + slices.Sort(uniqueAliases) + + linking.multipleAliases.Aliases = uniqueAliases + } + } + + store.Set(key, k.cdc.MustMarshal(&linking.multipleAliases)) + + bzEip155 := []byte(dymnsutils.MustGetEIP155ChainIdFromRollAppId(linking.rollAppId)) + for _, alias := range linking.multipleAliases.Aliases { + keyAtoRE155 := dymnstypes.AliasToRollAppEip155IdRvlKey(alias) + store.Set(keyAtoRE155, bzEip155) + } + } + + return +} diff --git a/x/dymns/keeper/migration_playground_test.go b/x/dymns/keeper/migration_playground_test.go new file mode 100644 index 000000000..c1a5bc65b --- /dev/null +++ b/x/dymns/keeper/migration_playground_test.go @@ -0,0 +1,439 @@ +package keeper_test + +import ( + dymnstypes "github.com/dymensionxyz/dymension/v3/x/dymns/types" + dymnsutils "github.com/dymensionxyz/dymension/v3/x/dymns/utils" +) + +func (s *KeeperTestSuite) Test_MigrateStoreForPlayground() { + rollApp1MultiAliases := *newRollApp("rollapp_1111-1").WithAlias("rol1").WithAlias("rol11") + rollApp2 := *newRollApp("rollapp_2222-2").WithAlias("rol2") + rollApp3WithoutAlias := *newRollApp("rollapp_3333-3") + rollApp4WithoutAlias := *newRollApp("rollapp_4444-4") + rollApp5WithoutAlias := *newRollApp("rollapp_5555-5") + + ownerAcc := testAddr(1) + + tests := []struct { + name string + setupFunc func(s *KeeperTestSuite) + testFunc func(s *KeeperTestSuite) + wantFirstRun bool + }{ + { + name: "can migrate Dym-Name", + setupFunc: func(s *KeeperTestSuite) { + s.persistRollApp(rollApp1MultiAliases) + + dymName := newDN("a", ownerAcc.bech32()). + cfgN(rollApp1MultiAliases.rollAppId, "", ownerAcc.bech32()). + build() + + s.Require().Equal(rollApp1MultiAliases.rollAppId, dymName.Configs[0].ChainId) + + err := s.dymNsKeeper.SetDymName(s.ctx, dymName) + s.Require().NoError(err) + }, + testFunc: func(s *KeeperTestSuite) { + dymNameLater := s.dymNsKeeper.GetDymName(s.ctx, "a") + s.Require().NotNil(dymNameLater) + + s.Require().Equal( + dymnsutils.MustGetEIP155ChainIdFromRollAppId(rollApp1MultiAliases.rollAppId), + dymNameLater.Configs[0].ChainId, + ) + }, + wantFirstRun: true, + }, + { + name: "can migrate multiple Dym-Names", + setupFunc: func(s *KeeperTestSuite) { + s.persistRollApp(rollApp1MultiAliases) + s.persistRollApp(rollApp2) + + for _, dn := range []string{"a", "b"} { + dymName := newDN(dn, ownerAcc.bech32()). + cfgN(rollApp1MultiAliases.rollAppId, "", ownerAcc.bech32()). + cfgN(rollApp2.rollAppId, "", ownerAcc.bech32()). + cfgN("blumbus_100-1", "", ownerAcc.bech32()). + build() + + s.Require().Equal(rollApp1MultiAliases.rollAppId, dymName.Configs[0].ChainId) + s.Require().Equal(rollApp2.rollAppId, dymName.Configs[1].ChainId) + + err := s.dymNsKeeper.SetDymName(s.ctx, dymName) + s.Require().NoError(err) + } + }, + testFunc: func(s *KeeperTestSuite) { + for _, dn := range []string{"a", "b"} { + dymNameLater := s.dymNsKeeper.GetDymName(s.ctx, dn) + s.Require().NotNil(dymNameLater) + + s.Equal( + dymnsutils.MustGetEIP155ChainIdFromRollAppId(rollApp1MultiAliases.rollAppId), + dymNameLater.Configs[0].ChainId, + ) + s.Equal( + dymnsutils.MustGetEIP155ChainIdFromRollAppId(rollApp2.rollAppId), + dymNameLater.Configs[1].ChainId, + ) + s.Equal( + "blumbus_100-1", + dymNameLater.Configs[2].ChainId, + "non-RollApp should not be changed", + ) + } + }, + wantFirstRun: true, + }, + { + name: "can migrate mixed migrated-state Dym-Names", + setupFunc: func(s *KeeperTestSuite) { + s.persistRollApp(rollApp1MultiAliases) + s.persistRollApp(rollApp2) + + for _, dn := range []string{"a", "b"} { + dymName := newDN(dn, ownerAcc.bech32()). + cfgN(rollApp1MultiAliases.rollAppId, "", ownerAcc.bech32()). + cfgNForRollApp(rollApp2.rollAppId, "", ownerAcc.bech32()). + cfgN("blumbus_100-1", "", ownerAcc.bech32()). + build() + + s.Require().Equal(rollApp1MultiAliases.rollAppId, dymName.Configs[0].ChainId) + s.Require().Equal(dymnsutils.MustGetEIP155ChainIdFromRollAppId(rollApp2.rollAppId), dymName.Configs[1].ChainId) + + err := s.dymNsKeeper.SetDymName(s.ctx, dymName) + s.Require().NoError(err) + } + }, + testFunc: func(s *KeeperTestSuite) { + for _, dn := range []string{"a", "b"} { + dymNameLater := s.dymNsKeeper.GetDymName(s.ctx, dn) + s.Require().NotNil(dymNameLater) + + s.Equal( + dymnsutils.MustGetEIP155ChainIdFromRollAppId(rollApp1MultiAliases.rollAppId), + dymNameLater.Configs[0].ChainId, + ) + s.Equal( + dymnsutils.MustGetEIP155ChainIdFromRollAppId(rollApp2.rollAppId), + dymNameLater.Configs[1].ChainId, + ) + s.Equal( + "blumbus_100-1", + dymNameLater.Configs[2].ChainId, + "non-RollApp should not be changed", + ) + } + }, + wantFirstRun: true, + }, + { + name: "can migrate Dym-Name and accept nothing need migration", + setupFunc: func(s *KeeperTestSuite) { + s.persistRollApp(rollApp1MultiAliases) + + dymName := newDN("a", ownerAcc.bech32()). + cfgNForRollApp(rollApp1MultiAliases.rollAppId, "", ownerAcc.bech32()). + build() + + s.Require().Equal( + dymnsutils.MustGetEIP155ChainIdFromRollAppId(rollApp1MultiAliases.rollAppId), + dymName.Configs[0].ChainId, + ) + + err := s.dymNsKeeper.SetDymName(s.ctx, dymName) + s.Require().NoError(err) + }, + testFunc: func(s *KeeperTestSuite) { + dymNameLater := s.dymNsKeeper.GetDymName(s.ctx, "a") + s.Require().NotNil(dymNameLater) + + s.Require().Equal( + dymnsutils.MustGetEIP155ChainIdFromRollAppId(rollApp1MultiAliases.rollAppId), + dymNameLater.Configs[0].ChainId, + ) + }, + wantFirstRun: false, // no need to migrate + }, + { + name: "can migrate alias", + setupFunc: func(s *KeeperTestSuite) { + s.persistRollApp(rollApp3WithoutAlias) + + const alias = "rol3" + + store := s.ctx.KVStore(s.dymNsStoreKey) + + // simulate the legacy alias => RollApp (full) ID mapping + store.Set(dymnstypes.AliasToBuyOrderIdsRvlKey(alias), []byte(rollApp3WithoutAlias.rollAppId)) + + // simulate the legacy RollApp (full) ID => alias mapping + store.Set(append(dymnstypes.KeyPrefixRollAppEip155IdToAliases, []byte(rollApp3WithoutAlias.rollAppId)...), s.cdc.MustMarshal(&dymnstypes.MultipleAliases{ + Aliases: []string{alias}, + })) + }, + testFunc: func(s *KeeperTestSuite) { + store := s.ctx.KVStore(s.dymNsStoreKey) + + const alias = "rol3" + eip155 := dymnsutils.MustGetEIP155ChainIdFromRollAppId(rollApp3WithoutAlias.rollAppId) + + s.Equal(eip155, string(store.Get(dymnstypes.AliasToRollAppEip155IdRvlKey(alias)))) + + s.False(store.Has(append(dymnstypes.KeyPrefixRollAppEip155IdToAliases, []byte(rollApp3WithoutAlias.rollAppId)...))) + + s.True(store.Has(append(dymnstypes.KeyPrefixRollAppEip155IdToAliases, []byte(eip155)...))) + + s.requireRollApp(rollApp3WithoutAlias.rollAppId).HasOnlyAlias(alias) + s.requireAlias(alias).LinkedToRollApp(rollApp3WithoutAlias.rollAppId) + }, + wantFirstRun: true, + }, + { + name: "can migrate multiple aliases", + setupFunc: func(s *KeeperTestSuite) { + s.persistRollApp(rollApp1MultiAliases) + s.persistRollApp(rollApp2) + s.persistRollApp(rollApp3WithoutAlias) + s.persistRollApp(rollApp4WithoutAlias) + s.persistRollApp(rollApp5WithoutAlias) + + { + const alias3 = "rol3" + + store := s.ctx.KVStore(s.dymNsStoreKey) + + // simulate the legacy alias => RollApp (full) ID mapping + store.Set(dymnstypes.AliasToBuyOrderIdsRvlKey(alias3), []byte(rollApp3WithoutAlias.rollAppId)) + + // simulate the legacy RollApp (full) ID => alias mapping + store.Set(append(dymnstypes.KeyPrefixRollAppEip155IdToAliases, []byte(rollApp3WithoutAlias.rollAppId)...), s.cdc.MustMarshal(&dymnstypes.MultipleAliases{ + Aliases: []string{alias3}, + })) + } + { + const alias4 = "rol4" + const alias42 = "rol42" + + store := s.ctx.KVStore(s.dymNsStoreKey) + + // simulate the legacy alias => RollApp (full) ID mapping + store.Set(dymnstypes.AliasToBuyOrderIdsRvlKey(alias4), []byte(rollApp4WithoutAlias.rollAppId)) + store.Set(dymnstypes.AliasToBuyOrderIdsRvlKey(alias42), []byte(rollApp4WithoutAlias.rollAppId)) + + // simulate the legacy RollApp (full) ID => alias mapping + store.Set(append(dymnstypes.KeyPrefixRollAppEip155IdToAliases, []byte(rollApp4WithoutAlias.rollAppId)...), s.cdc.MustMarshal(&dymnstypes.MultipleAliases{ + Aliases: []string{alias4, alias42}, + })) + } + }, + testFunc: func(s *KeeperTestSuite) { + store := s.ctx.KVStore(s.dymNsStoreKey) + + { + s.requireRollApp(rollApp1MultiAliases.rollAppId).HasAliasesWithOrder("rol1", "rol11") + s.requireAlias("rol1").LinkedToRollApp(rollApp1MultiAliases.rollAppId) + s.requireAlias("rol11").LinkedToRollApp(rollApp1MultiAliases.rollAppId) + } + + { + s.requireRollApp(rollApp2.rollAppId).HasOnlyAlias("rol2") + s.requireAlias("rol2").LinkedToRollApp(rollApp2.rollAppId) + } + + { + const alias3 = "rol3" + eip155 := dymnsutils.MustGetEIP155ChainIdFromRollAppId(rollApp3WithoutAlias.rollAppId) + + s.Equal(eip155, string(store.Get(dymnstypes.AliasToRollAppEip155IdRvlKey(alias3)))) + + s.False(store.Has(append(dymnstypes.KeyPrefixRollAppEip155IdToAliases, []byte(rollApp3WithoutAlias.rollAppId)...))) + + s.True(store.Has(append(dymnstypes.KeyPrefixRollAppEip155IdToAliases, []byte(eip155)...))) + + s.requireRollApp(rollApp3WithoutAlias.rollAppId).HasOnlyAlias(alias3) + s.requireAlias(alias3).LinkedToRollApp(rollApp3WithoutAlias.rollAppId) + } + + { + const alias4 = "rol4" + const alias42 = "rol42" + eip155 := dymnsutils.MustGetEIP155ChainIdFromRollAppId(rollApp4WithoutAlias.rollAppId) + + s.Equal(eip155, string(store.Get(dymnstypes.AliasToRollAppEip155IdRvlKey(alias4)))) + s.Equal(eip155, string(store.Get(dymnstypes.AliasToRollAppEip155IdRvlKey(alias42)))) + + s.False(store.Has(append(dymnstypes.KeyPrefixRollAppEip155IdToAliases, []byte(rollApp4WithoutAlias.rollAppId)...))) + + s.True(store.Has(append(dymnstypes.KeyPrefixRollAppEip155IdToAliases, []byte(eip155)...))) + + s.requireRollApp(rollApp4WithoutAlias.rollAppId).HasAliasesWithOrder(alias4, alias42) + s.requireAlias(alias4).LinkedToRollApp(rollApp4WithoutAlias.rollAppId) + s.requireAlias(alias42).LinkedToRollApp(rollApp4WithoutAlias.rollAppId) + } + + { + s.requireRollApp(rollApp5WithoutAlias.rollAppId).HasNoAlias() + + s.False(store.Has(append(dymnstypes.KeyPrefixRollAppEip155IdToAliases, []byte(rollApp5WithoutAlias.rollAppId)...))) + + eip155 := dymnsutils.MustGetEIP155ChainIdFromRollAppId(rollApp5WithoutAlias.rollAppId) + s.False(store.Has(append(dymnstypes.KeyPrefixRollAppEip155IdToAliases, []byte(eip155)...))) + } + }, + wantFirstRun: true, + }, + { + name: "can migrate mixed migrated-state aliases", + setupFunc: func(s *KeeperTestSuite) { + s.persistRollApp(rollApp1MultiAliases) + s.persistRollApp(rollApp2) + s.persistRollApp(rollApp3WithoutAlias) + s.persistRollApp(rollApp4WithoutAlias) + s.persistRollApp(rollApp5WithoutAlias) + + { + const alias22 = "rol22" + + store := s.ctx.KVStore(s.dymNsStoreKey) + + // simulate the legacy alias => RollApp (full) ID mapping + store.Set(dymnstypes.AliasToBuyOrderIdsRvlKey(alias22), []byte(rollApp2.rollAppId)) + + // simulate the legacy RollApp (full) ID => alias mapping + store.Set(append(dymnstypes.KeyPrefixRollAppEip155IdToAliases, []byte(rollApp2.rollAppId)...), s.cdc.MustMarshal(&dymnstypes.MultipleAliases{ + Aliases: []string{alias22}, + })) + } + + { + const alias3 = "rol3" + + store := s.ctx.KVStore(s.dymNsStoreKey) + + // simulate the legacy alias => RollApp (full) ID mapping + store.Set(dymnstypes.AliasToBuyOrderIdsRvlKey(alias3), []byte(rollApp3WithoutAlias.rollAppId)) + + // simulate the legacy RollApp (full) ID => alias mapping + store.Set(append(dymnstypes.KeyPrefixRollAppEip155IdToAliases, []byte(rollApp3WithoutAlias.rollAppId)...), s.cdc.MustMarshal(&dymnstypes.MultipleAliases{ + Aliases: []string{alias3}, + })) + } + { + const alias4 = "rol4" + const alias42 = "rol42" + + store := s.ctx.KVStore(s.dymNsStoreKey) + + // simulate the legacy alias => RollApp (full) ID mapping + store.Set(dymnstypes.AliasToBuyOrderIdsRvlKey(alias4), []byte(rollApp4WithoutAlias.rollAppId)) + store.Set(dymnstypes.AliasToBuyOrderIdsRvlKey(alias42), []byte(rollApp4WithoutAlias.rollAppId)) + + // simulate the legacy RollApp (full) ID => alias mapping + store.Set(append(dymnstypes.KeyPrefixRollAppEip155IdToAliases, []byte(rollApp4WithoutAlias.rollAppId)...), s.cdc.MustMarshal(&dymnstypes.MultipleAliases{ + Aliases: []string{alias4, alias42}, + })) + } + }, + testFunc: func(s *KeeperTestSuite) { + store := s.ctx.KVStore(s.dymNsStoreKey) + + { + s.requireRollApp(rollApp1MultiAliases.rollAppId).HasAliasesWithOrder("rol1", "rol11") + s.requireAlias("rol1").LinkedToRollApp(rollApp1MultiAliases.rollAppId) + s.requireAlias("rol11").LinkedToRollApp(rollApp1MultiAliases.rollAppId) + } + + { + s.requireRollApp(rollApp2.rollAppId).HasAliasesWithOrder("rol2", "rol22") + s.requireAlias("rol2").LinkedToRollApp(rollApp2.rollAppId) + s.requireAlias("rol22").LinkedToRollApp(rollApp2.rollAppId) + } + + { + const alias3 = "rol3" + eip155 := dymnsutils.MustGetEIP155ChainIdFromRollAppId(rollApp3WithoutAlias.rollAppId) + + s.Equal(eip155, string(store.Get(dymnstypes.AliasToRollAppEip155IdRvlKey(alias3)))) + + s.False(store.Has(append(dymnstypes.KeyPrefixRollAppEip155IdToAliases, []byte(rollApp3WithoutAlias.rollAppId)...))) + + s.True(store.Has(append(dymnstypes.KeyPrefixRollAppEip155IdToAliases, []byte(eip155)...))) + + s.requireRollApp(rollApp3WithoutAlias.rollAppId).HasOnlyAlias(alias3) + s.requireAlias(alias3).LinkedToRollApp(rollApp3WithoutAlias.rollAppId) + } + + { + const alias4 = "rol4" + const alias42 = "rol42" + eip155 := dymnsutils.MustGetEIP155ChainIdFromRollAppId(rollApp4WithoutAlias.rollAppId) + + s.Equal(eip155, string(store.Get(dymnstypes.AliasToRollAppEip155IdRvlKey(alias4)))) + s.Equal(eip155, string(store.Get(dymnstypes.AliasToRollAppEip155IdRvlKey(alias42)))) + + s.False(store.Has(append(dymnstypes.KeyPrefixRollAppEip155IdToAliases, []byte(rollApp4WithoutAlias.rollAppId)...))) + + s.True(store.Has(append(dymnstypes.KeyPrefixRollAppEip155IdToAliases, []byte(eip155)...))) + + s.requireRollApp(rollApp4WithoutAlias.rollAppId).HasAliasesWithOrder(alias4, alias42) + s.requireAlias(alias4).LinkedToRollApp(rollApp4WithoutAlias.rollAppId) + s.requireAlias(alias42).LinkedToRollApp(rollApp4WithoutAlias.rollAppId) + } + + { + s.requireRollApp(rollApp5WithoutAlias.rollAppId).HasNoAlias() + + s.False(store.Has(append(dymnstypes.KeyPrefixRollAppEip155IdToAliases, []byte(rollApp5WithoutAlias.rollAppId)...))) + + eip155 := dymnsutils.MustGetEIP155ChainIdFromRollAppId(rollApp5WithoutAlias.rollAppId) + s.False(store.Has(append(dymnstypes.KeyPrefixRollAppEip155IdToAliases, []byte(eip155)...))) + } + }, + wantFirstRun: true, + }, + { + name: "can migrate alias and accept nothing need migration", + setupFunc: func(s *KeeperTestSuite) { + s.persistRollApp(rollApp1MultiAliases) + }, + testFunc: func(s *KeeperTestSuite) { + s.requireRollApp(rollApp1MultiAliases.rollAppId).HasAliasesWithOrder("rol1", "rol11") + s.requireAlias("rol1").LinkedToRollApp(rollApp1MultiAliases.rollAppId) + s.requireAlias("rol11").LinkedToRollApp(rollApp1MultiAliases.rollAppId) + }, + wantFirstRun: false, + }, + } + for _, tt := range tests { + s.Run(tt.name, func() { + s.RefreshContext() + tt.setupFunc(s) + + for run := 1; run < 5; run++ { + anyMigrated := s.dymNsKeeper.MigrateStoreForPlayground(s.ctx) + + tt.testFunc(s) + + if run == 1 { + s.Equal(tt.wantFirstRun, anyMigrated) + } else { + s.False(anyMigrated) + } + + if anyMigrated { + var foundEvent bool + for _, event := range s.ctx.EventManager().Events() { + if event.Type == "dymns_migrated_for_playground" { + foundEvent = true + break + } + } + s.True(foundEvent, "event should be emitted") + } + } + }) + } +} diff --git a/x/dymns/keeper/msg_server_update_resolve_address.go b/x/dymns/keeper/msg_server_update_resolve_address.go index 4fb0a142f..cfdb5aac7 100644 --- a/x/dymns/keeper/msg_server_update_resolve_address.go +++ b/x/dymns/keeper/msg_server_update_resolve_address.go @@ -25,12 +25,17 @@ func (k msgServer) UpdateResolveAddress(goCtx context.Context, msg *dymnstypes.M } _, newConfig := msg.GetDymNameConfig() + var isRollAppChainId bool if newConfig.ChainId == ctx.ChainID() { newConfig.ChainId = "" + } else if k.IsRollAppId(ctx, newConfig.ChainId) { + // use EIP-155 chain id only + newConfig.ChainId = dymnsutils.MustGetEIP155ChainIdFromRollAppId(newConfig.ChainId) + isRollAppChainId = true } newConfigIdentity := newConfig.GetIdentity() - if newConfig.ChainId == "" || k.IsRollAppId(ctx, newConfig.ChainId) { + if newConfig.ChainId == "" || isRollAppChainId { // guarantee of case-insensitive on host and RollApps, // so we do normalize input newConfig.Value = strings.ToLower(newConfig.Value) diff --git a/x/dymns/keeper/msg_server_update_resolve_address_test.go b/x/dymns/keeper/msg_server_update_resolve_address_test.go index 217dc0f7f..ef38f4539 100644 --- a/x/dymns/keeper/msg_server_update_resolve_address_test.go +++ b/x/dymns/keeper/msg_server_update_resolve_address_test.go @@ -101,6 +101,7 @@ func (s *KeeperTestSuite) Test_msgServer_UpdateResolveAddress() { const recordName = "my-name" const rollAppId = "ra_9999-1" + const rollAppIdEip155 = "9999" //goland:noinspection SpellCheckingInspection nonBech32NonHexUpperCaseA := strings.ToUpper("X-avax1tzdcgj4ehsvhhgpl7zylwpw0gl2rxcg4r5afk5") @@ -325,6 +326,38 @@ func (s *KeeperTestSuite) Test_msgServer_UpdateResolveAddress() { s.requireFallbackAddress(controllerAcc.fallback()).notMappedToAnyDymName() }, }, + { + name: "fail - reject if config ChainID is EIP-155 format", + dymName: &dymnstypes.DymName{ + Owner: ownerAcc.bech32(), + Controller: controllerAcc.bech32(), + ExpireAt: s.now.Unix() + 100, + }, + msg: &dymnstypes.MsgUpdateResolveAddress{ + ResolveTo: ownerAcc.bech32(), + Controller: controllerAcc.bech32(), + ChainId: "155", + }, + preTestFunc: func(s *KeeperTestSuite) { + s.requireConfiguredAddress(ownerAcc.bech32()).mappedDymNames(recordName) + s.requireConfiguredAddress(controllerAcc.bech32()).notMappedToAnyDymName() + s.requireFallbackAddress(ownerAcc.fallback()).mappedDymNames(recordName) + s.requireFallbackAddress(controllerAcc.fallback()).notMappedToAnyDymName() + }, + wantErr: true, + wantErrContains: "chain-id cannot be numeric-only", + wantDymName: &dymnstypes.DymName{ + Owner: ownerAcc.bech32(), + Controller: controllerAcc.bech32(), + ExpireAt: s.now.Unix() + 100, + }, + postTestFunc: func(s *KeeperTestSuite) { + s.requireConfiguredAddress(ownerAcc.bech32()).mappedDymNames(recordName) + s.requireConfiguredAddress(controllerAcc.bech32()).notMappedToAnyDymName() + s.requireFallbackAddress(ownerAcc.fallback()).mappedDymNames(recordName) + s.requireFallbackAddress(controllerAcc.fallback()).notMappedToAnyDymName() + }, + }, { name: "pass - can update", dymName: &dymnstypes.DymName{ @@ -393,7 +426,7 @@ func (s *KeeperTestSuite) Test_msgServer_UpdateResolveAddress() { Configs: []dymnstypes.DymNameConfig{ { Type: dymnstypes.DymNameConfigType_DCT_NAME, - ChainId: rollAppId, + ChainId: rollAppIdEip155, Path: "", Value: anotherAcc.bech32C("rol"), }, @@ -444,6 +477,41 @@ func (s *KeeperTestSuite) Test_msgServer_UpdateResolveAddress() { s.requireConfiguredAddress(nonBech32NonHexUpperCaseA).mappedDymNames(recordName) }, }, + { + name: "pass - if chain-id is RollApp ID, use EIP-155 in config instead of full provided chain-id", + dymName: &dymnstypes.DymName{ + Owner: ownerAcc.bech32(), + Controller: controllerAcc.bech32(), + ExpireAt: s.now.Unix() + 100, + }, + preTestFunc: func(s *KeeperTestSuite) { + s.rollAppKeeper.SetRollapp(s.ctx, rollapptypes.Rollapp{ + RollappId: rollAppId, + Owner: anotherAcc.bech32(), + }) + }, + msg: &dymnstypes.MsgUpdateResolveAddress{ + ChainId: rollAppId, // <= full chain-id + ResolveTo: strings.ToUpper(anotherAcc.bech32C("rol")), // upper-cased + Controller: controllerAcc.bech32(), + }, + wantErr: false, + wantDymName: &dymnstypes.DymName{ + Owner: ownerAcc.bech32(), + Controller: controllerAcc.bech32(), + ExpireAt: s.now.Unix() + 100, + Configs: []dymnstypes.DymNameConfig{ + { + Type: dymnstypes.DymNameConfigType_DCT_NAME, + ChainId: rollAppIdEip155, // <= here + Path: "", + Value: anotherAcc.bech32C("rol"), + }, + }, + }, + wantMinGasConsumed: dymnstypes.OpGasConfig, + postTestFunc: func(s *KeeperTestSuite) {}, + }, { name: "pass - add new record if not exists", dymName: &dymnstypes.DymName{ @@ -1430,7 +1498,7 @@ func (s *KeeperTestSuite) Test_msgServer_UpdateResolveAddress() { Configs: []dymnstypes.DymNameConfig{ { Type: dymnstypes.DymNameConfigType_DCT_NAME, - ChainId: "nim_1122-1", + ChainId: "1122", Path: "a", Value: ownerAcc.bech32C("nim"), }, @@ -2010,6 +2078,8 @@ func (s *KeeperTestSuite) Test_msgServer_UpdateResolveAddress_ReverseMapping() { } if tt.hostChain { wantDymName.Configs[0].ChainId = "" + } else if tt.rollapp { + wantDymName.Configs[0].ChainId = dymnsutils.MustGetEIP155ChainIdFromRollAppId(msg.ChainId) } s.Require().Equal(wantDymName, *laterDymName) } diff --git a/x/dymns/keeper/params_test.go b/x/dymns/keeper/params_test.go index 1f27e7056..a5f7ab6cb 100644 --- a/x/dymns/keeper/params_test.go +++ b/x/dymns/keeper/params_test.go @@ -3,8 +3,6 @@ package keeper_test import ( "time" - rollapptypes "github.com/dymensionxyz/dymension/v3/x/rollapp/types" - dymnstypes "github.com/dymensionxyz/dymension/v3/x/dymns/types" ) @@ -206,6 +204,7 @@ func (s *KeeperTestSuite) TestKeeper_CanUseAliasForNewRegistration() { }, want: false, }, + /* // This test is not applicable anymore because cannot extract EIP-155 part from RollApp-ID { name: "pass - returns as NOT free if it is a RollApp-ID", alias: "bridge", @@ -219,6 +218,7 @@ func (s *KeeperTestSuite) TestKeeper_CanUseAliasForNewRegistration() { }, want: false, }, + */ } for _, tt := range tests { s.Run(tt.name, func() { diff --git a/x/dymns/keeper/rollapp.go b/x/dymns/keeper/rollapp.go index b113a24aa..029faddea 100644 --- a/x/dymns/keeper/rollapp.go +++ b/x/dymns/keeper/rollapp.go @@ -6,8 +6,7 @@ import ( // IsRollAppId checks if the chain-id is a RollApp-Id. func (k Keeper) IsRollAppId(ctx sdk.Context, chainId string) bool { - _, found := k.rollappKeeper.GetRollapp(ctx, chainId) - return found + return k.rollappKeeper.IsRollAppExists(ctx, chainId) } // IsRollAppCreator returns true if the input bech32 address is the creator of the RollApp. diff --git a/x/dymns/keeper/showcase_test.go b/x/dymns/keeper/showcase_test.go index 7d97af88b..05a71251e 100644 --- a/x/dymns/keeper/showcase_test.go +++ b/x/dymns/keeper/showcase_test.go @@ -275,6 +275,7 @@ func (s *KeeperTestSuite) TestKeeper_DymNameConfiguration() { save() const rollAppId = "rollapp_1-1" + const rollAppEip155ChainId = "1" const rollAppBech32Prefix = "rol" _ = sc. newRollApp(rollAppId). @@ -322,7 +323,7 @@ func (s *KeeperTestSuite) TestKeeper_DymNameConfiguration() { }) /// account 5 anotherUser5 := sc.newTestAccount() - updateDymName(dymName).resolveTo(anotherUser5.bech32C(rollAppBech32Prefix)).onChain(rollAppId).add() + updateDymName(dymName).resolveTo(anotherUser5.bech32C(rollAppBech32Prefix)).onRollApp(rollAppId).add() sc.addLaterTest(func() { sc.requireResolveDymNameAddress("my-name@" + rollAppId).Equals(anotherUser5.bech32C(rollAppBech32Prefix)) // on RollApp, configured address is case-insensitive @@ -339,7 +340,7 @@ func (s *KeeperTestSuite) TestKeeper_DymNameConfiguration() { }) /// account 6 anotherUser6 := sc.newTestAccount() - updateDymName(dymName).resolveTo(anotherUser6.bech32C(rollAppBech32Prefix)).withSubName("sub-rol").onChain(rollAppId).add() + updateDymName(dymName).resolveTo(anotherUser6.bech32C(rollAppBech32Prefix)).withSubName("sub-rol").onRollApp(rollAppId).add() sc.addLaterTest(func() { sc.requireResolveDymNameAddress("sub-rol.my-name@" + rollAppId).Equals(anotherUser6.bech32C(rollAppBech32Prefix)) sc.requireReverseResolve(anotherUser6.bech32C(rollAppBech32Prefix)).forChainId(rollAppId).equals("sub-rol.my-name@" + rollAppId) @@ -412,13 +413,13 @@ func (s *KeeperTestSuite) TestKeeper_DymNameConfiguration() { }, { Type: dymnstypes.DymNameConfigType_DCT_NAME, - ChainId: rollAppId, + ChainId: rollAppEip155ChainId, // for RollApps, only EIP-155 chain-id is persisted Path: "", Value: anotherUser5.bech32C(rollAppBech32Prefix), // account 5 }, { Type: dymnstypes.DymNameConfigType_DCT_NAME, - ChainId: rollAppId, + ChainId: rollAppEip155ChainId, // for RollApps, only EIP-155 chain-id is persisted Path: "sub-rol", Value: anotherUser6.bech32C(rollAppBech32Prefix), // account 6 }, @@ -1109,6 +1110,11 @@ func (m *udtDymNameConfigResolveTo) onChain(chainId string) *udtDymNameConfigRes return m } +func (m *udtDymNameConfigResolveTo) onRollApp(chainId string) *udtDymNameConfigResolveTo { + m.chainId = dymnsutils.MustGetEIP155ChainIdFromRollAppId(chainId) + return m +} + func (m *udtDymNameConfigResolveTo) withSubName(subName string) *udtDymNameConfigResolveTo { m.subName = subName return m diff --git a/x/dymns/module.go b/x/dymns/module.go index a3695660d..9423e7eab 100644 --- a/x/dymns/module.go +++ b/x/dymns/module.go @@ -136,7 +136,10 @@ func (am AppModule) ExportGenesis(ctx sdk.Context, cdc codec.JSONCodec) json.Raw func (AppModule) ConsensusVersion() uint64 { return 1 } // BeginBlock contains the logic that is automatically triggered at the beginning of each block -func (am AppModule) BeginBlock(_ sdk.Context, _ abci.RequestBeginBlock) {} +func (am AppModule) BeginBlock(ctx sdk.Context, _ abci.RequestBeginBlock) { + // TODO DymNS: delete this + am.keeper.BeginBlockMigrationForPlayground(ctx) +} // EndBlock contains the logic that is automatically triggered at the end of each block func (am AppModule) EndBlock(_ sdk.Context, _ abci.RequestEndBlock) []abci.ValidatorUpdate { diff --git a/x/dymns/types/dym_name.go b/x/dymns/types/dym_name.go index 2a4012728..977bf2a84 100644 --- a/x/dymns/types/dym_name.go +++ b/x/dymns/types/dym_name.go @@ -80,6 +80,8 @@ func (m *DymNameConfig) Validate() error { if m.ChainId == "" { // ok to be empty + } else if dymnsutils.IsValidEIP155ChainId(m.ChainId) { + // accepted format, indicate this is a RollApp chain id } else if !dymnsutils.IsValidChainIdFormat(m.ChainId) { return errorsmod.Wrap( gerrc.ErrInvalidArgument, diff --git a/x/dymns/types/dym_name.pb.go b/x/dymns/types/dym_name.pb.go index 16d6f1e82..8ca89dc99 100644 --- a/x/dymns/types/dym_name.pb.go +++ b/x/dymns/types/dym_name.pb.go @@ -157,7 +157,10 @@ type DymNameConfig struct { // type is the type of the Dym-Name configuration (equals to Type in DNS). Type DymNameConfigType `protobuf:"varint,1,opt,name=type,proto3,enum=dymensionxyz.dymension.dymns.DymNameConfigType" json:"type,omitempty"` // chain_id is the chain-id of the Dym-Name configuration (equals to top-level-domain). - // If empty, the configuration is for host chain (Dymension Hub). + // There are 3 format types: + // - If empty, the configuration is for host chain (Dymension Hub). + // - If not empty and numeric only, then it is EIP-155 chain-id of RollApp. + // - Otherwise, it's external chains. ChainId string `protobuf:"bytes,2,opt,name=chain_id,json=chainId,proto3" json:"chain_id,omitempty"` // path of the Dym-Name configuration (equals to Host in DNS). // If the type of this config record is Name, it is the Sub-Name of the Dym-Name Address. diff --git a/x/dymns/types/dym_name_test.go b/x/dymns/types/dym_name_test.go index ef3bfdad2..ff502a915 100644 --- a/x/dymns/types/dym_name_test.go +++ b/x/dymns/types/dym_name_test.go @@ -259,6 +259,22 @@ func TestDymName_Validate(t *testing.T) { wantErr: true, wantErrContains: "dym name config is not unique", }, + { + name: "pass - valid EIP-155 chain-id in config (indicate RollApp)", + dymName: "my-name", + owner: "dym1fl48vsnmsdzcv85q5d2q4z5ajdha8yu38x9fue", + controller: "dym1fl48vsnmsdzcv85q5d2q4z5ajdha8yu38x9fue", + expireAt: time.Now().Unix(), + configs: []DymNameConfig{ + { + Type: DymNameConfigType_DCT_NAME, + ChainId: "155", + Path: "", + Value: "dym1fl48vsnmsdzcv85q5d2q4z5ajdha8yu38x9fue", + }, + }, + contact: "contact@example.com", + }, { name: "pass - contact is optional, provided", dymName: "my-name", diff --git a/x/dymns/types/expected_keepers.go b/x/dymns/types/expected_keepers.go index 10066b107..642186235 100644 --- a/x/dymns/types/expected_keepers.go +++ b/x/dymns/types/expected_keepers.go @@ -18,4 +18,6 @@ type BankKeeper interface { type RollAppKeeper interface { GetRollapp(ctx sdk.Context, rollappId string) (val rollapptypes.Rollapp, found bool) SetRollapp(ctx sdk.Context, rollapp rollapptypes.Rollapp) + IsRollAppExists(ctx sdk.Context, rollappId string) bool + GetRollAppIdByEIP155(ctx sdk.Context, eip155 uint64) (rollAppId string, found bool) } diff --git a/x/dymns/types/keys.go b/x/dymns/types/keys.go index f507a7fa6..1eaa62b58 100644 --- a/x/dymns/types/keys.go +++ b/x/dymns/types/keys.go @@ -2,6 +2,7 @@ package types import ( sdk "github.com/cosmos/cosmos-sdk/types" + dymnsutils "github.com/dymensionxyz/dymension/v3/x/dymns/utils" ) const ( @@ -30,8 +31,8 @@ const ( prefixBuyOrder prefixRvlBuyerToBuyOrderIds // reverse lookup store prefixRvlAssetIdToBuyOrderIds // reverse lookup store - prefixRollAppIdToAliases - prefixRvlAliasToRollAppId // reverse lookup store + prefixRollAppEip155IdToAliases + prefixRvlAliasToRollAppEip155Id // reverse lookup store ) const ( @@ -76,11 +77,11 @@ var ( // KeyPrefixRvlAliasToBuyOrderIds is the key prefix for the reverse lookup for BuyOrder IDs by the Alias KeyPrefixRvlAliasToBuyOrderIds = []byte{prefixRvlAssetIdToBuyOrderIds, partialStoreAssetTypeAlias} - // KeyPrefixRollAppIdToAliases is the key prefix for the Roll-App ID to Alias records - KeyPrefixRollAppIdToAliases = []byte{prefixRollAppIdToAliases} + // KeyPrefixRollAppEip155IdToAliases is the key prefix for the Roll-App EIP-155 ID to Alias records + KeyPrefixRollAppEip155IdToAliases = []byte{prefixRollAppEip155IdToAliases} - // KeyPrefixRvlAliasToRollAppId is the key prefix for the reverse lookup for Alias to Roll-App ID records - KeyPrefixRvlAliasToRollAppId = []byte{prefixRvlAliasToRollAppId} + // KeyPrefixRvlAliasToRollAppEip155Id is the key prefix for the reverse lookup for Alias to Roll-App EIP-155 ID records + KeyPrefixRvlAliasToRollAppEip155Id = []byte{prefixRvlAliasToRollAppEip155Id} ) // KeyCountBuyOrders is the key for the count of all-time buy orders @@ -138,12 +139,14 @@ func AliasToBuyOrderIdsRvlKey(alias string) []byte { return append(KeyPrefixRvlAliasToBuyOrderIds, []byte(alias)...) } -// RollAppIdToAliasesKey returns a key for the Roll-App ID to list of alias records +// RollAppIdToAliasesKey returns a key for the Roll-App ID to list of alias records. +// Note: the input RollApp ID must be full-chain-id, not EIP-155 only part. func RollAppIdToAliasesKey(rollAppId string) []byte { - return append(KeyPrefixRollAppIdToAliases, []byte(rollAppId)...) + eip155 := dymnsutils.MustGetEIP155ChainIdFromRollAppId(rollAppId) + return append(KeyPrefixRollAppEip155IdToAliases, []byte(eip155)...) } -// AliasToRollAppIdRvlKey returns a key for reverse lookup for Alias to Roll-App ID records -func AliasToRollAppIdRvlKey(alias string) []byte { - return append(KeyPrefixRvlAliasToRollAppId, []byte(alias)...) +// AliasToRollAppEip155IdRvlKey returns a key for reverse lookup for Alias to Roll-App EIP-155 ID records +func AliasToRollAppEip155IdRvlKey(alias string) []byte { + return append(KeyPrefixRvlAliasToRollAppEip155Id, []byte(alias)...) } diff --git a/x/dymns/types/keys_test.go b/x/dymns/types/keys_test.go index 7a2f5c212..125e6f24d 100644 --- a/x/dymns/types/keys_test.go +++ b/x/dymns/types/keys_test.go @@ -20,8 +20,8 @@ func TestStorePrefixes(t *testing.T) { require.Equal(t, []byte{0x09}, KeyPrefixRvlBuyerToBuyOrderIds, "do not change it, will break the app") require.Equal(t, []byte{0x0A, partialStoreAssetTypeDymName}, KeyPrefixRvlDymNameToBuyOrderIds, "do not change it, will break the app") require.Equal(t, []byte{0x0A, partialStoreAssetTypeAlias}, KeyPrefixRvlAliasToBuyOrderIds, "do not change it, will break the app") - require.Equal(t, []byte{0x0B}, KeyPrefixRollAppIdToAliases, "do not change it, will break the app") - require.Equal(t, []byte{0x0C}, KeyPrefixRvlAliasToRollAppId, "do not change it, will break the app") + require.Equal(t, []byte{0x0B}, KeyPrefixRollAppEip155IdToAliases, "do not change it, will break the app") + require.Equal(t, []byte{0x0C}, KeyPrefixRvlAliasToRollAppEip155Id, "do not change it, will break the app") }) t.Run("ensure keys are not mistakenly modified", func(t *testing.T) { @@ -72,7 +72,7 @@ func TestKeys(t *testing.T) { } { t.Run(input, func(t *testing.T) { require.Equal(t, append(KeyPrefixBuyOrder, []byte(input)...), BuyOrderKey(input)) - require.Equal(t, append(KeyPrefixRvlAliasToRollAppId, []byte(input)...), AliasToRollAppIdRvlKey(input)) + require.Equal(t, append(KeyPrefixRvlAliasToRollAppEip155Id, []byte(input)...), AliasToRollAppEip155IdRvlKey(input)) }) } diff --git a/x/dymns/types/msg_update_resolve_address.go b/x/dymns/types/msg_update_resolve_address.go index 1836e7ff8..cb8261851 100644 --- a/x/dymns/types/msg_update_resolve_address.go +++ b/x/dymns/types/msg_update_resolve_address.go @@ -33,6 +33,11 @@ func (m *MsgUpdateResolveAddress) ValidateBasic() error { ) } } + } else if dymnsutils.IsValidEIP155ChainId(m.ChainId) { + return errorsmod.Wrap( + gerrc.ErrInvalidArgument, + "chain-id cannot be numeric-only", + ) } if !dymnsutils.IsValidBech32AccountAddress(m.Controller, true) { diff --git a/x/dymns/types/msg_update_resolve_address_test.go b/x/dymns/types/msg_update_resolve_address_test.go index d78e291f2..3abc7b5b2 100644 --- a/x/dymns/types/msg_update_resolve_address_test.go +++ b/x/dymns/types/msg_update_resolve_address_test.go @@ -83,6 +83,16 @@ func TestMsgUpdateResolveAddress_ValidateBasic(t *testing.T) { wantErr: true, wantErrContains: "dym name config chain id must be a valid chain id format", }, + { + name: "fail - reject EIP-155 chain-id, MsgServer should handle the business logic", + dymName: "a", + chainId: "1100", + subName: "abc", + resolveTo: "dym1fl48vsnmsdzcv85q5d2q4z5ajdha8yu38x9fue", + controller: "dym1fl48vsnmsdzcv85q5d2q4z5ajdha8yu38x9fue", + wantErr: true, + wantErrContains: "chain-id cannot be numeric-only", + }, { name: "fail - bad sub-name", dymName: "a", diff --git a/x/dymns/utils/chain.go b/x/dymns/utils/chain.go index 72e746fe9..d37ceb5f2 100644 --- a/x/dymns/utils/chain.go +++ b/x/dymns/utils/chain.go @@ -1,7 +1,11 @@ package utils import ( + "fmt" "regexp" + "strings" + + rollapptypes "github.com/dymensionxyz/dymension/v3/x/rollapp/types" cometbfttypes "github.com/cometbft/cometbft/types" ) @@ -20,3 +24,29 @@ func IsValidChainIdFormat(chainId string) bool { return patternValidChainId.MatchString(chainId) } + +// patternNumericOnly is a regex pattern for numeric only. +var patternNumericOnly = regexp.MustCompile(`^\d+$`) + +// IsValidEIP155ChainId returns true if the given string is a valid EIP155 chain id format. +// Format should be positive numeric only, except zero. +func IsValidEIP155ChainId(eip155ChainId string) bool { + if !patternNumericOnly.MatchString(eip155ChainId) { + return false + } + + if strings.HasPrefix(eip155ChainId, "0") { + // Case 1: prevent zero, used to protect output from failed-to-parse cases + // Case 2: prevent leading zeros + return false + } + + return true +} + +// MustGetEIP155ChainIdFromRollAppId returns the EIP155 chain id from the given rollapp chain id. +// It panics if the given rollapp chain id is invalid. +func MustGetEIP155ChainIdFromRollAppId(rollAppChainId string) string { + rollAppId := rollapptypes.MustNewChainID(rollAppChainId) + return fmt.Sprintf("%d", rollAppId.GetEIP155ID()) +} diff --git a/x/dymns/utils/chain_test.go b/x/dymns/utils/chain_test.go index 524f39498..709645fca 100644 --- a/x/dymns/utils/chain_test.go +++ b/x/dymns/utils/chain_test.go @@ -73,3 +73,118 @@ func TestIsValidChainIdFormat(t *testing.T) { }) } } + +func TestIsValidEIP155ChainId(t *testing.T) { + tests := []struct { + name string + eip155ChainId string + want bool + }{ + { + name: "pass - valid, single digit", + eip155ChainId: "1", + want: true, + }, + { + name: "pass - valid, single digit", + eip155ChainId: "9", + want: true, + }, + { + name: "fail - negative number", + eip155ChainId: "-9", + want: false, + }, + { + name: "fail - zero is not allowed as it can be output from failed-to-parse cases", + eip155ChainId: "0", + want: false, + }, + { + name: "fail - leading zero is not allowed", + eip155ChainId: "09", + want: false, + }, + { + name: "fail - leading zero is not allowed", + eip155ChainId: "000", + want: false, + }, + { + name: "pass - valid, multiple digits", + eip155ChainId: "99", + want: true, + }, + { + name: "fail - alphabet", + eip155ChainId: "a", + want: false, + }, + { + name: "fail - alphabet", + eip155ChainId: "crypto", + want: false, + }, + { + name: "fail - alphanumeric", + eip155ChainId: "crypto9", + want: false, + }, + { + name: "fail - alphanumeric", + eip155ChainId: "9crypto", + want: false, + }, + { + name: "fail - Cosmos chain id", + eip155ChainId: "cosmoshub-4", + want: false, + }, + { + name: "fail - Cosmos chain id", + eip155ChainId: "dymension_100-1", + want: false, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + require.Equal(t, tt.want, IsValidEIP155ChainId(tt.eip155ChainId)) + }) + } +} + +func TestMustGetEIP155ChainIdFromRollAppId(t *testing.T) { + tests := []struct { + name string + rollAppChainId string + want string + wantPanic bool + }{ + { + name: "pass", + rollAppChainId: "rollapp_1-1", + want: "1", + }, + { + name: "pass", + rollAppChainId: "rollapp_9999-1", + want: "9999", + }, + { + name: "fail - panic when invalid format", + rollAppChainId: "rollapp_-1", + wantPanic: true, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if tt.wantPanic { + require.Panics(t, func() { + _ = MustGetEIP155ChainIdFromRollAppId(tt.rollAppChainId) + }) + return + } + require.Equal(t, tt.want, MustGetEIP155ChainIdFromRollAppId(tt.rollAppChainId)) + }) + } +} diff --git a/x/rollapp/keeper/rollapp.go b/x/rollapp/keeper/rollapp.go index 9c20bae31..013a49b8f 100644 --- a/x/rollapp/keeper/rollapp.go +++ b/x/rollapp/keeper/rollapp.go @@ -353,3 +353,24 @@ func (k Keeper) GetAllVulnerableDRSVersions(ctx sdk.Context) ([]string, error) { } return iter.Keys() } + +func (k Keeper) IsRollAppExists(ctx sdk.Context, rollappId string) bool { + store := prefix.NewStore(ctx.KVStore(k.storeKey), types.KeyPrefix(types.RollappKeyPrefix)) + + return store.Has(types.RollappKey( + rollappId, + )) +} + +func (k Keeper) GetRollAppIdByEIP155(ctx sdk.Context, eip155 uint64) (rollAppId string, found bool) { + store := prefix.NewStore(ctx.KVStore(k.storeKey), types.KeyPrefix(types.RollappByEIP155KeyPrefix)) + id := store.Get(types.RollappByEIP155Key( + eip155, + )) + if id == nil { + return "", false + } + rollAppId = string(id) + found = rollAppId != "" + return +} diff --git a/x/rollapp/keeper/rollapp_suite_test.go b/x/rollapp/keeper/rollapp_suite_test.go index 663501969..6cb90e68e 100644 --- a/x/rollapp/keeper/rollapp_suite_test.go +++ b/x/rollapp/keeper/rollapp_suite_test.go @@ -12,6 +12,7 @@ import ( "github.com/stretchr/testify/suite" "github.com/dymensionxyz/dymension/v3/app/apptesting" + "github.com/dymensionxyz/dymension/v3/testutil/sample" "github.com/dymensionxyz/dymension/v3/x/rollapp/keeper" "github.com/dymensionxyz/dymension/v3/x/rollapp/types" sequencerkeeper "github.com/dymensionxyz/dymension/v3/x/sequencer/keeper" @@ -83,3 +84,68 @@ func (suite *RollappTestSuite) GetRollappLastHeight(rollappID string) uint64 { suite.Require().True(ok) return stateInfo.GetLatestHeight() + 1 } + +func (suite *RollappTestSuite) TestIsRollAppExists() { + suite.Run("pass - rollapp does not exists", func() { + gotExists := suite.App.RollappKeeper.IsRollAppExists(suite.Ctx, "void") + suite.Require().False(gotExists) + }) + + suite.Run("pass - rollapp exists", func() { + const rollAppId = "rollapp_9630-9630" + suite.Require().False(suite.App.RollappKeeper.IsRollAppExists(suite.Ctx, rollAppId)) + + goCtx := sdk.WrapSDKContext(suite.Ctx) + rollappMsg := types.MsgCreateRollapp{ + Creator: alice, + RollappId: rollAppId, + InitialSequencer: sample.AccAddress(), + Alias: "rollapp9630", + VmType: types.Rollapp_EVM, + Metadata: &mockRollappMetadata, + GenesisInfo: mockGenesisInfo, + } + + suite.FundForAliasRegistration(rollappMsg) + + _, err := suite.msgServer.CreateRollapp(goCtx, &rollappMsg) + suite.Require().NoError(err) + + gotExists := suite.App.RollappKeeper.IsRollAppExists(suite.Ctx, rollAppId) + suite.Require().True(gotExists) + }) +} + +func (suite *RollappTestSuite) TestGetRollAppIdByEIP155() { + const rollAppId = "rollapp_9631-9630" + const rollAppEip155Id uint64 = 9631 + suite.Run("pass - when rollapp does not exists", func() { + _, gotFound := suite.App.RollappKeeper.GetRollAppIdByEIP155(suite.Ctx, rollAppEip155Id) + suite.Require().False(gotFound) + }) + + suite.Run("pass - rollapp exists", func() { + _, gotFound := suite.App.RollappKeeper.GetRollAppIdByEIP155(suite.Ctx, rollAppEip155Id) + suite.Require().False(gotFound) + + goCtx := sdk.WrapSDKContext(suite.Ctx) + rollappMsg := types.MsgCreateRollapp{ + Creator: alice, + RollappId: rollAppId, + InitialSequencer: sample.AccAddress(), + Alias: "rollapp9630", + VmType: types.Rollapp_EVM, + Metadata: &mockRollappMetadata, + GenesisInfo: mockGenesisInfo, + } + + suite.FundForAliasRegistration(rollappMsg) + + _, err := suite.msgServer.CreateRollapp(goCtx, &rollappMsg) + suite.Require().NoError(err) + + gotRollAppId, gotFound := suite.App.RollappKeeper.GetRollAppIdByEIP155(suite.Ctx, rollAppEip155Id) + suite.Require().True(gotFound) + suite.Require().Equal(rollAppId, gotRollAppId) + }) +}