code.vegaprotocol.io/vega@v0.79.0/core/staking/account.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 staking 17 18 import ( 19 "errors" 20 "sort" 21 "time" 22 23 "code.vegaprotocol.io/vega/core/types" 24 "code.vegaprotocol.io/vega/libs/num" 25 ) 26 27 var ( 28 ErrEventAlreadyExists = errors.New("event already exists") 29 ErrInvalidAmount = errors.New("invalid amount") 30 ErrInvalidEventKind = errors.New("invalid event kind") 31 ErrMissingEventID = errors.New("missing event id") 32 ErrMissingTimestamp = errors.New("missing timestamp") 33 ErrNegativeBalance = errors.New("negative balance") 34 ErrInvalidParty = errors.New("invalid party") 35 ) 36 37 type Account struct { 38 Party string 39 Balance *num.Uint 40 Events []*types.StakeLinking 41 } 42 43 func NewStakingAccount(party string) *Account { 44 return &Account{ 45 Party: party, 46 Balance: num.UintZero(), 47 Events: []*types.StakeLinking{}, 48 } 49 } 50 51 func (s *Account) validateEvent(evt *types.StakeLinking) error { 52 if evt.Amount == nil || evt.Amount.IsZero() { 53 return ErrInvalidAmount 54 } 55 if evt.Type != types.StakeLinkingTypeDeposited && evt.Type != types.StakeLinkingTypeRemoved { 56 return ErrInvalidEventKind 57 } 58 if evt.TS <= 0 { 59 return ErrMissingTimestamp 60 } 61 if len(evt.ID) <= 0 { 62 return ErrMissingEventID 63 } 64 if evt.Party != s.Party { 65 return ErrInvalidParty 66 } 67 68 for _, v := range s.Events { 69 if evt.ID == v.ID { 70 return ErrEventAlreadyExists 71 } 72 } 73 74 return nil 75 } 76 77 // AddEvent will add a new event to the account. 78 func (s *Account) AddEvent(evt *types.StakeLinking) error { 79 if err := s.validateEvent(evt); err != nil { 80 return err 81 } 82 // save the new events 83 s.insertSorted(evt) 84 85 // now update the ongoing balance 86 return s.computeOngoingBalance() 87 } 88 89 func (s *Account) GetAvailableBalance() *num.Uint { 90 return s.Balance.Clone() 91 } 92 93 func (s *Account) GetAvailableBalanceAt(at time.Time) (*num.Uint, error) { 94 atUnix := at.UnixNano() 95 return s.calculateBalance(func(evt *types.StakeLinking) bool { 96 return evt.TS <= atUnix 97 }) 98 } 99 100 // GetAvailableBalanceInRange could return a negative balance 101 // if some event are still expected to be received from the bridge. 102 func (s *Account) GetAvailableBalanceInRange(from, to time.Time) (*num.Uint, error) { 103 // first compute the balance before the from time. 104 balance, err := s.GetAvailableBalanceAt(from) 105 if err != nil { 106 return num.UintZero(), err 107 } 108 109 minBalance := balance.Clone() 110 111 // now we have the balance at the from time. 112 // we will want to check how much was added / removed 113 // during the epoch, and make sure that the initial 114 // balance is still covered 115 var ( 116 fromUnix = from.UnixNano() 117 toUnix = to.UnixNano() 118 ) 119 for i := 0; i < len(s.Events) && s.Events[i].TS <= toUnix; i++ { 120 if s.Events[i].TS > fromUnix { 121 evt := s.Events[i] 122 switch evt.Type { 123 case types.StakeLinkingTypeDeposited: 124 balance.AddSum(evt.Amount) 125 case types.StakeLinkingTypeRemoved: 126 if balance.LT(evt.Amount) { 127 return num.UintZero(), ErrNegativeBalance 128 } 129 balance.Sub(balance, evt.Amount) 130 minBalance = num.Min(balance, minBalance) 131 } 132 } 133 } 134 135 return minBalance, nil 136 } 137 138 // computeOnGoingBalance can return only 1 error which would 139 // be ErrNegativeBalancem, while this sounds bad, it can happen 140 // because of event being processed out of order but we can't 141 // really prevent that, and would have to wait for the network 142 // to have seen all events before getting a positive balance. 143 func (s *Account) computeOngoingBalance() error { 144 balance, err := s.calculateBalance(func(evt *types.StakeLinking) bool { 145 return true 146 }) 147 s.Balance.Set(balance) 148 return err 149 } 150 151 func (s *Account) insertSorted(evt *types.StakeLinking) { 152 s.Events = append(s.Events, evt) 153 // sort anyway, but we would expect the events to come in a sorted manner 154 sort.SliceStable(s.Events, func(i, j int) bool { 155 // check if timestamps are the same 156 if s.Events[i].TS == s.Events[j].TS { 157 // now we want to put deposit first to avoid any remove 158 // event before a withdraw 159 if s.Events[i].Type == types.StakeLinkingTypeRemoved && s.Events[j].Type == types.StakeLinkingTypeDeposited { 160 // we return false so they can switched 161 return false 162 } 163 // any other case is find to be as they are 164 return true 165 } 166 167 return s.Events[i].TS < s.Events[j].TS 168 }) 169 } 170 171 type timeFilter func(*types.StakeLinking) bool 172 173 func (s *Account) calculateBalance(f timeFilter) (*num.Uint, error) { 174 balance := num.UintZero() 175 for _, evt := range s.Events { 176 if f(evt) { 177 switch evt.Type { 178 case types.StakeLinkingTypeDeposited: 179 balance.Add(balance, evt.Amount) 180 case types.StakeLinkingTypeRemoved: 181 if balance.LT(evt.Amount) { 182 return num.UintZero(), ErrNegativeBalance 183 } 184 balance.Sub(balance, evt.Amount) 185 } 186 } 187 } 188 return balance, nil 189 }