github.com/Finschia/finschia-sdk@v0.49.1/x/auth/legacy/v043/store.go (about)

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