github.com/cosmos/cosmos-sdk@v0.50.10/x/auth/migrations/v2/store.go (about) 1 // Package v2 creates in-place store migrations for fixing tracking 2 // delegations with vesting accounts. 3 // ref: https://github.com/cosmos/cosmos-sdk/issues/8601 4 // ref: https://github.com/cosmos/cosmos-sdk/issues/8812 5 // 6 // The migration script modifies x/auth state, hence lives in the `x/auth/legacy` 7 // folder. However, it needs access to staking and bank state. To avoid 8 // cyclic dependencies, we cannot import those 2 keepers in this file. To solve 9 // this, we use the baseapp router to do inter-module querying, by importing 10 // the `baseapp.QueryRouter grpc.Server`. This is really hacky. 11 // 12 // PLEASE DO NOT REPLICATE THIS PATTERN IN YOUR OWN APP. 13 // 14 // Proposals to refactor this file have been made in: 15 // https://github.com/cosmos/cosmos-sdk/issues/9070 16 // The preferred solution is to use inter-module communication (ADR-033), and 17 // this file will be refactored to use ADR-033 once it's ready. 18 package v2 19 20 import ( 21 "errors" 22 "fmt" 23 24 abci "github.com/cometbft/cometbft/abci/types" 25 "github.com/cosmos/gogoproto/grpc" 26 "github.com/cosmos/gogoproto/proto" 27 "google.golang.org/grpc/codes" 28 "google.golang.org/grpc/status" 29 30 "github.com/cosmos/cosmos-sdk/baseapp" 31 sdk "github.com/cosmos/cosmos-sdk/types" 32 sdkerrors "github.com/cosmos/cosmos-sdk/types/errors" 33 "github.com/cosmos/cosmos-sdk/x/auth/vesting/exported" 34 vestingtypes "github.com/cosmos/cosmos-sdk/x/auth/vesting/types" 35 banktypes "github.com/cosmos/cosmos-sdk/x/bank/types" 36 stakingtypes "github.com/cosmos/cosmos-sdk/x/staking/types" 37 ) 38 39 const ( 40 delegatorDelegationPath = "/cosmos.staking.v1beta1.Query/DelegatorDelegations" 41 stakingParamsPath = "/cosmos.staking.v1beta1.Query/Params" 42 delegatorUnbondingDelegationsPath = "/cosmos.staking.v1beta1.Query/DelegatorUnbondingDelegations" 43 balancesPath = "/cosmos.bank.v1beta1.Query/AllBalances" 44 ) 45 46 // We use the baseapp.QueryRouter here to do inter-module state querying. 47 // PLEASE DO NOT REPLICATE THIS PATTERN IN YOUR OWN APP. 48 func migrateVestingAccounts(ctx sdk.Context, account sdk.AccountI, queryServer grpc.Server) (sdk.AccountI, error) { 49 bondDenom, err := getBondDenom(ctx, queryServer) 50 if err != nil { 51 return nil, err 52 } 53 54 asVesting, ok := account.(exported.VestingAccount) 55 if !ok { 56 return nil, nil 57 } 58 59 addr := account.GetAddress().String() 60 balance, err := getBalance( 61 ctx, 62 addr, 63 queryServer, 64 ) 65 if err != nil { 66 return nil, err 67 } 68 69 delegations, err := getDelegatorDelegationsSum( 70 ctx, 71 addr, 72 queryServer, 73 ) 74 if err != nil { 75 return nil, err 76 } 77 78 unbondingDelegations, err := getDelegatorUnbondingDelegationsSum( 79 ctx, 80 addr, 81 bondDenom, 82 queryServer, 83 ) 84 if err != nil { 85 return nil, err 86 } 87 88 delegations = delegations.Add(unbondingDelegations...) 89 90 asVesting, ok = resetVestingDelegatedBalances(asVesting) 91 if !ok { 92 return nil, nil 93 } 94 95 // balance before any delegation includes balance of delegation 96 for _, coin := range delegations { 97 balance = balance.Add(coin) 98 } 99 100 asVesting.TrackDelegation(ctx.BlockTime(), balance, delegations) 101 102 return asVesting.(sdk.AccountI), nil 103 } 104 105 func resetVestingDelegatedBalances(evacct exported.VestingAccount) (exported.VestingAccount, bool) { 106 // reset `DelegatedVesting` and `DelegatedFree` to zero 107 df := sdk.NewCoins() 108 dv := sdk.NewCoins() 109 110 switch vacct := evacct.(type) { 111 case *vestingtypes.ContinuousVestingAccount: 112 vacct.DelegatedVesting = dv 113 vacct.DelegatedFree = df 114 return vacct, true 115 case *vestingtypes.DelayedVestingAccount: 116 vacct.DelegatedVesting = dv 117 vacct.DelegatedFree = df 118 return vacct, true 119 case *vestingtypes.PeriodicVestingAccount: 120 vacct.DelegatedVesting = dv 121 vacct.DelegatedFree = df 122 return vacct, true 123 default: 124 return nil, false 125 } 126 } 127 128 // We use the baseapp.QueryRouter here to do inter-module state querying. 129 // PLEASE DO NOT REPLICATE THIS PATTERN IN YOUR OWN APP. 130 func getDelegatorDelegationsSum(ctx sdk.Context, address string, queryServer grpc.Server) (sdk.Coins, error) { 131 querier, ok := queryServer.(*baseapp.GRPCQueryRouter) 132 if !ok { 133 return nil, fmt.Errorf("unexpected type: %T wanted *baseapp.GRPCQueryRouter", queryServer) 134 } 135 136 queryFn := querier.Route(delegatorDelegationPath) 137 138 q := &stakingtypes.QueryDelegatorDelegationsRequest{ 139 DelegatorAddr: address, 140 } 141 142 b, err := proto.Marshal(q) 143 if err != nil { 144 return nil, fmt.Errorf("cannot marshal staking type query request, %w", err) 145 } 146 req := abci.RequestQuery{ 147 Data: b, 148 Path: delegatorDelegationPath, 149 } 150 resp, err := queryFn(ctx, &req) 151 if err != nil { 152 e, ok := status.FromError(err) 153 if ok && e.Code() == codes.NotFound { 154 return nil, nil 155 } 156 return nil, fmt.Errorf("staking query error, %w", err) 157 } 158 159 balance := new(stakingtypes.QueryDelegatorDelegationsResponse) 160 if err := proto.Unmarshal(resp.Value, balance); err != nil { 161 return nil, fmt.Errorf("unable to unmarshal delegator query delegations: %w", err) 162 } 163 164 res := sdk.NewCoins() 165 for _, i := range balance.DelegationResponses { 166 res = res.Add(i.Balance) 167 } 168 169 return res, nil 170 } 171 172 // We use the baseapp.QueryRouter here to do inter-module state querying. 173 // PLEASE DO NOT REPLICATE THIS PATTERN IN YOUR OWN APP. 174 func getDelegatorUnbondingDelegationsSum(ctx sdk.Context, address, bondDenom string, queryServer grpc.Server) (sdk.Coins, error) { 175 querier, ok := queryServer.(*baseapp.GRPCQueryRouter) 176 if !ok { 177 return nil, fmt.Errorf("unexpected type: %T wanted *baseapp.GRPCQueryRouter", queryServer) 178 } 179 180 queryFn := querier.Route(delegatorUnbondingDelegationsPath) 181 182 q := &stakingtypes.QueryDelegatorUnbondingDelegationsRequest{ 183 DelegatorAddr: address, 184 } 185 186 b, err := proto.Marshal(q) 187 if err != nil { 188 return nil, fmt.Errorf("cannot marshal staking type query request, %w", err) 189 } 190 req := abci.RequestQuery{ 191 Data: b, 192 Path: delegatorUnbondingDelegationsPath, 193 } 194 resp, err := queryFn(ctx, &req) 195 if err != nil && !errors.Is(err, sdkerrors.ErrNotFound) { 196 e, ok := status.FromError(err) 197 if ok && e.Code() == codes.NotFound { 198 return nil, nil 199 } 200 return nil, fmt.Errorf("staking query error, %w", err) 201 } 202 203 balance := new(stakingtypes.QueryDelegatorUnbondingDelegationsResponse) 204 if err := proto.Unmarshal(resp.Value, balance); err != nil { 205 return nil, fmt.Errorf("unable to unmarshal delegator query delegations: %w", err) 206 } 207 208 res := sdk.NewCoins() 209 for _, i := range balance.UnbondingResponses { 210 for _, r := range i.Entries { 211 res = res.Add(sdk.NewCoin(bondDenom, r.Balance)) 212 } 213 } 214 215 return res, nil 216 } 217 218 // We use the baseapp.QueryRouter here to do inter-module state querying. 219 // PLEASE DO NOT REPLICATE THIS PATTERN IN YOUR OWN APP. 220 func getBalance(ctx sdk.Context, address string, queryServer grpc.Server) (sdk.Coins, error) { 221 querier, ok := queryServer.(*baseapp.GRPCQueryRouter) 222 if !ok { 223 return nil, fmt.Errorf("unexpected type: %T wanted *baseapp.GRPCQueryRouter", queryServer) 224 } 225 226 queryFn := querier.Route(balancesPath) 227 228 q := &banktypes.QueryAllBalancesRequest{ 229 Address: address, 230 Pagination: nil, 231 } 232 b, err := proto.Marshal(q) 233 if err != nil { 234 return nil, fmt.Errorf("cannot marshal bank type query request, %w", err) 235 } 236 237 req := abci.RequestQuery{ 238 Data: b, 239 Path: balancesPath, 240 } 241 resp, err := queryFn(ctx, &req) 242 if err != nil { 243 return nil, fmt.Errorf("bank query error, %w", err) 244 } 245 balance := new(banktypes.QueryAllBalancesResponse) 246 if err := proto.Unmarshal(resp.Value, balance); err != nil { 247 return nil, fmt.Errorf("unable to unmarshal bank balance response: %w", err) 248 } 249 return balance.Balances, nil 250 } 251 252 // We use the baseapp.QueryRouter here to do inter-module state querying. 253 // PLEASE DO NOT REPLICATE THIS PATTERN IN YOUR OWN APP. 254 func getBondDenom(ctx sdk.Context, queryServer grpc.Server) (string, error) { 255 querier, ok := queryServer.(*baseapp.GRPCQueryRouter) 256 if !ok { 257 return "", fmt.Errorf("unexpected type: %T wanted *baseapp.GRPCQueryRouter", queryServer) 258 } 259 260 queryFn := querier.Route(stakingParamsPath) 261 262 q := &stakingtypes.QueryParamsRequest{} 263 264 b, err := proto.Marshal(q) 265 if err != nil { 266 return "", fmt.Errorf("cannot marshal staking params query request, %w", err) 267 } 268 req := abci.RequestQuery{ 269 Data: b, 270 Path: stakingParamsPath, 271 } 272 273 resp, err := queryFn(ctx, &req) 274 if err != nil { 275 return "", fmt.Errorf("staking query error, %w", err) 276 } 277 278 params := new(stakingtypes.QueryParamsResponse) 279 if err := proto.Unmarshal(resp.Value, params); err != nil { 280 return "", fmt.Errorf("unable to unmarshal delegator query delegations: %w", err) 281 } 282 283 return params.Params.BondDenom, nil 284 } 285 286 // MigrateAccount migrates vesting account to make the DelegatedVesting and DelegatedFree fields correctly 287 // track delegations. 288 // References: https://github.com/cosmos/cosmos-sdk/issues/8601, https://github.com/cosmos/cosmos-sdk/issues/8812 289 // 290 // We use the baseapp.QueryRouter here to do inter-module state querying. 291 // PLEASE DO NOT REPLICATE THIS PATTERN IN YOUR OWN APP. 292 func MigrateAccount(ctx sdk.Context, account sdk.AccountI, queryServer grpc.Server) (sdk.AccountI, error) { 293 return migrateVestingAccounts(ctx, account, queryServer) 294 }