/
subaccount_helper.go
230 lines (202 loc) · 8.74 KB
/
subaccount_helper.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
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
package keeper
import (
"fmt"
"sort"
"github.com/dydxprotocol/v4-chain/protocol/dtypes"
"github.com/dydxprotocol/v4-chain/protocol/x/subaccounts/types"
)
// getUpdatedAssetPositions filters out all the asset positions on a subaccount that have
// been updated. This will include any asset postions that were closed due to an update.
// TODO(DEC-1295): look into reducing code duplication here using Generics+Reflect.
func getUpdatedAssetPositions(
update settledUpdate,
) []*types.AssetPosition {
assetIdToPositionMap := make(map[uint32]*types.AssetPosition)
for _, assetPosition := range update.SettledSubaccount.AssetPositions {
assetIdToPositionMap[assetPosition.AssetId] = assetPosition
}
updatedAssetIds := make(map[uint32]struct{})
for _, assetUpdate := range update.AssetUpdates {
updatedAssetIds[assetUpdate.AssetId] = struct{}{}
}
updatedAssetPositions := make([]*types.AssetPosition, 0, len(updatedAssetIds))
for updatedId := range updatedAssetIds {
assetPosition, exists := assetIdToPositionMap[updatedId]
// If a position does not exist on the subaccount with the asset id of an update, it must
// have been deleted due to quantums becoming 0. This needs to be included in the event, so we
// construct a position with the AssetId of the update and a Quantums value of 0. The other
// properties are left as the default values as a 0-sized position indicates the position is
// closed.
if !exists {
assetPosition = &types.AssetPosition{
AssetId: updatedId,
Quantums: dtypes.ZeroInt(),
}
}
updatedAssetPositions = append(updatedAssetPositions, assetPosition)
}
// Sort the asset positions in ascending order by asset id.
sort.Slice(updatedAssetPositions, func(i, j int) bool {
return updatedAssetPositions[i].GetId() < updatedAssetPositions[j].GetId()
})
return updatedAssetPositions
}
// getUpdatedPerpetualPositions filters out all the perpetual positions on a subaccount that have
// been updated. This will include any perpetual postions that were closed due to an update or that
// received / paid out funding payments..
func getUpdatedPerpetualPositions(
update settledUpdate,
fundingPayments map[uint32]dtypes.SerializableInt,
) []*types.PerpetualPosition {
perpetualIdToPositionMap := make(map[uint32]*types.PerpetualPosition)
for _, perpetualPosition := range update.SettledSubaccount.PerpetualPositions {
perpetualIdToPositionMap[perpetualPosition.PerpetualId] = perpetualPosition
}
// `updatedPerpetualIds` indicates which perpetuals were either explicitly updated
// (through update.PerpetualUpdates) or implicitly updated (had non-zero last funding
// payment).
updatedPerpetualIds := make(map[uint32]struct{})
for _, perpetualUpdate := range update.PerpetualUpdates {
updatedPerpetualIds[perpetualUpdate.PerpetualId] = struct{}{}
}
// Mark perpetuals with non-zero funding payment also as updated.
for perpetualIdWithNonZeroLastFunding := range fundingPayments {
updatedPerpetualIds[perpetualIdWithNonZeroLastFunding] = struct{}{}
}
updatedPerpetualPositions := make([]*types.PerpetualPosition, 0, len(updatedPerpetualIds))
for updatedId := range updatedPerpetualIds {
perpetualPosition, exists := perpetualIdToPositionMap[updatedId]
// If a position does not exist on the subaccount with the perpetual id of an update, it must
// have been deleted due to quantums becoming 0. This needs to be included in the event, so we
// construct a position with the PerpetualId of the update and a Quantums value of 0. The other
// properties are left as the default values as a 0-sized position indicates the position is
// closed and thus the funding index and the side of the position does not matter.
if !exists {
perpetualPosition = &types.PerpetualPosition{
PerpetualId: updatedId,
Quantums: dtypes.ZeroInt(),
}
}
updatedPerpetualPositions = append(updatedPerpetualPositions, perpetualPosition)
}
// Sort the perpetual positions in ascending order by perpetual id.
sort.Slice(updatedPerpetualPositions, func(i, j int) bool {
return updatedPerpetualPositions[i].GetId() < updatedPerpetualPositions[j].GetId()
})
return updatedPerpetualPositions
}
// For each settledUpdate in settledUpdates, updates its SettledSubaccount.PerpetualPositions
// to reflect settledUpdate.PerpetualUpdates.
// For newly created positions, use `perpIdToFundingIndex` map to populate the `FundingIndex` field.
func UpdatePerpetualPositions(
settledUpdates []settledUpdate,
perpIdToFundingIndex map[uint32]dtypes.SerializableInt,
) (
success bool,
err error,
) {
// Apply the updates.
for i, u := range settledUpdates {
// Build a map of all the Subaccount's Perpetual Positions by id.
perpetualPositionsMap := make(map[uint32]*types.PerpetualPosition)
for _, pp := range u.SettledSubaccount.PerpetualPositions {
perpetualPositionsMap[pp.PerpetualId] = pp
}
// Update the perpetual positions.
for _, pu := range u.PerpetualUpdates {
// Check if the `Subaccount` already has a position with the same id.
// If so – we update the size of the existing position, otherwise
// we create a new position.
if pp, exists := perpetualPositionsMap[pu.PerpetualId]; exists {
curQuantums := pp.GetBigQuantums()
updateQuantums := pu.GetBigQuantums()
newQuantums := curQuantums.Add(curQuantums, updateQuantums)
// Handle the case where the position is now closed.
if newQuantums.Sign() == 0 {
delete(perpetualPositionsMap, pu.PerpetualId)
}
pp.Quantums = dtypes.NewIntFromBigInt(newQuantums)
} else {
// This subaccount does not have a matching position for this update.
// Create the new position.
fundingIndex, exists := perpIdToFundingIndex[pu.PerpetualId]
if !exists {
// Invariant: `perpIdToFundingIndex` contains all existing perpetauls,
// and perpetual position update must refer to an existing perpetual.
panic(fmt.Sprintf("perpetual id %d not found in perpIdToFundingIndex", pu.PerpetualId))
}
perpetualPosition := &types.PerpetualPosition{
PerpetualId: pu.PerpetualId,
Quantums: dtypes.NewIntFromBigInt(pu.GetBigQuantums()),
FundingIndex: fundingIndex,
}
// Add the new position to the map.
perpetualPositionsMap[pu.PerpetualId] = perpetualPosition
}
}
// Convert the new PerpetualPostiion values back into a slice.
perpetualPositions := make([]*types.PerpetualPosition, 0, len(perpetualPositionsMap))
for _, value := range perpetualPositionsMap {
perpetualPositions = append(perpetualPositions, value)
}
// Sort the new PerpetualPositions in ascending order by Id.
sort.Slice(perpetualPositions, func(i, j int) bool {
return perpetualPositions[i].GetId() < perpetualPositions[j].GetId()
})
settledUpdates[i].SettledSubaccount.PerpetualPositions = perpetualPositions
}
return true, nil
}
// For each settledUpdate in settledUpdates, updates its SettledSubaccount.AssetPositions
// to reflect settledUpdate.AssetUpdates.
func UpdateAssetPositions(
settledUpdates []settledUpdate,
) (
success bool,
err error,
) {
// Apply the updates.
for i, u := range settledUpdates {
// Build a map of all the Subaccount's Asset Positions by id.
assetPositionsMap := make(map[uint32]*types.AssetPosition)
for _, ap := range u.SettledSubaccount.AssetPositions {
assetPositionsMap[ap.AssetId] = ap
}
// Update the asset positions.
for _, au := range u.AssetUpdates {
// Check if the `Subaccount` already has a position with the same id.
// If so - we update the size of the existing position, otherwise
// we create a new position.
if ap, exists := assetPositionsMap[au.AssetId]; exists {
curQuantums := ap.GetBigQuantums()
updateQuantums := au.GetBigQuantums()
newQuantums := curQuantums.Add(curQuantums, updateQuantums)
ap.Quantums = dtypes.NewIntFromBigInt(newQuantums)
// Handle the case where the position is now closed.
if ap.Quantums.BigInt().Sign() == 0 {
delete(assetPositionsMap, au.AssetId)
}
} else {
// This subaccount does not have a matching asset position for this update.
// Create the new asset position.
assetPosition := &types.AssetPosition{
AssetId: au.AssetId,
Quantums: dtypes.NewIntFromBigInt(au.GetBigQuantums()),
}
// Add the new asset position to the map.
assetPositionsMap[au.AssetId] = assetPosition
}
}
// Convert the new AssetPostiion values back into a slice.
assetPositions := make([]*types.AssetPosition, 0, len(assetPositionsMap))
for _, value := range assetPositionsMap {
assetPositions = append(assetPositions, value)
}
// Sort the new AssetPositions in ascending order by AssetId.
sort.Slice(assetPositions, func(i, j int) bool {
return assetPositions[i].GetId() < assetPositions[j].GetId()
})
settledUpdates[i].SettledSubaccount.AssetPositions = assetPositions
}
return true, nil
}