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