code.vegaprotocol.io/vega@v0.79.0/core/banking/gov_transfers.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 banking
    17  
    18  import (
    19  	"context"
    20  	"encoding/hex"
    21  	"errors"
    22  	"fmt"
    23  	"time"
    24  
    25  	"code.vegaprotocol.io/vega/core/events"
    26  	"code.vegaprotocol.io/vega/core/types"
    27  	"code.vegaprotocol.io/vega/libs/crypto"
    28  	"code.vegaprotocol.io/vega/libs/num"
    29  	"code.vegaprotocol.io/vega/libs/proto"
    30  	"code.vegaprotocol.io/vega/logging"
    31  	vegapb "code.vegaprotocol.io/vega/protos/vega"
    32  )
    33  
    34  var (
    35  	validSources = map[types.AccountType]struct{}{
    36  		types.AccountTypeInsurance:       {},
    37  		types.AccountTypeGlobalInsurance: {},
    38  		types.AccountTypeGlobalReward:    {},
    39  		types.AccountTypeNetworkTreasury: {},
    40  	}
    41  	validDestinations = map[types.AccountType]struct{}{
    42  		types.AccountTypeInsurance:              {},
    43  		types.AccountTypeGlobalInsurance:        {},
    44  		types.AccountTypeGlobalReward:           {},
    45  		types.AccountTypeNetworkTreasury:        {},
    46  		types.AccountTypeGeneral:                {},
    47  		types.AccountTypeMakerPaidFeeReward:     {},
    48  		types.AccountTypeMakerReceivedFeeReward: {},
    49  		types.AccountTypeMarketProposerReward:   {},
    50  		types.AccountTypeLPFeeReward:            {},
    51  		types.AccountTypeAverageNotionalReward:  {},
    52  		types.AccountTypeRelativeReturnReward:   {},
    53  		types.AccountTypeReturnVolatilityReward: {},
    54  		types.AccountTypeValidatorRankingReward: {},
    55  		types.AccountTypeRealisedReturnReward:   {},
    56  		types.AccountTypeEligibleEntitiesReward: {},
    57  	}
    58  )
    59  
    60  func (e *Engine) distributeScheduledGovernanceTransfers(ctx context.Context, now time.Time) {
    61  	timepoints := []int64{}
    62  	for k := range e.scheduledGovernanceTransfers {
    63  		if now.UnixNano() >= k {
    64  			timepoints = append(timepoints, k)
    65  		}
    66  	}
    67  
    68  	for _, t := range timepoints {
    69  		transfers := e.scheduledGovernanceTransfers[t]
    70  		for _, gTransfer := range transfers {
    71  			_, err := e.processGovernanceTransfer(ctx, gTransfer)
    72  			if err != nil {
    73  				gTransfer.Status = types.TransferStatusStopped
    74  				e.broker.Send(events.NewGovTransferFundsEventWithReason(ctx, gTransfer, gTransfer.Config.MaxAmount, err.Error(), e.getGovGameID(gTransfer)))
    75  			} else {
    76  				gTransfer.Status = types.TransferStatusDone
    77  				e.broker.Send(events.NewGovTransferFundsEvent(ctx, gTransfer, gTransfer.Config.MaxAmount, e.getGovGameID(gTransfer)))
    78  			}
    79  		}
    80  		delete(e.scheduledGovernanceTransfers, t)
    81  	}
    82  }
    83  
    84  func (e *Engine) distributeRecurringGovernanceTransfers(ctx context.Context) {
    85  	var (
    86  		transfersDone = []events.Event{}
    87  		doneIDs       = []string{}
    88  	)
    89  
    90  	for _, gTransfer := range e.recurringGovernanceTransfers {
    91  		e.log.Info("distributeRecurringGovernanceTransfers", logging.Uint64("epoch", e.currentEpoch), logging.String("transfer", gTransfer.IntoProto().String()))
    92  		if gTransfer.Config.RecurringTransferConfig.StartEpoch > e.currentEpoch {
    93  			continue
    94  		}
    95  
    96  		if gTransfer.Config.RecurringTransferConfig.DispatchStrategy != nil && gTransfer.Config.RecurringTransferConfig.DispatchStrategy.TransferInterval != nil &&
    97  			((e.currentEpoch-gTransfer.Config.RecurringTransferConfig.StartEpoch+1) < uint64(*gTransfer.Config.RecurringTransferConfig.DispatchStrategy.TransferInterval) ||
    98  				(e.currentEpoch-gTransfer.Config.RecurringTransferConfig.StartEpoch+1)%uint64(*gTransfer.Config.RecurringTransferConfig.DispatchStrategy.TransferInterval) != 0) {
    99  			continue
   100  		}
   101  
   102  		amount, err := e.processGovernanceTransfer(ctx, gTransfer)
   103  		amount = e.scaleAmountByTargetNotional(gTransfer.Config.RecurringTransferConfig.DispatchStrategy, amount)
   104  		e.log.Info("processed transfer", logging.String("amount", amount.String()))
   105  
   106  		if err != nil {
   107  			e.log.Error("error calculating transfer amount for governance transfer", logging.Error(err))
   108  			gTransfer.Status = types.TransferStatusStopped
   109  			transfersDone = append(transfersDone, events.NewGovTransferFundsEventWithReason(ctx, gTransfer, gTransfer.Config.MaxAmount, err.Error(), e.getGovGameID(gTransfer)))
   110  			doneIDs = append(doneIDs, gTransfer.ID)
   111  			continue
   112  		}
   113  
   114  		if gTransfer.Config.RecurringTransferConfig.EndEpoch != nil && *gTransfer.Config.RecurringTransferConfig.EndEpoch == e.currentEpoch {
   115  			gTransfer.Status = types.TransferStatusDone
   116  			transfersDone = append(transfersDone, events.NewGovTransferFundsEvent(ctx, gTransfer, gTransfer.Config.MaxAmount, e.getGovGameID(gTransfer)))
   117  			doneIDs = append(doneIDs, gTransfer.ID)
   118  			e.log.Info("recurrent transfer is done", logging.String("transfer ID", gTransfer.ID))
   119  			continue
   120  		}
   121  	}
   122  
   123  	if len(transfersDone) > 0 {
   124  		for _, id := range doneIDs {
   125  			e.deleteGovTransfer(id)
   126  		}
   127  		for _, d := range transfersDone {
   128  			e.log.Info("transfersDone", logging.String("event", d.StreamMessage().String()))
   129  		}
   130  
   131  		e.broker.SendBatch(transfersDone)
   132  	}
   133  }
   134  
   135  func (e *Engine) deleteGovTransfer(ID string) {
   136  	index := -1
   137  	for i, rt := range e.recurringGovernanceTransfers {
   138  		if rt.ID == ID {
   139  			index = i
   140  			e.unregisterDispatchStrategy(rt.Config.RecurringTransferConfig.DispatchStrategy)
   141  			break
   142  		}
   143  	}
   144  	if index >= 0 {
   145  		e.recurringGovernanceTransfers = append(e.recurringGovernanceTransfers[:index], e.recurringGovernanceTransfers[index+1:]...)
   146  		delete(e.recurringGovernanceTransfersMap, ID)
   147  	}
   148  }
   149  
   150  func (e *Engine) NewGovernanceTransfer(ctx context.Context, ID, reference string, config *types.NewTransferConfiguration) error {
   151  	var err error
   152  	var gTransfer *types.GovernanceTransfer
   153  
   154  	defer func() {
   155  		if err != nil {
   156  			e.broker.Send(events.NewGovTransferFundsEventWithReason(ctx, gTransfer, gTransfer.Config.MaxAmount, err.Error(), e.getGovGameID(gTransfer)))
   157  		} else {
   158  			e.broker.Send(events.NewGovTransferFundsEvent(ctx, gTransfer, gTransfer.Config.MaxAmount, e.getGovGameID(gTransfer)))
   159  		}
   160  	}()
   161  	now := e.timeService.GetTimeNow()
   162  	gTransfer = &types.GovernanceTransfer{
   163  		ID:        ID,
   164  		Reference: reference,
   165  		Config:    config,
   166  		Status:    types.TransferStatusPending,
   167  		Timestamp: now,
   168  	}
   169  	if config.Kind == types.TransferKindOneOff {
   170  		// one off governance transfer to be executed straight away
   171  		if config.OneOffTransferConfig.DeliverOn == 0 || config.OneOffTransferConfig.DeliverOn < now.UnixNano() {
   172  			_, err = e.processGovernanceTransfer(ctx, gTransfer)
   173  			if err != nil {
   174  				gTransfer.Status = types.TransferStatusRejected
   175  				return err
   176  			}
   177  			gTransfer.Status = types.TransferStatusDone
   178  			return nil
   179  		}
   180  		// scheduled one off governance transfer
   181  		if _, ok := e.scheduledGovernanceTransfers[config.OneOffTransferConfig.DeliverOn]; !ok {
   182  			e.scheduledGovernanceTransfers[config.OneOffTransferConfig.DeliverOn] = []*types.GovernanceTransfer{}
   183  		}
   184  		e.scheduledGovernanceTransfers[config.OneOffTransferConfig.DeliverOn] = append(e.scheduledGovernanceTransfers[config.OneOffTransferConfig.DeliverOn], gTransfer)
   185  		gTransfer.Status = types.TransferStatusPending
   186  		return nil
   187  	}
   188  	// recurring governance transfer
   189  	e.recurringGovernanceTransfers = append(e.recurringGovernanceTransfers, gTransfer)
   190  	e.recurringGovernanceTransfersMap[ID] = gTransfer
   191  	e.registerDispatchStrategy(gTransfer.Config.RecurringTransferConfig.DispatchStrategy)
   192  	return nil
   193  }
   194  
   195  func CalculateDecayedAmount(initialAmount *num.Uint, startEpoch, currentEpoch uint64, decayFactor string) *num.Uint {
   196  	if len(decayFactor) == 0 {
   197  		return initialAmount
   198  	}
   199  	factor := num.MustDecimalFromString(decayFactor)
   200  	amount, _ := num.UintFromDecimal(initialAmount.ToDecimal().Mul(factor.Pow(num.NewUint(currentEpoch).ToDecimal().Sub(num.NewUint(startEpoch).ToDecimal()))))
   201  	return amount
   202  }
   203  
   204  // processGovernanceTransfer process a governance transfer and emit ledger movement events.
   205  func (e *Engine) processGovernanceTransfer(
   206  	ctx context.Context,
   207  	gTransfer *types.GovernanceTransfer,
   208  ) (*num.Uint, error) {
   209  	amount := gTransfer.Config.MaxAmount
   210  	if gTransfer.Config.RecurringTransferConfig != nil {
   211  		amount = CalculateDecayedAmount(amount, gTransfer.Config.RecurringTransferConfig.StartEpoch, e.currentEpoch, gTransfer.Config.RecurringTransferConfig.Factor)
   212  	}
   213  
   214  	transferAmount, err := e.CalculateGovernanceTransferAmount(gTransfer.Config.Asset, gTransfer.Config.Source, gTransfer.Config.SourceType, gTransfer.Config.FractionOfBalance, amount, gTransfer.Config.TransferType)
   215  	if err != nil {
   216  		e.log.Error("failed to calculate amount for governance transfer", logging.String("proposal", gTransfer.ID), logging.String("error", err.Error()))
   217  		return num.UintZero(), err
   218  	}
   219  
   220  	from := "*"
   221  	fromMarket := gTransfer.Config.Source
   222  
   223  	toMarket := ""
   224  	to := gTransfer.Config.Destination
   225  	if gTransfer.Config.DestinationType == types.AccountTypeGlobalReward {
   226  		to = "*"
   227  	} else if gTransfer.Config.DestinationType == types.AccountTypeInsurance {
   228  		toMarket = to
   229  		to = "*"
   230  	}
   231  
   232  	if gTransfer.Config.RecurringTransferConfig != nil && gTransfer.Config.RecurringTransferConfig.DispatchStrategy != nil {
   233  		var resps []*types.LedgerMovement
   234  		ds := gTransfer.Config.RecurringTransferConfig.DispatchStrategy
   235  		// if the metric is market value we make the transfer to the market account (as opposed to the metric's hash account)
   236  		if ds.Metric == vegapb.DispatchMetric_DISPATCH_METRIC_MARKET_VALUE {
   237  			marketProposersScore := e.marketActivityTracker.GetMarketsWithEligibleProposer(ds.AssetForMetric, ds.Markets, gTransfer.Config.Asset, gTransfer.Config.Source, ds.EligibleKeys)
   238  			for _, fms := range marketProposersScore {
   239  				amt, _ := num.UintFromDecimal(transferAmount.ToDecimal().Mul(fms.Score))
   240  				if amt.IsZero() {
   241  					continue
   242  				}
   243  				fromTransfer, toTransfer := e.makeTransfers(from, to, gTransfer.Config.Asset, fromMarket, fms.Market, amt, &gTransfer.ID)
   244  				transfers := []*types.Transfer{fromTransfer, toTransfer}
   245  				accountTypes := []types.AccountType{gTransfer.Config.SourceType, gTransfer.Config.DestinationType}
   246  				references := []string{gTransfer.Reference, gTransfer.Reference}
   247  				tresps, err := e.col.GovernanceTransferFunds(ctx, transfers, accountTypes, references)
   248  				if err != nil {
   249  					e.log.Error("error transferring governance transfer funds", logging.Error(err))
   250  					return num.UintZero(), err
   251  				}
   252  
   253  				if fms.Score.IsPositive() {
   254  					e.marketActivityTracker.MarkPaidProposer(ds.AssetForMetric, fms.Market, gTransfer.Config.Asset, gTransfer.Config.RecurringTransferConfig.DispatchStrategy.Markets, from)
   255  				}
   256  				resps = append(resps, tresps...)
   257  			}
   258  		}
   259  		// here we transfer the governance transfer amount into the account: transfer_asset/dispatch_hash/reward_account_type
   260  		if e.dispatchRequired(ctx, gTransfer.Config.RecurringTransferConfig.DispatchStrategy) {
   261  			p, _ := proto.Marshal(gTransfer.Config.RecurringTransferConfig.DispatchStrategy)
   262  			hash := hex.EncodeToString(crypto.Hash(p))
   263  
   264  			fromTransfer, toTransfer := e.makeTransfers(from, to, gTransfer.Config.Asset, fromMarket, hash, transferAmount, &gTransfer.ID)
   265  			transfers := []*types.Transfer{fromTransfer, toTransfer}
   266  			accountTypes := []types.AccountType{gTransfer.Config.SourceType, gTransfer.Config.DestinationType}
   267  			references := []string{gTransfer.Reference, gTransfer.Reference}
   268  			tresps, err := e.col.GovernanceTransferFunds(ctx, transfers, accountTypes, references)
   269  			if err != nil {
   270  				e.log.Error("error transferring governance transfer funds", logging.Error(err))
   271  				return num.UintZero(), err
   272  			}
   273  
   274  			resps = append(resps, tresps...)
   275  		}
   276  		if len(resps) > 0 {
   277  			e.broker.Send(events.NewLedgerMovements(ctx, resps))
   278  			return transferAmount, nil
   279  		}
   280  
   281  		return num.UintZero(), nil
   282  	}
   283  
   284  	fromTransfer, toTransfer := e.makeTransfers(from, to, gTransfer.Config.Asset, fromMarket, toMarket, transferAmount, &gTransfer.ID)
   285  	transfers := []*types.Transfer{fromTransfer, toTransfer}
   286  	accountTypes := []types.AccountType{gTransfer.Config.SourceType, gTransfer.Config.DestinationType}
   287  	references := []string{gTransfer.Reference, gTransfer.Reference}
   288  	tresps, err := e.col.GovernanceTransferFunds(ctx, transfers, accountTypes, references)
   289  	if err != nil {
   290  		e.log.Error("error transferring governance transfer funds", logging.Error(err))
   291  		return num.UintZero(), err
   292  	}
   293  
   294  	for _, lm := range tresps {
   295  		e.log.Info("processGovernanceTransfer", logging.String("ledger-movement", lm.IntoProto().String()))
   296  	}
   297  
   298  	e.broker.Send(events.NewLedgerMovements(ctx, tresps))
   299  	return transferAmount, nil
   300  }
   301  
   302  // CalculateGovernanceTransferAmount calculates the balance of a governance transfer as follows:
   303  //
   304  // transfer_amount = min(
   305  //
   306  //	proposal.fraction_of_balance * source.balance,
   307  //	proposal.amount,
   308  //	NETWORK_MAX_AMOUNT,
   309  //	NETWORK_MAX_FRACTION * source.balance
   310  //
   311  // )
   312  // where
   313  // NETWORK_MAX_AMOUNT is a network parameter specifying the maximum absolute amount that can be transferred by governance for the source account type
   314  // NETWORK_MAX_FRACTION is a network parameter specifying the maximum fraction of the balance that can be transferred by governance for the source account type (must be <= 1)
   315  //
   316  // If type is "all or nothing" then the transfer will only proceed if:
   317  //
   318  //	transfer_amount == min(proposal.fraction_of_balance * source.balance,proposal.amount).
   319  func (e *Engine) CalculateGovernanceTransferAmount(asset string, market string, accountType types.AccountType, fraction num.Decimal, amount *num.Uint, transferType vegapb.GovernanceTransferType) (*num.Uint, error) {
   320  	balance, err := e.col.GetSystemAccountBalance(asset, market, accountType)
   321  	if err != nil {
   322  		e.log.Error("could not find system account balance for", logging.String("asset", asset), logging.String("market", market), logging.String("account-type", accountType.String()))
   323  		return nil, err
   324  	}
   325  
   326  	a, err := e.assets.Get(asset)
   327  	if err != nil {
   328  		e.log.Debug("cannot transfer funds, invalid asset", logging.Error(err))
   329  		return nil, fmt.Errorf("could not transfer funds, %w", err)
   330  	}
   331  
   332  	quantum := a.Type().Details.Quantum
   333  	globalMaxAmount, _ := num.UintFromDecimal(quantum.Mul(e.maxGovTransferQunatumMultiplier))
   334  	amountFromMaxFraction, _ := num.UintFromDecimal(e.maxGovTransferFraction.Mul(balance.ToDecimal()))
   335  	amountFromProposalFraction, _ := num.UintFromDecimal(fraction.Mul(balance.ToDecimal()))
   336  	min1 := num.Min(amountFromMaxFraction, amountFromProposalFraction)
   337  	min2 := num.Min(amount, globalMaxAmount)
   338  	amt := num.Min(min1, min2)
   339  
   340  	if transferType == vegapb.GovernanceTransferType_GOVERNANCE_TRANSFER_TYPE_ALL_OR_NOTHING && amt.NEQ(num.Min(amountFromProposalFraction, amount)) {
   341  		e.log.Error("could not process governance transfer with type all of nothing", logging.String("transfer-amount", amt.String()), logging.String("fraction-of-balance", amountFromProposalFraction.String()), logging.String("amount", amount.String()))
   342  		return nil, errors.New("invalid transfer amount for transfer type all or nothing")
   343  	}
   344  
   345  	return amt, nil
   346  }
   347  
   348  func (e *Engine) VerifyGovernanceTransfer(transfer *types.NewTransferConfiguration) error {
   349  	if transfer == nil {
   350  		return errors.New("missing transfer configuration")
   351  	}
   352  
   353  	// check source type is valid
   354  	if _, ok := validSources[transfer.SourceType]; !ok {
   355  		return errors.New("invalid source type for governance transfer")
   356  	}
   357  
   358  	// check destination type is valid
   359  	if _, ok := validDestinations[transfer.DestinationType]; !ok {
   360  		return errors.New("invalid destination for governance transfer")
   361  	}
   362  
   363  	// check asset is not empty
   364  	if len(transfer.Asset) == 0 {
   365  		return errors.New("missing asset for governance transfer")
   366  	}
   367  
   368  	// check if destination market insurance account exist
   369  	if transfer.DestinationType == types.AccountTypeInsurance && len(transfer.Destination) > 0 {
   370  		_, err := e.col.GetSystemAccountBalance(transfer.Asset, transfer.Destination, transfer.DestinationType)
   371  		if err != nil {
   372  			return err
   373  		}
   374  	}
   375  
   376  	// verify systemn destination account which ought to preexist actually exists
   377  	if (transfer.RecurringTransferConfig == nil || transfer.RecurringTransferConfig.DispatchStrategy == nil) &&
   378  		len(transfer.Destination) == 0 &&
   379  		transfer.DestinationType != types.AccountTypeGeneral {
   380  		_, err := e.col.GetSystemAccountBalance(transfer.Asset, transfer.Destination, transfer.DestinationType)
   381  		if err != nil {
   382  			return err
   383  		}
   384  	}
   385  
   386  	if transfer.RecurringTransferConfig != nil && transfer.RecurringTransferConfig.DispatchStrategy != nil {
   387  		if len(transfer.RecurringTransferConfig.DispatchStrategy.AssetForMetric) > 0 {
   388  			if _, err := e.assets.Get(transfer.RecurringTransferConfig.DispatchStrategy.AssetForMetric); err != nil {
   389  				return fmt.Errorf("could not transfer funds, invalid asset for metric: %w", err)
   390  			}
   391  		}
   392  	}
   393  
   394  	// check source account exists
   395  	if _, err := e.col.GetSystemAccountBalance(transfer.Asset, transfer.Source, transfer.SourceType); err != nil {
   396  		return err
   397  	}
   398  
   399  	// check transfer type is specified
   400  	if transfer.TransferType == vegapb.GovernanceTransferType_GOVERNANCE_TRANSFER_TYPE_UNSPECIFIED {
   401  		return errors.New("invalid governance transfer type")
   402  	}
   403  
   404  	// check max amount is positive
   405  	if transfer.MaxAmount == nil || transfer.MaxAmount.IsNegative() || transfer.MaxAmount.IsZero() {
   406  		return errors.New("invalid max amount for governance transfer")
   407  	}
   408  
   409  	// check fraction of balance is positive
   410  	if !transfer.FractionOfBalance.IsPositive() {
   411  		return errors.New("invalid fraction of balance for governance transfer")
   412  	}
   413  
   414  	// verify recurring transfer starting epoch is not in the past
   415  	if transfer.RecurringTransferConfig != nil && transfer.RecurringTransferConfig.StartEpoch < e.currentEpoch {
   416  		return ErrStartEpochInThePast
   417  	}
   418  
   419  	return nil
   420  }
   421  
   422  func (e *Engine) VerifyCancelGovernanceTransfer(transferID string) error {
   423  	if _, ok := e.recurringGovernanceTransfersMap[transferID]; !ok {
   424  		return fmt.Errorf("Governance transfer %s not found", transferID)
   425  	}
   426  	return nil
   427  }
   428  
   429  func (e *Engine) getGovGameID(transfer *types.GovernanceTransfer) *string {
   430  	if transfer.Config.RecurringTransferConfig == nil || transfer.Config.RecurringTransferConfig.DispatchStrategy == nil {
   431  		return nil
   432  	}
   433  	gameID := e.hashDispatchStrategy(transfer.Config.RecurringTransferConfig.DispatchStrategy)
   434  	return &gameID
   435  }