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  }