code.vegaprotocol.io/vega@v0.79.0/core/banking/oneoff_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 "errors" 21 "fmt" 22 "sort" 23 "time" 24 25 "code.vegaprotocol.io/vega/core/events" 26 "code.vegaprotocol.io/vega/core/types" 27 vgcontext "code.vegaprotocol.io/vega/libs/context" 28 "code.vegaprotocol.io/vega/libs/crypto" 29 "code.vegaprotocol.io/vega/logging" 30 checkpoint "code.vegaprotocol.io/vega/protos/vega/checkpoint/v1" 31 ) 32 33 var ErrUnsupportedTransferKind = errors.New("unsupported transfer kind") 34 35 type scheduledTransfer struct { 36 // to send events 37 oneoff *types.OneOffTransfer 38 transfer *types.Transfer 39 accountType types.AccountType 40 reference string 41 } 42 43 func (s *scheduledTransfer) ToProto() *checkpoint.ScheduledTransfer { 44 return &checkpoint.ScheduledTransfer{ 45 OneoffTransfer: s.oneoff.IntoEvent(nil), 46 Transfer: s.transfer.IntoProto(), 47 AccountType: s.accountType, 48 Reference: s.reference, 49 } 50 } 51 52 func scheduledTransferFromProto(p *checkpoint.ScheduledTransfer) (scheduledTransfer, error) { 53 transfer, err := types.TransferFromProto(p.Transfer) 54 if err != nil { 55 return scheduledTransfer{}, err 56 } 57 58 return scheduledTransfer{ 59 oneoff: types.OneOffTransferFromEvent(p.OneoffTransfer), 60 transfer: transfer, 61 accountType: p.AccountType, 62 reference: p.Reference, 63 }, nil 64 } 65 66 func (e *Engine) updateStakingAccounts( 67 ctx context.Context, transfer *types.OneOffTransfer, 68 ) { 69 if transfer.Asset != e.stakingAsset { 70 // nothing to do 71 return 72 } 73 74 var ( 75 now = e.timeService.GetTimeNow().Unix() 76 height, _ = vgcontext.BlockHeightFromContext(ctx) 77 txhash, _ = vgcontext.TxHashFromContext(ctx) 78 id = crypto.HashStrToHex(fmt.Sprintf("%v%v", txhash, height)) 79 stakeLinking *types.StakeLinking 80 ) 81 82 // manually send funds from the general account to the locked for staking 83 if transfer.FromAccountType == types.AccountTypeGeneral && transfer.ToAccountType == types.AccountTypeLockedForStaking { 84 stakeLinking = &types.StakeLinking{ 85 ID: id, 86 Type: types.StakeLinkingTypeDeposited, 87 TS: now, 88 Party: transfer.From, 89 Amount: transfer.Amount.Clone(), 90 Status: types.StakeLinkingStatusAccepted, 91 FinalizedAt: now, 92 TxHash: txhash, 93 BlockHeight: height, 94 BlockTime: now, 95 LogIndex: 1, 96 EthereumAddress: "", 97 } 98 } 99 100 // from staking account or vested rewards, we send a remove event 101 if (transfer.FromAccountType == types.AccountTypeLockedForStaking && transfer.ToAccountType == types.AccountTypeGeneral) || 102 (transfer.FromAccountType == types.AccountTypeVestedRewards && transfer.ToAccountType == types.AccountTypeGeneral) { 103 stakeLinking = &types.StakeLinking{ 104 ID: id, 105 Type: types.StakeLinkingTypeRemoved, 106 TS: now, 107 Party: transfer.From, 108 Amount: transfer.Amount.Clone(), 109 Status: types.StakeLinkingStatusAccepted, 110 FinalizedAt: now, 111 TxHash: txhash, 112 BlockHeight: height, 113 BlockTime: now, 114 LogIndex: 1, 115 EthereumAddress: "", 116 } 117 } 118 119 if stakeLinking != nil { 120 e.stakeAccounting.AddEvent(ctx, stakeLinking) 121 e.broker.Send(events.NewStakeLinking(ctx, *stakeLinking)) 122 } 123 } 124 125 func (e *Engine) oneOffTransfer( 126 ctx context.Context, 127 transfer *types.OneOffTransfer, 128 ) (err error) { 129 defer func() { 130 if err != nil { 131 e.broker.Send(events.NewOneOffTransferFundsEventWithReason(ctx, transfer, err.Error())) 132 } else { 133 e.broker.Send(events.NewOneOffTransferFundsEvent(ctx, transfer)) 134 e.updateStakingAccounts(ctx, transfer) 135 } 136 }() 137 138 // ensure asset exists 139 a, err := e.assets.Get(transfer.Asset) 140 if err != nil { 141 transfer.Status = types.TransferStatusRejected 142 e.log.Debug("cannot transfer funds, invalid asset", logging.Error(err)) 143 return fmt.Errorf("could not transfer funds: %w", err) 144 } 145 146 if err := transfer.IsValid(); err != nil { 147 transfer.Status = types.TransferStatusRejected 148 return err 149 } 150 151 if transfer.FromDerivedKey != nil { 152 if ownsDerivedKey := e.parties.CheckDerivedKeyOwnership(types.PartyID(transfer.From), *transfer.FromDerivedKey); !ownsDerivedKey { 153 transfer.Status = types.TransferStatusRejected 154 return fmt.Errorf("party %s does not own derived key %s", transfer.From, *transfer.FromDerivedKey) 155 } 156 } 157 158 if err := e.ensureMinimalTransferAmount(a, transfer.Amount, transfer.FromAccountType, transfer.From, transfer.FromDerivedKey); err != nil { 159 transfer.Status = types.TransferStatusRejected 160 return err 161 } 162 163 tresps, err := e.processTransfer( 164 ctx, a, transfer.From, transfer.To, "", transfer.FromAccountType, 165 transfer.ToAccountType, transfer.Amount, transfer.Reference, transfer.ID, e.currentEpoch, transfer.FromDerivedKey, 166 transfer, 167 ) 168 if err != nil { 169 transfer.Status = types.TransferStatusRejected 170 return err 171 } 172 173 // all was OK 174 transfer.Status = types.TransferStatusDone 175 e.broker.Send(events.NewLedgerMovements(ctx, tresps)) 176 177 return nil 178 } 179 180 type timesToTransfers struct { 181 deliverOn int64 182 transfer []scheduledTransfer 183 } 184 185 func (e *Engine) distributeScheduledTransfers(ctx context.Context, now time.Time) error { 186 ttfs := []timesToTransfers{} 187 188 // iterate over those scheduled transfers to sort them by time 189 for k, v := range e.scheduledTransfers { 190 if now.UnixNano() >= k { 191 ttfs = append(ttfs, timesToTransfers{k, v}) 192 delete(e.scheduledTransfers, k) 193 } 194 } 195 196 // sort slice by time. 197 // no need to sort transfers they are going out as first in first out. 198 sort.SliceStable(ttfs, func(i, j int) bool { 199 return ttfs[i].deliverOn < ttfs[j].deliverOn 200 }) 201 202 transfers := []*types.Transfer{} 203 accountTypes := []types.AccountType{} 204 references := []string{} 205 evts := []events.Event{} 206 for _, v := range ttfs { 207 for _, t := range v.transfer { 208 t.oneoff.Status = types.TransferStatusDone 209 evts = append(evts, events.NewOneOffTransferFundsEvent(ctx, t.oneoff)) 210 transfers = append(transfers, t.transfer) 211 accountTypes = append(accountTypes, t.accountType) 212 references = append(references, t.reference) 213 } 214 } 215 216 if len(transfers) <= 0 { 217 // nothing to do yeay 218 return nil 219 } 220 221 // at least 1 transfer updated, set to true 222 tresps, err := e.col.TransferFunds( 223 ctx, transfers, accountTypes, references, nil, nil, // no fees required there, they've been paid already 224 ) 225 if err != nil { 226 return err 227 } 228 229 e.broker.Send(events.NewLedgerMovements(ctx, tresps)) 230 e.broker.SendBatch(evts) 231 232 return nil 233 } 234 235 func (e *Engine) scheduleTransfer( 236 oneoff *types.OneOffTransfer, 237 t *types.Transfer, 238 ty types.AccountType, 239 reference string, 240 deliverOn time.Time, 241 ) { 242 sts, ok := e.scheduledTransfers[deliverOn.UnixNano()] 243 if !ok { 244 e.scheduledTransfers[deliverOn.UnixNano()] = []scheduledTransfer{} 245 sts = e.scheduledTransfers[deliverOn.UnixNano()] 246 } 247 248 sts = append(sts, scheduledTransfer{ 249 oneoff: oneoff, 250 transfer: t, 251 accountType: ty, 252 reference: reference, 253 }) 254 e.scheduledTransfers[deliverOn.UnixNano()] = sts 255 }