code.vegaprotocol.io/vega@v0.79.0/core/banking/transfers_common.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 "fmt" 21 "time" 22 23 "code.vegaprotocol.io/vega/core/assets" 24 "code.vegaprotocol.io/vega/core/events" 25 "code.vegaprotocol.io/vega/core/types" 26 "code.vegaprotocol.io/vega/libs/num" 27 "code.vegaprotocol.io/vega/logging" 28 ) 29 30 func (e *Engine) OnRewardsUpdateFrequencyUpdate(ctx context.Context, d time.Duration) error { 31 if !e.nextMetricUpdate.IsZero() { 32 e.nextMetricUpdate = e.nextMetricUpdate.Add(-e.metricUpdateFrequency) 33 } 34 e.nextMetricUpdate = e.nextMetricUpdate.Add(d) 35 e.metricUpdateFrequency = d 36 return nil 37 } 38 39 func (e *Engine) OnTransferFeeFactorUpdate(ctx context.Context, f num.Decimal) error { 40 e.transferFeeFactor = f 41 return nil 42 } 43 44 func (e *Engine) OnMinTransferQuantumMultiple(ctx context.Context, f num.Decimal) error { 45 e.minTransferQuantumMultiple = f 46 return nil 47 } 48 49 func (e *Engine) OnMaxQuantumAmountUpdate(ctx context.Context, f num.Decimal) error { 50 e.maxQuantumAmount = f 51 return nil 52 } 53 54 func (e *Engine) OnTransferFeeDiscountDecayFractionUpdate(ctx context.Context, v num.Decimal) error { 55 e.feeDiscountDecayFraction = v 56 return nil 57 } 58 59 func (e *Engine) OnTransferFeeDiscountMinimumTrackedAmountUpdate(ctx context.Context, v num.Decimal) error { 60 e.feeDiscountMinimumTrackedAmount = v 61 return nil 62 } 63 64 func (e *Engine) TransferFunds( 65 ctx context.Context, 66 transfer *types.TransferFunds, 67 ) error { 68 now := e.timeService.GetTimeNow() 69 // add timestamps straight away 70 switch transfer.Kind { 71 case types.TransferCommandKindOneOff: 72 transfer.OneOff.Timestamp = now 73 return e.oneOffTransfer(ctx, transfer.OneOff) 74 case types.TransferCommandKindRecurring: 75 transfer.Recurring.Timestamp = now 76 return e.recurringTransfer(ctx, transfer.Recurring) 77 default: 78 return ErrUnsupportedTransferKind 79 } 80 } 81 82 func (e *Engine) CheckTransfer(t *types.TransferBase) error { 83 // ensure asset exists 84 a, err := e.assets.Get(t.Asset) 85 if err != nil { 86 e.log.Debug("cannot transfer funds, invalid asset", logging.Error(err)) 87 return fmt.Errorf("could not transfer funds, %w", err) 88 } 89 90 if err = t.IsValid(); err != nil { 91 return fmt.Errorf("could not transfer funds, %w", err) 92 } 93 94 if err := e.ensureMinimalTransferAmount(a, t.Amount, t.FromAccountType, t.From, t.FromDerivedKey); err != nil { 95 return err 96 } 97 98 if err = e.ensureFeeForTransferFunds(a, t.Amount, t.From, t.FromAccountType, t.FromDerivedKey, t.To, t.ToAccountType); err != nil { 99 return fmt.Errorf("could not transfer funds, %w", err) 100 } 101 return nil 102 } 103 104 func (e *Engine) ensureMinimalTransferAmount( 105 a *assets.Asset, 106 amount *num.Uint, 107 fromAccType types.AccountType, 108 from string, 109 fromSubAccount *string, 110 ) error { 111 quantum := a.Type().Details.Quantum 112 // no reason this would produce an error 113 minAmount, _ := num.UintFromDecimal(quantum.Mul(e.minTransferQuantumMultiple)) 114 115 // no verify amount 116 if amount.LT(minAmount) { 117 if fromAccType == types.AccountTypeVestedRewards { 118 if fromSubAccount != nil { 119 from = *fromSubAccount 120 } 121 return e.ensureMinimalTransferAmountFromVested(amount, from, a.Type().ID) 122 } 123 124 e.log.Debug("cannot transfer funds, less than minimal amount requested to transfer", 125 logging.BigUint("min-amount", minAmount), 126 logging.BigUint("requested-amount", amount), 127 ) 128 return fmt.Errorf("could not transfer funds, less than minimal amount requested to transfer") 129 } 130 131 return nil 132 } 133 134 func (e *Engine) ensureMinimalTransferAmountFromVested( 135 transferAmount *num.Uint, 136 from, asset string, 137 ) error { 138 account, err := e.col.GetPartyVestedRewardAccount(from, asset) 139 if err != nil { 140 return err 141 } 142 143 if transferAmount.EQ(account.Balance) { 144 return nil 145 } 146 147 return fmt.Errorf("transfer from vested account under minimal transfer amount must be the full balance") 148 } 149 150 func (e *Engine) processTransfer( 151 ctx context.Context, 152 asset *assets.Asset, 153 from, to, toMarket string, 154 fromAcc, toAcc types.AccountType, 155 amount *num.Uint, 156 reference string, 157 transferID string, 158 epoch uint64, 159 // optional from derived key transfer 160 fromDerivedKey *string, 161 // optional oneoff transfer 162 // in case we need to schedule the delivery 163 oneoff *types.OneOffTransfer, 164 ) ([]*types.LedgerMovement, error) { 165 assetType := asset.ToAssetType() 166 167 // ensure the party have enough funds for both the 168 // amount and the fee for the transfer 169 feeTransfer, discount, err := e.makeFeeTransferForFundsTransfer(ctx, assetType, amount, from, fromAcc, fromDerivedKey, to, toAcc) 170 if err != nil { 171 return nil, fmt.Errorf("could not pay the fee for transfer: %w", err) 172 } 173 feeTransferAccountType := []types.AccountType{fromAcc} 174 175 // transfer from sub account to owners general account 176 if fromDerivedKey != nil { 177 from = *fromDerivedKey 178 } 179 180 fromTransfer, toTransfer := e.makeTransfers(from, to, assetType.ID, "", toMarket, amount, &transferID) 181 transfers := []*types.Transfer{fromTransfer} 182 accountTypes := []types.AccountType{fromAcc} 183 references := []string{reference} 184 185 // does the transfer needs to be finalized now? 186 now := e.timeService.GetTimeNow() 187 if oneoff == nil || (oneoff.DeliverOn == nil || oneoff.DeliverOn.Before(now)) { 188 transfers = append(transfers, toTransfer) 189 accountTypes = append(accountTypes, toAcc) 190 references = append(references, reference) 191 // if this goes well the whole transfer will be done 192 // so we can set it to the proper status 193 } else { 194 // schedule the transfer 195 e.scheduleTransfer( 196 oneoff, 197 toTransfer, 198 toAcc, 199 reference, 200 *oneoff.DeliverOn, 201 ) 202 } 203 204 // process the transfer 205 tresps, err := e.col.TransferFunds( 206 ctx, transfers, accountTypes, references, []*types.Transfer{feeTransfer}, feeTransferAccountType, 207 ) 208 if err != nil { 209 return nil, err 210 } 211 212 e.broker.Send(events.NewTransferFeesEvent(ctx, transferID, feeTransfer.Amount.Amount, discount, epoch)) 213 214 return tresps, nil 215 } 216 217 func (e *Engine) makeTransfers( 218 from, to, asset, fromMarket, toMarket string, 219 amount *num.Uint, 220 transferID *string, 221 ) (*types.Transfer, *types.Transfer) { 222 return &types.Transfer{ 223 Owner: from, 224 Amount: &types.FinancialAmount{ 225 Amount: amount.Clone(), 226 Asset: asset, 227 }, 228 Type: types.TransferTypeTransferFundsSend, 229 MinAmount: amount.Clone(), 230 Market: fromMarket, 231 TransferID: transferID, 232 }, &types.Transfer{ 233 Owner: to, 234 Amount: &types.FinancialAmount{ 235 Amount: amount.Clone(), 236 Asset: asset, 237 }, 238 Type: types.TransferTypeTransferFundsDistribute, 239 MinAmount: amount.Clone(), 240 Market: toMarket, 241 TransferID: transferID, 242 } 243 } 244 245 func (e *Engine) calculateFeeTransferForTransfer( 246 asset *types.Asset, 247 amount *num.Uint, 248 from string, 249 fromAccountType types.AccountType, 250 fromDerivedKey *string, 251 to string, 252 toAccountType types.AccountType, 253 ) *num.Uint { 254 return calculateFeeForTransfer( 255 asset.Details.Quantum, 256 e.maxQuantumAmount, 257 e.transferFeeFactor, 258 amount, 259 from, 260 fromAccountType, 261 fromDerivedKey, 262 to, 263 toAccountType, 264 ) 265 } 266 267 func (e *Engine) makeFeeTransferForFundsTransfer( 268 ctx context.Context, 269 asset *types.Asset, 270 amount *num.Uint, 271 from string, 272 fromAccountType types.AccountType, 273 fromDerivedKey *string, 274 to string, 275 toAccountType types.AccountType, 276 ) (*types.Transfer, *num.Uint, error) { 277 theoreticalFee := e.calculateFeeTransferForTransfer(asset, amount, from, fromAccountType, fromDerivedKey, to, toAccountType) 278 feeAmount, discountAmount := e.ApplyFeeDiscount(ctx, asset.ID, from, theoreticalFee) 279 280 if err := e.ensureEnoughFundsForTransfer(asset, amount, from, fromAccountType, fromDerivedKey, feeAmount); err != nil { 281 return nil, nil, err 282 } 283 284 switch fromAccountType { 285 case types.AccountTypeGeneral, types.AccountTypeVestedRewards, types.AccountTypeLockedForStaking: 286 default: 287 e.log.Panic("from account not supported", 288 logging.String("account-type", fromAccountType.String()), 289 logging.String("asset", asset.ID), 290 logging.String("from", from), 291 ) 292 } 293 294 return &types.Transfer{ 295 Owner: from, 296 Amount: &types.FinancialAmount{ 297 Amount: feeAmount.Clone(), 298 Asset: asset.ID, 299 }, 300 Type: types.TransferTypeInfrastructureFeePay, 301 MinAmount: feeAmount, 302 }, discountAmount, nil 303 } 304 305 func (e *Engine) ensureFeeForTransferFunds( 306 asset *assets.Asset, 307 amount *num.Uint, 308 from string, 309 fromAccountType types.AccountType, 310 fromDerivedKey *string, 311 to string, 312 toAccountType types.AccountType, 313 ) error { 314 assetType := asset.ToAssetType() 315 theoreticalFee := e.calculateFeeTransferForTransfer(assetType, amount, from, fromAccountType, fromDerivedKey, to, toAccountType) 316 feeAmount, _ := e.EstimateFeeDiscount(assetType.ID, from, theoreticalFee) 317 return e.ensureEnoughFundsForTransfer(assetType, amount, from, fromAccountType, fromDerivedKey, feeAmount) 318 } 319 320 func (e *Engine) ensureEnoughFundsForTransfer( 321 asset *types.Asset, 322 amount *num.Uint, 323 from string, 324 fromAccountType types.AccountType, 325 fromDerivedKey *string, 326 feeAmount *num.Uint, 327 ) error { 328 var ( 329 totalAmount = num.Sum(feeAmount, amount) 330 account *types.Account 331 err error 332 ) 333 switch fromAccountType { 334 case types.AccountTypeGeneral: 335 account, err = e.col.GetPartyGeneralAccount(from, asset.ID) 336 if err != nil { 337 return err 338 } 339 case types.AccountTypeLockedForStaking: 340 account, err = e.col.GetPartyLockedForStaking(from, asset.ID) 341 if err != nil { 342 return err 343 } 344 case types.AccountTypeVestedRewards: 345 // sending from sub account to owners general account 346 if fromDerivedKey != nil { 347 from = *fromDerivedKey 348 } 349 350 account, err = e.col.GetPartyVestedRewardAccount(from, asset.ID) 351 if err != nil { 352 return err 353 } 354 355 default: 356 e.log.Panic("from account not supported", 357 logging.String("account-type", fromAccountType.String()), 358 logging.String("asset", asset.ID), 359 logging.String("from", from), 360 ) 361 } 362 363 if account.Balance.LT(totalAmount) { 364 e.log.Debug("not enough funds to transfer", 365 logging.BigUint("amount", amount), 366 logging.BigUint("fee", feeAmount), 367 logging.BigUint("total-amount", totalAmount), 368 logging.BigUint("account-balance", account.Balance), 369 logging.String("account-type", fromAccountType.String()), 370 logging.String("asset", asset.ID), 371 logging.String("from", from), 372 ) 373 return ErrNotEnoughFundsToTransfer 374 } 375 return nil 376 }