-
Notifications
You must be signed in to change notification settings - Fork 0
/
redemptions.go
124 lines (105 loc) · 4.87 KB
/
redemptions.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
package types
import (
"cosmossdk.io/math"
sdk "github.com/cosmos/cosmos-sdk/types"
)
func DetermineAllocationsForUndelegation(currentAllocations map[string]math.Int, currentSum math.Int, targetAllocations ValidatorIntents, availablePerValidator map[string]math.Int, amount sdk.Coins) map[string]math.Int {
input := amount[0].Amount
deltas := CalculateDeltas(currentAllocations, currentSum.Sub(input), targetAllocations)
sum := sdk.ZeroInt()
outSum := sdk.ZeroInt()
outWeights := make(map[string]math.Int)
// deltas: +ve is below target; -ve is above target.
// q1: can we satisfy this unbonding using _just_ above target allocations.
// example:
// we have v1: 5000, v2: 1800; v3: 1200; v4: 1000 and targets of 50%, 20%, 15% and 5% respectively.
// deltas == 500, 0, -150, 550
// an unbonding of 300 should come from v1, v4 (as they has an excess of > unbond amount) _before_ touching anything else.
for idx := range deltas {
if deltas[idx].Weight.IsNegative() {
sum = sum.Add(deltas[idx].Weight.TruncateInt().Abs())
}
}
overAllocationSplit := sdk.MinInt(sum, input)
if !overAllocationSplit.IsZero() {
for idx := range deltas {
if !deltas[idx].Weight.IsNegative() {
continue
}
outWeights[deltas[idx].ValoperAddress] = deltas[idx].Weight.Quo(sdk.NewDecFromInt(sum)).Mul(sdk.NewDecFromInt(overAllocationSplit)).TruncateInt().Abs()
if outWeights[deltas[idx].ValoperAddress].GT(availablePerValidator[deltas[idx].ValoperAddress]) {
outWeights[deltas[idx].ValoperAddress] = availablePerValidator[deltas[idx].ValoperAddress]
availablePerValidator[deltas[idx].ValoperAddress] = sdk.ZeroInt()
} else {
availablePerValidator[deltas[idx].ValoperAddress] = availablePerValidator[deltas[idx].ValoperAddress].Sub(outWeights[deltas[idx].ValoperAddress])
}
deltas[idx].Weight = deltas[idx].Weight.Add(sdk.NewDecFromInt(outWeights[deltas[idx].ValoperAddress]))
outSum = outSum.Add(outWeights[deltas[idx].ValoperAddress])
}
}
input = input.Sub(outSum)
if input.IsZero() {
return outWeights
}
maxValue := MaxDeltas(deltas)
sum = sdk.ZeroInt()
// drop all deltas such that the maximum value is zero, and invert.
for idx := range deltas {
deltas[idx].Weight = deltas[idx].Weight.Sub(sdk.NewDecFromInt(maxValue)).Abs()
sum = sum.Add(deltas[idx].Weight.TruncateInt().Abs())
}
// unequalSplit is the portion of input that should be distributed in attempt to make targets == 0
unequalSplit := sdk.MinInt(sum, input)
if !unequalSplit.IsZero() {
for idx := range deltas {
allocation := deltas[idx].Weight.Quo(sdk.NewDecFromInt(sum)).Mul(sdk.NewDecFromInt(unequalSplit))
_, ok := availablePerValidator[deltas[idx].ValoperAddress]
if !ok {
availablePerValidator[deltas[idx].ValoperAddress] = sdk.ZeroInt()
}
if allocation.TruncateInt().GT(availablePerValidator[deltas[idx].ValoperAddress]) {
allocation = sdk.NewDecFromInt(availablePerValidator[deltas[idx].ValoperAddress])
availablePerValidator[deltas[idx].ValoperAddress] = sdk.ZeroInt()
} else {
availablePerValidator[deltas[idx].ValoperAddress] = availablePerValidator[deltas[idx].ValoperAddress].Sub(allocation.TruncateInt())
}
deltas[idx].Weight = deltas[idx].Weight.Sub(allocation)
value, ok := outWeights[deltas[idx].ValoperAddress]
if !ok {
value = sdk.ZeroInt()
}
outWeights[deltas[idx].ValoperAddress] = value.Add(allocation.TruncateInt())
outSum = outSum.Add(allocation.TruncateInt())
input = input.Sub(allocation.TruncateInt())
}
}
// equalSplit is the portion of input that should be distributed equally across all validators, once targets are met.
if !outSum.Equal(amount[0].Amount) {
each := sdk.NewDecFromInt(input).Quo(sdk.NewDec(int64(len(deltas))))
for idx := range deltas {
value, ok := outWeights[deltas[idx].ValoperAddress]
if !ok {
value = sdk.ZeroInt()
}
if each.TruncateInt().GT(availablePerValidator[deltas[idx].ValoperAddress]) {
each = sdk.NewDecFromInt(availablePerValidator[deltas[idx].ValoperAddress])
availablePerValidator[deltas[idx].ValoperAddress] = sdk.ZeroInt()
} else {
availablePerValidator[deltas[idx].ValoperAddress] = availablePerValidator[deltas[idx].ValoperAddress].Sub(each.TruncateInt())
}
outWeights[deltas[idx].ValoperAddress] = value.Add(each.TruncateInt())
outSum = outSum.Add(each.TruncateInt())
input = input.Sub(each.TruncateInt())
}
}
// dust is the portion of the input that was truncated in previous calculations; add this to the last validator in the list,
// which should be the biggest source. This will always be a small amount, and will count toward the delta calculations on the next run.
dust := amount[0].Amount.Sub(outSum)
for idx := len(deltas) - 1; idx >= 0; idx-- {
if dust.LTE(availablePerValidator[deltas[idx].ValoperAddress]) {
outWeights[deltas[idx].ValoperAddress] = outWeights[deltas[idx].ValoperAddress].Add(dust)
break
}
}
return outWeights
}