code.vegaprotocol.io/vega@v0.79.0/core/collateral/checkpoint.go (about)

     1  // Copyright (C) 2023 Gobalsky Labs Limited
     2  //
     3  // This program is free software: you can redistribute it and/or modify
     4  // it under the terms of the GNU Affero General Public License as
     5  // published by the Free Software Foundation, either version 3 of the
     6  // License, or (at your option) any later version.
     7  //
     8  // This program is distributed in the hope that it will be useful,
     9  // but WITHOUT ANY WARRANTY; without even the implied warranty of
    10  // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    11  // GNU Affero General Public License for more details.
    12  //
    13  // You should have received a copy of the GNU Affero General Public License
    14  // along with this program.  If not, see <http://www.gnu.org/licenses/>.
    15  
    16  package collateral
    17  
    18  import (
    19  	"context"
    20  	"sort"
    21  	"strings"
    22  
    23  	"code.vegaprotocol.io/vega/core/events"
    24  	"code.vegaprotocol.io/vega/core/types"
    25  	"code.vegaprotocol.io/vega/libs/num"
    26  	"code.vegaprotocol.io/vega/libs/proto"
    27  	"code.vegaprotocol.io/vega/logging"
    28  	checkpoint "code.vegaprotocol.io/vega/protos/vega/checkpoint/v1"
    29  )
    30  
    31  const (
    32  	separator            = "___"
    33  	vestingAccountPrefix = "vesting"
    34  )
    35  
    36  func (e *Engine) Name() types.CheckpointName {
    37  	return types.CollateralCheckpoint
    38  }
    39  
    40  func (e *Engine) Checkpoint() ([]byte, error) {
    41  	msg := &checkpoint.Collateral{
    42  		Balances: e.getCheckpointBalances(),
    43  	}
    44  	ret, err := proto.Marshal(msg)
    45  	if err != nil {
    46  		return nil, err
    47  	}
    48  	return ret, nil
    49  }
    50  
    51  var partyOverrideAlias = map[string]string{
    52  	systemOwner + types.AccountTypeNetworkTreasury.String(): systemOwner,
    53  }
    54  
    55  var partyOverrides = map[string]types.AccountType{
    56  	systemOwner: types.AccountTypeNetworkTreasury,
    57  	systemOwner + types.AccountTypeGlobalInsurance.String():        types.AccountTypeGlobalInsurance,
    58  	systemOwner + types.AccountTypeGlobalReward.String():           types.AccountTypeGlobalReward,
    59  	systemOwner + types.AccountTypeMakerReceivedFeeReward.String(): types.AccountTypeMakerReceivedFeeReward,
    60  	systemOwner + types.AccountTypeMakerPaidFeeReward.String():     types.AccountTypeMakerPaidFeeReward,
    61  	systemOwner + types.AccountTypeLPFeeReward.String():            types.AccountTypeLPFeeReward,
    62  	systemOwner + types.AccountTypeAverageNotionalReward.String():  types.AccountTypeAverageNotionalReward,
    63  	systemOwner + types.AccountTypeRelativeReturnReward.String():   types.AccountTypeRelativeReturnReward,
    64  	systemOwner + types.AccountTypeReturnVolatilityReward.String(): types.AccountTypeReturnVolatilityReward,
    65  	systemOwner + types.AccountTypeValidatorRankingReward.String(): types.AccountTypeValidatorRankingReward,
    66  	systemOwner + types.AccountTypeMarketProposerReward.String():   types.AccountTypeMarketProposerReward,
    67  	systemOwner + types.AccountTypeFeesInfrastructure.String():     types.AccountTypeFeesInfrastructure,
    68  	systemOwner + types.AccountTypePendingTransfers.String():       types.AccountTypePendingTransfers,
    69  	systemOwner + types.AccountTypeRealisedReturnReward.String():   types.AccountTypeRealisedReturnReward,
    70  	systemOwner + types.AccountTypeEligibleEntitiesReward.String(): types.AccountTypeEligibleEntitiesReward,
    71  }
    72  
    73  var tradingRewardAccountTypes = map[types.AccountType]struct{}{
    74  	types.AccountTypeMakerReceivedFeeReward: {},
    75  	types.AccountTypeMakerPaidFeeReward:     {},
    76  	types.AccountTypeLPFeeReward:            {},
    77  	types.AccountTypeMarketProposerReward:   {},
    78  	types.AccountTypeAverageNotionalReward:  {},
    79  	types.AccountTypeRelativeReturnReward:   {},
    80  	types.AccountTypeReturnVolatilityReward: {},
    81  	types.AccountTypeValidatorRankingReward: {},
    82  	types.AccountTypeRealisedReturnReward:   {},
    83  	types.AccountTypeEligibleEntitiesReward: {},
    84  }
    85  
    86  func (e *Engine) Load(ctx context.Context, data []byte) error {
    87  	msg := checkpoint.Collateral{}
    88  	if err := proto.Unmarshal(data, &msg); err != nil {
    89  		return err
    90  	}
    91  
    92  	ledgerMovements := []*types.LedgerMovement{}
    93  	assets := map[string]struct{}{}
    94  
    95  	for _, balance := range msg.Balances {
    96  		ub, _ := num.UintFromString(balance.Balance, 10)
    97  		isVesting := strings.HasPrefix(balance.Party, vestingAccountPrefix)
    98  		if isVesting {
    99  			balance.Party = strings.TrimPrefix(balance.Party, vestingAccountPrefix)
   100  		}
   101  		partyComponents := strings.Split(balance.Party, separator)
   102  		owner := partyComponents[0]
   103  		market := noMarket
   104  		if len(partyComponents) > 1 {
   105  			market = partyComponents[1]
   106  		}
   107  
   108  		if alias, aliasExists := partyOverrideAlias[owner]; aliasExists {
   109  			owner = alias
   110  		}
   111  
   112  		// for backward compatibility check both - after this is already out checkpoints will always have the type for global accounts
   113  		if tp, ok := partyOverrides[owner]; ok {
   114  			accID := e.accountID(market, systemOwner, balance.Asset, tp)
   115  			if _, ok := tradingRewardAccountTypes[tp]; ok {
   116  				e.GetOrCreateRewardAccount(ctx, balance.Asset, market, tp)
   117  			}
   118  			_, err := e.GetAccountByID(accID)
   119  			if err != nil {
   120  				return err
   121  			}
   122  			lm, err := e.RestoreCheckpointBalance(
   123  				ctx, market, systemOwner, balance.Asset, tp, ub.Clone())
   124  			if err != nil {
   125  				return err
   126  			}
   127  			assets[balance.Asset] = struct{}{}
   128  			ledgerMovements = append(ledgerMovements, lm)
   129  			continue
   130  		}
   131  		var (
   132  			lm  *types.LedgerMovement
   133  			err error
   134  		)
   135  
   136  		if isVesting {
   137  			accID := e.accountID(market, balance.Party, balance.Asset, types.AccountTypeVestingRewards)
   138  			if _, err := e.GetAccountByID(accID); err != nil {
   139  				_ = e.GetOrCreatePartyVestingRewardAccount(ctx, balance.Party, balance.Asset)
   140  			}
   141  			lm, err = e.RestoreCheckpointBalance(
   142  				ctx, noMarket, balance.Party, balance.Asset, types.AccountTypeVestingRewards, ub.Clone())
   143  			if err != nil {
   144  				return err
   145  			}
   146  
   147  			e.addToVesting(balance.Party, balance.Asset, ub.Clone())
   148  		} else {
   149  			accID := e.accountID(market, balance.Party, balance.Asset, types.AccountTypeGeneral)
   150  			if _, err := e.GetAccountByID(accID); err != nil {
   151  				_, _ = e.CreatePartyGeneralAccount(ctx, balance.Party, balance.Asset)
   152  			}
   153  			lm, err = e.RestoreCheckpointBalance(
   154  				ctx, noMarket, balance.Party, balance.Asset, types.AccountTypeGeneral, ub.Clone())
   155  			if err != nil {
   156  				return err
   157  			}
   158  		}
   159  		ledgerMovements = append(ledgerMovements, lm)
   160  	}
   161  
   162  	if len(ledgerMovements) > 0 {
   163  		e.broker.Send(events.NewLedgerMovements(ctx, ledgerMovements))
   164  	}
   165  	return nil
   166  }
   167  
   168  // get all balances for checkpoint.
   169  func (e *Engine) getCheckpointBalances() []*checkpoint.AssetBalance {
   170  	// party -> asset -> balance
   171  	balances := make(map[string]map[string]*num.Uint, len(e.accs))
   172  	for _, acc := range e.accs {
   173  		if acc.Balance.IsZero() {
   174  			continue
   175  		}
   176  		switch acc.Type {
   177  		// vesting rewards needs to be stored separately
   178  		// so that vesting can be started again
   179  		case types.AccountTypeVestingRewards:
   180  			owner := vestingAccountPrefix + acc.Owner
   181  
   182  			assets, ok := balances[owner]
   183  			if !ok {
   184  				assets = map[string]*num.Uint{}
   185  				balances[owner] = assets
   186  			}
   187  			balance, ok := assets[acc.Asset]
   188  			if !ok {
   189  				balance = num.UintZero()
   190  				assets[acc.Asset] = balance
   191  			}
   192  			balance.AddSum(acc.Balance)
   193  
   194  		case types.AccountTypeMargin, types.AccountTypeOrderMargin, types.AccountTypeGeneral, types.AccountTypeHolding, types.AccountTypeBond, types.AccountTypeFeesLiquidity,
   195  			types.AccountTypeInsurance, types.AccountTypeGlobalReward, types.AccountTypeLiquidityFeesBonusDistribution, types.AccountTypeLPLiquidityFees,
   196  			types.AccountTypeLPFeeReward, types.AccountTypeMakerReceivedFeeReward, types.AccountTypeMakerPaidFeeReward,
   197  			types.AccountTypeMarketProposerReward, types.AccountTypeFeesInfrastructure, types.AccountTypePendingTransfers,
   198  			types.AccountTypeNetworkTreasury, types.AccountTypeGlobalInsurance, types.AccountTypeVestedRewards,
   199  			types.AccountTypeAverageNotionalReward, types.AccountTypeRelativeReturnReward, types.AccountTypeRealisedReturnReward,
   200  			types.AccountTypeReturnVolatilityReward, types.AccountTypeValidatorRankingReward, types.AccountTypeEligibleEntitiesReward:
   201  			owner := acc.Owner
   202  			// NB: market insurance accounts funds will flow implicitly using this logic into the network treasury for the asset
   203  			// similarly LP Fee bonus distribution bonus account would fall over into the network treasury of the asset.
   204  			if owner == systemOwner {
   205  				for k, v := range partyOverrides {
   206  					if acc.Type == v {
   207  						owner = k
   208  					}
   209  				}
   210  				if acc.Type == types.AccountTypeInsurance {
   211  					// let the market insurnace fall into the global insurance account
   212  					owner = systemOwner + types.AccountTypeGlobalInsurance.String()
   213  				}
   214  			}
   215  
   216  			// NB: for market based reward accounts we don't want to move the funds to the network treasury but rather keep them
   217  			if acc.Type == types.AccountTypeLPFeeReward ||
   218  				acc.Type == types.AccountTypeMakerReceivedFeeReward ||
   219  				acc.Type == types.AccountTypeMakerPaidFeeReward ||
   220  				acc.Type == types.AccountTypeMarketProposerReward ||
   221  				acc.Type == types.AccountTypeAverageNotionalReward ||
   222  				acc.Type == types.AccountTypeRelativeReturnReward ||
   223  				acc.Type == types.AccountTypeReturnVolatilityReward ||
   224  				acc.Type == types.AccountTypeValidatorRankingReward ||
   225  				acc.Type == types.AccountTypeRealisedReturnReward ||
   226  				acc.Type == types.AccountTypeEligibleEntitiesReward {
   227  				owner += separator + acc.MarketID
   228  			}
   229  
   230  			assets, ok := balances[owner]
   231  			if !ok {
   232  				assets = map[string]*num.Uint{}
   233  				balances[owner] = assets
   234  			}
   235  			balance, ok := assets[acc.Asset]
   236  			if !ok {
   237  				balance = num.UintZero()
   238  				assets[acc.Asset] = balance
   239  			}
   240  			balance.AddSum(acc.Balance)
   241  		case types.AccountTypeSettlement:
   242  			if !acc.Balance.IsZero() {
   243  				e.log.Panic("Settlement balance is not zero",
   244  					logging.String("market-id", acc.MarketID))
   245  			}
   246  		}
   247  	}
   248  
   249  	out := make([]*checkpoint.AssetBalance, 0, len(balances))
   250  	for owner, assets := range balances {
   251  		for asset, balance := range assets {
   252  			out = append(out, &checkpoint.AssetBalance{
   253  				Party:   owner,
   254  				Asset:   asset,
   255  				Balance: balance.String(),
   256  			})
   257  		}
   258  	}
   259  
   260  	sort.Slice(out, func(i, j int) bool {
   261  		switch strings.Compare(out[i].Party, out[j].Party) {
   262  		case -1:
   263  			return true
   264  		case 1:
   265  			return false
   266  		}
   267  		return out[i].Asset < out[j].Asset
   268  	})
   269  	return out
   270  }