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 }